diff --git a/back/src/auth/auth.controller.ts b/back/src/auth/auth.controller.ts index d4320bf..bfc9463 100644 --- a/back/src/auth/auth.controller.ts +++ b/back/src/auth/auth.controller.ts @@ -44,16 +44,16 @@ export class AuthController { private settingsService: SettingsService, ) {} - @Get("login/google") + @Get('login/google') @UseGuards(AuthGuard('google')) - googleLogin() { } + googleLogin() {} - @Get("logged/google") + @Get('logged/google') @UseGuards(AuthGuard('google')) async googleLoginCallbakc(@Req() req: any) { - let user = await this.usersService.user({googleID: req.user.googleID}); + let user = await this.usersService.user({ googleID: req.user.googleID }); if (!user) { - user = await this.usersService.createUser(req.user) + user = await this.usersService.createUser(req.user); await this.settingsService.createUserSetting(user.id); } return this.authService.login(user); @@ -62,9 +62,9 @@ export class AuthController { @Post('register') async register(@Body() registerDto: RegisterDto): Promise { try { - const user = await this.usersService.createUser(registerDto) + const user = await this.usersService.createUser(registerDto); await this.settingsService.createUserSetting(user.id); - } catch(e) { + } catch (e) { console.error(e); throw new BadRequestException(); } @@ -86,6 +86,15 @@ export class AuthController { return this.authService.login(user); } + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOkResponse({ description: 'The user profile picture' }) + @ApiUnauthorizedResponse({ description: 'Invalid token' }) + @Get('me/picture') + async getProfilePicture(@Request() req: any) { + return await this.usersService.getProfilePicture(req.user.id); + } + @UseGuards(JwtAuthGuard) @ApiBearerAuth() @ApiOkResponse({ description: 'Successfully logged in', type: User }) @@ -133,25 +142,28 @@ export class AuthController { @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({description: 'Successfully edited settings', type: Setting}) - @ApiUnauthorizedResponse({description: 'Invalid token'}) + @ApiOkResponse({ description: 'Successfully edited settings', type: Setting }) + @ApiUnauthorizedResponse({ description: 'Invalid token' }) @Patch('me/settings') udpateSettings( @Request() req: any, - @Body() settingUserDto: UpdateSettingDto): Promise { + @Body() settingUserDto: UpdateSettingDto, + ): Promise { return this.settingsService.updateUserSettings({ - where: { userId: +req.user.id}, + where: { userId: +req.user.id }, data: settingUserDto, }); } @UseGuards(JwtAuthGuard) @ApiBearerAuth() - @ApiOkResponse({description: 'Successfully edited settings', type: Setting}) - @ApiUnauthorizedResponse({description: 'Invalid token'}) + @ApiOkResponse({ description: 'Successfully edited settings', type: Setting }) + @ApiUnauthorizedResponse({ description: 'Invalid token' }) @Get('me/settings') async getSettings(@Request() req: any): Promise { - const result = await this.settingsService.getUserSetting({ userId: +req.user.id }); + const result = await this.settingsService.getUserSetting({ + userId: +req.user.id, + }); if (!result) throw new NotFoundException(); return result; } diff --git a/back/src/users/users.controller.ts b/back/src/users/users.controller.ts index 257ebba..91e2975 100644 --- a/back/src/users/users.controller.ts +++ b/back/src/users/users.controller.ts @@ -20,4 +20,9 @@ export class UsersController { if (!ret) throw new NotFoundException(); return ret; } + + @Get(':id/picture') + async getPicture(@Param('id') id: number) { + return await this.usersService.getProfilePicture(+id); + } } diff --git a/back/src/users/users.service.ts b/back/src/users/users.service.ts index 1cbfc20..738dc0a 100644 --- a/back/src/users/users.service.ts +++ b/back/src/users/users.service.ts @@ -1,8 +1,14 @@ -import { Injectable } from '@nestjs/common'; +import { + Injectable, + InternalServerErrorException, + NotFoundException, + StreamableFile, +} from '@nestjs/common'; import { User, Prisma } from '@prisma/client'; import { PrismaService } from 'src/prisma/prisma.service'; import * as bcrypt from 'bcryptjs'; -import { randomUUID } from 'crypto'; +import { createHash, randomUUID } from 'crypto'; +import { createReadStream, existsSync } from 'fs'; @Injectable() export class UsersService { @@ -34,8 +40,7 @@ export class UsersService { } async createUser(data: Prisma.UserCreateInput): Promise { - if (data.password) - data.password = await bcrypt.hash(data.password, 8); + if (data.password) data.password = await bcrypt.hash(data.password, 8); return this.prisma.user.create({ data, }); @@ -73,4 +78,23 @@ export class UsersService { where, }); } + + async getProfilePicture(userId: number) { + const path = `/data/${userId}.png`; + if (existsSync(path)) { + const file = createReadStream(path); + return new StreamableFile(file); + } + // We could not find a profile icon locally, using gravatar instead. + const user = await this.user({ id: userId }); + if (!user) throw new InternalServerErrorException(); + const hash = createHash('md5') + .update(user.email.trim().toLowerCase()) + .digest('hex'); + const resp = await fetch( + `https://www.gravatar.com/avatar/${hash}.jpg?d=404`, + ); + if (!resp.ok) throw new NotFoundException('No image found for user'); + return resp.arrayBuffer(); + } } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c6e925e..e011bb6 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -9,6 +9,7 @@ services: volumes: - ./back:/app - ./assets:/assets + - data:/data depends_on: db: condition: service_healthy @@ -54,3 +55,6 @@ services: - "back" env_file: - .env + +volumes: + data: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 64cca51..bd901c4 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -10,6 +10,7 @@ services: - .env volumes: - ./assets:/assets + - data:/data scorometer: image: ghcr.io/chroma-case/scorometer:main ports: @@ -43,3 +44,6 @@ services: - "back" env_file: - .env + +volumes: + data: diff --git a/docker-compose.yml b/docker-compose.yml index aefda37..3ddf329 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - .env volumes: - ./assets:/assets + - data:/data scorometer: build: ./scorometer ports: @@ -52,3 +53,4 @@ services: volumes: db: + data: