import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Redirect, Render, Req, Res, Session, UnauthorizedException, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { Request, Response } from 'express'; import { SessionData } from 'express-session'; import { unlink } from 'fs/promises'; import { OAuth2ClientService } from 'src/modules/objects/oauth2-client/oauth2-client.service'; import { UploadService } from 'src/modules/objects/upload/upload.service'; import { UserService } from 'src/modules/objects/user/user.service'; import { FormUtilityService } from 'src/modules/utility/services/form-utility.service'; import { SettingsService } from './settings.service'; @Controller('/account') export class SettingsController { constructor( private readonly _service: SettingsService, private readonly _form: FormUtilityService, private readonly _upload: UploadService, private readonly _user: UserService, private readonly _client: OAuth2ClientService, ) {} @Get() @Redirect('/account/general') public redirectGeneral() { return; } @Get('general') @Render('settings/general') public general(@Req() req: Request) { return this._form.populateTemplate(req, { user: req.user }); } @Post('general') public async updateDisplayName( @Req() req: Request, @Res() res: Response, @Body() body: { display_name?: string }, ) { try { const { display_name } = body; if (!display_name) { throw new Error('Display name is required.'); } if (display_name.length < 3 || display_name.length > 32) { throw new Error( 'Display name must be between 3 and 32 characters long.', ); } req.user.display_name = display_name; await this._user.updateUser(req.user); req.flash('message', { error: false, text: 'Display name has been changed!', }); } catch (e: any) { req.flash('message', { error: true, text: e.message, }); } res.redirect('/account/general'); } @Post('avatar') @UseInterceptors(FileInterceptor('file')) async uploadAvatarFile( @Req() req: Request, @UploadedFile() file: Express.Multer.File, ) { if (req.body.csrf !== req.session.csrf) { throw new BadRequestException('Invalid session. Please try again.'); } if (!file) { throw new BadRequestException('Avatar upload failed'); } try { const matches = await this._upload.checkImageAspect(file); if (!matches) { throw new BadRequestException( 'Avatar should be with a 1:1 aspect ratio.', ); } } catch (e) { await unlink(file.path); throw e; } const upload = await this._upload.registerUploadedFile(file, req.user); await this._user.updateAvatar(req.user, upload); return { file: upload.file, }; } @Post('avatar/delete') public async deleteUserAvatar(@Req() req: Request, @Res() res: Response) { this._user.deleteAvatar(req.user); req.flash('message', { error: false, text: 'Avatar removed successfully.', }); res.redirect('/account/general'); } @Get('oauth2') @Render('settings/oauth2') public async authorizations(@Req() req: Request) { const authorizations = await this._client.getAuthorizations(req.user); return this._form.populateTemplate(req, { authorizations }); } @Post('oauth2/revoke/:id') public async revokeAuthorization( @Req() req: Request, @Res() res: Response, @Param('id') id: number, ) { const getAuth = await this._client.getAuthorization(req.user, id); const jsreq = req.header('content-type').startsWith('application/json') || req.header('accept').startsWith('application/json'); if (!getAuth) { if (jsreq) { throw new UnauthorizedException( 'Unauthorized or invalid revokation request', ); } req.flash('message', { error: true, text: 'Unauthorized revokation.', }); res.redirect('/account/oauth2'); return; } await this._client.revokeAuthorization(getAuth); if (jsreq) { return res.json({ success: true }); } res.redirect('/account/oauth2'); } }