瀏覽代碼

Upload profile picture and convert into webp

Alex Tran 3 年之前
父節點
當前提交
e33566a04a

+ 2 - 2
server/src/api-v1/asset/asset.controller.ts

@@ -19,7 +19,7 @@ import {
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { AssetService } from './asset.service';
 import { FileFieldsInterceptor } from '@nestjs/platform-express';
-import { multerOption } from '../../config/multer-option.config';
+import { assetUploadOption } from '../../config/asset-upload.config';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 import { CreateAssetDto } from './dto/create-asset.dto';
 import { ServeFileDto } from './dto/serve-file.dto';
@@ -48,7 +48,7 @@ export class AssetController {
         { name: 'assetData', maxCount: 1 },
         { name: 'thumbnailData', maxCount: 1 },
       ],
-      multerOption,
+      assetUploadOption,
     ),
   )
   async uploadFile(

+ 1 - 1
server/src/api-v1/auth/auth.service.ts

@@ -19,7 +19,7 @@ export class AuthService {
   private async validateUser(loginCredential: LoginCredentialDto): Promise<UserEntity> {
     const user = await this.userRepository.findOne(
       { email: loginCredential.email },
-      { select: ['id', 'email', 'password', 'salt', 'firstName', 'lastName', 'isAdmin'] },
+      { select: ['id', 'email', 'password', 'salt', 'firstName', 'lastName', 'isAdmin', 'profileImagePath', 'isFirstLoggedIn'] },
     );
 
     const isAuthenticated = await this.validatePassword(user.password, loginCredential.password, user.salt);

+ 15 - 2
server/src/api-v1/user/user.controller.ts

@@ -1,11 +1,12 @@
-import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, ValidationPipe, Put, Query } from '@nestjs/common';
+import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, ValidationPipe, Put, Query, UseInterceptors, UploadedFile } from '@nestjs/common';
 import { UserService } from './user.service';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 import { CreateUserDto } from './dto/create-user.dto';
 import { AdminRolesGuard } from '../../middlewares/admin-role-guard.middleware';
 import { UpdateUserDto } from './dto/update-user.dto';
-import { boolean } from 'joi';
+import { FileInterceptor } from '@nestjs/platform-express';
+import { profileImageUploadOption } from '../../config/profile-image-upload.config';
 
 @Controller('user')
 export class UserController {
@@ -35,4 +36,16 @@ export class UserController {
   async updateUser(@Body(ValidationPipe) updateUserDto: UpdateUserDto) {
     return await this.userService.updateUser(updateUserDto)
   }
+
+  @UseGuards(JwtAuthGuard)
+  @UseInterceptors(FileInterceptor('file', profileImageUploadOption))
+  @Post('/profile-image')
+  async createProfileImage(@GetAuthUser() authUser: AuthUserDto, @UploadedFile() fileInfo: Express.Multer.File) {
+    return await this.userService.createProfileImage(authUser, fileInfo);
+  }
+
+  @Get('/profile-image/:userId')
+  async getProfileImage(@Param('assetId') assetId: string) {
+
+  }
 }

+ 32 - 1
server/src/api-v1/user/user.service.ts

@@ -6,7 +6,7 @@ import { CreateUserDto } from './dto/create-user.dto';
 import { UpdateUserDto } from './dto/update-user.dto';
 import { UserEntity } from './entities/user.entity';
 import * as bcrypt from 'bcrypt';
-
+import sharp from 'sharp';
 
 @Injectable()
 export class UserService {
@@ -124,4 +124,35 @@ export class UserService {
       throw new InternalServerErrorException('Failed to register new user');
     }
   }
+
+  async createProfileImage(authUser: AuthUserDto, fileInfo: Express.Multer.File) {
+    try {
+      // Convert file to jpeg
+      let filePath = ''
+      const fileSave = await sharp(fileInfo.path).webp().resize(512, 512).toFile(fileInfo.path + '.webp')
+
+      if (fileSave) {
+        filePath = fileInfo.path + '.webp';
+        await this.userRepository.update(authUser.id, {
+          profileImagePath: filePath
+        })
+      } else {
+        filePath = fileInfo.path;
+        await this.userRepository.update(authUser.id, {
+          profileImagePath: filePath
+
+        })
+      }
+
+
+      return {
+        userId: authUser.id,
+        profileImagePath: filePath
+      };
+    } catch (e) {
+      Logger.error(e, 'Create User Profile Image');
+      throw new InternalServerErrorException('Failed to create new user profile image');
+    }
+
+  }
 }

+ 6 - 4
server/src/config/multer-option.config.ts → server/src/config/asset-upload.config.ts

@@ -8,7 +8,7 @@ import { APP_UPLOAD_LOCATION } from '../constants/upload_location.constant';
 import { randomUUID } from 'crypto';
 import { CreateAssetDto } from '../api-v1/asset/dto/create-asset.dto';
 
-export const multerOption: MulterOptions = {
+export const assetUploadOption: MulterOptions = {
   fileFilter: (req: Request, file: any, cb: any) => {
     if (file.mimetype.match(/\/(jpg|jpeg|png|gif|mp4|x-msvideo|quicktime|heic|heif|dng|webp)$/)) {
       cb(null, true);
@@ -19,14 +19,14 @@ export const multerOption: MulterOptions = {
 
   storage: diskStorage({
     destination: (req: Request, file: Express.Multer.File, cb: any) => {
-      const uploadPath = APP_UPLOAD_LOCATION;
+      const basePath = APP_UPLOAD_LOCATION;
       const fileInfo = req.body as CreateAssetDto;
 
       const yearInfo = new Date(fileInfo.createdAt).getFullYear();
       const monthInfo = new Date(fileInfo.createdAt).getMonth();
 
       if (file.fieldname == 'assetData') {
-        const originalUploadFolder = `${uploadPath}/${req.user['id']}/original/${req.body['deviceId']}`;
+        const originalUploadFolder = `${basePath}/${req.user['id']}/original/${req.body['deviceId']}`;
 
         if (!existsSync(originalUploadFolder)) {
           mkdirSync(originalUploadFolder, { recursive: true });
@@ -35,7 +35,7 @@ export const multerOption: MulterOptions = {
         // Save original to disk
         cb(null, originalUploadFolder);
       } else if (file.fieldname == 'thumbnailData') {
-        const thumbnailUploadFolder = `${uploadPath}/${req.user['id']}/thumb/${req.body['deviceId']}`;
+        const thumbnailUploadFolder = `${basePath}/${req.user['id']}/thumb/${req.body['deviceId']}`;
 
         if (!existsSync(thumbnailUploadFolder)) {
           mkdirSync(thumbnailUploadFolder, { recursive: true });
@@ -56,3 +56,5 @@ export const multerOption: MulterOptions = {
     },
   }),
 };
+
+

+ 38 - 0
server/src/config/profile-image-upload.config.ts

@@ -0,0 +1,38 @@
+import { HttpException, HttpStatus } from '@nestjs/common';
+import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
+import { existsSync, mkdirSync } from 'fs';
+import { diskStorage } from 'multer';
+import { extname } from 'path';
+import { Request } from 'express';
+import { APP_UPLOAD_LOCATION } from '../constants/upload_location.constant';
+
+export const profileImageUploadOption: MulterOptions = {
+  fileFilter: (req: Request, file: any, cb: any) => {
+    if (file.mimetype.match(/\/(jpg|jpeg|png|heic|heif|dng|webp)$/)) {
+      cb(null, true);
+    } else {
+      cb(new HttpException(`Unsupported file type ${extname(file.originalname)}`, HttpStatus.BAD_REQUEST), false);
+    }
+  },
+
+  storage: diskStorage({
+    destination: (req: Request, file: Express.Multer.File, cb: any) => {
+      const basePath = APP_UPLOAD_LOCATION;
+      const profileImageLocation = `${basePath}/${req.user['id']}/profile`;
+      if (!existsSync(profileImageLocation)) {
+        mkdirSync(profileImageLocation, { recursive: true });
+      }
+
+
+      cb(null, profileImageLocation);
+    },
+
+    filename: (req: Request, file: Express.Multer.File, cb: any) => {
+      const userId = req.user['id'];
+
+      cb(null, `${userId}`);
+    },
+  }),
+};
+
+