mirror of
https://github.com/immich-app/immich.git
synced 2024-12-22 01:47:08 +02:00
Upload profile picture and convert into webp
This commit is contained in:
parent
c28251b8b4
commit
e33566a04a
@ -19,7 +19,7 @@ import {
|
|||||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||||
import { AssetService } from './asset.service';
|
import { AssetService } from './asset.service';
|
||||||
import { FileFieldsInterceptor } from '@nestjs/platform-express';
|
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 { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
import { ServeFileDto } from './dto/serve-file.dto';
|
import { ServeFileDto } from './dto/serve-file.dto';
|
||||||
@ -48,7 +48,7 @@ export class AssetController {
|
|||||||
{ name: 'assetData', maxCount: 1 },
|
{ name: 'assetData', maxCount: 1 },
|
||||||
{ name: 'thumbnailData', maxCount: 1 },
|
{ name: 'thumbnailData', maxCount: 1 },
|
||||||
],
|
],
|
||||||
multerOption,
|
assetUploadOption,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async uploadFile(
|
async uploadFile(
|
||||||
|
@ -19,7 +19,7 @@ export class AuthService {
|
|||||||
private async validateUser(loginCredential: LoginCredentialDto): Promise<UserEntity> {
|
private async validateUser(loginCredential: LoginCredentialDto): Promise<UserEntity> {
|
||||||
const user = await this.userRepository.findOne(
|
const user = await this.userRepository.findOne(
|
||||||
{ email: loginCredential.email },
|
{ 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);
|
const isAuthenticated = await this.validatePassword(user.password, loginCredential.password, user.salt);
|
||||||
|
@ -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 { UserService } from './user.service';
|
||||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
||||||
import { CreateUserDto } from './dto/create-user.dto';
|
import { CreateUserDto } from './dto/create-user.dto';
|
||||||
import { AdminRolesGuard } from '../../middlewares/admin-role-guard.middleware';
|
import { AdminRolesGuard } from '../../middlewares/admin-role-guard.middleware';
|
||||||
import { UpdateUserDto } from './dto/update-user.dto';
|
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')
|
@Controller('user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
@ -35,4 +36,16 @@ export class UserController {
|
|||||||
async updateUser(@Body(ValidationPipe) updateUserDto: UpdateUserDto) {
|
async updateUser(@Body(ValidationPipe) updateUserDto: UpdateUserDto) {
|
||||||
return await this.userService.updateUser(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) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { CreateUserDto } from './dto/create-user.dto';
|
|||||||
import { UpdateUserDto } from './dto/update-user.dto';
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
import { UserEntity } from './entities/user.entity';
|
import { UserEntity } from './entities/user.entity';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
@ -124,4 +124,35 @@ export class UserService {
|
|||||||
throw new InternalServerErrorException('Failed to register new user');
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { APP_UPLOAD_LOCATION } from '../constants/upload_location.constant';
|
|||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { CreateAssetDto } from '../api-v1/asset/dto/create-asset.dto';
|
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) => {
|
fileFilter: (req: Request, file: any, cb: any) => {
|
||||||
if (file.mimetype.match(/\/(jpg|jpeg|png|gif|mp4|x-msvideo|quicktime|heic|heif|dng|webp)$/)) {
|
if (file.mimetype.match(/\/(jpg|jpeg|png|gif|mp4|x-msvideo|quicktime|heic|heif|dng|webp)$/)) {
|
||||||
cb(null, true);
|
cb(null, true);
|
||||||
@ -19,14 +19,14 @@ export const multerOption: MulterOptions = {
|
|||||||
|
|
||||||
storage: diskStorage({
|
storage: diskStorage({
|
||||||
destination: (req: Request, file: Express.Multer.File, cb: any) => {
|
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 fileInfo = req.body as CreateAssetDto;
|
||||||
|
|
||||||
const yearInfo = new Date(fileInfo.createdAt).getFullYear();
|
const yearInfo = new Date(fileInfo.createdAt).getFullYear();
|
||||||
const monthInfo = new Date(fileInfo.createdAt).getMonth();
|
const monthInfo = new Date(fileInfo.createdAt).getMonth();
|
||||||
|
|
||||||
if (file.fieldname == 'assetData') {
|
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)) {
|
if (!existsSync(originalUploadFolder)) {
|
||||||
mkdirSync(originalUploadFolder, { recursive: true });
|
mkdirSync(originalUploadFolder, { recursive: true });
|
||||||
@ -35,7 +35,7 @@ export const multerOption: MulterOptions = {
|
|||||||
// Save original to disk
|
// Save original to disk
|
||||||
cb(null, originalUploadFolder);
|
cb(null, originalUploadFolder);
|
||||||
} else if (file.fieldname == 'thumbnailData') {
|
} 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)) {
|
if (!existsSync(thumbnailUploadFolder)) {
|
||||||
mkdirSync(thumbnailUploadFolder, { recursive: true });
|
mkdirSync(thumbnailUploadFolder, { recursive: true });
|
||||||
@ -56,3 +56,5 @@ export const multerOption: MulterOptions = {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
38
server/src/config/profile-image-upload.config.ts
Normal file
38
server/src/config/profile-image-upload.config.ts
Normal file
@ -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}`);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user