chore: move controllers and middleware (#8119)

This commit is contained in:
Jason Rasmussen 2024-03-20 15:15:01 -05:00 committed by GitHub
parent 81f0265095
commit 40e079a247
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 55 additions and 60 deletions

View file

@ -0,0 +1,50 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Query, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import {
ActivityCreateDto,
ActivityDto,
ActivityResponseDto,
ActivitySearchDto,
ActivityStatisticsResponseDto,
} from 'src/domain/activity/activity.dto';
import { ActivityService } from 'src/domain/activity/activity.service';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Activity')
@Controller('activity')
@Authenticated()
export class ActivityController {
constructor(private service: ActivityService) {}
@Get()
getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
return this.service.getAll(auth, dto);
}
@Get('statistics')
getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
return this.service.getStatistics(auth, dto);
}
@Post()
async createActivity(
@Auth() auth: AuthDto,
@Body() dto: ActivityCreateDto,
@Res({ passthrough: true }) res: Response,
): Promise<ActivityResponseDto> {
const { duplicate, value } = await this.service.create(auth, dto);
if (duplicate) {
res.status(HttpStatus.OK);
}
return value;
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}
}

View file

@ -0,0 +1,96 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AlbumCountResponseDto, AlbumResponseDto } from 'src/domain/album/album-response.dto';
import { AlbumService } from 'src/domain/album/album.service';
import { AddUsersDto } from 'src/domain/album/dto/album-add-users.dto';
import { CreateAlbumDto } from 'src/domain/album/dto/album-create.dto';
import { UpdateAlbumDto } from 'src/domain/album/dto/album-update.dto';
import { AlbumInfoDto } from 'src/domain/album/dto/album.dto';
import { GetAlbumsDto } from 'src/domain/album/dto/get-albums.dto';
import { BulkIdResponseDto, BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard';
import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation';
@ApiTags('Album')
@Controller('album')
@Authenticated()
export class AlbumController {
constructor(private service: AlbumService) {}
@Get('count')
getAlbumCount(@Auth() auth: AuthDto): Promise<AlbumCountResponseDto> {
return this.service.getCount(auth);
}
@Get()
getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
return this.service.getAll(auth, query);
}
@Post()
createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> {
return this.service.create(auth, dto);
}
@SharedLinkRoute()
@Get(':id')
getAlbumInfo(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Query() dto: AlbumInfoDto,
): Promise<AlbumResponseDto> {
return this.service.get(auth, id, dto);
}
@Patch(':id')
updateAlbumInfo(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: UpdateAlbumDto,
): Promise<AlbumResponseDto> {
return this.service.update(auth, id, dto);
}
@Delete(':id')
deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
return this.service.delete(auth, id);
}
@SharedLinkRoute()
@Put(':id/assets')
addAssetsToAlbum(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: BulkIdsDto,
): Promise<BulkIdResponseDto[]> {
return this.service.addAssets(auth, id, dto);
}
@Delete(':id/assets')
removeAssetFromAlbum(
@Auth() auth: AuthDto,
@Body() dto: BulkIdsDto,
@Param() { id }: UUIDParamDto,
): Promise<BulkIdResponseDto[]> {
return this.service.removeAssets(auth, id, dto);
}
@Put(':id/users')
addUsersToAlbum(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: AddUsersDto,
): Promise<AlbumResponseDto> {
return this.service.addUsers(auth, id, dto);
}
@Delete(':id/user/:userId')
removeUserFromAlbum(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
) {
return this.service.removeUser(auth, id, userId);
}
}

View file

@ -0,0 +1,48 @@
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
APIKeyCreateDto,
APIKeyCreateResponseDto,
APIKeyResponseDto,
APIKeyUpdateDto,
} from 'src/domain/api-key/api-key.dto';
import { APIKeyService } from 'src/domain/api-key/api-key.service';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('API Key')
@Controller('api-key')
@Authenticated()
export class APIKeyController {
constructor(private service: APIKeyService) {}
@Post()
createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
return this.service.create(auth, dto);
}
@Get()
getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
return this.service.getAll(auth);
}
@Get(':id')
getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
return this.service.getById(auth, id);
}
@Put(':id')
updateApiKey(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: APIKeyUpdateDto,
): Promise<APIKeyResponseDto> {
return this.service.update(auth, id, dto);
}
@Delete(':id')
deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}
}

View file

@ -0,0 +1,27 @@
import { Controller, Get, Header } from '@nestjs/common';
import { ApiExcludeEndpoint } from '@nestjs/swagger';
import { SystemConfigService } from 'src/domain/system-config/system-config.service';
import { PublicRoute } from 'src/middleware/auth.guard';
@Controller()
export class AppController {
constructor(private service: SystemConfigService) {}
@ApiExcludeEndpoint()
@Get('.well-known/immich')
getImmichWellKnown() {
return {
api: {
endpoint: '/api',
},
};
}
@ApiExcludeEndpoint()
@PublicRoute()
@Get('custom.css')
@Header('Content-Type', 'text/css')
getCustomCss() {
return this.service.getCustomCss();
}
}

View file

@ -0,0 +1,127 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { AssetService } from 'src/domain/asset/asset.service';
import { AssetJobsDto } from 'src/domain/asset/dto/asset-ids.dto';
import { UpdateStackParentDto } from 'src/domain/asset/dto/asset-stack.dto';
import { AssetStatsDto, AssetStatsResponseDto } from 'src/domain/asset/dto/asset-statistics.dto';
import {
AssetBulkDeleteDto,
AssetBulkUpdateDto,
DeviceIdDto,
RandomAssetsDto,
UpdateAssetDto,
} from 'src/domain/asset/dto/asset.dto';
import { MapMarkerDto } from 'src/domain/asset/dto/map-marker.dto';
import { MemoryLaneDto } from 'src/domain/asset/dto/memory-lane.dto';
import { TimeBucketAssetDto, TimeBucketDto } from 'src/domain/asset/dto/time-bucket.dto';
import { AssetResponseDto, MemoryLaneResponseDto } from 'src/domain/asset/response-dto/asset-response.dto';
import { MapMarkerResponseDto } from 'src/domain/asset/response-dto/map-marker-response.dto';
import { TimeBucketResponseDto } from 'src/domain/asset/response-dto/time-bucket-response.dto';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { MetadataSearchDto } from 'src/domain/search/dto/search.dto';
import { SearchService } from 'src/domain/search/search.service';
import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard';
import { Route } from 'src/middleware/file-upload.interceptor';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Asset')
@Controller('assets')
@Authenticated()
export class AssetsController {
constructor(private searchService: SearchService) {}
@Get()
@ApiOperation({ deprecated: true })
async searchAssets(@Auth() auth: AuthDto, @Query() dto: MetadataSearchDto): Promise<AssetResponseDto[]> {
const {
assets: { items },
} = await this.searchService.searchMetadata(auth, dto);
return items;
}
}
@ApiTags('Asset')
@Controller(Route.ASSET)
@Authenticated()
export class AssetController {
constructor(private service: AssetService) {}
@Get('map-marker')
getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
return this.service.getMapMarkers(auth, options);
}
@Get('memory-lane')
getMemoryLane(@Auth() auth: AuthDto, @Query() dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
return this.service.getMemoryLane(auth, dto);
}
@Get('random')
getRandom(@Auth() auth: AuthDto, @Query() dto: RandomAssetsDto): Promise<AssetResponseDto[]> {
return this.service.getRandom(auth, dto.count ?? 1);
}
/**
* Get all asset of a device that are in the database, ID only.
*/
@Get('/device/:deviceId')
getAllUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) {
return this.service.getUserAssetsByDeviceId(auth, deviceId);
}
@Get('statistics')
getAssetStatistics(@Auth() auth: AuthDto, @Query() dto: AssetStatsDto): Promise<AssetStatsResponseDto> {
return this.service.getStatistics(auth, dto);
}
@Authenticated({ isShared: true })
@Get('time-buckets')
getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
return this.service.getTimeBuckets(auth, dto);
}
@Authenticated({ isShared: true })
@Get('time-bucket')
getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
return this.service.getTimeBucket(auth, dto) as Promise<AssetResponseDto[]>;
}
@Post('jobs')
@HttpCode(HttpStatus.NO_CONTENT)
runAssetJobs(@Auth() auth: AuthDto, @Body() dto: AssetJobsDto): Promise<void> {
return this.service.run(auth, dto);
}
@Put()
@HttpCode(HttpStatus.NO_CONTENT)
updateAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise<void> {
return this.service.updateAll(auth, dto);
}
@Delete()
@HttpCode(HttpStatus.NO_CONTENT)
deleteAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkDeleteDto): Promise<void> {
return this.service.deleteAll(auth, dto);
}
@Put('stack/parent')
@HttpCode(HttpStatus.OK)
updateStackParent(@Auth() auth: AuthDto, @Body() dto: UpdateStackParentDto): Promise<void> {
return this.service.updateStackParent(auth, dto);
}
@SharedLinkRoute()
@Get(':id')
getAssetInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto> {
return this.service.get(auth, id) as Promise<AssetResponseDto>;
}
@Put(':id')
updateAsset(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: UpdateAssetDto,
): Promise<AssetResponseDto> {
return this.service.update(auth, id, dto);
}
}

View file

@ -0,0 +1,43 @@
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
AuditDeletesDto,
AuditDeletesResponseDto,
FileChecksumDto,
FileChecksumResponseDto,
FileReportDto,
FileReportFixDto,
} from 'src/domain/audit/audit.dto';
import { AuditService } from 'src/domain/audit/audit.service';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { AdminRoute, Auth, Authenticated } from 'src/middleware/auth.guard';
@ApiTags('Audit')
@Controller('audit')
@Authenticated()
export class AuditController {
constructor(private service: AuditService) {}
@Get('deletes')
getAuditDeletes(@Auth() auth: AuthDto, @Query() dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> {
return this.service.getDeletes(auth, dto);
}
@AdminRoute()
@Get('file-report')
getAuditFiles(): Promise<FileReportDto> {
return this.service.getFileReport();
}
@AdminRoute()
@Post('file-report/checksum')
getFileChecksums(@Body() dto: FileChecksumDto): Promise<FileChecksumResponseDto[]> {
return this.service.getChecksums(dto);
}
@AdminRoute()
@Post('file-report/fix')
fixAuditFiles(@Body() dto: FileReportFixDto): Promise<void> {
return this.service.fixItems(dto.items);
}
}

View file

@ -0,0 +1,86 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { IMMICH_ACCESS_COOKIE, IMMICH_AUTH_TYPE_COOKIE, IMMICH_IS_AUTHENTICATED } from 'src/domain/auth/auth.constant';
import {
AuthDeviceResponseDto,
AuthDto,
ChangePasswordDto,
LoginCredentialDto,
LoginResponseDto,
LogoutResponseDto,
SignUpDto,
ValidateAccessTokenResponseDto,
} from 'src/domain/auth/auth.dto';
import { AuthService, LoginDetails } from 'src/domain/auth/auth.service';
import { UserResponseDto, mapUser } from 'src/domain/user/response-dto/user-response.dto';
import { Auth, Authenticated, GetLoginDetails, PublicRoute } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Authentication')
@Controller('auth')
@Authenticated()
export class AuthController {
constructor(private service: AuthService) {}
@PublicRoute()
@Post('login')
async login(
@Body() loginCredential: LoginCredentialDto,
@Res({ passthrough: true }) res: Response,
@GetLoginDetails() loginDetails: LoginDetails,
): Promise<LoginResponseDto> {
const { response, cookie } = await this.service.login(loginCredential, loginDetails);
res.header('Set-Cookie', cookie);
return response;
}
@PublicRoute()
@Post('admin-sign-up')
signUpAdmin(@Body() dto: SignUpDto): Promise<UserResponseDto> {
return this.service.adminSignUp(dto);
}
@Get('devices')
getAuthDevices(@Auth() auth: AuthDto): Promise<AuthDeviceResponseDto[]> {
return this.service.getDevices(auth);
}
@Delete('devices')
@HttpCode(HttpStatus.NO_CONTENT)
logoutAuthDevices(@Auth() auth: AuthDto): Promise<void> {
return this.service.logoutDevices(auth);
}
@Delete('devices/:id')
@HttpCode(HttpStatus.NO_CONTENT)
logoutAuthDevice(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.logoutDevice(auth, id);
}
@Post('validateToken')
@HttpCode(HttpStatus.OK)
validateAccessToken(): ValidateAccessTokenResponseDto {
return { authStatus: true };
}
@Post('change-password')
@HttpCode(HttpStatus.OK)
changePassword(@Auth() auth: AuthDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> {
return this.service.changePassword(auth, dto).then(mapUser);
}
@Post('logout')
@HttpCode(HttpStatus.OK)
logout(
@Req() request: Request,
@Res({ passthrough: true }) res: Response,
@Auth() auth: AuthDto,
): Promise<LogoutResponseDto> {
res.clearCookie(IMMICH_ACCESS_COOKIE);
res.clearCookie(IMMICH_AUTH_TYPE_COOKIE);
res.clearCookie(IMMICH_IS_AUTHENTICATED);
return this.service.logout(auth, (request.cookies || {})[IMMICH_AUTH_TYPE_COOKIE]);
}
}

View file

@ -0,0 +1,44 @@
import { Body, Controller, HttpCode, HttpStatus, Next, Param, Post, Res, StreamableFile } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { NextFunction, Response } from 'express';
import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { DownloadInfoDto, DownloadResponseDto } from 'src/domain/download/download.dto';
import { DownloadService } from 'src/domain/download/download.service';
import { asStreamableFile, sendFile } from 'src/immich/app.utils';
import { Auth, Authenticated, FileResponse, SharedLinkRoute } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Download')
@Controller('download')
@Authenticated()
export class DownloadController {
constructor(private service: DownloadService) {}
@SharedLinkRoute()
@Post('info')
getDownloadInfo(@Auth() auth: AuthDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> {
return this.service.getDownloadInfo(auth, dto);
}
@SharedLinkRoute()
@Post('archive')
@HttpCode(HttpStatus.OK)
@FileResponse()
downloadArchive(@Auth() auth: AuthDto, @Body() dto: AssetIdsDto): Promise<StreamableFile> {
return this.service.downloadArchive(auth, dto).then(asStreamableFile);
}
@SharedLinkRoute()
@Post('asset/:id')
@HttpCode(HttpStatus.OK)
@FileResponse()
async downloadFile(
@Res() res: Response,
@Next() next: NextFunction,
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
) {
await sendFile(res, next, () => this.service.downloadFile(auth, id));
}
}

View file

@ -0,0 +1,28 @@
import { Body, Controller, Get, Param, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { AssetFaceResponseDto, FaceDto, PersonResponseDto } from 'src/domain/person/person.dto';
import { PersonService } from 'src/domain/person/person.service';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Face')
@Controller('face')
@Authenticated()
export class FaceController {
constructor(private service: PersonService) {}
@Get()
getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
return this.service.getFacesById(auth, dto);
}
@Put(':id')
reassignFacesById(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: FaceDto,
): Promise<PersonResponseDto> {
return this.service.reassignFacesById(auth, id, dto);
}
}

View file

@ -0,0 +1,22 @@
import { Body, Controller, Get, Param, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AllJobStatusResponseDto, JobCommandDto, JobIdParamDto, JobStatusDto } from 'src/domain/job/job.dto';
import { JobService } from 'src/domain/job/job.service';
import { Authenticated } from 'src/middleware/auth.guard';
@ApiTags('Job')
@Controller('jobs')
@Authenticated({ admin: true })
export class JobController {
constructor(private service: JobService) {}
@Get()
getAllJobsStatus(): Promise<AllJobStatusResponseDto> {
return this.service.getAllJobsStatus();
}
@Put(':id')
sendJobCommand(@Param() { id }: JobIdParamDto, @Body() dto: JobCommandDto): Promise<JobStatusDto> {
return this.service.handleCommand(id, dto);
}
}

View file

@ -0,0 +1,73 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
CreateLibraryDto,
LibraryResponseDto,
LibraryStatsResponseDto,
ScanLibraryDto,
SearchLibraryDto,
UpdateLibraryDto,
ValidateLibraryDto,
ValidateLibraryResponseDto,
} from 'src/domain/library/library.dto';
import { LibraryService } from 'src/domain/library/library.service';
import { AdminRoute, Authenticated } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Library')
@Controller('library')
@Authenticated()
@AdminRoute()
export class LibraryController {
constructor(private service: LibraryService) {}
@Get()
getAllLibraries(@Query() dto: SearchLibraryDto): Promise<LibraryResponseDto[]> {
return this.service.getAll(dto);
}
@Post()
createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> {
return this.service.create(dto);
}
@Put(':id')
updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
return this.service.update(id, dto);
}
@Get(':id')
getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> {
return this.service.get(id);
}
@Post(':id/validate')
@HttpCode(200)
// TODO: change endpoint to validate current settings instead
validate(@Param() { id }: UUIDParamDto, @Body() dto: ValidateLibraryDto): Promise<ValidateLibraryResponseDto> {
return this.service.validate(id, dto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(id);
}
@Get(':id/statistics')
getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
return this.service.getStatistics(id);
}
@Post(':id/scan')
@HttpCode(HttpStatus.NO_CONTENT)
scanLibrary(@Param() { id }: UUIDParamDto, @Body() dto: ScanLibraryDto) {
return this.service.queueScan(id, dto);
}
@Post(':id/removeOffline')
@HttpCode(HttpStatus.NO_CONTENT)
removeOfflineFiles(@Param() { id }: UUIDParamDto) {
return this.service.queueRemoveOffline(id);
}
}

View file

@ -0,0 +1,58 @@
import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import {
AuthDto,
LoginResponseDto,
OAuthAuthorizeResponseDto,
OAuthCallbackDto,
OAuthConfigDto,
} from 'src/domain/auth/auth.dto';
import { AuthService, LoginDetails } from 'src/domain/auth/auth.service';
import { UserResponseDto } from 'src/domain/user/response-dto/user-response.dto';
import { Auth, Authenticated, GetLoginDetails, PublicRoute } from 'src/middleware/auth.guard';
@ApiTags('OAuth')
@Controller('oauth')
@Authenticated()
export class OAuthController {
constructor(private service: AuthService) {}
@PublicRoute()
@Get('mobile-redirect')
@Redirect()
redirectOAuthToMobile(@Req() request: Request) {
return {
url: this.service.getMobileRedirect(request.url),
statusCode: HttpStatus.TEMPORARY_REDIRECT,
};
}
@PublicRoute()
@Post('authorize')
startOAuth(@Body() dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> {
return this.service.authorize(dto);
}
@PublicRoute()
@Post('callback')
async finishOAuth(
@Res({ passthrough: true }) res: Response,
@Body() dto: OAuthCallbackDto,
@GetLoginDetails() loginDetails: LoginDetails,
): Promise<LoginResponseDto> {
const { response, cookie } = await this.service.callback(dto, loginDetails);
res.header('Set-Cookie', cookie);
return response;
}
@Post('link')
linkOAuthAccount(@Auth() auth: AuthDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> {
return this.service.link(auth, dto);
}
@Post('unlink')
unlinkOAuthAccount(@Auth() auth: AuthDto): Promise<UserResponseDto> {
return this.service.unlink(auth);
}
}

View file

@ -0,0 +1,41 @@
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
import { ApiQuery, ApiTags } from '@nestjs/swagger';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { PartnerResponseDto, UpdatePartnerDto } from 'src/domain/partner/partner.dto';
import { PartnerService } from 'src/domain/partner/partner.service';
import { PartnerDirection } from 'src/domain/repositories/partner.repository';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Partner')
@Controller('partner')
@Authenticated()
export class PartnerController {
constructor(private service: PartnerService) {}
@Get()
@ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true })
// TODO: remove 'direction' and convert to full query dto
getPartners(@Auth() auth: AuthDto, @Query('direction') direction: PartnerDirection): Promise<PartnerResponseDto[]> {
return this.service.getAll(auth, direction);
}
@Post(':id')
createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
return this.service.create(auth, id);
}
@Put(':id')
updatePartner(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: UpdatePartnerDto,
): Promise<PartnerResponseDto> {
return this.service.update(auth, id, dto);
}
@Delete(':id')
removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.remove(auth, id);
}
}

View file

@ -0,0 +1,96 @@
import { Body, Controller, Get, Next, Param, Post, Put, Query, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { NextFunction, Response } from 'express';
import { BulkIdResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto';
import { AuthDto } from 'src/domain/auth/auth.dto';
import {
AssetFaceUpdateDto,
MergePersonDto,
PeopleResponseDto,
PeopleUpdateDto,
PersonCreateDto,
PersonResponseDto,
PersonSearchDto,
PersonStatisticsResponseDto,
PersonUpdateDto,
} from 'src/domain/person/person.dto';
import { PersonService } from 'src/domain/person/person.service';
import { sendFile } from 'src/immich/app.utils';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Person')
@Controller('person')
@Authenticated()
export class PersonController {
constructor(private service: PersonService) {}
@Get()
getAllPeople(@Auth() auth: AuthDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> {
return this.service.getAll(auth, withHidden);
}
@Post()
createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
return this.service.create(auth, dto);
}
@Put()
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
return this.service.updateAll(auth, dto);
}
@Get(':id')
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
return this.service.getById(auth, id);
}
@Put(':id')
updatePerson(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: PersonUpdateDto,
): Promise<PersonResponseDto> {
return this.service.update(auth, id, dto);
}
@Get(':id/statistics')
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
return this.service.getStatistics(auth, id);
}
@Get(':id/thumbnail')
@FileResponse()
async getPersonThumbnail(
@Res() res: Response,
@Next() next: NextFunction,
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
) {
await sendFile(res, next, () => this.service.getThumbnail(auth, id));
}
@Get(':id/assets')
getPersonAssets(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
return this.service.getAssets(auth, id);
}
@Put(':id/reassign')
reassignFaces(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: AssetFaceUpdateDto,
): Promise<PersonResponseDto[]> {
return this.service.reassignFaces(auth, id, dto);
}
@Post(':id/merge')
mergePerson(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: MergePersonDto,
): Promise<BulkIdResponseDto[]> {
return this.service.mergePerson(auth, id, dto);
}
}

View file

@ -0,0 +1,68 @@
import { Body, Controller, Get, HttpCode, HttpStatus, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { PersonResponseDto } from 'src/domain/person/person.dto';
import { SearchSuggestionRequestDto } from 'src/domain/search/dto/search-suggestion.dto';
import {
MetadataSearchDto,
PlacesResponseDto,
SearchDto,
SearchPeopleDto,
SearchPlacesDto,
SmartSearchDto,
} from 'src/domain/search/dto/search.dto';
import { SearchExploreResponseDto } from 'src/domain/search/response-dto/search-explore.response.dto';
import { SearchResponseDto } from 'src/domain/search/response-dto/search-response.dto';
import { SearchService } from 'src/domain/search/search.service';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
@ApiTags('Search')
@Controller('search')
@Authenticated()
export class SearchController {
constructor(private service: SearchService) {}
@Get()
@ApiOperation({ deprecated: true })
search(@Auth() auth: AuthDto, @Query() dto: SearchDto): Promise<SearchResponseDto> {
return this.service.search(auth, dto);
}
@Post('metadata')
@HttpCode(HttpStatus.OK)
searchMetadata(@Auth() auth: AuthDto, @Body() dto: MetadataSearchDto): Promise<SearchResponseDto> {
return this.service.searchMetadata(auth, dto);
}
@Post('smart')
@HttpCode(HttpStatus.OK)
searchSmart(@Auth() auth: AuthDto, @Body() dto: SmartSearchDto): Promise<SearchResponseDto> {
return this.service.searchSmart(auth, dto);
}
@Get('explore')
getExploreData(@Auth() auth: AuthDto): Promise<SearchExploreResponseDto[]> {
return this.service.getExploreData(auth) as Promise<SearchExploreResponseDto[]>;
}
@Get('person')
searchPerson(@Auth() auth: AuthDto, @Query() dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
return this.service.searchPerson(auth, dto);
}
@Get('places')
searchPlaces(@Query() dto: SearchPlacesDto): Promise<PlacesResponseDto[]> {
return this.service.searchPlaces(dto);
}
@Get('cities')
getAssetsByCity(@Auth() auth: AuthDto): Promise<AssetResponseDto[]> {
return this.service.getAssetsByCity(auth);
}
@Get('suggestions')
getSearchSuggestions(@Auth() auth: AuthDto, @Query() dto: SearchSuggestionRequestDto): Promise<string[]> {
return this.service.getSearchSuggestions(auth, dto);
}
}

View file

@ -0,0 +1,75 @@
import { Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
ServerConfigDto,
ServerFeaturesDto,
ServerInfoResponseDto,
ServerMediaTypesResponseDto,
ServerPingResponse,
ServerStatsResponseDto,
ServerThemeDto,
ServerVersionResponseDto,
} from 'src/domain/server-info/server-info.dto';
import { ServerInfoService } from 'src/domain/server-info/server-info.service';
import { AdminRoute, Authenticated, PublicRoute } from 'src/middleware/auth.guard';
@ApiTags('Server Info')
@Controller('server-info')
@Authenticated()
export class ServerInfoController {
constructor(private service: ServerInfoService) {}
@Get()
getServerInfo(): Promise<ServerInfoResponseDto> {
return this.service.getInfo();
}
@PublicRoute()
@Get('ping')
pingServer(): ServerPingResponse {
return this.service.ping();
}
@PublicRoute()
@Get('version')
getServerVersion(): ServerVersionResponseDto {
return this.service.getVersion();
}
@PublicRoute()
@Get('features')
getServerFeatures(): Promise<ServerFeaturesDto> {
return this.service.getFeatures();
}
@PublicRoute()
@Get('theme')
getTheme(): Promise<ServerThemeDto> {
return this.service.getTheme();
}
@PublicRoute()
@Get('config')
getServerConfig(): Promise<ServerConfigDto> {
return this.service.getConfig();
}
@AdminRoute()
@Get('statistics')
getServerStatistics(): Promise<ServerStatsResponseDto> {
return this.service.getStatistics();
}
@PublicRoute()
@Get('media-types')
getSupportedMediaTypes(): ServerMediaTypesResponseDto {
return this.service.getSupportedMediaTypes();
}
@AdminRoute()
@Post('admin-onboarding')
@HttpCode(HttpStatus.NO_CONTENT)
setAdminOnboarding(): Promise<void> {
return this.service.setAdminOnboarding();
}
}

View file

@ -0,0 +1,91 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto';
import { AssetIdsResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
import { IMMICH_SHARED_LINK_ACCESS_COOKIE } from 'src/domain/auth/auth.constant';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { SharedLinkResponseDto } from 'src/domain/shared-link/shared-link-response.dto';
import { SharedLinkCreateDto, SharedLinkEditDto, SharedLinkPasswordDto } from 'src/domain/shared-link/shared-link.dto';
import { SharedLinkService } from 'src/domain/shared-link/shared-link.service';
import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Shared Link')
@Controller('shared-link')
@Authenticated()
export class SharedLinkController {
constructor(private readonly service: SharedLinkService) {}
@Get()
getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
return this.service.getAll(auth);
}
@SharedLinkRoute()
@Get('me')
async getMySharedLink(
@Auth() auth: AuthDto,
@Query() dto: SharedLinkPasswordDto,
@Req() request: Request,
@Res({ passthrough: true }) res: Response,
): Promise<SharedLinkResponseDto> {
const sharedLinkToken = request.cookies?.[IMMICH_SHARED_LINK_ACCESS_COOKIE];
if (sharedLinkToken) {
dto.token = sharedLinkToken;
}
const response = await this.service.getMine(auth, dto);
if (response.token) {
res.cookie(IMMICH_SHARED_LINK_ACCESS_COOKIE, response.token, {
expires: new Date(Date.now() + 1000 * 60 * 60 * 24),
httpOnly: true,
sameSite: 'lax',
});
}
return response;
}
@Get(':id')
getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
return this.service.get(auth, id);
}
@Post()
createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
return this.service.create(auth, dto);
}
@Patch(':id')
updateSharedLink(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: SharedLinkEditDto,
): Promise<SharedLinkResponseDto> {
return this.service.update(auth, id, dto);
}
@Delete(':id')
removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.remove(auth, id);
}
@SharedLinkRoute()
@Put(':id/assets')
addSharedLinkAssets(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: AssetIdsDto,
): Promise<AssetIdsResponseDto[]> {
return this.service.addAssets(auth, id, dto);
}
@SharedLinkRoute()
@Delete(':id/assets')
removeSharedLinkAssets(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: AssetIdsDto,
): Promise<AssetIdsResponseDto[]> {
return this.service.removeAssets(auth, id, dto);
}
}

View file

@ -0,0 +1,40 @@
import { Body, Controller, Get, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { SystemConfigDto } from 'src/domain/system-config/dto/system-config.dto';
import { SystemConfigTemplateStorageOptionDto } from 'src/domain/system-config/response-dto/system-config-template-storage-option.dto';
import { MapThemeDto } from 'src/domain/system-config/system-config-map-theme.dto';
import { SystemConfigService } from 'src/domain/system-config/system-config.service';
import { AdminRoute, Authenticated } from 'src/middleware/auth.guard';
@ApiTags('System Config')
@Controller('system-config')
@Authenticated({ admin: true })
export class SystemConfigController {
constructor(private readonly service: SystemConfigService) {}
@Get()
getConfig(): Promise<SystemConfigDto> {
return this.service.getConfig();
}
@Get('defaults')
getConfigDefaults(): SystemConfigDto {
return this.service.getDefaults();
}
@Put()
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
return this.service.updateConfig(dto);
}
@Get('storage-template-options')
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
return this.service.getStorageTemplateOptions();
}
@AdminRoute(false)
@Get('map/style.json')
getMapStyle(@Query() dto: MapThemeDto) {
return this.service.getMapStyle(dto.theme);
}
}

View file

@ -0,0 +1,66 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto';
import { AssetIdsResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { TagResponseDto } from 'src/domain/tag/tag-response.dto';
import { CreateTagDto, UpdateTagDto } from 'src/domain/tag/tag.dto';
import { TagService } from 'src/domain/tag/tag.service';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Tag')
@Controller('tag')
@Authenticated()
export class TagController {
constructor(private service: TagService) {}
@Post()
createTag(@Auth() auth: AuthDto, @Body() dto: CreateTagDto): Promise<TagResponseDto> {
return this.service.create(auth, dto);
}
@Get()
getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
return this.service.getAll(auth);
}
@Get(':id')
getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
return this.service.getById(auth, id);
}
@Patch(':id')
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateTagDto): Promise<TagResponseDto> {
return this.service.update(auth, id, dto);
}
@Delete(':id')
deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.remove(auth, id);
}
@Get(':id/assets')
getTagAssets(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
return this.service.getAssets(auth, id);
}
@Put(':id/assets')
tagAssets(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: AssetIdsDto,
): Promise<AssetIdsResponseDto[]> {
return this.service.addAssets(auth, id, dto);
}
@Delete(':id/assets')
untagAssets(
@Auth() auth: AuthDto,
@Body() dto: AssetIdsDto,
@Param() { id }: UUIDParamDto,
): Promise<AssetIdsResponseDto[]> {
return this.service.removeAssets(auth, id, dto);
}
}

View file

@ -0,0 +1,31 @@
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { TrashService } from 'src/domain/trash/trash.service';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
@ApiTags('Trash')
@Controller('trash')
@Authenticated()
export class TrashController {
constructor(private service: TrashService) {}
@Post('empty')
@HttpCode(HttpStatus.NO_CONTENT)
emptyTrash(@Auth() auth: AuthDto): Promise<void> {
return this.service.empty(auth);
}
@Post('restore')
@HttpCode(HttpStatus.NO_CONTENT)
restoreTrash(@Auth() auth: AuthDto): Promise<void> {
return this.service.restore(auth);
}
@Post('restore/assets')
@HttpCode(HttpStatus.NO_CONTENT)
restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
return this.service.restoreAssets(auth, dto);
}
}

View file

@ -0,0 +1,103 @@
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Next,
Param,
Post,
Put,
Query,
Res,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
import { NextFunction, Response } from 'express';
import { AuthDto } from 'src/domain/auth/auth.dto';
import { CreateProfileImageDto } from 'src/domain/user/dto/create-profile-image.dto';
import { CreateUserDto } from 'src/domain/user/dto/create-user.dto';
import { DeleteUserDto } from 'src/domain/user/dto/delete-user.dto';
import { UpdateUserDto } from 'src/domain/user/dto/update-user.dto';
import { CreateProfileImageResponseDto } from 'src/domain/user/response-dto/create-profile-image-response.dto';
import { UserResponseDto } from 'src/domain/user/response-dto/user-response.dto';
import { UserService } from 'src/domain/user/user.service';
import { sendFile } from 'src/immich/app.utils';
import { AdminRoute, Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { FileUploadInterceptor, Route } from 'src/middleware/file-upload.interceptor';
import { UUIDParamDto } from 'src/validation';
@ApiTags('User')
@Controller(Route.USER)
@Authenticated()
export class UserController {
constructor(private service: UserService) {}
@Get()
getAllUsers(@Auth() auth: AuthDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> {
return this.service.getAll(auth, isAll);
}
@Get('info/:id')
getUserById(@Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
return this.service.get(id);
}
@Get('me')
getMyUserInfo(@Auth() auth: AuthDto): Promise<UserResponseDto> {
return this.service.getMe(auth);
}
@AdminRoute()
@Post()
createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
return this.service.create(createUserDto);
}
@Delete('profile-image')
@HttpCode(HttpStatus.NO_CONTENT)
deleteProfileImage(@Auth() auth: AuthDto): Promise<void> {
return this.service.deleteProfileImage(auth);
}
@AdminRoute()
@Delete(':id')
deleteUser(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Body() dto: DeleteUserDto,
): Promise<UserResponseDto> {
return this.service.delete(auth, id, dto);
}
@AdminRoute()
@Post(':id/restore')
restoreUser(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
return this.service.restore(auth, id);
}
// TODO: replace with @Put(':id')
@Put()
updateUser(@Auth() auth: AuthDto, @Body() updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
return this.service.update(auth, updateUserDto);
}
@UseInterceptors(FileUploadInterceptor)
@ApiConsumes('multipart/form-data')
@ApiBody({ description: 'A new avatar for the user', type: CreateProfileImageDto })
@Post('profile-image')
createProfileImage(
@Auth() auth: AuthDto,
@UploadedFile() fileInfo: Express.Multer.File,
): Promise<CreateProfileImageResponseDto> {
return this.service.createProfileImage(auth, fileInfo);
}
@Get('profile-image/:id')
@FileResponse()
async getProfileImage(@Res() res: Response, @Next() next: NextFunction, @Param() { id }: UUIDParamDto) {
await sendFile(res, next, () => this.service.getProfileImage(id));
}
}