diff --git a/api/prisma/migrations/20240728012747_add_mime_type_to_image/migration.sql b/api/prisma/migrations/20240728012747_add_mime_type_to_image/migration.sql
new file mode 100644
index 0000000..656d7c6
--- /dev/null
+++ b/api/prisma/migrations/20240728012747_add_mime_type_to_image/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Image" ADD COLUMN "mime_type" TEXT NOT NULL DEFAULT '';
diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma
index 8a2d503..53d2259 100644
--- a/api/prisma/schema.prisma
+++ b/api/prisma/schema.prisma
@@ -85,9 +85,10 @@ model Comment {
}
model Image {
- id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
- name String @unique
- image Bytes
+ id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
+ name String @unique
+ image Bytes
+ mimetype String @map("mime_type") @default("")
}
enum Role {
diff --git a/api/src/image/daos/image.dao.ts b/api/src/image/daos/image.dao.ts
index 7e7439b..21de2df 100644
--- a/api/src/image/daos/image.dao.ts
+++ b/api/src/image/daos/image.dao.ts
@@ -5,24 +5,31 @@ import { PrismaService } from '../../prisma/prisma.service';
export class ImageDao {
constructor(private readonly prisma: PrismaService) {}
- getImage(filename: string) {
+ getImage(username: string) {
return this.prisma.image.findUniqueOrThrow({
where: {
- name: filename,
+ name: username,
},
});
}
- createImage(filename: string, image: Buffer) {
- return this.prisma.image.create({
- data: { name: filename, image },
+ createImage(username: string, mimetype: string, image: Buffer) {
+ return this.prisma.image.upsert({
+ where: {
+ name: username,
+ },
+ update: {
+ mimetype,
+ image,
+ },
+ create: { name: username, image, mimetype },
});
}
- async deleteImage(filename: string) {
- return await this.prisma.image.delete({
+ deleteImage(username: string) {
+ this.prisma.image.delete({
where: {
- name: filename,
+ name: username,
},
});
}
diff --git a/api/src/image/image.controller.ts b/api/src/image/image.controller.ts
index 735bf36..df3fd8b 100644
--- a/api/src/image/image.controller.ts
+++ b/api/src/image/image.controller.ts
@@ -10,8 +10,8 @@ export class ImageController {
@Public()
@Get('/:name')
async getImage(@Param('name') name: string, @Res() res: Response) {
- const image = await this.imageService.getImage(name);
+ const { image, mimetype } = await this.imageService.getImage(name);
- res.end(image);
+ res.contentType(mimetype).end(image);
}
}
diff --git a/api/src/image/image.service.ts b/api/src/image/image.service.ts
index d84c8fc..d3587e3 100644
--- a/api/src/image/image.service.ts
+++ b/api/src/image/image.service.ts
@@ -1,25 +1,19 @@
-import { Injectable, NotFoundException } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
import { ImageDao } from './daos/image.dao';
@Injectable()
export class ImageService {
constructor(private imageDao: ImageDao) {}
- async getImage(filename: string) {
- const { image } = await this.imageDao.getImage(filename);
-
- if (!image) {
- throw new NotFoundException('No image found');
- }
-
- return image;
+ async getImage(username: string) {
+ return this.imageDao.getImage(username);
}
- async createImage(filename: string, image: Buffer) {
- return this.imageDao.createImage(filename, image);
+ async createImage(username: string, mimetype: string, image: Buffer) {
+ return this.imageDao.createImage(username, mimetype, image);
}
- async deleteImage(filename: string) {
- return await this.imageDao.deleteImage(filename);
+ async deleteImage(username: string) {
+ return this.imageDao.deleteImage(username);
}
}
diff --git a/api/src/user/user.controller.spec.ts b/api/src/user/user.controller.spec.ts
index 2fa1a9c..a275aaf 100644
--- a/api/src/user/user.controller.spec.ts
+++ b/api/src/user/user.controller.spec.ts
@@ -97,8 +97,13 @@ describe('UserController', () => {
},
};
fakeImageService = {
- async getImage(name: string) {
- return Buffer.from(name);
+ getImage: (name: string) => {
+ return Promise.resolve({
+ id: '1234',
+ name,
+ image: Buffer.from(name),
+ mimetype: 'image/jpeg',
+ });
},
};
diff --git a/api/src/user/user.controller.ts b/api/src/user/user.controller.ts
index 5ec15a2..195e327 100644
--- a/api/src/user/user.controller.ts
+++ b/api/src/user/user.controller.ts
@@ -29,6 +29,7 @@ import { QueryDto } from './dto/query.dto';
import { RemoveFieldsInterceptor } from './interceptor/remove-fields.interceptor';
import { FriendStatus } from './user.service';
import { ImageService } from '../image/image.service';
+import { AVATAR_MAX_SIZE } from 'src/utils';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
@@ -108,14 +109,14 @@ export class UserController {
}
@UseInterceptors(RemoveFieldsInterceptor)
- @HttpCode(HttpStatus.NO_CONTENT)
+ @HttpCode(HttpStatus.CREATED)
@Post('/avatar')
@UseInterceptors(FileInterceptor('image'))
async createAvatar(
@UploadedFile(
new ParseFilePipe({
validators: [
- new MaxFileSizeValidator({ maxSize: 5 * 1024 * 1024 }),
+ new MaxFileSizeValidator({ maxSize: AVATAR_MAX_SIZE }),
new FileTypeValidator({ fileType: '.(png|jpeg|gif)' }),
],
}),
@@ -125,27 +126,18 @@ export class UserController {
) {
if (!req.user) throw new UnauthorizedException('user not found');
- const extension = file.originalname
- .split('.')
- .slice(-1) as unknown as string;
const { username } = req.user;
- const avatarName = `${username}.${extension}`;
-
- if (req.user.avatarName) {
- this.imageService.deleteImage(req.user.avatarName);
- }
-
- this.userService.updateUser(req.user.id, { avatarName });
+ const { mimetype } = file;
- return this.imageService.createImage(avatarName, file?.buffer);
+ return this.imageService.createImage(username, mimetype, file?.buffer);
}
@UseInterceptors(RemoveFieldsInterceptor)
@UseGuards(AdminGuard)
@HttpCode(HttpStatus.NO_CONTENT)
@Delete('/avatar')
- deleteAvatar(@Req() req: Request) {
+ async deleteAvatar(@Req() req: Request) {
if (!req.user) throw new UnauthorizedException('user not found');
- return this.imageService.deleteImage(req.user?.username);
+ await this.imageService.deleteImage(req.user?.username);
}
}
diff --git a/api/src/utils/constants.ts b/api/src/utils/constants.ts
index 237789c..8f13a54 100644
--- a/api/src/utils/constants.ts
+++ b/api/src/utils/constants.ts
@@ -1,2 +1,3 @@
export const PER_PAGE = 500;
export const PAGE_NUMBER = 0;
+export const AVATAR_MAX_SIZE = 5 * 1024 * 1024;
diff --git a/web/src/ambient.d.ts b/web/src/ambient.d.ts
index da8dedf..0b676a0 100644
--- a/web/src/ambient.d.ts
+++ b/web/src/ambient.d.ts
@@ -59,3 +59,11 @@ export type ToastType = {
dismissible: boolean;
timeout: number;
};
+
+export type Stats = {
+ id?: string;
+ listCount: number;
+ moviesCount: number;
+ sharedListCount: number;
+ commentsCount: number;
+};
diff --git a/web/src/lib/Avatar.svelte b/web/src/lib/Avatar.svelte
index 11a8dbb..8a82625 100644
--- a/web/src/lib/Avatar.svelte
+++ b/web/src/lib/Avatar.svelte
@@ -1,6 +1,7 @@
+