chore(server): cleanup controllers (#2065)

This commit is contained in:
Jason Rasmussen 2023-03-24 00:53:56 -04:00 committed by GitHub
parent 6745826f35
commit 54f98053a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 93 additions and 84 deletions

View File

@ -22,7 +22,7 @@ import { Authenticated } from '../decorators/authenticated.decorator';
@ApiTags('Authentication') @ApiTags('Authentication')
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
constructor(private readonly authService: AuthService) {} constructor(private readonly service: AuthService) {}
@Post('login') @Post('login')
async login( async login(
@ -31,8 +31,8 @@ export class AuthController {
@Req() req: Request, @Req() req: Request,
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
): Promise<LoginResponseDto> { ): Promise<LoginResponseDto> {
const { response, cookie } = await this.authService.login(loginCredential, clientIp, req.secure); const { response, cookie } = await this.service.login(loginCredential, clientIp, req.secure);
res.setHeader('Set-Cookie', cookie); res.header('Set-Cookie', cookie);
return response; return response;
} }
@ -41,25 +41,24 @@ export class AuthController {
adminSignUp( adminSignUp(
@Body(new ValidationPipe({ transform: true })) signUpCredential: SignUpDto, @Body(new ValidationPipe({ transform: true })) signUpCredential: SignUpDto,
): Promise<AdminSignupResponseDto> { ): Promise<AdminSignupResponseDto> {
return this.authService.adminSignUp(signUpCredential); return this.service.adminSignUp(signUpCredential);
} }
@Authenticated() @Authenticated()
@Post('validateToken') @Post('validateToken')
// eslint-disable-next-line @typescript-eslint/no-unused-vars validateAccessToken(): ValidateAccessTokenResponseDto {
validateAccessToken(@GetAuthUser() authUser: AuthUserDto): ValidateAccessTokenResponseDto {
return { authStatus: true }; return { authStatus: true };
} }
@Authenticated() @Authenticated()
@Post('change-password') @Post('change-password')
async changePassword(@GetAuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> { changePassword(@GetAuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> {
return this.authService.changePassword(authUser, dto); return this.service.changePassword(authUser, dto);
} }
@Authenticated() @Authenticated()
@Post('logout') @Post('logout')
async logout( logout(
@Req() req: Request, @Req() req: Request,
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@ -69,6 +68,6 @@ export class AuthController {
res.clearCookie(IMMICH_ACCESS_COOKIE); res.clearCookie(IMMICH_ACCESS_COOKIE);
res.clearCookie(IMMICH_AUTH_TYPE_COOKIE); res.clearCookie(IMMICH_AUTH_TYPE_COOKIE);
return this.authService.logout(authUser, authType); return this.service.logout(authUser, authType);
} }
} }

View File

@ -9,9 +9,9 @@ import { ApiTags } from '@nestjs/swagger';
import { GetAuthUser } from '../decorators/auth-user.decorator'; import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator'; import { Authenticated } from '../decorators/authenticated.decorator';
@Authenticated()
@ApiTags('Device Info') @ApiTags('Device Info')
@Controller('device-info') @Controller('device-info')
@Authenticated()
export class DeviceInfoController { export class DeviceInfoController {
constructor(private readonly service: DeviceInfoService) {} constructor(private readonly service: DeviceInfoService) {}

View File

@ -3,19 +3,19 @@ import { Body, Controller, Get, Param, Put, ValidationPipe } from '@nestjs/commo
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { Authenticated } from '../decorators/authenticated.decorator'; import { Authenticated } from '../decorators/authenticated.decorator';
@Authenticated({ admin: true })
@ApiTags('Job') @ApiTags('Job')
@Controller('jobs') @Controller('jobs')
@Authenticated({ admin: true })
export class JobController { export class JobController {
constructor(private readonly jobService: JobService) {} constructor(private service: JobService) {}
@Get() @Get()
getAllJobsStatus(): Promise<AllJobStatusResponseDto> { getAllJobsStatus(): Promise<AllJobStatusResponseDto> {
return this.jobService.getAllJobsStatus(); return this.service.getAllJobsStatus();
} }
@Put('/:jobId') @Put('/:jobId')
sendJobCommand(@Param(ValidationPipe) { jobId }: JobIdDto, @Body(ValidationPipe) dto: JobCommandDto): Promise<void> { sendJobCommand(@Param(ValidationPipe) { jobId }: JobIdDto, @Body(ValidationPipe) dto: JobCommandDto): Promise<void> {
return this.jobService.handleCommand(jobId, dto); return this.service.handleCommand(jobId, dto);
} }
} }

View File

@ -1,7 +1,6 @@
import { import {
AuthUserDto, AuthUserDto,
LoginResponseDto, LoginResponseDto,
MOBILE_REDIRECT,
OAuthCallbackDto, OAuthCallbackDto,
OAuthConfigDto, OAuthConfigDto,
OAuthConfigResponseDto, OAuthConfigResponseDto,
@ -17,43 +16,42 @@ import { Authenticated } from '../decorators/authenticated.decorator';
@ApiTags('OAuth') @ApiTags('OAuth')
@Controller('oauth') @Controller('oauth')
export class OAuthController { export class OAuthController {
constructor(private readonly oauthService: OAuthService) {} constructor(private service: OAuthService) {}
@Get('mobile-redirect') @Get('mobile-redirect')
@Redirect() @Redirect()
public mobileRedirect(@Req() req: Request) { mobileRedirect(@Req() req: Request) {
const url = `${MOBILE_REDIRECT}?${req.url.split('?')[1] || ''}`; return {
return { url, statusCode: HttpStatus.TEMPORARY_REDIRECT }; url: this.service.getMobileRedirect(req.url),
statusCode: HttpStatus.TEMPORARY_REDIRECT,
};
} }
@Post('config') @Post('config')
public generateConfig(@Body(ValidationPipe) dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> { generateConfig(@Body(ValidationPipe) dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> {
return this.oauthService.generateConfig(dto); return this.service.generateConfig(dto);
} }
@Post('callback') @Post('callback')
public async callback( async callback(
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
@Body(ValidationPipe) dto: OAuthCallbackDto, @Body(ValidationPipe) dto: OAuthCallbackDto,
@Req() req: Request, @Req() req: Request,
): Promise<LoginResponseDto> { ): Promise<LoginResponseDto> {
const { response, cookie } = await this.oauthService.login(dto, req.secure); const { response, cookie } = await this.service.login(dto, req.secure);
res.setHeader('Set-Cookie', cookie); res.header('Set-Cookie', cookie);
return response; return response;
} }
@Authenticated() @Authenticated()
@Post('link') @Post('link')
public async link( link(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) dto: OAuthCallbackDto): Promise<UserResponseDto> {
@GetAuthUser() authUser: AuthUserDto, return this.service.link(authUser, dto);
@Body(ValidationPipe) dto: OAuthCallbackDto,
): Promise<UserResponseDto> {
return this.oauthService.link(authUser, dto);
} }
@Authenticated() @Authenticated()
@Post('unlink') @Post('unlink')
public async unlink(@GetAuthUser() authUser: AuthUserDto): Promise<UserResponseDto> { unlink(@GetAuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
return this.oauthService.unlink(authUser); return this.service.unlink(authUser);
} }
} }

View File

@ -12,26 +12,26 @@ import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator'; import { Authenticated } from '../decorators/authenticated.decorator';
@ApiTags('Search') @ApiTags('Search')
@Authenticated()
@Controller('search') @Controller('search')
@Authenticated()
export class SearchController { export class SearchController {
constructor(private readonly searchService: SearchService) {} constructor(private service: SearchService) {}
@Get() @Get()
async search( search(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Query(new ValidationPipe({ transform: true })) dto: SearchDto, @Query(new ValidationPipe({ transform: true })) dto: SearchDto,
): Promise<SearchResponseDto> { ): Promise<SearchResponseDto> {
return this.searchService.search(authUser, dto); return this.service.search(authUser, dto);
} }
@Get('config') @Get('config')
getSearchConfig(): SearchConfigResponseDto { getSearchConfig(): SearchConfigResponseDto {
return this.searchService.getConfig(); return this.service.getConfig();
} }
@Get('explore') @Get('explore')
getExploreData(@GetAuthUser() authUser: AuthUserDto): Promise<SearchExploreResponseDto[]> { getExploreData(@GetAuthUser() authUser: AuthUserDto): Promise<SearchExploreResponseDto[]> {
return this.searchService.getExploreData(authUser) as Promise<SearchExploreResponseDto[]>; return this.service.getExploreData(authUser) as Promise<SearchExploreResponseDto[]>;
} }
} }

View File

@ -12,7 +12,7 @@ import { Authenticated } from '../decorators/authenticated.decorator';
@ApiTags('Server Info') @ApiTags('Server Info')
@Controller('server-info') @Controller('server-info')
export class ServerInfoController { export class ServerInfoController {
constructor(private readonly service: ServerInfoService) {} constructor(private service: ServerInfoService) {}
@Get() @Get()
getServerInfo(): Promise<ServerInfoResponseDto> { getServerInfo(): Promise<ServerInfoResponseDto> {

View File

@ -1,35 +1,36 @@
import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, ShareService } from '@app/domain';
import { Body, Controller, Delete, Get, Param, Patch, ValidationPipe } from '@nestjs/common'; import { Body, Controller, Delete, Get, Param, Patch, ValidationPipe } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { GetAuthUser } from '../decorators/auth-user.decorator'; import { GetAuthUser } from '../decorators/auth-user.decorator';
import { Authenticated } from '../decorators/authenticated.decorator'; import { Authenticated } from '../decorators/authenticated.decorator';
import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, ShareService } from '@app/domain';
@ApiTags('share') @ApiTags('share')
@Controller('share') @Controller('share')
export class ShareController { export class ShareController {
constructor(private readonly shareService: ShareService) {} constructor(private readonly service: ShareService) {}
@Authenticated() @Authenticated()
@Get() @Get()
getAllSharedLinks(@GetAuthUser() authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> { getAllSharedLinks(@GetAuthUser() authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
return this.shareService.getAll(authUser); return this.service.getAll(authUser);
} }
@Authenticated({ isShared: true }) @Authenticated({ isShared: true })
@Get('me') @Get('me')
getMySharedLink(@GetAuthUser() authUser: AuthUserDto): Promise<SharedLinkResponseDto> { getMySharedLink(@GetAuthUser() authUser: AuthUserDto): Promise<SharedLinkResponseDto> {
return this.shareService.getMine(authUser); return this.service.getMine(authUser);
} }
@Authenticated() @Authenticated()
@Get(':id') @Get(':id')
getSharedLinkById(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<SharedLinkResponseDto> { getSharedLinkById(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<SharedLinkResponseDto> {
return this.shareService.getById(authUser, id, true); return this.service.getById(authUser, id, true);
} }
@Authenticated() @Authenticated()
@Delete(':id') @Delete(':id')
removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<void> { removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<void> {
return this.shareService.remove(authUser, id); return this.service.remove(authUser, id);
} }
@Authenticated() @Authenticated()
@ -37,8 +38,8 @@ export class ShareController {
editSharedLink( editSharedLink(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Param('id') id: string, @Param('id') id: string,
@Body(new ValidationPipe()) dto: EditSharedLinkDto, @Body(ValidationPipe) dto: EditSharedLinkDto,
): Promise<SharedLinkResponseDto> { ): Promise<SharedLinkResponseDto> {
return this.shareService.edit(authUser, id, dto); return this.service.edit(authUser, id, dto);
} }
} }

View File

@ -4,28 +4,28 @@ import { ApiTags } from '@nestjs/swagger';
import { Authenticated } from '../decorators/authenticated.decorator'; import { Authenticated } from '../decorators/authenticated.decorator';
@ApiTags('System Config') @ApiTags('System Config')
@Authenticated({ admin: true })
@Controller('system-config') @Controller('system-config')
@Authenticated({ admin: true })
export class SystemConfigController { export class SystemConfigController {
constructor(private readonly systemConfigService: SystemConfigService) {} constructor(private readonly service: SystemConfigService) {}
@Get() @Get()
public getConfig(): Promise<SystemConfigDto> { getConfig(): Promise<SystemConfigDto> {
return this.systemConfigService.getConfig(); return this.service.getConfig();
} }
@Get('defaults') @Get('defaults')
public getDefaults(): SystemConfigDto { getDefaults(): SystemConfigDto {
return this.systemConfigService.getDefaults(); return this.service.getDefaults();
} }
@Put() @Put()
public updateConfig(@Body(ValidationPipe) dto: SystemConfigDto): Promise<SystemConfigDto> { updateConfig(@Body(ValidationPipe) dto: SystemConfigDto): Promise<SystemConfigDto> {
return this.systemConfigService.updateConfig(dto); return this.service.updateConfig(dto);
} }
@Get('storage-template-options') @Get('storage-template-options')
public getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
return this.systemConfigService.getStorageTemplateOptions(); return this.service.getStorageTemplateOptions();
} }
} }

View File

@ -33,60 +33,58 @@ import { UserCountDto } from '@app/domain';
@ApiTags('User') @ApiTags('User')
@Controller('user') @Controller('user')
export class UserController { export class UserController {
constructor(private readonly userService: UserService) {} constructor(private service: UserService) {}
@Authenticated() @Authenticated()
@Get() @Get()
async getAllUsers( getAllUsers(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Query('isAll', ParseBoolPipe) isAll: boolean, @Query('isAll', ParseBoolPipe) isAll: boolean,
): Promise<UserResponseDto[]> { ): Promise<UserResponseDto[]> {
return await this.userService.getAllUsers(authUser, isAll); return this.service.getAllUsers(authUser, isAll);
} }
@Get('/info/:userId') @Get('/info/:userId')
async getUserById(@Param('userId') userId: string): Promise<UserResponseDto> { getUserById(@Param('userId') userId: string): Promise<UserResponseDto> {
return await this.userService.getUserById(userId); return this.service.getUserById(userId);
} }
@Authenticated() @Authenticated()
@Get('me') @Get('me')
async getMyUserInfo(@GetAuthUser() authUser: AuthUserDto): Promise<UserResponseDto> { getMyUserInfo(@GetAuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
return await this.userService.getUserInfo(authUser); return this.service.getUserInfo(authUser);
} }
@Authenticated({ admin: true }) @Authenticated({ admin: true })
@Post() @Post()
async createUser( createUser(@Body(new ValidationPipe({ transform: true })) createUserDto: CreateUserDto): Promise<UserResponseDto> {
@Body(new ValidationPipe({ transform: true })) createUserDto: CreateUserDto, return this.service.createUser(createUserDto);
): Promise<UserResponseDto> {
return await this.userService.createUser(createUserDto);
} }
@Get('/count') @Get('/count')
async getUserCount(@Query(new ValidationPipe({ transform: true })) dto: UserCountDto): Promise<UserCountResponseDto> { getUserCount(@Query(new ValidationPipe({ transform: true })) dto: UserCountDto): Promise<UserCountResponseDto> {
return await this.userService.getUserCount(dto); return this.service.getUserCount(dto);
} }
@Authenticated({ admin: true }) @Authenticated({ admin: true })
@Delete('/:userId') @Delete('/:userId')
async deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise<UserResponseDto> { deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise<UserResponseDto> {
return await this.userService.deleteUser(authUser, userId); return this.service.deleteUser(authUser, userId);
} }
@Authenticated({ admin: true }) @Authenticated({ admin: true })
@Post('/:userId/restore') @Post('/:userId/restore')
async restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise<UserResponseDto> { restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise<UserResponseDto> {
return await this.userService.restoreUser(authUser, userId); return this.service.restoreUser(authUser, userId);
} }
@Authenticated() @Authenticated()
@Put() @Put()
async updateUser( updateUser(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) updateUserDto: UpdateUserDto, @Body(ValidationPipe) updateUserDto: UpdateUserDto,
): Promise<UserResponseDto> { ): Promise<UserResponseDto> {
return await this.userService.updateUser(authUser, updateUserDto); return this.service.updateUser(authUser, updateUserDto);
} }
@UseInterceptors(FileInterceptor('file', profileImageUploadOption)) @UseInterceptors(FileInterceptor('file', profileImageUploadOption))
@ -97,20 +95,18 @@ export class UserController {
type: CreateProfileImageDto, type: CreateProfileImageDto,
}) })
@Post('/profile-image') @Post('/profile-image')
async createProfileImage( createProfileImage(
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@UploadedFile() fileInfo: Express.Multer.File, @UploadedFile() fileInfo: Express.Multer.File,
): Promise<CreateProfileImageResponseDto> { ): Promise<CreateProfileImageResponseDto> {
return await this.userService.createProfileImage(authUser, fileInfo); return this.service.createProfileImage(authUser, fileInfo);
} }
@Get('/profile-image/:userId') @Get('/profile-image/:userId')
@Header('Cache-Control', 'max-age=600') @Header('Cache-Control', 'max-age=600')
async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise<any> { async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise<any> {
const readableStream = await this.userService.getUserProfileImage(userId); const readableStream = await this.service.getUserProfileImage(userId);
res.set({ res.header('Content-Type', 'image/jpeg');
'Content-Type': 'image/jpeg',
});
return new StreamableFile(readableStream); return new StreamableFile(readableStream);
} }
} }

View File

@ -63,6 +63,16 @@ describe('OAuthService', () => {
expect(sut).toBeDefined(); expect(sut).toBeDefined();
}); });
describe('getMobileRedirect', () => {
it('should pass along the query params', () => {
expect(sut.getMobileRedirect('http://immich.app?code=123&state=456')).toEqual('app.immich:/?code=123&state=456');
});
it('should work if called without query params', () => {
expect(sut.getMobileRedirect('http://immich.app')).toEqual('app.immich:/?');
});
});
describe('generateConfig', () => { describe('generateConfig', () => {
it('should work when oauth is not configured', async () => { it('should work when oauth is not configured', async () => {
await expect(sut.generateConfig({ redirectUri: 'http://callback' })).resolves.toEqual({ await expect(sut.generateConfig({ redirectUri: 'http://callback' })).resolves.toEqual({

View File

@ -1,14 +1,15 @@
import { SystemConfig } from '@app/infra/db/entities'; import { SystemConfig } from '@app/infra/db/entities';
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
import { AuthType, AuthUserDto, LoginResponseDto } from '../auth'; import { AuthType, AuthUserDto, LoginResponseDto } from '../auth';
import { ICryptoRepository } from '../crypto';
import { AuthCore } from '../auth/auth.core'; import { AuthCore } from '../auth/auth.core';
import { ICryptoRepository } from '../crypto';
import { INITIAL_SYSTEM_CONFIG, ISystemConfigRepository } from '../system-config'; import { INITIAL_SYSTEM_CONFIG, ISystemConfigRepository } from '../system-config';
import { IUserRepository, UserCore, UserResponseDto } from '../user'; import { IUserRepository, UserCore, UserResponseDto } from '../user';
import { IUserTokenRepository } from '../user-token';
import { OAuthCallbackDto, OAuthConfigDto } from './dto'; import { OAuthCallbackDto, OAuthConfigDto } from './dto';
import { MOBILE_REDIRECT } from './oauth.constants';
import { OAuthCore } from './oauth.core'; import { OAuthCore } from './oauth.core';
import { OAuthConfigResponseDto } from './response-dto'; import { OAuthConfigResponseDto } from './response-dto';
import { IUserTokenRepository } from '../user-token';
@Injectable() @Injectable()
export class OAuthService { export class OAuthService {
@ -30,6 +31,10 @@ export class OAuthService {
this.oauthCore = new OAuthCore(configRepository, initialConfig); this.oauthCore = new OAuthCore(configRepository, initialConfig);
} }
getMobileRedirect(url: string) {
return `${MOBILE_REDIRECT}?${url.split('?')[1] || ''}`;
}
generateConfig(dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> { generateConfig(dto: OAuthConfigDto): Promise<OAuthConfigResponseDto> {
return this.oauthCore.generateConfig(dto); return this.oauthCore.generateConfig(dto);
} }