refactor: enum casing (#19946)

This commit is contained in:
Jason Rasmussen 2025-07-15 14:50:13 -04:00 committed by GitHub
parent 920d7de349
commit e73abe0762
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
174 changed files with 2675 additions and 2459 deletions

View File

@ -85,13 +85,13 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
@Module({
imports: [...imports, ScheduleModule.forRoot()],
controllers: [...controllers],
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.API }],
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.Api }],
})
export class ApiModule extends BaseModule {}
@Module({
imports: [...imports],
providers: [...common, { provide: IWorker, useValue: ImmichWorker.MICROSERVICES }, SchedulerRegistry],
providers: [...common, { provide: IWorker, useValue: ImmichWorker.Microservices }, SchedulerRegistry],
})
export class MicroservicesModule extends BaseModule {}

View File

@ -8,7 +8,7 @@ import {
OAuthTokenEndpointAuthMethod,
QueueName,
ToneMapping,
TranscodeHWAccel,
TranscodeHardwareAcceleration,
TranscodePolicy,
VideoCodec,
VideoContainer,
@ -42,7 +42,7 @@ export interface SystemConfig {
twoPass: boolean;
preferredHwDevice: string;
transcode: TranscodePolicy;
accel: TranscodeHWAccel;
accel: TranscodeHardwareAcceleration;
accelDecode: boolean;
tonemap: ToneMapping;
};
@ -190,39 +190,39 @@ export const defaults = Object.freeze<SystemConfig>({
preset: 'ultrafast',
targetVideoCodec: VideoCodec.H264,
acceptedVideoCodecs: [VideoCodec.H264],
targetAudioCodec: AudioCodec.AAC,
acceptedAudioCodecs: [AudioCodec.AAC, AudioCodec.MP3, AudioCodec.LIBOPUS, AudioCodec.PCMS16LE],
acceptedContainers: [VideoContainer.MOV, VideoContainer.OGG, VideoContainer.WEBM],
targetAudioCodec: AudioCodec.Aac,
acceptedAudioCodecs: [AudioCodec.Aac, AudioCodec.Mp3, AudioCodec.LibOpus, AudioCodec.PcmS16le],
acceptedContainers: [VideoContainer.Mov, VideoContainer.Ogg, VideoContainer.Webm],
targetResolution: '720',
maxBitrate: '0',
bframes: -1,
refs: 0,
gopSize: 0,
temporalAQ: false,
cqMode: CQMode.AUTO,
cqMode: CQMode.Auto,
twoPass: false,
preferredHwDevice: 'auto',
transcode: TranscodePolicy.REQUIRED,
tonemap: ToneMapping.HABLE,
accel: TranscodeHWAccel.DISABLED,
transcode: TranscodePolicy.Required,
tonemap: ToneMapping.Hable,
accel: TranscodeHardwareAcceleration.Disabled,
accelDecode: false,
},
job: {
[QueueName.BACKGROUND_TASK]: { concurrency: 5 },
[QueueName.SMART_SEARCH]: { concurrency: 2 },
[QueueName.METADATA_EXTRACTION]: { concurrency: 5 },
[QueueName.FACE_DETECTION]: { concurrency: 2 },
[QueueName.SEARCH]: { concurrency: 5 },
[QueueName.SIDECAR]: { concurrency: 5 },
[QueueName.LIBRARY]: { concurrency: 5 },
[QueueName.MIGRATION]: { concurrency: 5 },
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 3 },
[QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
[QueueName.NOTIFICATION]: { concurrency: 5 },
[QueueName.BackgroundTask]: { concurrency: 5 },
[QueueName.SmartSearch]: { concurrency: 2 },
[QueueName.MetadataExtraction]: { concurrency: 5 },
[QueueName.FaceDetection]: { concurrency: 2 },
[QueueName.Search]: { concurrency: 5 },
[QueueName.Sidecar]: { concurrency: 5 },
[QueueName.Library]: { concurrency: 5 },
[QueueName.Migration]: { concurrency: 5 },
[QueueName.ThumbnailGeneration]: { concurrency: 3 },
[QueueName.VideoConversion]: { concurrency: 1 },
[QueueName.Notification]: { concurrency: 5 },
},
logging: {
enabled: true,
level: LogLevel.LOG,
level: LogLevel.Log,
},
machineLearning: {
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
@ -273,7 +273,7 @@ export const defaults = Object.freeze<SystemConfig>({
storageLabelClaim: 'preferred_username',
storageQuotaClaim: 'immich_quota',
roleClaim: 'immich_role',
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST,
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.ClientSecretPost,
timeout: 30_000,
},
passwordLogin: {
@ -286,12 +286,12 @@ export const defaults = Object.freeze<SystemConfig>({
},
image: {
thumbnail: {
format: ImageFormat.WEBP,
format: ImageFormat.Webp,
size: 250,
quality: 80,
},
preview: {
format: ImageFormat.JPEG,
format: ImageFormat.Jpeg,
size: 1440,
quality: 80,
},
@ -299,7 +299,7 @@ export const defaults = Object.freeze<SystemConfig>({
extractEmbedded: false,
fullsize: {
enabled: false,
format: ImageFormat.JPEG,
format: ImageFormat.Jpeg,
quality: 80,
},
},

View File

@ -25,14 +25,14 @@ export const EXTENSION_NAMES: Record<DatabaseExtension, string> = {
} as const;
export const VECTOR_EXTENSIONS = [
DatabaseExtension.VECTORCHORD,
DatabaseExtension.VECTORS,
DatabaseExtension.VECTOR,
DatabaseExtension.VectorChord,
DatabaseExtension.Vectors,
DatabaseExtension.Vector,
] as const;
export const VECTOR_INDEX_TABLES = {
[VectorIndex.CLIP]: 'smart_search',
[VectorIndex.FACE]: 'face_search',
[VectorIndex.Clip]: 'smart_search',
[VectorIndex.Face]: 'face_search',
} as const;
export const VECTORCHORD_LIST_SLACK_FACTOR = 1.2;

View File

@ -20,13 +20,13 @@ export class ActivityController {
constructor(private service: ActivityService) {}
@Get()
@Authenticated({ permission: Permission.ACTIVITY_READ })
@Authenticated({ permission: Permission.ActivityRead })
getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
return this.service.getAll(auth, dto);
}
@Post()
@Authenticated({ permission: Permission.ACTIVITY_CREATE })
@Authenticated({ permission: Permission.ActivityCreate })
async createActivity(
@Auth() auth: AuthDto,
@Body() dto: ActivityCreateDto,
@ -40,14 +40,14 @@ export class ActivityController {
}
@Get('statistics')
@Authenticated({ permission: Permission.ACTIVITY_STATISTICS })
@Authenticated({ permission: Permission.ActivityStatistics })
getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
return this.service.getStatistics(auth, dto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.ACTIVITY_DELETE })
@Authenticated({ permission: Permission.ActivityDelete })
deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}

View File

@ -23,24 +23,24 @@ export class AlbumController {
constructor(private service: AlbumService) {}
@Get()
@Authenticated({ permission: Permission.ALBUM_READ })
@Authenticated({ permission: Permission.AlbumRead })
getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
return this.service.getAll(auth, query);
}
@Post()
@Authenticated({ permission: Permission.ALBUM_CREATE })
@Authenticated({ permission: Permission.AlbumCreate })
createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> {
return this.service.create(auth, dto);
}
@Get('statistics')
@Authenticated({ permission: Permission.ALBUM_STATISTICS })
@Authenticated({ permission: Permission.AlbumStatistics })
getAlbumStatistics(@Auth() auth: AuthDto): Promise<AlbumStatisticsResponseDto> {
return this.service.getStatistics(auth);
}
@Authenticated({ permission: Permission.ALBUM_READ, sharedLink: true })
@Authenticated({ permission: Permission.AlbumRead, sharedLink: true })
@Get(':id')
getAlbumInfo(
@Auth() auth: AuthDto,
@ -51,7 +51,7 @@ export class AlbumController {
}
@Patch(':id')
@Authenticated({ permission: Permission.ALBUM_UPDATE })
@Authenticated({ permission: Permission.AlbumUpdate })
updateAlbumInfo(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -61,7 +61,7 @@ export class AlbumController {
}
@Delete(':id')
@Authenticated({ permission: Permission.ALBUM_DELETE })
@Authenticated({ permission: Permission.AlbumDelete })
deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
return this.service.delete(auth, id);
}

View File

@ -55,7 +55,7 @@ describe(APIKeyController.name, () => {
it('should require a valid uuid', async () => {
const { status, body } = await request(ctx.getHttpServer())
.put(`/api-keys/123`)
.send({ name: 'new name', permissions: [Permission.ALL] });
.send({ name: 'new name', permissions: [Permission.All] });
expect(status).toBe(400);
expect(body).toEqual(factory.responses.badRequest(['id must be a UUID']));
});

View File

@ -13,25 +13,25 @@ export class APIKeyController {
constructor(private service: ApiKeyService) {}
@Post()
@Authenticated({ permission: Permission.API_KEY_CREATE })
@Authenticated({ permission: Permission.ApiKeyCreate })
createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
return this.service.create(auth, dto);
}
@Get()
@Authenticated({ permission: Permission.API_KEY_READ })
@Authenticated({ permission: Permission.ApiKeyRead })
getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
return this.service.getAll(auth);
}
@Get(':id')
@Authenticated({ permission: Permission.API_KEY_READ })
@Authenticated({ permission: Permission.ApiKeyRead })
getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
return this.service.getById(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.API_KEY_UPDATE })
@Authenticated({ permission: Permission.ApiKeyUpdate })
updateApiKey(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -42,7 +42,7 @@ export class APIKeyController {
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.API_KEY_DELETE })
@Authenticated({ permission: Permission.ApiKeyDelete })
deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}

View File

@ -45,7 +45,7 @@ import { ImmichFileResponse, sendFile } from 'src/utils/file';
import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation';
@ApiTags('Assets')
@Controller(RouteKey.ASSET)
@Controller(RouteKey.Asset)
export class AssetMediaController {
constructor(
private logger: LoggingRepository,
@ -56,7 +56,7 @@ export class AssetMediaController {
@UseInterceptors(AssetUploadInterceptor, FileUploadInterceptor)
@ApiConsumes('multipart/form-data')
@ApiHeader({
name: ImmichHeader.CHECKSUM,
name: ImmichHeader.Checksum,
description: 'sha1 checksum that can be used for duplicate detection before the file is uploaded',
required: false,
})

View File

@ -19,7 +19,7 @@ import { AssetService } from 'src/services/asset.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Assets')
@Controller(RouteKey.ASSET)
@Controller(RouteKey.Asset)
export class AssetController {
constructor(private service: AssetService) {}

View File

@ -36,9 +36,9 @@ export class AuthController {
return respondWithCookie(res, body, {
isSecure: loginDetails.isSecure,
values: [
{ key: ImmichCookie.ACCESS_TOKEN, value: body.accessToken },
{ key: ImmichCookie.AUTH_TYPE, value: AuthType.PASSWORD },
{ key: ImmichCookie.IS_AUTHENTICATED, value: 'true' },
{ key: ImmichCookie.AccessToken, value: body.accessToken },
{ key: ImmichCookie.AuthType, value: AuthType.Password },
{ key: ImmichCookie.IsAuthenticated, value: 'true' },
],
});
}
@ -70,13 +70,13 @@ export class AuthController {
@Res({ passthrough: true }) res: Response,
@Auth() auth: AuthDto,
): Promise<LogoutResponseDto> {
const authType = (request.cookies || {})[ImmichCookie.AUTH_TYPE];
const authType = (request.cookies || {})[ImmichCookie.AuthType];
const body = await this.service.logout(auth, authType);
return respondWithoutCookie(res, body, [
ImmichCookie.ACCESS_TOKEN,
ImmichCookie.AUTH_TYPE,
ImmichCookie.IS_AUTHENTICATED,
ImmichCookie.AccessToken,
ImmichCookie.AuthType,
ImmichCookie.IsAuthenticated,
]);
}

View File

@ -19,19 +19,19 @@ export class FaceController {
constructor(private service: PersonService) {}
@Post()
@Authenticated({ permission: Permission.FACE_CREATE })
@Authenticated({ permission: Permission.FaceCreate })
createFace(@Auth() auth: AuthDto, @Body() dto: AssetFaceCreateDto) {
return this.service.createFace(auth, dto);
}
@Get()
@Authenticated({ permission: Permission.FACE_READ })
@Authenticated({ permission: Permission.FaceRead })
getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
return this.service.getFacesById(auth, dto);
}
@Put(':id')
@Authenticated({ permission: Permission.FACE_UPDATE })
@Authenticated({ permission: Permission.FaceUpdate })
reassignFacesById(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -41,7 +41,7 @@ export class FaceController {
}
@Delete(':id')
@Authenticated({ permission: Permission.FACE_DELETE })
@Authenticated({ permission: Permission.FaceDelete })
deleteFace(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: AssetFaceDeleteDto) {
return this.service.deleteFace(auth, id, dto);
}

View File

@ -19,32 +19,32 @@ export class LibraryController {
constructor(private service: LibraryService) {}
@Get()
@Authenticated({ permission: Permission.LIBRARY_READ, admin: true })
@Authenticated({ permission: Permission.LibraryRead, admin: true })
getAllLibraries(): Promise<LibraryResponseDto[]> {
return this.service.getAll();
}
@Post()
@Authenticated({ permission: Permission.LIBRARY_CREATE, admin: true })
@Authenticated({ permission: Permission.LibraryCreate, admin: true })
createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> {
return this.service.create(dto);
}
@Get(':id')
@Authenticated({ permission: Permission.LIBRARY_READ, admin: true })
@Authenticated({ permission: Permission.LibraryRead, admin: true })
getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> {
return this.service.get(id);
}
@Put(':id')
@Authenticated({ permission: Permission.LIBRARY_UPDATE, admin: true })
@Authenticated({ permission: Permission.LibraryUpdate, admin: true })
updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
return this.service.update(id, dto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.LIBRARY_DELETE, admin: true })
@Authenticated({ permission: Permission.LibraryDelete, admin: true })
deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(id);
}
@ -58,14 +58,14 @@ export class LibraryController {
}
@Get(':id/statistics')
@Authenticated({ permission: Permission.LIBRARY_STATISTICS, admin: true })
@Authenticated({ permission: Permission.LibraryStatistics, admin: true })
getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
return this.service.getStatistics(id);
}
@Post(':id/scan')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.LIBRARY_UPDATE, admin: true })
@Authenticated({ permission: Permission.LibraryUpdate, admin: true })
scanLibrary(@Param() { id }: UUIDParamDto) {
return this.service.queueScan(id);
}

View File

@ -20,31 +20,31 @@ export class MemoryController {
constructor(private service: MemoryService) {}
@Get()
@Authenticated({ permission: Permission.MEMORY_READ })
@Authenticated({ permission: Permission.MemoryRead })
searchMemories(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryResponseDto[]> {
return this.service.search(auth, dto);
}
@Post()
@Authenticated({ permission: Permission.MEMORY_CREATE })
@Authenticated({ permission: Permission.MemoryCreate })
createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise<MemoryResponseDto> {
return this.service.create(auth, dto);
}
@Get('statistics')
@Authenticated({ permission: Permission.MEMORY_READ })
@Authenticated({ permission: Permission.MemoryRead })
memoriesStatistics(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryStatisticsResponseDto> {
return this.service.statistics(auth, dto);
}
@Get(':id')
@Authenticated({ permission: Permission.MEMORY_READ })
@Authenticated({ permission: Permission.MemoryRead })
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
return this.service.get(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.MEMORY_UPDATE })
@Authenticated({ permission: Permission.MemoryUpdate })
updateMemory(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -55,7 +55,7 @@ export class MemoryController {
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.MEMORY_DELETE })
@Authenticated({ permission: Permission.MemoryDelete })
deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.remove(auth, id);
}

View File

@ -19,31 +19,31 @@ export class NotificationController {
constructor(private service: NotificationService) {}
@Get()
@Authenticated({ permission: Permission.NOTIFICATION_READ })
@Authenticated({ permission: Permission.NotificationRead })
getNotifications(@Auth() auth: AuthDto, @Query() dto: NotificationSearchDto): Promise<NotificationDto[]> {
return this.service.search(auth, dto);
}
@Put()
@Authenticated({ permission: Permission.NOTIFICATION_UPDATE })
@Authenticated({ permission: Permission.NotificationUpdate })
updateNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationUpdateAllDto): Promise<void> {
return this.service.updateAll(auth, dto);
}
@Delete()
@Authenticated({ permission: Permission.NOTIFICATION_DELETE })
@Authenticated({ permission: Permission.NotificationDelete })
deleteNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationDeleteAllDto): Promise<void> {
return this.service.deleteAll(auth, dto);
}
@Get(':id')
@Authenticated({ permission: Permission.NOTIFICATION_READ })
@Authenticated({ permission: Permission.NotificationRead })
getNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<NotificationDto> {
return this.service.get(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.NOTIFICATION_UPDATE })
@Authenticated({ permission: Permission.NotificationUpdate })
updateNotification(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -53,7 +53,7 @@ export class NotificationController {
}
@Delete(':id')
@Authenticated({ permission: Permission.NOTIFICATION_DELETE })
@Authenticated({ permission: Permission.NotificationDelete })
deleteNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}

View File

@ -41,8 +41,8 @@ export class OAuthController {
{
isSecure: loginDetails.isSecure,
values: [
{ key: ImmichCookie.OAUTH_STATE, value: state },
{ key: ImmichCookie.OAUTH_CODE_VERIFIER, value: codeVerifier },
{ key: ImmichCookie.OAuthState, value: state },
{ key: ImmichCookie.OAuthCodeVerifier, value: codeVerifier },
],
},
);
@ -56,14 +56,14 @@ export class OAuthController {
@GetLoginDetails() loginDetails: LoginDetails,
): Promise<LoginResponseDto> {
const body = await this.service.callback(dto, request.headers, loginDetails);
res.clearCookie(ImmichCookie.OAUTH_STATE);
res.clearCookie(ImmichCookie.OAUTH_CODE_VERIFIER);
res.clearCookie(ImmichCookie.OAuthState);
res.clearCookie(ImmichCookie.OAuthCodeVerifier);
return respondWithCookie(res, body, {
isSecure: loginDetails.isSecure,
values: [
{ key: ImmichCookie.ACCESS_TOKEN, value: body.accessToken },
{ key: ImmichCookie.AUTH_TYPE, value: AuthType.OAUTH },
{ key: ImmichCookie.IS_AUTHENTICATED, value: 'true' },
{ key: ImmichCookie.AccessToken, value: body.accessToken },
{ key: ImmichCookie.AuthType, value: AuthType.OAuth },
{ key: ImmichCookie.IsAuthenticated, value: 'true' },
],
});
}

View File

@ -13,19 +13,19 @@ export class PartnerController {
constructor(private service: PartnerService) {}
@Get()
@Authenticated({ permission: Permission.PARTNER_READ })
@Authenticated({ permission: Permission.PartnerRead })
getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise<PartnerResponseDto[]> {
return this.service.search(auth, dto);
}
@Post(':id')
@Authenticated({ permission: Permission.PARTNER_CREATE })
@Authenticated({ permission: Permission.PartnerCreate })
createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
return this.service.create(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.PARTNER_UPDATE })
@Authenticated({ permission: Permission.PartnerUpdate })
updatePartner(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -35,7 +35,7 @@ export class PartnerController {
}
@Delete(':id')
@Authenticated({ permission: Permission.PARTNER_DELETE })
@Authenticated({ permission: Permission.PartnerDelete })
removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.remove(auth, id);
}

View File

@ -45,38 +45,38 @@ export class PersonController {
}
@Get()
@Authenticated({ permission: Permission.PERSON_READ })
@Authenticated({ permission: Permission.PersonRead })
getAllPeople(@Auth() auth: AuthDto, @Query() options: PersonSearchDto): Promise<PeopleResponseDto> {
return this.service.getAll(auth, options);
}
@Post()
@Authenticated({ permission: Permission.PERSON_CREATE })
@Authenticated({ permission: Permission.PersonCreate })
createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
return this.service.create(auth, dto);
}
@Put()
@Authenticated({ permission: Permission.PERSON_UPDATE })
@Authenticated({ permission: Permission.PersonUpdate })
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
return this.service.updateAll(auth, dto);
}
@Delete()
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.PERSON_DELETE })
@Authenticated({ permission: Permission.PersonDelete })
deletePeople(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
return this.service.deleteAll(auth, dto);
}
@Get(':id')
@Authenticated({ permission: Permission.PERSON_READ })
@Authenticated({ permission: Permission.PersonRead })
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
return this.service.getById(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.PERSON_UPDATE })
@Authenticated({ permission: Permission.PersonUpdate })
updatePerson(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -87,20 +87,20 @@ export class PersonController {
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.PERSON_DELETE })
@Authenticated({ permission: Permission.PersonDelete })
deletePerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}
@Get(':id/statistics')
@Authenticated({ permission: Permission.PERSON_STATISTICS })
@Authenticated({ permission: Permission.PersonStatistics })
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
return this.service.getStatistics(auth, id);
}
@Get(':id/thumbnail')
@FileResponse()
@Authenticated({ permission: Permission.PERSON_READ })
@Authenticated({ permission: Permission.PersonRead })
async getPersonThumbnail(
@Res() res: Response,
@Next() next: NextFunction,
@ -111,7 +111,7 @@ export class PersonController {
}
@Put(':id/reassign')
@Authenticated({ permission: Permission.PERSON_REASSIGN })
@Authenticated({ permission: Permission.PersonReassign })
reassignFaces(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -121,7 +121,7 @@ export class PersonController {
}
@Post(':id/merge')
@Authenticated({ permission: Permission.PERSON_MERGE })
@Authenticated({ permission: Permission.PersonMerge })
mergePerson(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,

View File

@ -13,26 +13,26 @@ export class SessionController {
constructor(private service: SessionService) {}
@Post()
@Authenticated({ permission: Permission.SESSION_CREATE })
@Authenticated({ permission: Permission.SessionCreate })
createSession(@Auth() auth: AuthDto, @Body() dto: SessionCreateDto): Promise<SessionCreateResponseDto> {
return this.service.create(auth, dto);
}
@Get()
@Authenticated({ permission: Permission.SESSION_READ })
@Authenticated({ permission: Permission.SessionRead })
getSessions(@Auth() auth: AuthDto): Promise<SessionResponseDto[]> {
return this.service.getAll(auth);
}
@Delete()
@Authenticated({ permission: Permission.SESSION_DELETE })
@Authenticated({ permission: Permission.SessionDelete })
@HttpCode(HttpStatus.NO_CONTENT)
deleteAllSessions(@Auth() auth: AuthDto): Promise<void> {
return this.service.deleteAll(auth);
}
@Put(':id')
@Authenticated({ permission: Permission.SESSION_UPDATE })
@Authenticated({ permission: Permission.SessionUpdate })
updateSession(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -42,14 +42,14 @@ export class SessionController {
}
@Delete(':id')
@Authenticated({ permission: Permission.SESSION_DELETE })
@Authenticated({ permission: Permission.SessionDelete })
@HttpCode(HttpStatus.NO_CONTENT)
deleteSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}
@Post(':id/lock')
@Authenticated({ permission: Permission.SESSION_LOCK })
@Authenticated({ permission: Permission.SessionLock })
@HttpCode(HttpStatus.NO_CONTENT)
lockSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.lock(auth, id);

View File

@ -24,7 +24,7 @@ export class SharedLinkController {
constructor(private service: SharedLinkService) {}
@Get()
@Authenticated({ permission: Permission.SHARED_LINK_READ })
@Authenticated({ permission: Permission.SharedLinkRead })
getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> {
return this.service.getAll(auth, dto);
}
@ -38,31 +38,31 @@ export class SharedLinkController {
@Res({ passthrough: true }) res: Response,
@GetLoginDetails() loginDetails: LoginDetails,
): Promise<SharedLinkResponseDto> {
const sharedLinkToken = request.cookies?.[ImmichCookie.SHARED_LINK_TOKEN];
const sharedLinkToken = request.cookies?.[ImmichCookie.SharedLinkToken];
if (sharedLinkToken) {
dto.token = sharedLinkToken;
}
const body = await this.service.getMine(auth, dto);
return respondWithCookie(res, body, {
isSecure: loginDetails.isSecure,
values: body.token ? [{ key: ImmichCookie.SHARED_LINK_TOKEN, value: body.token }] : [],
values: body.token ? [{ key: ImmichCookie.SharedLinkToken, value: body.token }] : [],
});
}
@Get(':id')
@Authenticated({ permission: Permission.SHARED_LINK_READ })
@Authenticated({ permission: Permission.SharedLinkRead })
getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
return this.service.get(auth, id);
}
@Post()
@Authenticated({ permission: Permission.SHARED_LINK_CREATE })
@Authenticated({ permission: Permission.SharedLinkCreate })
createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
return this.service.create(auth, dto);
}
@Patch(':id')
@Authenticated({ permission: Permission.SHARED_LINK_UPDATE })
@Authenticated({ permission: Permission.SharedLinkUpdate })
updateSharedLink(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -72,7 +72,7 @@ export class SharedLinkController {
}
@Delete(':id')
@Authenticated({ permission: Permission.SHARED_LINK_DELETE })
@Authenticated({ permission: Permission.SharedLinkDelete })
removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.remove(auth, id);
}

View File

@ -14,32 +14,32 @@ export class StackController {
constructor(private service: StackService) {}
@Get()
@Authenticated({ permission: Permission.STACK_READ })
@Authenticated({ permission: Permission.StackRead })
searchStacks(@Auth() auth: AuthDto, @Query() query: StackSearchDto): Promise<StackResponseDto[]> {
return this.service.search(auth, query);
}
@Post()
@Authenticated({ permission: Permission.STACK_CREATE })
@Authenticated({ permission: Permission.StackCreate })
createStack(@Auth() auth: AuthDto, @Body() dto: StackCreateDto): Promise<StackResponseDto> {
return this.service.create(auth, dto);
}
@Delete()
@Authenticated({ permission: Permission.STACK_DELETE })
@Authenticated({ permission: Permission.StackDelete })
@HttpCode(HttpStatus.NO_CONTENT)
deleteStacks(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
return this.service.deleteAll(auth, dto);
}
@Get(':id')
@Authenticated({ permission: Permission.STACK_READ })
@Authenticated({ permission: Permission.StackRead })
getStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<StackResponseDto> {
return this.service.get(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.STACK_UPDATE })
@Authenticated({ permission: Permission.StackUpdate })
updateStack(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -50,7 +50,7 @@ export class StackController {
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.STACK_DELETE })
@Authenticated({ permission: Permission.StackDelete })
deleteStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}

View File

@ -15,25 +15,25 @@ export class SystemConfigController {
) {}
@Get()
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
getConfig(): Promise<SystemConfigDto> {
return this.service.getSystemConfig();
}
@Get('defaults')
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
getConfigDefaults(): SystemConfigDto {
return this.service.getDefaults();
}
@Put()
@Authenticated({ permission: Permission.SYSTEM_CONFIG_UPDATE, admin: true })
@Authenticated({ permission: Permission.SystemConfigUpdate, admin: true })
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
return this.service.updateSystemConfig(dto);
}
@Get('storage-template-options')
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
return this.storageTemplateService.getStorageTemplateOptions();
}

View File

@ -15,26 +15,26 @@ export class SystemMetadataController {
constructor(private service: SystemMetadataService) {}
@Get('admin-onboarding')
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> {
return this.service.getAdminOnboarding();
}
@Post('admin-onboarding')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.SYSTEM_METADATA_UPDATE, admin: true })
@Authenticated({ permission: Permission.SystemMetadataUpdate, admin: true })
updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> {
return this.service.updateAdminOnboarding(dto);
}
@Get('reverse-geocoding-state')
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
return this.service.getReverseGeocodingState();
}
@Get('version-check-state')
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
getVersionCheckState(): Promise<VersionCheckStateResponseDto> {
return this.service.getVersionCheckState();
}

View File

@ -21,50 +21,50 @@ export class TagController {
constructor(private service: TagService) {}
@Post()
@Authenticated({ permission: Permission.TAG_CREATE })
@Authenticated({ permission: Permission.TagCreate })
createTag(@Auth() auth: AuthDto, @Body() dto: TagCreateDto): Promise<TagResponseDto> {
return this.service.create(auth, dto);
}
@Get()
@Authenticated({ permission: Permission.TAG_READ })
@Authenticated({ permission: Permission.TagRead })
getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
return this.service.getAll(auth);
}
@Put()
@Authenticated({ permission: Permission.TAG_CREATE })
@Authenticated({ permission: Permission.TagCreate })
upsertTags(@Auth() auth: AuthDto, @Body() dto: TagUpsertDto): Promise<TagResponseDto[]> {
return this.service.upsert(auth, dto);
}
@Put('assets')
@Authenticated({ permission: Permission.TAG_ASSET })
@Authenticated({ permission: Permission.TagAsset })
bulkTagAssets(@Auth() auth: AuthDto, @Body() dto: TagBulkAssetsDto): Promise<TagBulkAssetsResponseDto> {
return this.service.bulkTagAssets(auth, dto);
}
@Get(':id')
@Authenticated({ permission: Permission.TAG_READ })
@Authenticated({ permission: Permission.TagRead })
getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
return this.service.get(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.TAG_UPDATE })
@Authenticated({ permission: Permission.TagUpdate })
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: TagUpdateDto): Promise<TagResponseDto> {
return this.service.update(auth, id, dto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated({ permission: Permission.TAG_DELETE })
@Authenticated({ permission: Permission.TagDelete })
deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.remove(auth, id);
}
@Put(':id/assets')
@Authenticated({ permission: Permission.TAG_ASSET })
@Authenticated({ permission: Permission.TagAsset })
tagAssets(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -74,7 +74,7 @@ export class TagController {
}
@Delete(':id/assets')
@Authenticated({ permission: Permission.TAG_ASSET })
@Authenticated({ permission: Permission.TagAsset })
untagAssets(
@Auth() auth: AuthDto,
@Body() dto: BulkIdsDto,

View File

@ -12,13 +12,13 @@ export class TimelineController {
constructor(private service: TimelineService) {}
@Get('buckets')
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
@Authenticated({ permission: Permission.AssetRead, sharedLink: true })
getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto) {
return this.service.getTimeBuckets(auth, dto);
}
@Get('bucket')
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
@Authenticated({ permission: Permission.AssetRead, sharedLink: true })
@ApiOkResponse({ type: TimeBucketAssetResponseDto })
@Header('Content-Type', 'application/json')
getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto) {

View File

@ -14,21 +14,21 @@ export class TrashController {
@Post('empty')
@HttpCode(HttpStatus.OK)
@Authenticated({ permission: Permission.ASSET_DELETE })
@Authenticated({ permission: Permission.AssetDelete })
emptyTrash(@Auth() auth: AuthDto): Promise<TrashResponseDto> {
return this.service.empty(auth);
}
@Post('restore')
@HttpCode(HttpStatus.OK)
@Authenticated({ permission: Permission.ASSET_DELETE })
@Authenticated({ permission: Permission.AssetDelete })
restoreTrash(@Auth() auth: AuthDto): Promise<TrashResponseDto> {
return this.service.restore(auth);
}
@Post('restore/assets')
@HttpCode(HttpStatus.OK)
@Authenticated({ permission: Permission.ASSET_DELETE })
@Authenticated({ permission: Permission.AssetDelete })
restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<TrashResponseDto> {
return this.service.restoreAssets(auth, dto);
}

View File

@ -21,25 +21,25 @@ export class UserAdminController {
constructor(private service: UserAdminService) {}
@Get()
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
return this.service.search(auth, dto);
}
@Post()
@Authenticated({ permission: Permission.ADMIN_USER_CREATE, admin: true })
@Authenticated({ permission: Permission.AdminUserCreate, admin: true })
createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
return this.service.create(createUserDto);
}
@Get(':id')
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
getUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
return this.service.get(auth, id);
}
@Put(':id')
@Authenticated({ permission: Permission.ADMIN_USER_UPDATE, admin: true })
@Authenticated({ permission: Permission.AdminUserUpdate, admin: true })
updateUserAdmin(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -49,7 +49,7 @@ export class UserAdminController {
}
@Delete(':id')
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
@Authenticated({ permission: Permission.AdminUserDelete, admin: true })
deleteUserAdmin(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -59,7 +59,7 @@ export class UserAdminController {
}
@Get(':id/statistics')
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
getUserStatisticsAdmin(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -69,13 +69,13 @@ export class UserAdminController {
}
@Get(':id/preferences')
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
getUserPreferencesAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserPreferencesResponseDto> {
return this.service.getPreferences(auth, id);
}
@Put(':id/preferences')
@Authenticated({ permission: Permission.ADMIN_USER_UPDATE, admin: true })
@Authenticated({ permission: Permission.AdminUserUpdate, admin: true })
updateUserPreferencesAdmin(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -85,7 +85,7 @@ export class UserAdminController {
}
@Post(':id/restore')
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
@Authenticated({ permission: Permission.AdminUserDelete, admin: true })
@HttpCode(HttpStatus.OK)
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
return this.service.restore(auth, id);

View File

@ -30,7 +30,7 @@ import { sendFile } from 'src/utils/file';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Users')
@Controller(RouteKey.USER)
@Controller(RouteKey.User)
export class UserController {
constructor(
private service: UserService,

View File

@ -25,8 +25,8 @@ export interface MoveRequest {
};
}
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL | AssetPathType.FULLSIZE;
export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO;
export type GeneratedImageType = AssetPathType.Preview | AssetPathType.Thumbnail | AssetPathType.FullSize;
export type GeneratedAssetType = GeneratedImageType | AssetPathType.EncodedVideo;
export type ThumbnailPathEntity = { id: string; ownerId: string };
@ -79,7 +79,7 @@ export class StorageCore {
}
static getLibraryFolder(user: { storageLabel: string | null; id: string }) {
return join(StorageCore.getBaseFolder(StorageFolder.LIBRARY), user.storageLabel || user.id);
return join(StorageCore.getBaseFolder(StorageFolder.Library), user.storageLabel || user.id);
}
static getBaseFolder(folder: StorageFolder) {
@ -87,23 +87,23 @@ export class StorageCore {
}
static getPersonThumbnailPath(person: ThumbnailPathEntity) {
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`);
return StorageCore.getNestedPath(StorageFolder.Thumbnails, person.ownerId, `${person.id}.jpeg`);
}
static getImagePath(asset: ThumbnailPathEntity, type: GeneratedImageType, format: 'jpeg' | 'webp') {
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`);
return StorageCore.getNestedPath(StorageFolder.Thumbnails, asset.ownerId, `${asset.id}-${type}.${format}`);
}
static getEncodedVideoPath(asset: ThumbnailPathEntity) {
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}.mp4`);
return StorageCore.getNestedPath(StorageFolder.EncodedVideo, asset.ownerId, `${asset.id}.mp4`);
}
static getAndroidMotionPath(asset: ThumbnailPathEntity, uuid: string) {
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${uuid}-MP.mp4`);
return StorageCore.getNestedPath(StorageFolder.EncodedVideo, asset.ownerId, `${uuid}-MP.mp4`);
}
static isAndroidMotionPath(originalPath: string) {
return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.ENCODED_VIDEO));
return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.EncodedVideo));
}
static isImmichPath(path: string) {
@ -130,7 +130,7 @@ export class StorageCore {
async moveAssetVideo(asset: StorageAsset) {
return this.moveFile({
entityId: asset.id,
pathType: AssetPathType.ENCODED_VIDEO,
pathType: AssetPathType.EncodedVideo,
oldPath: asset.encodedVideoPath,
newPath: StorageCore.getEncodedVideoPath(asset),
});
@ -139,7 +139,7 @@ export class StorageCore {
async movePersonFile(person: { id: string; ownerId: string; thumbnailPath: string }, pathType: PersonPathType) {
const { id: entityId, thumbnailPath } = person;
switch (pathType) {
case PersonPathType.FACE: {
case PersonPathType.Face: {
await this.moveFile({
entityId,
pathType,
@ -188,7 +188,7 @@ export class StorageCore {
move = await this.moveRepository.create({ entityId, pathType, oldPath, newPath });
}
if (pathType === AssetPathType.ORIGINAL && !assetInfo) {
if (pathType === AssetPathType.Original && !assetInfo) {
this.logger.warn(`Unable to complete move. Missing asset info for ${entityId}`);
return;
}
@ -274,25 +274,25 @@ export class StorageCore {
private savePath(pathType: PathType, id: string, newPath: string) {
switch (pathType) {
case AssetPathType.ORIGINAL: {
case AssetPathType.Original: {
return this.assetRepository.update({ id, originalPath: newPath });
}
case AssetPathType.FULLSIZE: {
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.FULLSIZE, path: newPath });
case AssetPathType.FullSize: {
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.FullSize, path: newPath });
}
case AssetPathType.PREVIEW: {
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.PREVIEW, path: newPath });
case AssetPathType.Preview: {
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Preview, path: newPath });
}
case AssetPathType.THUMBNAIL: {
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.THUMBNAIL, path: newPath });
case AssetPathType.Thumbnail: {
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Thumbnail, path: newPath });
}
case AssetPathType.ENCODED_VIDEO: {
case AssetPathType.EncodedVideo: {
return this.assetRepository.update({ id, encodedVideoPath: newPath });
}
case AssetPathType.SIDECAR: {
case AssetPathType.Sidecar: {
return this.assetRepository.update({ id, sidecarPath: newPath });
}
case PersonPathType.FACE: {
case PersonPathType.Face: {
return this.personRepository.update({ id, thumbnailPath: newPath });
}
}

View File

@ -131,7 +131,7 @@ export interface GenerateSqlQueries {
}
export const Telemetry = (options: { enabled?: boolean }) =>
SetMetadata(MetadataKey.TELEMETRY_ENABLED, options?.enabled ?? true);
SetMetadata(MetadataKey.TelemetryEnabled, options?.enabled ?? true);
/** Decorator to enable versioning/tracking of generated Sql */
export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options);
@ -145,13 +145,13 @@ export type EventConfig = {
/** register events for these workers, defaults to all workers */
workers?: ImmichWorker[];
};
export const OnEvent = (config: EventConfig) => SetMetadata(MetadataKey.EVENT_CONFIG, config);
export const OnEvent = (config: EventConfig) => SetMetadata(MetadataKey.EventConfig, config);
export type JobConfig = {
name: JobName;
queue: QueueName;
};
export const OnJob = (config: JobConfig) => SetMetadata(MetadataKey.JOB_CONFIG, config);
export const OnJob = (config: JobConfig) => SetMetadata(MetadataKey.JobConfig, config);
type LifecycleRelease = 'NEXT_RELEASE' | string;
type LifecycleMetadata = {

View File

@ -18,7 +18,7 @@ export class AlbumUserAddDto {
@ValidateUUID()
userId!: string;
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', default: AlbumUserRole.EDITOR })
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', default: AlbumUserRole.Editor })
role?: AlbumUserRole;
}

View File

@ -205,7 +205,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
localDateTime: entity.localDateTime,
updatedAt: entity.updatedAt,
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
isArchived: entity.visibility === AssetVisibility.ARCHIVE,
isArchived: entity.visibility === AssetVisibility.Archive,
isTrashed: !!entity.deletedAt,
visibility: entity.visibility,
duration: entity.duration ?? '0:00:00.00000',

View File

@ -126,8 +126,8 @@ export class AssetStatsResponseDto {
export const mapStats = (stats: AssetStats): AssetStatsResponseDto => {
return {
images: stats[AssetType.IMAGE],
videos: stats[AssetType.VIDEO],
images: stats[AssetType.Image],
videos: stats[AssetType.Video],
total: Object.values(stats).reduce((total, value) => total + value, 0),
};
};

View File

@ -45,7 +45,7 @@ export class LoginResponseDto {
export function mapLoginResponse(entity: UserAdmin, accessToken: string): LoginResponseDto {
const onboardingMetadata = entity.metadata.find(
(item): item is UserMetadataItem<UserMetadataKey.ONBOARDING> => item.key === UserMetadataKey.ONBOARDING,
(item): item is UserMetadataItem<UserMetadataKey.Onboarding> => item.key === UserMetadataKey.Onboarding,
)?.value;
return {

View File

@ -50,47 +50,47 @@ export class JobStatusDto {
export class AllJobStatusResponseDto implements Record<QueueName, JobStatusDto> {
@ApiProperty({ type: JobStatusDto })
[QueueName.THUMBNAIL_GENERATION]!: JobStatusDto;
[QueueName.ThumbnailGeneration]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.METADATA_EXTRACTION]!: JobStatusDto;
[QueueName.MetadataExtraction]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.VIDEO_CONVERSION]!: JobStatusDto;
[QueueName.VideoConversion]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.SMART_SEARCH]!: JobStatusDto;
[QueueName.SmartSearch]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.STORAGE_TEMPLATE_MIGRATION]!: JobStatusDto;
[QueueName.StorageTemplateMigration]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.MIGRATION]!: JobStatusDto;
[QueueName.Migration]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.BACKGROUND_TASK]!: JobStatusDto;
[QueueName.BackgroundTask]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.SEARCH]!: JobStatusDto;
[QueueName.Search]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.DUPLICATE_DETECTION]!: JobStatusDto;
[QueueName.DuplicateDetection]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.FACE_DETECTION]!: JobStatusDto;
[QueueName.FaceDetection]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.FACIAL_RECOGNITION]!: JobStatusDto;
[QueueName.FacialRecognition]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.SIDECAR]!: JobStatusDto;
[QueueName.Sidecar]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.LIBRARY]!: JobStatusDto;
[QueueName.Library]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.NOTIFICATION]!: JobStatusDto;
[QueueName.Notification]!: JobStatusDto;
@ApiProperty({ type: JobStatusDto })
[QueueName.BACKUP_DATABASE]!: JobStatusDto;
[QueueName.BackupDatabase]!: JobStatusDto;
}

View File

@ -50,7 +50,7 @@ export class MemoryCreateDto extends MemoryBaseDto {
@ValidateNested()
@Type((options) => {
switch (options?.object.type) {
case MemoryType.ON_THIS_DAY: {
case MemoryType.OnThisDay: {
return OnThisDayDto;
}

View File

@ -170,7 +170,7 @@ export class MetadataSearchDto extends RandomSearchDto {
@Optional()
encodedVideoPath?: string;
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.DESC })
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.Desc })
order?: AssetOrder;
@IsInt()

View File

@ -26,7 +26,7 @@ import {
OAuthTokenEndpointAuthMethod,
QueueName,
ToneMapping,
TranscodeHWAccel,
TranscodeHardwareAcceleration,
TranscodePolicy,
VideoCodec,
VideoContainer,
@ -136,8 +136,8 @@ export class SystemConfigFFmpegDto {
@ValidateEnum({ enum: TranscodePolicy, name: 'TranscodePolicy' })
transcode!: TranscodePolicy;
@ValidateEnum({ enum: TranscodeHWAccel, name: 'TranscodeHWAccel' })
accel!: TranscodeHWAccel;
@ValidateEnum({ enum: TranscodeHardwareAcceleration, name: 'TranscodeHWAccel' })
accel!: TranscodeHardwareAcceleration;
@ValidateBoolean()
accelDecode!: boolean;
@ -158,67 +158,67 @@ class SystemConfigJobDto implements Record<ConcurrentQueueName, JobSettingsDto>
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.THUMBNAIL_GENERATION]!: JobSettingsDto;
[QueueName.ThumbnailGeneration]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.METADATA_EXTRACTION]!: JobSettingsDto;
[QueueName.MetadataExtraction]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.VIDEO_CONVERSION]!: JobSettingsDto;
[QueueName.VideoConversion]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.SMART_SEARCH]!: JobSettingsDto;
[QueueName.SmartSearch]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.MIGRATION]!: JobSettingsDto;
[QueueName.Migration]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.BACKGROUND_TASK]!: JobSettingsDto;
[QueueName.BackgroundTask]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.SEARCH]!: JobSettingsDto;
[QueueName.Search]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.FACE_DETECTION]!: JobSettingsDto;
[QueueName.FaceDetection]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.SIDECAR]!: JobSettingsDto;
[QueueName.Sidecar]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.LIBRARY]!: JobSettingsDto;
[QueueName.Library]!: JobSettingsDto;
@ApiProperty({ type: JobSettingsDto })
@ValidateNested()
@IsObject()
@Type(() => JobSettingsDto)
[QueueName.NOTIFICATION]!: JobSettingsDto;
[QueueName.Notification]!: JobSettingsDto;
}
class SystemConfigLibraryScanDto {

View File

@ -157,7 +157,7 @@ export class UserPreferencesUpdateDto {
class AlbumsResponse {
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' })
defaultAssetOrder: AssetOrder = AssetOrder.DESC;
defaultAssetOrder: AssetOrder = AssetOrder.Desc;
}
class RatingsResponse {

View File

@ -171,7 +171,7 @@ export class UserAdminResponseDto extends UserResponseDto {
export function mapUserAdmin(entity: UserAdmin): UserAdminResponseDto {
const metadata = entity.metadata || [];
const license = metadata.find(
(item): item is UserMetadataItem<UserMetadataKey.LICENSE> => item.key === UserMetadataKey.LICENSE,
(item): item is UserMetadataItem<UserMetadataKey.License> => item.key === UserMetadataKey.License,
)?.value;
return {
...mapUser(entity),

View File

@ -1,402 +1,397 @@
export enum AuthType {
PASSWORD = 'password',
OAUTH = 'oauth',
Password = 'password',
OAuth = 'oauth',
}
export enum ImmichCookie {
ACCESS_TOKEN = 'immich_access_token',
AUTH_TYPE = 'immich_auth_type',
IS_AUTHENTICATED = 'immich_is_authenticated',
SHARED_LINK_TOKEN = 'immich_shared_link_token',
OAUTH_STATE = 'immich_oauth_state',
OAUTH_CODE_VERIFIER = 'immich_oauth_code_verifier',
AccessToken = 'immich_access_token',
AuthType = 'immich_auth_type',
IsAuthenticated = 'immich_is_authenticated',
SharedLinkToken = 'immich_shared_link_token',
OAuthState = 'immich_oauth_state',
OAuthCodeVerifier = 'immich_oauth_code_verifier',
}
export enum ImmichHeader {
API_KEY = 'x-api-key',
USER_TOKEN = 'x-immich-user-token',
SESSION_TOKEN = 'x-immich-session-token',
SHARED_LINK_KEY = 'x-immich-share-key',
CHECKSUM = 'x-immich-checksum',
CID = 'x-immich-cid',
ApiKey = 'x-api-key',
UserToken = 'x-immich-user-token',
SessionToken = 'x-immich-session-token',
SharedLinkKey = 'x-immich-share-key',
Checksum = 'x-immich-checksum',
Cid = 'x-immich-cid',
}
export enum ImmichQuery {
SHARED_LINK_KEY = 'key',
API_KEY = 'apiKey',
SESSION_KEY = 'sessionKey',
SharedLinkKey = 'key',
ApiKey = 'apiKey',
SessionKey = 'sessionKey',
}
export enum AssetType {
IMAGE = 'IMAGE',
VIDEO = 'VIDEO',
AUDIO = 'AUDIO',
OTHER = 'OTHER',
Image = 'IMAGE',
Video = 'VIDEO',
Audio = 'AUDIO',
Other = 'OTHER',
}
export enum AssetFileType {
/**
* An full/large-size image extracted/converted from RAW photos
*/
FULLSIZE = 'fullsize',
PREVIEW = 'preview',
THUMBNAIL = 'thumbnail',
FullSize = 'fullsize',
Preview = 'preview',
Thumbnail = 'thumbnail',
}
export enum AlbumUserRole {
EDITOR = 'editor',
VIEWER = 'viewer',
Editor = 'editor',
Viewer = 'viewer',
}
export enum AssetOrder {
ASC = 'asc',
DESC = 'desc',
Asc = 'asc',
Desc = 'desc',
}
export enum DatabaseAction {
CREATE = 'CREATE',
UPDATE = 'UPDATE',
DELETE = 'DELETE',
Create = 'CREATE',
Update = 'UPDATE',
Delete = 'DELETE',
}
export enum EntityType {
ASSET = 'ASSET',
ALBUM = 'ALBUM',
Asset = 'ASSET',
Album = 'ALBUM',
}
export enum MemoryType {
/** pictures taken on this day X years ago */
ON_THIS_DAY = 'on_this_day',
OnThisDay = 'on_this_day',
}
export enum Permission {
ALL = 'all',
All = 'all',
ACTIVITY_CREATE = 'activity.create',
ACTIVITY_READ = 'activity.read',
ACTIVITY_UPDATE = 'activity.update',
ACTIVITY_DELETE = 'activity.delete',
ACTIVITY_STATISTICS = 'activity.statistics',
ActivityCreate = 'activity.create',
ActivityRead = 'activity.read',
ActivityUpdate = 'activity.update',
ActivityDelete = 'activity.delete',
ActivityStatistics = 'activity.statistics',
API_KEY_CREATE = 'apiKey.create',
API_KEY_READ = 'apiKey.read',
API_KEY_UPDATE = 'apiKey.update',
API_KEY_DELETE = 'apiKey.delete',
ApiKeyCreate = 'apiKey.create',
ApiKeyRead = 'apiKey.read',
ApiKeyUpdate = 'apiKey.update',
ApiKeyDelete = 'apiKey.delete',
// ASSET_CREATE = 'asset.create',
ASSET_READ = 'asset.read',
ASSET_UPDATE = 'asset.update',
ASSET_DELETE = 'asset.delete',
ASSET_SHARE = 'asset.share',
ASSET_VIEW = 'asset.view',
ASSET_DOWNLOAD = 'asset.download',
ASSET_UPLOAD = 'asset.upload',
AssetRead = 'asset.read',
AssetUpdate = 'asset.update',
AssetDelete = 'asset.delete',
AssetShare = 'asset.share',
AssetView = 'asset.view',
AssetDownload = 'asset.download',
AssetUpload = 'asset.upload',
ALBUM_CREATE = 'album.create',
ALBUM_READ = 'album.read',
ALBUM_UPDATE = 'album.update',
ALBUM_DELETE = 'album.delete',
ALBUM_STATISTICS = 'album.statistics',
AlbumCreate = 'album.create',
AlbumRead = 'album.read',
AlbumUpdate = 'album.update',
AlbumDelete = 'album.delete',
AlbumStatistics = 'album.statistics',
ALBUM_ADD_ASSET = 'album.addAsset',
ALBUM_REMOVE_ASSET = 'album.removeAsset',
ALBUM_SHARE = 'album.share',
ALBUM_DOWNLOAD = 'album.download',
AlbumAddAsset = 'album.addAsset',
AlbumRemoveAsset = 'album.removeAsset',
AlbumShare = 'album.share',
AlbumDownload = 'album.download',
AUTH_DEVICE_DELETE = 'authDevice.delete',
AuthDeviceDelete = 'authDevice.delete',
ARCHIVE_READ = 'archive.read',
ArchiveRead = 'archive.read',
FACE_CREATE = 'face.create',
FACE_READ = 'face.read',
FACE_UPDATE = 'face.update',
FACE_DELETE = 'face.delete',
FaceCreate = 'face.create',
FaceRead = 'face.read',
FaceUpdate = 'face.update',
FaceDelete = 'face.delete',
LIBRARY_CREATE = 'library.create',
LIBRARY_READ = 'library.read',
LIBRARY_UPDATE = 'library.update',
LIBRARY_DELETE = 'library.delete',
LIBRARY_STATISTICS = 'library.statistics',
LibraryCreate = 'library.create',
LibraryRead = 'library.read',
LibraryUpdate = 'library.update',
LibraryDelete = 'library.delete',
LibraryStatistics = 'library.statistics',
TIMELINE_READ = 'timeline.read',
TIMELINE_DOWNLOAD = 'timeline.download',
TimelineRead = 'timeline.read',
TimelineDownload = 'timeline.download',
MEMORY_CREATE = 'memory.create',
MEMORY_READ = 'memory.read',
MEMORY_UPDATE = 'memory.update',
MEMORY_DELETE = 'memory.delete',
MemoryCreate = 'memory.create',
MemoryRead = 'memory.read',
MemoryUpdate = 'memory.update',
MemoryDelete = 'memory.delete',
NOTIFICATION_CREATE = 'notification.create',
NOTIFICATION_READ = 'notification.read',
NOTIFICATION_UPDATE = 'notification.update',
NOTIFICATION_DELETE = 'notification.delete',
NotificationCreate = 'notification.create',
NotificationRead = 'notification.read',
NotificationUpdate = 'notification.update',
NotificationDelete = 'notification.delete',
PARTNER_CREATE = 'partner.create',
PARTNER_READ = 'partner.read',
PARTNER_UPDATE = 'partner.update',
PARTNER_DELETE = 'partner.delete',
PartnerCreate = 'partner.create',
PartnerRead = 'partner.read',
PartnerUpdate = 'partner.update',
PartnerDelete = 'partner.delete',
PERSON_CREATE = 'person.create',
PERSON_READ = 'person.read',
PERSON_UPDATE = 'person.update',
PERSON_DELETE = 'person.delete',
PERSON_STATISTICS = 'person.statistics',
PERSON_MERGE = 'person.merge',
PERSON_REASSIGN = 'person.reassign',
PersonCreate = 'person.create',
PersonRead = 'person.read',
PersonUpdate = 'person.update',
PersonDelete = 'person.delete',
PersonStatistics = 'person.statistics',
PersonMerge = 'person.merge',
PersonReassign = 'person.reassign',
SESSION_CREATE = 'session.create',
SESSION_READ = 'session.read',
SESSION_UPDATE = 'session.update',
SESSION_DELETE = 'session.delete',
SESSION_LOCK = 'session.lock',
SessionCreate = 'session.create',
SessionRead = 'session.read',
SessionUpdate = 'session.update',
SessionDelete = 'session.delete',
SessionLock = 'session.lock',
SHARED_LINK_CREATE = 'sharedLink.create',
SHARED_LINK_READ = 'sharedLink.read',
SHARED_LINK_UPDATE = 'sharedLink.update',
SHARED_LINK_DELETE = 'sharedLink.delete',
SharedLinkCreate = 'sharedLink.create',
SharedLinkRead = 'sharedLink.read',
SharedLinkUpdate = 'sharedLink.update',
SharedLinkDelete = 'sharedLink.delete',
STACK_CREATE = 'stack.create',
STACK_READ = 'stack.read',
STACK_UPDATE = 'stack.update',
STACK_DELETE = 'stack.delete',
StackCreate = 'stack.create',
StackRead = 'stack.read',
StackUpdate = 'stack.update',
StackDelete = 'stack.delete',
SYSTEM_CONFIG_READ = 'systemConfig.read',
SYSTEM_CONFIG_UPDATE = 'systemConfig.update',
SystemConfigRead = 'systemConfig.read',
SystemConfigUpdate = 'systemConfig.update',
SYSTEM_METADATA_READ = 'systemMetadata.read',
SYSTEM_METADATA_UPDATE = 'systemMetadata.update',
SystemMetadataRead = 'systemMetadata.read',
SystemMetadataUpdate = 'systemMetadata.update',
TAG_CREATE = 'tag.create',
TAG_READ = 'tag.read',
TAG_UPDATE = 'tag.update',
TAG_DELETE = 'tag.delete',
TAG_ASSET = 'tag.asset',
TagCreate = 'tag.create',
TagRead = 'tag.read',
TagUpdate = 'tag.update',
TagDelete = 'tag.delete',
TagAsset = 'tag.asset',
ADMIN_USER_CREATE = 'admin.user.create',
ADMIN_USER_READ = 'admin.user.read',
ADMIN_USER_UPDATE = 'admin.user.update',
ADMIN_USER_DELETE = 'admin.user.delete',
AdminUserCreate = 'admin.user.create',
AdminUserRead = 'admin.user.read',
AdminUserUpdate = 'admin.user.update',
AdminUserDelete = 'admin.user.delete',
}
export enum SharedLinkType {
ALBUM = 'ALBUM',
Album = 'ALBUM',
/**
* Individual asset
* or group of assets that are not in an album
*/
INDIVIDUAL = 'INDIVIDUAL',
Individual = 'INDIVIDUAL',
}
export enum StorageFolder {
ENCODED_VIDEO = 'encoded-video',
LIBRARY = 'library',
UPLOAD = 'upload',
PROFILE = 'profile',
THUMBNAILS = 'thumbs',
BACKUPS = 'backups',
EncodedVideo = 'encoded-video',
Library = 'library',
Upload = 'upload',
Profile = 'profile',
Thumbnails = 'thumbs',
Backups = 'backups',
}
export enum SystemMetadataKey {
REVERSE_GEOCODING_STATE = 'reverse-geocoding-state',
FACIAL_RECOGNITION_STATE = 'facial-recognition-state',
MEMORIES_STATE = 'memories-state',
ADMIN_ONBOARDING = 'admin-onboarding',
SYSTEM_CONFIG = 'system-config',
SYSTEM_FLAGS = 'system-flags',
VERSION_CHECK_STATE = 'version-check-state',
LICENSE = 'license',
ReverseGeocodingState = 'reverse-geocoding-state',
FacialRecognitionState = 'facial-recognition-state',
MemoriesState = 'memories-state',
AdminOnboarding = 'admin-onboarding',
SystemConfig = 'system-config',
SystemFlags = 'system-flags',
VersionCheckState = 'version-check-state',
License = 'license',
}
export enum UserMetadataKey {
PREFERENCES = 'preferences',
LICENSE = 'license',
ONBOARDING = 'onboarding',
Preferences = 'preferences',
License = 'license',
Onboarding = 'onboarding',
}
export enum UserAvatarColor {
PRIMARY = 'primary',
PINK = 'pink',
RED = 'red',
YELLOW = 'yellow',
BLUE = 'blue',
GREEN = 'green',
PURPLE = 'purple',
ORANGE = 'orange',
GRAY = 'gray',
AMBER = 'amber',
Primary = 'primary',
Pink = 'pink',
Red = 'red',
Yellow = 'yellow',
Blue = 'blue',
Green = 'green',
Purple = 'purple',
Orange = 'orange',
Gray = 'gray',
Amber = 'amber',
}
export enum UserStatus {
ACTIVE = 'active',
REMOVING = 'removing',
DELETED = 'deleted',
Active = 'active',
Removing = 'removing',
Deleted = 'deleted',
}
export enum AssetStatus {
ACTIVE = 'active',
TRASHED = 'trashed',
DELETED = 'deleted',
Active = 'active',
Trashed = 'trashed',
Deleted = 'deleted',
}
export enum SourceType {
MACHINE_LEARNING = 'machine-learning',
EXIF = 'exif',
MANUAL = 'manual',
MachineLearning = 'machine-learning',
Exif = 'exif',
Manual = 'manual',
}
export enum ManualJobName {
PERSON_CLEANUP = 'person-cleanup',
TAG_CLEANUP = 'tag-cleanup',
USER_CLEANUP = 'user-cleanup',
MEMORY_CLEANUP = 'memory-cleanup',
MEMORY_CREATE = 'memory-create',
BACKUP_DATABASE = 'backup-database',
PersonCleanup = 'person-cleanup',
TagCleanup = 'tag-cleanup',
UserCleanup = 'user-cleanup',
MemoryCleanup = 'memory-cleanup',
MemoryCreate = 'memory-create',
BackupDatabase = 'backup-database',
}
export enum AssetPathType {
ORIGINAL = 'original',
FULLSIZE = 'fullsize',
PREVIEW = 'preview',
THUMBNAIL = 'thumbnail',
ENCODED_VIDEO = 'encoded_video',
SIDECAR = 'sidecar',
Original = 'original',
FullSize = 'fullsize',
Preview = 'preview',
Thumbnail = 'thumbnail',
EncodedVideo = 'encoded_video',
Sidecar = 'sidecar',
}
export enum PersonPathType {
FACE = 'face',
Face = 'face',
}
export enum UserPathType {
PROFILE = 'profile',
Profile = 'profile',
}
export type PathType = AssetPathType | PersonPathType | UserPathType;
export enum TranscodePolicy {
ALL = 'all',
OPTIMAL = 'optimal',
BITRATE = 'bitrate',
REQUIRED = 'required',
DISABLED = 'disabled',
All = 'all',
Optimal = 'optimal',
Bitrate = 'bitrate',
Required = 'required',
Disabled = 'disabled',
}
export enum TranscodeTarget {
NONE,
AUDIO,
VIDEO,
ALL,
None = 'NONE',
Audio = 'AUDIO',
Video = 'VIDEO',
All = 'ALL',
}
export enum VideoCodec {
H264 = 'h264',
HEVC = 'hevc',
VP9 = 'vp9',
AV1 = 'av1',
Hevc = 'hevc',
Vp9 = 'vp9',
Av1 = 'av1',
}
export enum AudioCodec {
MP3 = 'mp3',
AAC = 'aac',
LIBOPUS = 'libopus',
PCMS16LE = 'pcm_s16le',
Mp3 = 'mp3',
Aac = 'aac',
LibOpus = 'libopus',
PcmS16le = 'pcm_s16le',
}
export enum VideoContainer {
MOV = 'mov',
MP4 = 'mp4',
OGG = 'ogg',
WEBM = 'webm',
Mov = 'mov',
Mp4 = 'mp4',
Ogg = 'ogg',
Webm = 'webm',
}
export enum TranscodeHWAccel {
NVENC = 'nvenc',
QSV = 'qsv',
VAAPI = 'vaapi',
RKMPP = 'rkmpp',
DISABLED = 'disabled',
export enum TranscodeHardwareAcceleration {
Nvenc = 'nvenc',
Qsv = 'qsv',
Vaapi = 'vaapi',
Rkmpp = 'rkmpp',
Disabled = 'disabled',
}
export enum ToneMapping {
HABLE = 'hable',
MOBIUS = 'mobius',
REINHARD = 'reinhard',
DISABLED = 'disabled',
Hable = 'hable',
Mobius = 'mobius',
Reinhard = 'reinhard',
Disabled = 'disabled',
}
export enum CQMode {
AUTO = 'auto',
CQP = 'cqp',
ICQ = 'icq',
Auto = 'auto',
Cqp = 'cqp',
Icq = 'icq',
}
export enum Colorspace {
SRGB = 'srgb',
Srgb = 'srgb',
P3 = 'p3',
}
export enum ImageFormat {
JPEG = 'jpeg',
WEBP = 'webp',
Jpeg = 'jpeg',
Webp = 'webp',
}
export enum RawExtractedFormat {
JPEG = 'jpeg',
JXL = 'jxl',
Jpeg = 'jpeg',
Jxl = 'jxl',
}
export enum LogLevel {
VERBOSE = 'verbose',
DEBUG = 'debug',
LOG = 'log',
WARN = 'warn',
ERROR = 'error',
FATAL = 'fatal',
Verbose = 'verbose',
Debug = 'debug',
Log = 'log',
Warn = 'warn',
Error = 'error',
Fatal = 'fatal',
}
export enum MetadataKey {
AUTH_ROUTE = 'auth_route',
ADMIN_ROUTE = 'admin_route',
SHARED_ROUTE = 'shared_route',
API_KEY_SECURITY = 'api_key',
EVENT_CONFIG = 'event_config',
JOB_CONFIG = 'job_config',
TELEMETRY_ENABLED = 'telemetry_enabled',
AuthRoute = 'auth_route',
AdminRoute = 'admin_route',
SharedRoute = 'shared_route',
ApiKeySecurity = 'api_key',
EventConfig = 'event_config',
JobConfig = 'job_config',
TelemetryEnabled = 'telemetry_enabled',
}
export enum RouteKey {
ASSET = 'assets',
USER = 'users',
Asset = 'assets',
User = 'users',
}
export enum CacheControl {
PRIVATE_WITH_CACHE = 'private_with_cache',
PRIVATE_WITHOUT_CACHE = 'private_without_cache',
NONE = 'none',
}
export enum PaginationMode {
LIMIT_OFFSET = 'limit-offset',
SKIP_TAKE = 'skip-take',
PrivateWithCache = 'private_with_cache',
PrivateWithoutCache = 'private_without_cache',
None = 'none',
}
export enum ImmichEnvironment {
DEVELOPMENT = 'development',
TESTING = 'testing',
PRODUCTION = 'production',
Development = 'development',
Testing = 'testing',
Production = 'production',
}
export enum ImmichWorker {
API = 'api',
MICROSERVICES = 'microservices',
Api = 'api',
Microservices = 'microservices',
}
export enum ImmichTelemetry {
HOST = 'host',
API = 'api',
IO = 'io',
REPO = 'repo',
JOB = 'job',
Host = 'host',
Api = 'api',
Io = 'io',
Repo = 'repo',
Job = 'job',
}
export enum ExifOrientation {
@ -411,11 +406,11 @@ export enum ExifOrientation {
}
export enum DatabaseExtension {
CUBE = 'cube',
EARTH_DISTANCE = 'earthdistance',
VECTOR = 'vector',
VECTORS = 'vectors',
VECTORCHORD = 'vchord',
Cube = 'cube',
EarthDistance = 'earthdistance',
Vector = 'vector',
Vectors = 'vectors',
VectorChord = 'vchord',
}
export enum BootstrapEventPriority {
@ -428,135 +423,135 @@ export enum BootstrapEventPriority {
}
export enum QueueName {
THUMBNAIL_GENERATION = 'thumbnailGeneration',
METADATA_EXTRACTION = 'metadataExtraction',
VIDEO_CONVERSION = 'videoConversion',
FACE_DETECTION = 'faceDetection',
FACIAL_RECOGNITION = 'facialRecognition',
SMART_SEARCH = 'smartSearch',
DUPLICATE_DETECTION = 'duplicateDetection',
BACKGROUND_TASK = 'backgroundTask',
STORAGE_TEMPLATE_MIGRATION = 'storageTemplateMigration',
MIGRATION = 'migration',
SEARCH = 'search',
SIDECAR = 'sidecar',
LIBRARY = 'library',
NOTIFICATION = 'notifications',
BACKUP_DATABASE = 'backupDatabase',
ThumbnailGeneration = 'thumbnailGeneration',
MetadataExtraction = 'metadataExtraction',
VideoConversion = 'videoConversion',
FaceDetection = 'faceDetection',
FacialRecognition = 'facialRecognition',
SmartSearch = 'smartSearch',
DuplicateDetection = 'duplicateDetection',
BackgroundTask = 'backgroundTask',
StorageTemplateMigration = 'storageTemplateMigration',
Migration = 'migration',
Search = 'search',
Sidecar = 'sidecar',
Library = 'library',
Notification = 'notifications',
BackupDatabase = 'backupDatabase',
}
export enum JobName {
//backups
BACKUP_DATABASE = 'database-backup',
BackupDatabase = 'database-backup',
// conversion
QUEUE_VIDEO_CONVERSION = 'queue-video-conversion',
VIDEO_CONVERSION = 'video-conversion',
QueueVideoConversion = 'queue-video-conversion',
VideoConversation = 'video-conversion',
// thumbnails
QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails',
GENERATE_THUMBNAILS = 'generate-thumbnails',
GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail',
QueueGenerateThumbnails = 'queue-generate-thumbnails',
GenerateThumbnails = 'generate-thumbnails',
GeneratePersonThumbnail = 'generate-person-thumbnail',
// metadata
QUEUE_METADATA_EXTRACTION = 'queue-metadata-extraction',
METADATA_EXTRACTION = 'metadata-extraction',
QueueMetadataExtraction = 'queue-metadata-extraction',
MetadataExtraction = 'metadata-extraction',
// user
USER_DELETION = 'user-deletion',
USER_DELETE_CHECK = 'user-delete-check',
USER_SYNC_USAGE = 'user-sync-usage',
UserDeletion = 'user-deletion',
UserDeleteCheck = 'user-delete-check',
userSyncUsage = 'user-sync-usage',
// asset
ASSET_DELETION = 'asset-deletion',
ASSET_DELETION_CHECK = 'asset-deletion-check',
AssetDeletion = 'asset-deletion',
AssetDeletionCheck = 'asset-deletion-check',
// storage template
STORAGE_TEMPLATE_MIGRATION = 'storage-template-migration',
STORAGE_TEMPLATE_MIGRATION_SINGLE = 'storage-template-migration-single',
StorageTemplateMigration = 'storage-template-migration',
StorageTemplateMigrationSingle = 'storage-template-migration-single',
// tags
TAG_CLEANUP = 'tag-cleanup',
TagCleanup = 'tag-cleanup',
// migration
QUEUE_MIGRATION = 'queue-migration',
MIGRATE_ASSET = 'migrate-asset',
MIGRATE_PERSON = 'migrate-person',
QueueMigration = 'queue-migration',
MigrateAsset = 'migrate-asset',
MigratePerson = 'migrate-person',
// facial recognition
PERSON_CLEANUP = 'person-cleanup',
QUEUE_FACE_DETECTION = 'queue-face-detection',
FACE_DETECTION = 'face-detection',
QUEUE_FACIAL_RECOGNITION = 'queue-facial-recognition',
FACIAL_RECOGNITION = 'facial-recognition',
PersonCleanup = 'person-cleanup',
QueueFaceDetection = 'queue-face-detection',
FaceDetection = 'face-detection',
QueueFacialRecognition = 'queue-facial-recognition',
FacialRecognition = 'facial-recognition',
// library management
LIBRARY_QUEUE_SYNC_FILES = 'library-queue-sync-files',
LIBRARY_QUEUE_SYNC_ASSETS = 'library-queue-sync-assets',
LIBRARY_SYNC_FILES = 'library-sync-files',
LIBRARY_SYNC_ASSETS = 'library-sync-assets',
LIBRARY_ASSET_REMOVAL = 'handle-library-file-deletion',
LIBRARY_DELETE = 'library-delete',
LIBRARY_QUEUE_SCAN_ALL = 'library-queue-scan-all',
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
LibraryQueueSyncFiles = 'library-queue-sync-files',
LibraryQueueSyncAssets = 'library-queue-sync-assets',
LibrarySyncFiles = 'library-sync-files',
LibrarySyncAssets = 'library-sync-assets',
LibraryAssetRemoval = 'handle-library-file-deletion',
LibraryDelete = 'library-delete',
LibraryQueueScanAll = 'library-queue-scan-all',
LibraryQueueCleanup = 'library-queue-cleanup',
// cleanup
DELETE_FILES = 'delete-files',
CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
CLEAN_OLD_SESSION_TOKENS = 'clean-old-session-tokens',
DeleteFiles = 'delete-files',
CleanOldAuditLogs = 'clean-old-audit-logs',
CleanOldSessionTokens = 'clean-old-session-tokens',
// memories
MEMORIES_CLEANUP = 'memories-cleanup',
MEMORIES_CREATE = 'memories-create',
MemoriesCleanup = 'memories-cleanup',
MemoriesCreate = 'memories-create',
// smart search
QUEUE_SMART_SEARCH = 'queue-smart-search',
SMART_SEARCH = 'smart-search',
QueueSmartSearch = 'queue-smart-search',
SmartSearch = 'smart-search',
QUEUE_TRASH_EMPTY = 'queue-trash-empty',
QueueTrashEmpty = 'queue-trash-empty',
// duplicate detection
QUEUE_DUPLICATE_DETECTION = 'queue-duplicate-detection',
DUPLICATE_DETECTION = 'duplicate-detection',
QueueDuplicateDetection = 'queue-duplicate-detection',
DuplicateDetection = 'duplicate-detection',
// XMP sidecars
QUEUE_SIDECAR = 'queue-sidecar',
SIDECAR_DISCOVERY = 'sidecar-discovery',
SIDECAR_SYNC = 'sidecar-sync',
SIDECAR_WRITE = 'sidecar-write',
QueueSidecar = 'queue-sidecar',
SidecarDiscovery = 'sidecar-discovery',
SidecarSync = 'sidecar-sync',
SidecarWrite = 'sidecar-write',
// Notification
NOTIFY_SIGNUP = 'notify-signup',
NOTIFY_ALBUM_INVITE = 'notify-album-invite',
NOTIFY_ALBUM_UPDATE = 'notify-album-update',
NOTIFICATIONS_CLEANUP = 'notifications-cleanup',
SEND_EMAIL = 'notification-send-email',
NotifySignup = 'notify-signup',
NotifyAlbumInvite = 'notify-album-invite',
NotifyAlbumUpdate = 'notify-album-update',
NotificationsCleanup = 'notifications-cleanup',
SendMail = 'notification-send-email',
// Version check
VERSION_CHECK = 'version-check',
VersionCheck = 'version-check',
}
export enum JobCommand {
START = 'start',
PAUSE = 'pause',
RESUME = 'resume',
EMPTY = 'empty',
CLEAR_FAILED = 'clear-failed',
Start = 'start',
Pause = 'pause',
Resume = 'resume',
Empty = 'empty',
ClearFailed = 'clear-failed',
}
export enum JobStatus {
SUCCESS = 'success',
FAILED = 'failed',
SKIPPED = 'skipped',
Success = 'success',
Failed = 'failed',
Skipped = 'skipped',
}
export enum QueueCleanType {
FAILED = 'failed',
Failed = 'failed',
}
export enum VectorIndex {
CLIP = 'clip_index',
FACE = 'face_index',
Clip = 'clip_index',
Face = 'face_index',
}
export enum DatabaseLock {
@ -663,8 +658,8 @@ export enum NotificationType {
}
export enum OAuthTokenEndpointAuthMethod {
CLIENT_SECRET_POST = 'client_secret_post',
CLIENT_SECRET_BASIC = 'client_secret_basic',
ClientSecretPost = 'client_secret_post',
ClientSecretBasic = 'client_secret_basic',
}
export enum DatabaseSslMode {
@ -676,14 +671,14 @@ export enum DatabaseSslMode {
}
export enum AssetVisibility {
ARCHIVE = 'archive',
TIMELINE = 'timeline',
Archive = 'archive',
Timeline = 'timeline',
/**
* Video part of the LivePhotos and MotionPhotos
*/
HIDDEN = 'hidden',
LOCKED = 'locked',
Hidden = 'hidden',
Locked = 'locked',
}
export enum CronJob {

View File

@ -20,7 +20,7 @@ const onExit = (name: string, exitCode: number | null) => {
if (exitCode !== 0) {
console.error(`${name} worker exited with code ${exitCode}`);
if (apiProcess && name !== ImmichWorker.API) {
if (apiProcess && name !== ImmichWorker.Api) {
console.error('Killing api process');
apiProcess.kill('SIGTERM');
apiProcess = undefined;
@ -34,7 +34,7 @@ function bootstrapWorker(name: ImmichWorker) {
console.log(`Starting ${name} worker`);
let worker: Worker | ChildProcess;
if (name === ImmichWorker.API) {
if (name === ImmichWorker.Api) {
worker = fork(`./dist/workers/${name}.js`, [], {
execArgv: process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)),
});
@ -50,7 +50,7 @@ function bootstrapWorker(name: ImmichWorker) {
function bootstrap() {
if (immichApp === 'immich-admin') {
process.title = 'immich_admin_cli';
process.env.IMMICH_LOG_LEVEL = LogLevel.WARN;
process.env.IMMICH_LOG_LEVEL = LogLevel.Warn;
return CommandFactory.run(ImmichAdminModule);
}

View File

@ -15,7 +15,7 @@ export class AssetUploadInterceptor implements NestInterceptor {
const req = context.switchToHttp().getRequest<AuthenticatedRequest>();
const res = context.switchToHttp().getResponse<Response<AssetMediaResponseDto>>();
const checksum = fromMaybeArray(req.headers[ImmichHeader.CHECKSUM]);
const checksum = fromMaybeArray(req.headers[ImmichHeader.Checksum]);
const response = await this.service.getUploadAssetIdByChecksum(req.user, checksum);
if (response) {
res.status(200);

View File

@ -23,12 +23,12 @@ export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator =
const decorators: MethodDecorator[] = [
ApiBearerAuth(),
ApiCookieAuth(),
ApiSecurity(MetadataKey.API_KEY_SECURITY),
SetMetadata(MetadataKey.AUTH_ROUTE, options || {}),
ApiSecurity(MetadataKey.ApiKeySecurity),
SetMetadata(MetadataKey.AuthRoute, options || {}),
];
if ((options as SharedLinkRoute)?.sharedLink) {
decorators.push(ApiQuery({ name: ImmichQuery.SHARED_LINK_KEY, type: String, required: false }));
decorators.push(ApiQuery({ name: ImmichQuery.SharedLinkKey, type: String, required: false }));
}
return applyDecorators(...decorators);
@ -76,7 +76,7 @@ export class AuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const targets = [context.getHandler()];
const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(MetadataKey.AUTH_ROUTE, targets);
const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(MetadataKey.AuthRoute, targets);
if (!options) {
return true;
}

View File

@ -154,11 +154,11 @@ export class FileUploadInterceptor implements NestInterceptor {
private getHandler(route: RouteKey) {
switch (route) {
case RouteKey.ASSET: {
case RouteKey.Asset: {
return this.handlers.assetUpload;
}
case RouteKey.USER: {
case RouteKey.User: {
return this.handlers.userProfile;
}

View File

@ -6,7 +6,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const vectorExtension = await getVectorExtension(queryRunner);
if (vectorExtension === DatabaseExtension.VECTORS) {
if (vectorExtension === DatabaseExtension.Vectors) {
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
}
@ -52,7 +52,7 @@ export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
public async down(queryRunner: QueryRunner): Promise<void> {
const vectorExtension = await getVectorExtension(queryRunner);
if (vectorExtension === DatabaseExtension.VECTORS) {
if (vectorExtension === DatabaseExtension.Vectors) {
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
}

View File

@ -91,7 +91,7 @@ class AlbumAccess {
}
const accessRole =
access === AlbumUserRole.EDITOR ? [AlbumUserRole.EDITOR] : [AlbumUserRole.EDITOR, AlbumUserRole.VIEWER];
access === AlbumUserRole.Editor ? [AlbumUserRole.Editor] : [AlbumUserRole.Editor, AlbumUserRole.Viewer];
return this.db
.selectFrom('album')
@ -178,7 +178,7 @@ class AssetAccess {
.select('asset.id')
.where('asset.id', 'in', [...assetIds])
.where('asset.ownerId', '=', userId)
.$if(!hasElevatedPermission, (eb) => eb.where('asset.visibility', '!=', AssetVisibility.LOCKED))
.$if(!hasElevatedPermission, (eb) => eb.where('asset.visibility', '!=', AssetVisibility.Locked))
.execute()
.then((assets) => new Set(assets.map((asset) => asset.id)));
}
@ -200,8 +200,8 @@ class AssetAccess {
.where('partner.sharedWithId', '=', userId)
.where((eb) =>
eb.or([
eb('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE)),
eb('asset.visibility', '=', sql.lit(AssetVisibility.HIDDEN)),
eb('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)),
eb('asset.visibility', '=', sql.lit(AssetVisibility.Hidden)),
]),
)

View File

@ -90,7 +90,7 @@ export class ActivityRepository {
.where('activity.albumId', '=', albumId)
.where(({ or, and, eb }) =>
or([
and([eb('asset.deletedAt', 'is', null), eb('asset.visibility', '!=', sql.lit(AssetVisibility.LOCKED))]),
and([eb('asset.deletedAt', 'is', null), eb('asset.visibility', '!=', sql.lit(AssetVisibility.Locked))]),
eb('asset.id', 'is', null),
]),
)

View File

@ -24,7 +24,7 @@ export class AlbumUserRepository {
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] })
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.Viewer }] })
update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable<AlbumUserTable>) {
return this.db
.updateTable('album_user')

View File

@ -62,7 +62,7 @@ export class AssetJobRepository {
.select(['asset.id', 'asset.thumbhash'])
.select(withFiles)
.where('asset.deletedAt', 'is', null)
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.visibility', '!=', AssetVisibility.Hidden)
.$if(!force, (qb) =>
qb
// If there aren't any entries, metadata extraction hasn't run yet which is required for thumbnails
@ -117,7 +117,7 @@ export class AssetJobRepository {
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID, AssetFileType.THUMBNAIL] })
@GenerateSql({ params: [DummyValue.UUID, AssetFileType.Thumbnail] })
getAlbumThumbnailFiles(id: string, fileType?: AssetFileType) {
return this.db
.selectFrom('asset_file')
@ -130,7 +130,7 @@ export class AssetJobRepository {
private assetsWithPreviews() {
return this.db
.selectFrom('asset')
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.visibility', '!=', AssetVisibility.Hidden)
.where('asset.deletedAt', 'is', null)
.innerJoin('asset_job_status as job_status', 'assetId', 'asset.id')
.where('job_status.previewAt', 'is not', null);
@ -167,7 +167,7 @@ export class AssetJobRepository {
return this.db
.selectFrom('asset')
.select(['asset.id', 'asset.visibility'])
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
.select((eb) => withFiles(eb, AssetFileType.Preview))
.where('asset.id', '=', id)
.executeTakeFirst();
}
@ -179,7 +179,7 @@ export class AssetJobRepository {
.select(['asset.id', 'asset.visibility'])
.$call(withExifInner)
.select((eb) => withFaces(eb, true))
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
.select((eb) => withFiles(eb, AssetFileType.Preview))
.where('asset.id', '=', id)
.executeTakeFirst();
}
@ -225,7 +225,7 @@ export class AssetJobRepository {
.select(['stack.id', 'stack.primaryAssetId'])
.select((eb) => eb.fn<Asset[]>('array_agg', [eb.table('stacked')]).as('assets'))
.where('stacked.deletedAt', 'is not', null)
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
.where('stacked.visibility', '=', AssetVisibility.Timeline)
.whereRef('stacked.stackId', '=', 'stack.id')
.groupBy('stack.id')
.as('stacked_assets'),
@ -241,11 +241,11 @@ export class AssetJobRepository {
return this.db
.selectFrom('asset')
.select(['asset.id'])
.where('asset.type', '=', AssetType.VIDEO)
.where('asset.type', '=', AssetType.Video)
.$if(!force, (qb) =>
qb
.where((eb) => eb.or([eb('asset.encodedVideoPath', 'is', null), eb('asset.encodedVideoPath', '=', '')]))
.where('asset.visibility', '!=', AssetVisibility.HIDDEN),
.where('asset.visibility', '!=', AssetVisibility.Hidden),
)
.where('asset.deletedAt', 'is', null)
.stream();
@ -257,7 +257,7 @@ export class AssetJobRepository {
.selectFrom('asset')
.select(['asset.id', 'asset.ownerId', 'asset.originalPath', 'asset.encodedVideoPath'])
.where('asset.id', '=', id)
.where('asset.type', '=', AssetType.VIDEO)
.where('asset.type', '=', AssetType.Video)
.executeTakeFirst();
}
@ -327,7 +327,7 @@ export class AssetJobRepository {
.$if(!force, (qb) =>
qb.where((eb) => eb.or([eb('asset.sidecarPath', '=', ''), eb('asset.sidecarPath', 'is', null)])),
)
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.visibility', '!=', AssetVisibility.Hidden)
.stream();
}

View File

@ -230,13 +230,13 @@ export class AssetRepository {
.where('asset_job_status.previewAt', 'is not', null)
.where(sql`(asset."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`)
.where('asset.ownerId', '=', anyUuid(ownerIds))
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
.where('asset.visibility', '=', AssetVisibility.Timeline)
.where((eb) =>
eb.exists((qb) =>
qb
.selectFrom('asset_file')
.whereRef('assetId', '=', 'asset.id')
.where('asset_file.type', '=', AssetFileType.PREVIEW),
.where('asset_file.type', '=', AssetFileType.Preview),
),
)
.where('asset.deletedAt', 'is', null)
@ -318,7 +318,7 @@ export class AssetRepository {
.select(['deviceAssetId'])
.where('ownerId', '=', asUuid(ownerId))
.where('deviceId', '=', deviceId)
.where('visibility', '!=', AssetVisibility.HIDDEN)
.where('visibility', '!=', AssetVisibility.Hidden)
.where('deletedAt', 'is', null)
.execute();
@ -363,7 +363,7 @@ export class AssetRepository {
.whereRef('stacked.stackId', '=', 'stack.id')
.whereRef('stacked.id', '!=', 'stack.primaryAssetId')
.where('stacked.deletedAt', 'is', null)
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
.where('stacked.visibility', '=', AssetVisibility.Timeline)
.groupBy('stack.id')
.as('stacked_assets'),
(join) => join.on('stack.id', 'is not', null),
@ -463,15 +463,15 @@ export class AssetRepository {
getStatistics(ownerId: string, { visibility, isFavorite, isTrashed }: AssetStatsOptions): Promise<AssetStats> {
return this.db
.selectFrom('asset')
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.AUDIO).as(AssetType.AUDIO))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.IMAGE).as(AssetType.IMAGE))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.VIDEO).as(AssetType.VIDEO))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.OTHER).as(AssetType.OTHER))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.Audio).as(AssetType.Audio))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.Image).as(AssetType.Image))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.Video).as(AssetType.Video))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.Other).as(AssetType.Other))
.where('ownerId', '=', asUuid(ownerId))
.$if(visibility === undefined, withDefaultVisibility)
.$if(!!visibility, (qb) => qb.where('asset.visibility', '=', visibility!))
.$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!))
.$if(!!isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
.$if(!!isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
.where('deletedAt', isTrashed ? 'is not' : 'is', null)
.executeTakeFirstOrThrow();
}
@ -496,7 +496,7 @@ export class AssetRepository {
qb
.selectFrom('asset')
.select(truncatedDate<Date>().as('timeBucket'))
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility === undefined, withDefaultVisibility)
.$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!))
@ -606,7 +606,7 @@ export class AssetRepository {
.select(sql`array[stacked."stackId"::text, count('stacked')::text]`.as('stack'))
.whereRef('stacked.stackId', '=', 'asset.stackId')
.where('stacked.deletedAt', 'is', null)
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
.where('stacked.visibility', '=', AssetVisibility.Timeline)
.groupBy('stacked.stackId')
.as('stacked_assets'),
(join) => join.onTrue(),
@ -617,7 +617,7 @@ export class AssetRepository {
.$if(options.isDuplicate !== undefined, (qb) =>
qb.where('asset.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
)
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
.orderBy('asset.fileCreatedAt', options.order ?? 'desc'),
)
@ -671,8 +671,8 @@ export class AssetRepository {
.select(['assetId as data', 'asset_exif.city as value'])
.$narrowType<{ value: NotNull }>()
.where('ownerId', '=', asUuid(ownerId))
.where('visibility', '=', AssetVisibility.TIMELINE)
.where('type', '=', AssetType.IMAGE)
.where('visibility', '=', AssetVisibility.Timeline)
.where('type', '=', AssetType.Image)
.where('deletedAt', 'is', null)
.limit(maxFields)
.execute();
@ -710,7 +710,7 @@ export class AssetRepository {
)
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo<Stack | null>().as('stack'))
.where('asset.ownerId', '=', asUuid(ownerId))
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.visibility', '!=', AssetVisibility.Hidden)
.where('asset.updatedAt', '<=', updatedUntil)
.$if(!!lastId, (qb) => qb.where('asset.id', '>', lastId!))
.orderBy('asset.id')
@ -738,7 +738,7 @@ export class AssetRepository {
)
.select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo<Stack | null>()).as('stack'))
.where('asset.ownerId', '=', anyUuid(options.userIds))
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.visibility', '!=', AssetVisibility.Hidden)
.where('asset.updatedAt', '>', options.updatedAfter)
.limit(options.limit)
.execute();

View File

@ -18,7 +18,7 @@ export class AuditRepository {
@GenerateSql({
params: [
DummyValue.DATE,
{ action: DatabaseAction.CREATE, entityType: EntityType.ASSET, userIds: [DummyValue.UUID] },
{ action: DatabaseAction.Create, entityType: EntityType.Asset, userIds: [DummyValue.UUID] },
],
})
async getAfter(since: Date, options: AuditSearch): Promise<string[]> {

View File

@ -275,14 +275,14 @@ describe('getEnv', () => {
process.env.IMMICH_TELEMETRY_EXCLUDE = 'job';
const { telemetry } = getEnv();
expect(telemetry.metrics).toEqual(
new Set([ImmichTelemetry.API, ImmichTelemetry.HOST, ImmichTelemetry.IO, ImmichTelemetry.REPO]),
new Set([ImmichTelemetry.Api, ImmichTelemetry.Host, ImmichTelemetry.Io, ImmichTelemetry.Repo]),
);
});
it('should run with specific telemetry metrics', () => {
process.env.IMMICH_TELEMETRY_INCLUDE = 'io, host, api';
const { telemetry } = getEnv();
expect(telemetry.metrics).toEqual(new Set([ImmichTelemetry.API, ImmichTelemetry.HOST, ImmichTelemetry.IO]));
expect(telemetry.metrics).toEqual(new Set([ImmichTelemetry.Api, ImmichTelemetry.Host, ImmichTelemetry.Io]));
});
});
});

View File

@ -136,7 +136,7 @@ const getEnv = (): EnvData => {
);
}
const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.API, ImmichWorker.MICROSERVICES]);
const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]);
const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
const workers = [...setDifference(includedWorkers, excludedWorkers)];
for (const worker of workers) {
@ -145,8 +145,8 @@ const getEnv = (): EnvData => {
}
}
const environment = dto.IMMICH_ENV || ImmichEnvironment.PRODUCTION;
const isProd = environment === ImmichEnvironment.PRODUCTION;
const environment = dto.IMMICH_ENV || ImmichEnvironment.Production;
const isProd = environment === ImmichEnvironment.Production;
const buildFolder = dto.IMMICH_BUILD_DATA || '/build';
const folders = {
geodata: join(buildFolder, 'geodata'),
@ -199,15 +199,15 @@ const getEnv = (): EnvData => {
let vectorExtension: VectorExtension | undefined;
switch (dto.DB_VECTOR_EXTENSION) {
case 'pgvector': {
vectorExtension = DatabaseExtension.VECTOR;
vectorExtension = DatabaseExtension.Vector;
break;
}
case 'pgvecto.rs': {
vectorExtension = DatabaseExtension.VECTORS;
vectorExtension = DatabaseExtension.Vectors;
break;
}
case 'vectorchord': {
vectorExtension = DatabaseExtension.VECTORCHORD;
vectorExtension = DatabaseExtension.VectorChord;
break;
}
}
@ -254,11 +254,11 @@ const getEnv = (): EnvData => {
mount: true,
generateId: true,
setup: (cls, req: Request, res: Response) => {
const headerValues = req.headers[ImmichHeader.CID];
const headerValues = req.headers[ImmichHeader.Cid];
const headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues;
const cid = headerValue || cls.get(CLS_ID);
cls.set(CLS_ID, cid);
res.header(ImmichHeader.CID, cid);
res.header(ImmichHeader.Cid, cid);
},
},
},
@ -278,9 +278,9 @@ const getEnv = (): EnvData => {
otel: {
metrics: {
hostMetrics: telemetries.has(ImmichTelemetry.HOST),
hostMetrics: telemetries.has(ImmichTelemetry.Host),
apiMetrics: {
enable: telemetries.has(ImmichTelemetry.API),
enable: telemetries.has(ImmichTelemetry.Api),
ignoreRoutes: excludePaths,
},
},
@ -335,7 +335,7 @@ export class ConfigRepository {
}
isDev() {
return this.getEnv().environment === ImmichEnvironment.DEVELOPMENT;
return this.getEnv().environment === ImmichEnvironment.Development;
}
getWorker() {

View File

@ -53,8 +53,8 @@ export async function getVectorExtension(runner: Kysely<DB> | QueryRunner): Prom
}
export const probes: Record<VectorIndex, number> = {
[VectorIndex.CLIP]: 1,
[VectorIndex.FACE]: 1,
[VectorIndex.Clip]: 1,
[VectorIndex.Face]: 1,
};
@Injectable()
@ -77,7 +77,7 @@ export class DatabaseRepository {
return getVectorExtension(this.db);
}
@GenerateSql({ params: [[DatabaseExtension.VECTORS]] })
@GenerateSql({ params: [[DatabaseExtension.Vectors]] })
async getExtensionVersions(extensions: readonly DatabaseExtension[]): Promise<ExtensionVersion[]> {
const { rows } = await sql<ExtensionVersion>`
SELECT name, default_version as "availableVersion", installed_version as "installedVersion"
@ -89,13 +89,13 @@ export class DatabaseRepository {
getExtensionVersionRange(extension: VectorExtension): string {
switch (extension) {
case DatabaseExtension.VECTORCHORD: {
case DatabaseExtension.VectorChord: {
return VECTORCHORD_VERSION_RANGE;
}
case DatabaseExtension.VECTORS: {
case DatabaseExtension.Vectors: {
return VECTORS_VERSION_RANGE;
}
case DatabaseExtension.VECTOR: {
case DatabaseExtension.Vector: {
return VECTOR_VERSION_RANGE;
}
default: {
@ -117,7 +117,7 @@ export class DatabaseRepository {
async createExtension(extension: DatabaseExtension): Promise<void> {
this.logger.log(`Creating ${EXTENSION_NAMES[extension]} extension`);
await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db);
if (extension === DatabaseExtension.VECTORCHORD) {
if (extension === DatabaseExtension.VectorChord) {
const dbName = sql.id(await this.getDatabaseName());
await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db);
await sql`SET vchordrq.probes = 1`.execute(this.db);
@ -147,8 +147,8 @@ export class DatabaseRepository {
}
await Promise.all([
this.db.schema.dropIndex(VectorIndex.CLIP).ifExists().execute(),
this.db.schema.dropIndex(VectorIndex.FACE).ifExists().execute(),
this.db.schema.dropIndex(VectorIndex.Clip).ifExists().execute(),
this.db.schema.dropIndex(VectorIndex.Face).ifExists().execute(),
]);
await this.db.transaction().execute(async (tx) => {
@ -156,14 +156,14 @@ export class DatabaseRepository {
await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx);
if (extension === DatabaseExtension.VECTORS && (diff === 'major' || diff === 'minor')) {
if (extension === DatabaseExtension.Vectors && (diff === 'major' || diff === 'minor')) {
await sql`SELECT pgvectors_upgrade()`.execute(tx);
restartRequired = true;
}
});
if (!restartRequired) {
await Promise.all([this.reindexVectors(VectorIndex.CLIP), this.reindexVectors(VectorIndex.FACE)]);
await Promise.all([this.reindexVectors(VectorIndex.Clip), this.reindexVectors(VectorIndex.Face)]);
}
return { restartRequired };
@ -171,7 +171,7 @@ export class DatabaseRepository {
async prewarm(index: VectorIndex): Promise<void> {
const vectorExtension = await getVectorExtension(this.db);
if (vectorExtension !== DatabaseExtension.VECTORCHORD) {
if (vectorExtension !== DatabaseExtension.VectorChord) {
return;
}
this.logger.debug(`Prewarming ${index}`);
@ -196,19 +196,19 @@ export class DatabaseRepository {
}
switch (vectorExtension) {
case DatabaseExtension.VECTOR: {
case DatabaseExtension.Vector: {
if (!row.indexdef.toLowerCase().includes('using hnsw')) {
promises.push(this.reindexVectors(indexName));
}
break;
}
case DatabaseExtension.VECTORS: {
case DatabaseExtension.Vectors: {
if (!row.indexdef.toLowerCase().includes('using vectors')) {
promises.push(this.reindexVectors(indexName));
}
break;
}
case DatabaseExtension.VECTORCHORD: {
case DatabaseExtension.VectorChord: {
const matches = row.indexdef.match(/(?<=lists = \[)\d+/g);
const lists = matches && matches.length > 0 ? Number(matches[0]) : 1;
promises.push(
@ -264,7 +264,7 @@ export class DatabaseRepository {
await sql`ALTER TABLE ${sql.raw(table)} ADD COLUMN embedding real[] NOT NULL`.execute(tx);
}
await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE real[]`.execute(tx);
const schema = vectorExtension === DatabaseExtension.VECTORS ? 'vectors.' : '';
const schema = vectorExtension === DatabaseExtension.Vectors ? 'vectors.' : '';
await sql`
ALTER TABLE ${sql.raw(table)}
ALTER COLUMN embedding
@ -329,11 +329,11 @@ export class DatabaseRepository {
.alterColumn('embedding', (col) => col.setDataType(sql.raw(`vector(${dimSize})`)))
.execute();
await sql
.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: VectorIndex.CLIP }))
.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: VectorIndex.Clip }))
.execute(trx);
await trx.schema.alterTable('smart_search').dropConstraint('dim_size_constraint').ifExists().execute();
});
probes[VectorIndex.CLIP] = 1;
probes[VectorIndex.Clip] = 1;
await sql`vacuum analyze ${sql.table('smart_search')}`.execute(this.db);
}

View File

@ -34,7 +34,7 @@ export class DownloadRepository {
downloadUserId(userId: string) {
return builder(this.db)
.where('asset.ownerId', '=', userId)
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.visibility', '!=', AssetVisibility.Hidden)
.stream();
}
}

View File

@ -109,14 +109,14 @@ export class DuplicateRepository {
assetId: DummyValue.UUID,
embedding: DummyValue.VECTOR,
maxDistance: 0.6,
type: AssetType.IMAGE,
type: AssetType.Image,
userIds: [DummyValue.UUID],
},
],
})
search({ assetId, embedding, maxDistance, type, userIds }: DuplicateSearch) {
return this.db.transaction().execute(async (trx) => {
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.CLIP])}`.execute(trx);
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Clip])}`.execute(trx);
return await trx
.with('cte', (qb) =>
qb

View File

@ -166,7 +166,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
continue;
}
const event = reflector.get<EventConfig>(MetadataKey.EVENT_CONFIG, handler);
const event = reflector.get<EventConfig>(MetadataKey.EventConfig, handler);
if (!event) {
continue;
}

View File

@ -41,7 +41,7 @@ export class JobRepository {
const instance = this.moduleRef.get<any>(Service);
for (const methodName of getMethodNames(instance)) {
const handler = instance[methodName];
const config = reflector.get<JobConfig>(MetadataKey.JOB_CONFIG, handler);
const config = reflector.get<JobConfig>(MetadataKey.JobConfig, handler);
if (!config) {
continue;
}
@ -99,7 +99,7 @@ export class JobRepository {
const item = this.handlers[name as JobName];
if (!item) {
this.logger.warn(`Skipping unknown job: "${name}"`);
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
return item.handler(data);
@ -205,20 +205,20 @@ export class JobRepository {
private getJobOptions(item: JobItem): JobsOptions | null {
switch (item.name) {
case JobName.NOTIFY_ALBUM_UPDATE: {
case JobName.NotifyAlbumUpdate: {
return {
jobId: `${item.data.id}/${item.data.recipientId}`,
delay: item.data?.delay,
};
}
case JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE: {
case JobName.StorageTemplateMigrationSingle: {
return { jobId: item.data.id };
}
case JobName.GENERATE_PERSON_THUMBNAIL: {
case JobName.GeneratePersonThumbnail: {
return { priority: 1 };
}
case JobName.QUEUE_FACIAL_RECOGNITION: {
return { jobId: JobName.QUEUE_FACIAL_RECOGNITION };
case JobName.QueueFacialRecognition: {
return { jobId: JobName.QueueFacialRecognition };
}
default: {
return null;

View File

@ -79,7 +79,7 @@ export class LibraryRepository {
eb.fn
.countAll<number>()
.filterWhere((eb) =>
eb.and([eb('asset.type', '=', AssetType.IMAGE), eb('asset.visibility', '!=', AssetVisibility.HIDDEN)]),
eb.and([eb('asset.type', '=', AssetType.Image), eb('asset.visibility', '!=', AssetVisibility.Hidden)]),
)
.as('photos'),
)
@ -87,7 +87,7 @@ export class LibraryRepository {
eb.fn
.countAll<number>()
.filterWhere((eb) =>
eb.and([eb('asset.type', '=', AssetType.VIDEO), eb('asset.visibility', '!=', AssetVisibility.HIDDEN)]),
eb.and([eb('asset.type', '=', AssetType.Video), eb('asset.visibility', '!=', AssetVisibility.Hidden)]),
)
.as('videos'),
)

View File

@ -22,7 +22,7 @@ describe(LoggingRepository.name, () => {
describe('formatContext', () => {
it('should use colors', () => {
sut = new LoggingRepository(clsMock, configMock);
sut.setAppName(ImmichWorker.API);
sut.setAppName(ImmichWorker.Api);
const logger = new MyConsoleLogger(clsMock, { color: true });
@ -31,7 +31,7 @@ describe(LoggingRepository.name, () => {
it('should not use colors when color is false', () => {
sut = new LoggingRepository(clsMock, configMock);
sut.setAppName(ImmichWorker.API);
sut.setAppName(ImmichWorker.Api);
const logger = new MyConsoleLogger(clsMock, { color: false });

View File

@ -8,7 +8,7 @@ import { ConfigRepository } from 'src/repositories/config.repository';
type LogDetails = any;
type LogFunction = () => string;
const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
const LOG_LEVELS = [LogLevel.Verbose, LogLevel.Debug, LogLevel.Log, LogLevel.Warn, LogLevel.Error, LogLevel.Fatal];
enum LogColor {
RED = 31,
@ -20,7 +20,7 @@ enum LogColor {
}
let appName: string | undefined;
let logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
let logLevels: LogLevel[] = [LogLevel.Log, LogLevel.Warn, LogLevel.Error, LogLevel.Fatal];
export class MyConsoleLogger extends ConsoleLogger {
private isColorEnabled: boolean;
@ -106,35 +106,35 @@ export class LoggingRepository {
}
verbose(message: string, ...details: LogDetails) {
this.handleMessage(LogLevel.VERBOSE, message, details);
this.handleMessage(LogLevel.Verbose, message, details);
}
verboseFn(message: LogFunction, ...details: LogDetails) {
this.handleFunction(LogLevel.VERBOSE, message, details);
this.handleFunction(LogLevel.Verbose, message, details);
}
debug(message: string, ...details: LogDetails) {
this.handleMessage(LogLevel.DEBUG, message, details);
this.handleMessage(LogLevel.Debug, message, details);
}
debugFn(message: LogFunction, ...details: LogDetails) {
this.handleFunction(LogLevel.DEBUG, message, details);
this.handleFunction(LogLevel.Debug, message, details);
}
log(message: string, ...details: LogDetails) {
this.handleMessage(LogLevel.LOG, message, details);
this.handleMessage(LogLevel.Log, message, details);
}
warn(message: string, ...details: LogDetails) {
this.handleMessage(LogLevel.WARN, message, details);
this.handleMessage(LogLevel.Warn, message, details);
}
error(message: string | Error, ...details: LogDetails) {
this.handleMessage(LogLevel.ERROR, message, details);
this.handleMessage(LogLevel.Error, message, details);
}
fatal(message: string, ...details: LogDetails) {
this.handleMessage(LogLevel.FATAL, message, details);
this.handleMessage(LogLevel.Fatal, message, details);
}
private handleFunction(level: LogLevel, message: LogFunction, details: LogDetails[]) {
@ -145,32 +145,32 @@ export class LoggingRepository {
private handleMessage(level: LogLevel, message: string | Error, details: LogDetails[]) {
switch (level) {
case LogLevel.VERBOSE: {
case LogLevel.Verbose: {
this.logger.verbose(message, ...details);
break;
}
case LogLevel.DEBUG: {
case LogLevel.Debug: {
this.logger.debug(message, ...details);
break;
}
case LogLevel.LOG: {
case LogLevel.Log: {
this.logger.log(message, ...details);
break;
}
case LogLevel.WARN: {
case LogLevel.Warn: {
this.logger.warn(message, ...details);
break;
}
case LogLevel.ERROR: {
case LogLevel.Error: {
this.logger.error(message, ...details);
break;
}
case LogLevel.FATAL: {
case LogLevel.Fatal: {
this.logger.fatal(message, ...details);
break;
}

View File

@ -61,14 +61,14 @@ export class MapRepository {
const geodataDate = await readFile(resourcePaths.geodata.dateFile, 'utf8');
// TODO move to service init
const geocodingMetadata = await this.metadataRepository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE);
const geocodingMetadata = await this.metadataRepository.get(SystemMetadataKey.ReverseGeocodingState);
if (geocodingMetadata?.lastUpdate === geodataDate) {
return;
}
await Promise.all([this.importGeodata(), this.importNaturalEarthCountries()]);
await this.metadataRepository.set(SystemMetadataKey.REVERSE_GEOCODING_STATE, {
await this.metadataRepository.set(SystemMetadataKey.ReverseGeocodingState, {
lastUpdate: geodataDate,
lastImportFileName: citiesFile,
});
@ -102,13 +102,13 @@ export class MapRepository {
.$if(isArchived === true, (qb) =>
qb.where((eb) =>
eb.or([
eb('asset.visibility', '=', AssetVisibility.TIMELINE),
eb('asset.visibility', '=', AssetVisibility.ARCHIVE),
eb('asset.visibility', '=', AssetVisibility.Timeline),
eb('asset.visibility', '=', AssetVisibility.Archive),
]),
),
)
.$if(isArchived === false || isArchived === undefined, (qb) =>
qb.where('asset.visibility', '=', AssetVisibility.TIMELINE),
qb.where('asset.visibility', '=', AssetVisibility.Timeline),
)
.$if(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!))
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))

View File

@ -55,28 +55,28 @@ export class MediaRepository {
async extract(input: string): Promise<ExtractResult | null> {
try {
const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw2', input);
return { buffer, format: RawExtractedFormat.JPEG };
return { buffer, format: RawExtractedFormat.Jpeg };
} catch (error: any) {
this.logger.debug('Could not extract JpgFromRaw2 buffer from image, trying JPEG from RAW next', error.message);
}
try {
const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw', input);
return { buffer, format: RawExtractedFormat.JPEG };
return { buffer, format: RawExtractedFormat.Jpeg };
} catch (error: any) {
this.logger.debug('Could not extract JPEG buffer from image, trying PreviewJXL next', error.message);
}
try {
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewJXL', input);
return { buffer, format: RawExtractedFormat.JXL };
return { buffer, format: RawExtractedFormat.Jxl };
} catch (error: any) {
this.logger.debug('Could not extract PreviewJXL buffer from image, trying PreviewImage next', error.message);
}
try {
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewImage', input);
return { buffer, format: RawExtractedFormat.JPEG };
return { buffer, format: RawExtractedFormat.Jpeg };
} catch (error: any) {
this.logger.debug('Could not extract preview buffer from image', error.message);
return null;
@ -142,7 +142,7 @@ export class MediaRepository {
limitInputPixels: false,
raw: options.raw,
})
.pipelineColorspace(options.colorspace === Colorspace.SRGB ? 'srgb' : 'rgb16')
.pipelineColorspace(options.colorspace === Colorspace.Srgb ? 'srgb' : 'rgb16')
.withIccProfile(options.colorspace);
if (!options.raw) {
@ -267,7 +267,7 @@ export class MediaRepository {
const { frameCount, percentInterval } = options.progress;
const frameInterval = Math.ceil(frameCount / (100 / percentInterval));
if (this.logger.isLevelEnabled(LogLevel.DEBUG) && frameCount && frameInterval) {
if (this.logger.isLevelEnabled(LogLevel.Debug) && frameCount && frameInterval) {
let lastProgressFrame: number = 0;
ffmpegCall.on('progress', (progress: ProgressEvent) => {
if (progress.frames - lastProgressFrame < frameInterval) {

View File

@ -19,7 +19,7 @@ export class MemoryRepository implements IBulkAsset {
.deleteFrom('memory_asset')
.using('asset')
.whereRef('memory_asset.assetsId', '=', 'asset.id')
.where('asset.visibility', '!=', AssetVisibility.TIMELINE)
.where('asset.visibility', '!=', AssetVisibility.Timeline)
.execute();
return this.db
@ -67,7 +67,7 @@ export class MemoryRepository implements IBulkAsset {
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
.orderBy('asset.fileCreatedAt', 'asc')
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
.where('asset.deletedAt', 'is', null),
).as('assets'),
)
@ -158,7 +158,7 @@ export class MemoryRepository implements IBulkAsset {
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
.orderBy('asset.fileCreatedAt', 'asc')
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
.where('asset.deletedAt', 'is', null),
).as('assets'),
)

View File

@ -48,7 +48,7 @@ export class MoveRepository {
eb.selectFrom('asset').select('id').whereRef('asset.id', '=', 'move_history.entityId'),
),
)
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
.where('move_history.pathType', '=', sql.lit(AssetPathType.Original))
.execute();
}
@ -56,7 +56,7 @@ export class MoveRepository {
async cleanMoveHistorySingle(assetId: string): Promise<void> {
await this.db
.deleteFrom('move_history')
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
.where('move_history.pathType', '=', sql.lit(AssetPathType.Original))
.where('entityId', '=', assetId)
.execute();
}

View File

@ -138,11 +138,11 @@ export class OAuthRepository {
}
switch (tokenEndpointAuthMethod) {
case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST: {
case OAuthTokenEndpointAuthMethod.ClientSecretPost: {
return ClientSecretPost(clientSecret);
}
case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_BASIC: {
case OAuthTokenEndpointAuthMethod.ClientSecretBasic: {
return ClientSecretBasic(clientSecret);
}

View File

@ -151,7 +151,7 @@ export class PersonRepository {
.innerJoin('asset', (join) =>
join
.onRef('asset_face.assetId', '=', 'asset.id')
.on('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
.on('asset.deletedAt', 'is', null),
)
.where('person.ownerId', '=', userId)
@ -276,7 +276,7 @@ export class PersonRepository {
.selectFrom('asset_file')
.select('asset_file.path')
.whereRef('asset_file.assetId', '=', 'asset.id')
.where('asset_file.type', '=', sql.lit(AssetFileType.PREVIEW))
.where('asset_file.type', '=', sql.lit(AssetFileType.Preview))
.as('previewPath'),
)
.where('person.id', '=', id)
@ -341,7 +341,7 @@ export class PersonRepository {
join
.onRef('asset.id', '=', 'asset_face.assetId')
.on('asset_face.personId', '=', personId)
.on('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
.on('asset.deletedAt', 'is', null),
)
.select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'))
@ -369,7 +369,7 @@ export class PersonRepository {
eb
.selectFrom('asset')
.whereRef('asset.id', '=', 'asset_face.assetId')
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
.where('asset.deletedAt', 'is', null),
),
),

View File

@ -256,7 +256,7 @@ export class SearchRepository {
}
return this.db.transaction().execute(async (trx) => {
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.CLIP])}`.execute(trx);
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Clip])}`.execute(trx);
const items = await searchAssetBuilder(trx, options)
.selectAll('asset')
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
@ -284,7 +284,7 @@ export class SearchRepository {
}
return this.db.transaction().execute(async (trx) => {
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.FACE])}`.execute(trx);
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Face])}`.execute(trx);
return await trx
.with('cte', (qb) =>
qb
@ -351,8 +351,8 @@ export class SearchRepository {
.select(['city', 'assetId'])
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
.where('asset.ownerId', '=', anyUuid(userIds))
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
.where('asset.type', '=', AssetType.IMAGE)
.where('asset.visibility', '=', AssetVisibility.Timeline)
.where('asset.type', '=', AssetType.Image)
.where('asset.deletedAt', 'is', null)
.orderBy('city')
.limit(1);
@ -367,8 +367,8 @@ export class SearchRepository {
.select(['city', 'assetId'])
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
.where('asset.ownerId', '=', anyUuid(userIds))
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
.where('asset.type', '=', AssetType.IMAGE)
.where('asset.visibility', '=', AssetVisibility.Timeline)
.where('asset.type', '=', AssetType.Image)
.where('asset.deletedAt', 'is', null)
.whereRef('asset_exif.city', '>', 'cte.city')
.orderBy('city')
@ -450,7 +450,7 @@ export class SearchRepository {
.distinctOn(field)
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
.where('ownerId', '=', anyUuid(userIds))
.where('visibility', '=', AssetVisibility.TIMELINE)
.where('visibility', '=', AssetVisibility.Timeline)
.where('deletedAt', 'is', null)
.where(field, 'is not', null);
}

View File

@ -103,7 +103,7 @@ export class SharedLinkRepository {
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
.where('shared_link.id', '=', id)
.where('shared_link.userId', '=', userId)
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)]))
.orderBy('shared_link.createdAt', 'desc')
.executeTakeFirst();
}
@ -165,7 +165,7 @@ export class SharedLinkRepository {
(join) => join.onTrue(),
)
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)]))
.$if(!!albumId, (eb) => eb.where('shared_link.albumId', '=', albumId!))
.orderBy('shared_link.createdAt', 'desc')
.distinctOn(['shared_link.createdAt'])
@ -185,7 +185,7 @@ export class SharedLinkRepository {
eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'),
).as('user'),
])
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)]))
.executeTakeFirst();
}

View File

@ -112,21 +112,21 @@ export class TelemetryRepository {
const { telemetry } = this.configRepository.getEnv();
const { metrics } = telemetry;
this.api = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.API) });
this.host = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.HOST) });
this.jobs = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.JOB) });
this.repo = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.REPO) });
this.api = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Api) });
this.host = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Host) });
this.jobs = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Job) });
this.repo = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Repo) });
}
setup({ repositories }: { repositories: ClassConstructor<unknown>[] }) {
const { telemetry } = this.configRepository.getEnv();
const { metrics } = telemetry;
if (!metrics.has(ImmichTelemetry.REPO)) {
if (!metrics.has(ImmichTelemetry.Repo)) {
return;
}
for (const Repository of repositories) {
const isEnabled = this.reflect.get(MetadataKey.TELEMETRY_ENABLED, Repository) ?? true;
const isEnabled = this.reflect.get(MetadataKey.TelemetryEnabled, Repository) ?? true;
if (!isEnabled) {
this.logger.debug(`Telemetry disabled for ${Repository.name}`);
continue;

View File

@ -8,7 +8,7 @@ export class TrashRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
getDeletedIds(): AsyncIterableIterator<{ id: string }> {
return this.db.selectFrom('asset').select(['id']).where('status', '=', AssetStatus.DELETED).stream();
return this.db.selectFrom('asset').select(['id']).where('status', '=', AssetStatus.Deleted).stream();
}
@GenerateSql({ params: [DummyValue.UUID] })
@ -16,8 +16,8 @@ export class TrashRepository {
const { numUpdatedRows } = await this.db
.updateTable('asset')
.where('ownerId', '=', userId)
.where('status', '=', AssetStatus.TRASHED)
.set({ status: AssetStatus.ACTIVE, deletedAt: null })
.where('status', '=', AssetStatus.Trashed)
.set({ status: AssetStatus.Active, deletedAt: null })
.executeTakeFirst();
return Number(numUpdatedRows);
@ -28,8 +28,8 @@ export class TrashRepository {
const { numUpdatedRows } = await this.db
.updateTable('asset')
.where('ownerId', '=', userId)
.where('status', '=', AssetStatus.TRASHED)
.set({ status: AssetStatus.DELETED })
.where('status', '=', AssetStatus.Trashed)
.set({ status: AssetStatus.Deleted })
.executeTakeFirst();
return Number(numUpdatedRows);
@ -43,9 +43,9 @@ export class TrashRepository {
const { numUpdatedRows } = await this.db
.updateTable('asset')
.where('status', '=', AssetStatus.TRASHED)
.where('status', '=', AssetStatus.Trashed)
.where('id', 'in', ids)
.set({ status: AssetStatus.ACTIVE, deletedAt: null })
.set({ status: AssetStatus.Active, deletedAt: null })
.executeTakeFirst();
return Number(numUpdatedRows);

View File

@ -187,7 +187,7 @@ export class UserRepository {
restore(id: string) {
return this.db
.updateTable('user')
.set({ status: UserStatus.ACTIVE, deletedAt: null })
.set({ status: UserStatus.Active, deletedAt: null })
.where('user.id', '=', asUuid(id))
.returning(columns.userAdmin)
.returning(withMetadata)
@ -229,8 +229,8 @@ export class UserRepository {
.countAll<number>()
.filterWhere((eb) =>
eb.and([
eb('asset.type', '=', sql.lit(AssetType.IMAGE)),
eb('asset.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
eb('asset.type', '=', sql.lit(AssetType.Image)),
eb('asset.visibility', '!=', sql.lit(AssetVisibility.Hidden)),
]),
)
.as('photos'),
@ -238,8 +238,8 @@ export class UserRepository {
.countAll<number>()
.filterWhere((eb) =>
eb.and([
eb('asset.type', '=', sql.lit(AssetType.VIDEO)),
eb('asset.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
eb('asset.type', '=', sql.lit(AssetType.Video)),
eb('asset.visibility', '!=', sql.lit(AssetVisibility.Hidden)),
]),
)
.as('videos'),
@ -254,7 +254,7 @@ export class UserRepository {
eb.fn
.sum<number>('asset_exif.fileSizeInByte')
.filterWhere((eb) =>
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.IMAGE))]),
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.Image))]),
),
eb.lit(0),
)
@ -264,7 +264,7 @@ export class UserRepository {
eb.fn
.sum<number>('asset_exif.fileSizeInByte')
.filterWhere((eb) =>
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.VIDEO))]),
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.Video))]),
),
eb.lit(0),
)

View File

@ -15,7 +15,7 @@ export class ViewRepository {
.select((eb) => eb.fn<string>('substring', ['asset.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath'))
.distinct()
.where('ownerId', '=', asUuid(userId))
.where('visibility', '=', AssetVisibility.TIMELINE)
.where('visibility', '=', AssetVisibility.Timeline)
.where('deletedAt', 'is', null)
.where('fileCreatedAt', 'is not', null)
.where('fileModifiedAt', 'is not', null)
@ -34,7 +34,7 @@ export class ViewRepository {
.selectAll('asset')
.$call(withExif)
.where('ownerId', '=', asUuid(userId))
.where('visibility', '=', AssetVisibility.TIMELINE)
.where('visibility', '=', AssetVisibility.Timeline)
.where('deletedAt', 'is', null)
.where('fileCreatedAt', 'is not', null)
.where('fileModifiedAt', 'is not', null)

View File

@ -16,9 +16,7 @@ export async function up(db: Kysely<any>): Promise<void> {
rows: [lastMigration],
} = await lastMigrationSql.execute(db);
if (lastMigration?.name !== 'AddMissingIndex1744910873956') {
throw new Error(
'Invalid upgrade path. For more information, see https://immich.app/errors#typeorm-upgrade',
);
throw new Error('Invalid upgrade path. For more information, see https://immich.app/errors#typeorm-upgrade');
}
logger.log('Database has up to date TypeORM migrations, skipping initial Kysely migration');
return;
@ -108,152 +106,344 @@ export async function up(db: Kysely<any>): Promise<void> {
RETURN NULL;
END;
$$;`.execute(db);
if (vectorExtension === DatabaseExtension.VECTORS) {
if (vectorExtension === DatabaseExtension.Vectors) {
await sql`SET search_path TO "$user", public, vectors`.execute(db);
}
await sql`CREATE TYPE "assets_status_enum" AS ENUM ('active','trashed','deleted');`.execute(db);
await sql`CREATE TYPE "sourcetype" AS ENUM ('machine-learning','exif','manual');`.execute(db);
await sql`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "email" character varying NOT NULL, "password" character varying NOT NULL DEFAULT '', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "profileImagePath" character varying NOT NULL DEFAULT '', "isAdmin" boolean NOT NULL DEFAULT false, "shouldChangePassword" boolean NOT NULL DEFAULT true, "deletedAt" timestamp with time zone, "oauthId" character varying NOT NULL DEFAULT '', "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "storageLabel" character varying, "name" character varying NOT NULL DEFAULT '', "quotaSizeInBytes" bigint, "quotaUsageInBytes" bigint NOT NULL DEFAULT 0, "status" character varying NOT NULL DEFAULT 'active', "profileChangedAt" timestamp with time zone NOT NULL DEFAULT now(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "libraries" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "ownerId" uuid NOT NULL, "importPaths" text[] NOT NULL, "exclusionPatterns" text[] NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "refreshedAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "asset_stack" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "primaryAssetId" uuid NOT NULL, "ownerId" uuid NOT NULL);`.execute(db);
await sql`CREATE TABLE "assets" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "deviceAssetId" character varying NOT NULL, "ownerId" uuid NOT NULL, "deviceId" character varying NOT NULL, "type" character varying NOT NULL, "originalPath" character varying NOT NULL, "fileCreatedAt" timestamp with time zone NOT NULL, "fileModifiedAt" timestamp with time zone NOT NULL, "isFavorite" boolean NOT NULL DEFAULT false, "duration" character varying, "encodedVideoPath" character varying DEFAULT '', "checksum" bytea NOT NULL, "isVisible" boolean NOT NULL DEFAULT true, "livePhotoVideoId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "originalFileName" character varying NOT NULL, "sidecarPath" character varying, "thumbhash" bytea, "isOffline" boolean NOT NULL DEFAULT false, "libraryId" uuid, "isExternal" boolean NOT NULL DEFAULT false, "deletedAt" timestamp with time zone, "localDateTime" timestamp with time zone NOT NULL, "stackId" uuid, "duplicateId" uuid, "status" assets_status_enum NOT NULL DEFAULT 'active', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "albums" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "ownerId" uuid NOT NULL, "albumName" character varying NOT NULL DEFAULT 'Untitled Album', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "albumThumbnailAssetId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "description" text NOT NULL DEFAULT '', "deletedAt" timestamp with time zone, "isActivityEnabled" boolean NOT NULL DEFAULT true, "order" character varying NOT NULL DEFAULT 'desc', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "email" character varying NOT NULL, "password" character varying NOT NULL DEFAULT '', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "profileImagePath" character varying NOT NULL DEFAULT '', "isAdmin" boolean NOT NULL DEFAULT false, "shouldChangePassword" boolean NOT NULL DEFAULT true, "deletedAt" timestamp with time zone, "oauthId" character varying NOT NULL DEFAULT '', "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "storageLabel" character varying, "name" character varying NOT NULL DEFAULT '', "quotaSizeInBytes" bigint, "quotaUsageInBytes" bigint NOT NULL DEFAULT 0, "status" character varying NOT NULL DEFAULT 'active', "profileChangedAt" timestamp with time zone NOT NULL DEFAULT now(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "libraries" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "ownerId" uuid NOT NULL, "importPaths" text[] NOT NULL, "exclusionPatterns" text[] NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "refreshedAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "asset_stack" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "primaryAssetId" uuid NOT NULL, "ownerId" uuid NOT NULL);`.execute(
db,
);
await sql`CREATE TABLE "assets" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "deviceAssetId" character varying NOT NULL, "ownerId" uuid NOT NULL, "deviceId" character varying NOT NULL, "type" character varying NOT NULL, "originalPath" character varying NOT NULL, "fileCreatedAt" timestamp with time zone NOT NULL, "fileModifiedAt" timestamp with time zone NOT NULL, "isFavorite" boolean NOT NULL DEFAULT false, "duration" character varying, "encodedVideoPath" character varying DEFAULT '', "checksum" bytea NOT NULL, "isVisible" boolean NOT NULL DEFAULT true, "livePhotoVideoId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "originalFileName" character varying NOT NULL, "sidecarPath" character varying, "thumbhash" bytea, "isOffline" boolean NOT NULL DEFAULT false, "libraryId" uuid, "isExternal" boolean NOT NULL DEFAULT false, "deletedAt" timestamp with time zone, "localDateTime" timestamp with time zone NOT NULL, "stackId" uuid, "duplicateId" uuid, "status" assets_status_enum NOT NULL DEFAULT 'active', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "albums" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "ownerId" uuid NOT NULL, "albumName" character varying NOT NULL DEFAULT 'Untitled Album', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "albumThumbnailAssetId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "description" text NOT NULL DEFAULT '', "deletedAt" timestamp with time zone, "isActivityEnabled" boolean NOT NULL DEFAULT true, "order" character varying NOT NULL DEFAULT 'desc', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`COMMENT ON COLUMN "albums"."albumThumbnailAssetId" IS 'Asset ID to be used as thumbnail';`.execute(db);
await sql`CREATE TABLE "activity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "albumId" uuid NOT NULL, "userId" uuid NOT NULL, "assetId" uuid, "comment" text, "isLiked" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "albums_assets_assets" ("albumsId" uuid NOT NULL, "assetsId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(db);
await sql`CREATE TABLE "albums_shared_users_users" ("albumsId" uuid NOT NULL, "usersId" uuid NOT NULL, "role" character varying NOT NULL DEFAULT 'editor');`.execute(db);
await sql`CREATE TABLE "api_keys" ("name" character varying NOT NULL, "key" character varying NOT NULL, "userId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "permissions" character varying[] NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "assetId" uuid NOT NULL, "ownerId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db);
await sql`CREATE TABLE "person" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ownerId" uuid NOT NULL, "name" character varying NOT NULL DEFAULT '', "thumbnailPath" character varying NOT NULL DEFAULT '', "isHidden" boolean NOT NULL DEFAULT false, "birthDate" date, "faceAssetId" uuid, "isFavorite" boolean NOT NULL DEFAULT false, "color" character varying, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "asset_faces" ("assetId" uuid NOT NULL, "personId" uuid, "imageWidth" integer NOT NULL DEFAULT 0, "imageHeight" integer NOT NULL DEFAULT 0, "boundingBoxX1" integer NOT NULL DEFAULT 0, "boundingBoxY1" integer NOT NULL DEFAULT 0, "boundingBoxX2" integer NOT NULL DEFAULT 0, "boundingBoxY2" integer NOT NULL DEFAULT 0, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "sourceType" sourcetype NOT NULL DEFAULT 'machine-learning', "deletedAt" timestamp with time zone);`.execute(db);
await sql`CREATE TABLE "asset_files" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "assetId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "type" character varying NOT NULL, "path" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "asset_job_status" ("assetId" uuid NOT NULL, "facesRecognizedAt" timestamp with time zone, "metadataExtractedAt" timestamp with time zone, "duplicatesDetectedAt" timestamp with time zone, "previewAt" timestamp with time zone, "thumbnailAt" timestamp with time zone);`.execute(db);
await sql`CREATE TABLE "audit" ("id" serial NOT NULL, "entityType" character varying NOT NULL, "entityId" uuid NOT NULL, "action" character varying NOT NULL, "ownerId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(db);
await sql`CREATE TABLE "exif" ("assetId" uuid NOT NULL, "make" character varying, "model" character varying, "exifImageWidth" integer, "exifImageHeight" integer, "fileSizeInByte" bigint, "orientation" character varying, "dateTimeOriginal" timestamp with time zone, "modifyDate" timestamp with time zone, "lensModel" character varying, "fNumber" double precision, "focalLength" double precision, "iso" integer, "latitude" double precision, "longitude" double precision, "city" character varying, "state" character varying, "country" character varying, "description" text NOT NULL DEFAULT '', "fps" double precision, "exposureTime" character varying, "livePhotoCID" character varying, "timeZone" character varying, "projectionType" character varying, "profileDescription" character varying, "colorspace" character varying, "bitsPerSample" integer, "autoStackId" character varying, "rating" integer, "updatedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "activity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "albumId" uuid NOT NULL, "userId" uuid NOT NULL, "assetId" uuid, "comment" text, "isLiked" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "albums_assets_assets" ("albumsId" uuid NOT NULL, "assetsId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(
db,
);
await sql`CREATE TABLE "albums_shared_users_users" ("albumsId" uuid NOT NULL, "usersId" uuid NOT NULL, "role" character varying NOT NULL DEFAULT 'editor');`.execute(
db,
);
await sql`CREATE TABLE "api_keys" ("name" character varying NOT NULL, "key" character varying NOT NULL, "userId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "permissions" character varying[] NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "assetId" uuid NOT NULL, "ownerId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(
db,
);
await sql`CREATE TABLE "person" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ownerId" uuid NOT NULL, "name" character varying NOT NULL DEFAULT '', "thumbnailPath" character varying NOT NULL DEFAULT '', "isHidden" boolean NOT NULL DEFAULT false, "birthDate" date, "faceAssetId" uuid, "isFavorite" boolean NOT NULL DEFAULT false, "color" character varying, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "asset_faces" ("assetId" uuid NOT NULL, "personId" uuid, "imageWidth" integer NOT NULL DEFAULT 0, "imageHeight" integer NOT NULL DEFAULT 0, "boundingBoxX1" integer NOT NULL DEFAULT 0, "boundingBoxY1" integer NOT NULL DEFAULT 0, "boundingBoxX2" integer NOT NULL DEFAULT 0, "boundingBoxY2" integer NOT NULL DEFAULT 0, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "sourceType" sourcetype NOT NULL DEFAULT 'machine-learning', "deletedAt" timestamp with time zone);`.execute(
db,
);
await sql`CREATE TABLE "asset_files" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "assetId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "type" character varying NOT NULL, "path" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "asset_job_status" ("assetId" uuid NOT NULL, "facesRecognizedAt" timestamp with time zone, "metadataExtractedAt" timestamp with time zone, "duplicatesDetectedAt" timestamp with time zone, "previewAt" timestamp with time zone, "thumbnailAt" timestamp with time zone);`.execute(
db,
);
await sql`CREATE TABLE "audit" ("id" serial NOT NULL, "entityType" character varying NOT NULL, "entityId" uuid NOT NULL, "action" character varying NOT NULL, "ownerId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(
db,
);
await sql`CREATE TABLE "exif" ("assetId" uuid NOT NULL, "make" character varying, "model" character varying, "exifImageWidth" integer, "exifImageHeight" integer, "fileSizeInByte" bigint, "orientation" character varying, "dateTimeOriginal" timestamp with time zone, "modifyDate" timestamp with time zone, "lensModel" character varying, "fNumber" double precision, "focalLength" double precision, "iso" integer, "latitude" double precision, "longitude" double precision, "city" character varying, "state" character varying, "country" character varying, "description" text NOT NULL DEFAULT '', "fps" double precision, "exposureTime" character varying, "livePhotoCID" character varying, "timeZone" character varying, "projectionType" character varying, "profileDescription" character varying, "colorspace" character varying, "bitsPerSample" integer, "autoStackId" character varying, "rating" integer, "updatedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "face_search" ("faceId" uuid NOT NULL, "embedding" vector(512) NOT NULL);`.execute(db);
await sql`CREATE TABLE "geodata_places" ("id" integer NOT NULL, "name" character varying(200) NOT NULL, "longitude" double precision NOT NULL, "latitude" double precision NOT NULL, "countryCode" character(2) NOT NULL, "admin1Code" character varying(20), "admin2Code" character varying(80), "modificationDate" date NOT NULL, "admin1Name" character varying, "admin2Name" character varying, "alternateNames" character varying);`.execute(db);
await sql`CREATE TABLE "memories" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "ownerId" uuid NOT NULL, "type" character varying NOT NULL, "data" jsonb NOT NULL, "isSaved" boolean NOT NULL DEFAULT false, "memoryAt" timestamp with time zone NOT NULL, "seenAt" timestamp with time zone, "showAt" timestamp with time zone, "hideAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "geodata_places" ("id" integer NOT NULL, "name" character varying(200) NOT NULL, "longitude" double precision NOT NULL, "latitude" double precision NOT NULL, "countryCode" character(2) NOT NULL, "admin1Code" character varying(20), "admin2Code" character varying(80), "modificationDate" date NOT NULL, "admin1Name" character varying, "admin2Name" character varying, "alternateNames" character varying);`.execute(
db,
);
await sql`CREATE TABLE "memories" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "ownerId" uuid NOT NULL, "type" character varying NOT NULL, "data" jsonb NOT NULL, "isSaved" boolean NOT NULL DEFAULT false, "memoryAt" timestamp with time zone NOT NULL, "seenAt" timestamp with time zone, "showAt" timestamp with time zone, "hideAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "memories_assets_assets" ("memoriesId" uuid NOT NULL, "assetsId" uuid NOT NULL);`.execute(db);
await sql`CREATE TABLE "move_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "entityId" uuid NOT NULL, "pathType" character varying NOT NULL, "oldPath" character varying NOT NULL, "newPath" character varying NOT NULL);`.execute(db);
await sql`CREATE TABLE "naturalearth_countries" ("id" integer NOT NULL GENERATED ALWAYS AS IDENTITY, "admin" character varying(50) NOT NULL, "admin_a3" character varying(3) NOT NULL, "type" character varying(50) NOT NULL, "coordinates" polygon NOT NULL);`.execute(db);
await sql`CREATE TABLE "partners_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db);
await sql`CREATE TABLE "partners" ("sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "inTimeline" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "sessions" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "token" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "userId" uuid NOT NULL, "deviceType" character varying NOT NULL DEFAULT '', "deviceOS" character varying NOT NULL DEFAULT '', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "shared_links" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "description" character varying, "userId" uuid NOT NULL, "key" bytea NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "expiresAt" timestamp with time zone, "allowUpload" boolean NOT NULL DEFAULT false, "albumId" uuid, "allowDownload" boolean NOT NULL DEFAULT true, "showExif" boolean NOT NULL DEFAULT true, "password" character varying);`.execute(db);
await sql`CREATE TABLE "move_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "entityId" uuid NOT NULL, "pathType" character varying NOT NULL, "oldPath" character varying NOT NULL, "newPath" character varying NOT NULL);`.execute(
db,
);
await sql`CREATE TABLE "naturalearth_countries" ("id" integer NOT NULL GENERATED ALWAYS AS IDENTITY, "admin" character varying(50) NOT NULL, "admin_a3" character varying(3) NOT NULL, "type" character varying(50) NOT NULL, "coordinates" polygon NOT NULL);`.execute(
db,
);
await sql`CREATE TABLE "partners_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(
db,
);
await sql`CREATE TABLE "partners" ("sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "inTimeline" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "sessions" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "token" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "userId" uuid NOT NULL, "deviceType" character varying NOT NULL DEFAULT '', "deviceOS" character varying NOT NULL DEFAULT '', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "shared_links" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "description" character varying, "userId" uuid NOT NULL, "key" bytea NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "expiresAt" timestamp with time zone, "allowUpload" boolean NOT NULL DEFAULT false, "albumId" uuid, "allowDownload" boolean NOT NULL DEFAULT true, "showExif" boolean NOT NULL DEFAULT true, "password" character varying);`.execute(
db,
);
await sql`CREATE TABLE "shared_link__asset" ("assetsId" uuid NOT NULL, "sharedLinksId" uuid NOT NULL);`.execute(db);
await sql`CREATE TABLE "smart_search" ("assetId" uuid NOT NULL, "embedding" vector(512) NOT NULL);`.execute(db);
await sql`ALTER TABLE "smart_search" ALTER COLUMN "embedding" SET STORAGE EXTERNAL;`.execute(db);
await sql`CREATE TABLE "session_sync_checkpoints" ("sessionId" uuid NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ack" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "session_sync_checkpoints" ("sessionId" uuid NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ack" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "system_metadata" ("key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(db);
await sql`CREATE TABLE "tags" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid NOT NULL, "value" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "color" character varying, "parentId" uuid, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "tags" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid NOT NULL, "value" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "color" character varying, "parentId" uuid, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "tag_asset" ("assetsId" uuid NOT NULL, "tagsId" uuid NOT NULL);`.execute(db);
await sql`CREATE TABLE "tags_closure" ("id_ancestor" uuid NOT NULL, "id_descendant" uuid NOT NULL);`.execute(db);
await sql`CREATE TABLE "users_audit" ("userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "id" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
await sql`CREATE TABLE "user_metadata" ("userId" uuid NOT NULL, "key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(db);
await sql`CREATE TABLE "version_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "version" character varying NOT NULL);`.execute(db);
await sql`CREATE TABLE "users_audit" ("userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "id" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
db,
);
await sql`CREATE TABLE "user_metadata" ("userId" uuid NOT NULL, "key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(
db,
);
await sql`CREATE TABLE "version_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "version" character varying NOT NULL);`.execute(
db,
);
await sql`ALTER TABLE "users" ADD CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "libraries" ADD CONSTRAINT "PK_505fedfcad00a09b3734b4223de" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "PK_74a27e7fcbd5852463d0af3034b" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "PK_da96729a8b113377cfb6a62439c" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "albums" ADD CONSTRAINT "PK_7f71c7b5bc7c87b8f94c9a93a00" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "PK_24625a1d6b1b089c8ae206fe467" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180" PRIMARY KEY ("albumsId", "assetsId");`.execute(db);
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8" PRIMARY KEY ("albumsId", "usersId");`.execute(db);
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180" PRIMARY KEY ("albumsId", "assetsId");`.execute(
db,
);
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8" PRIMARY KEY ("albumsId", "usersId");`.execute(
db,
);
await sql`ALTER TABLE "api_keys" ADD CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "assets_audit" ADD CONSTRAINT "PK_99bd5c015f81a641927a32b4212" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "person" ADD CONSTRAINT "PK_5fdaf670315c4b7e70cce85daa3" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "PK_6df76ab2eb6f5b57b7c2f1fc684" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "PK_c41dc3e9ef5e1c57ca5a08a0004" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "PK_420bec36fc02813bddf5c8b73d4" PRIMARY KEY ("assetId");`.execute(db);
await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "PK_420bec36fc02813bddf5c8b73d4" PRIMARY KEY ("assetId");`.execute(
db,
);
await sql`ALTER TABLE "audit" ADD CONSTRAINT "PK_1d3d120ddaf7bc9b1ed68ed463a" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "exif" ADD CONSTRAINT "PK_c0117fdbc50b917ef9067740c44" PRIMARY KEY ("assetId");`.execute(db);
await sql`ALTER TABLE "face_search" ADD CONSTRAINT "face_search_pkey" PRIMARY KEY ("faceId");`.execute(db);
await sql`ALTER TABLE "geodata_places" ADD CONSTRAINT "PK_c29918988912ef4036f3d7fbff4" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "geodata_places" ADD CONSTRAINT "PK_c29918988912ef4036f3d7fbff4" PRIMARY KEY ("id");`.execute(
db,
);
await sql`ALTER TABLE "memories" ADD CONSTRAINT "PK_aaa0692d9496fe827b0568612f8" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "PK_fcaf7112a013d1703c011c6793d" PRIMARY KEY ("memoriesId", "assetsId");`.execute(db);
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "PK_fcaf7112a013d1703c011c6793d" PRIMARY KEY ("memoriesId", "assetsId");`.execute(
db,
);
await sql`ALTER TABLE "move_history" ADD CONSTRAINT "PK_af608f132233acf123f2949678d" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "naturalearth_countries" ADD CONSTRAINT "PK_21a6d86d1ab5d841648212e5353" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "partners_audit" ADD CONSTRAINT "PK_952b50217ff78198a7e380f0359" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "partners" ADD CONSTRAINT "PK_f1cc8f73d16b367f426261a8736" PRIMARY KEY ("sharedById", "sharedWithId");`.execute(db);
await sql`ALTER TABLE "naturalearth_countries" ADD CONSTRAINT "PK_21a6d86d1ab5d841648212e5353" PRIMARY KEY ("id");`.execute(
db,
);
await sql`ALTER TABLE "partners_audit" ADD CONSTRAINT "PK_952b50217ff78198a7e380f0359" PRIMARY KEY ("id");`.execute(
db,
);
await sql`ALTER TABLE "partners" ADD CONSTRAINT "PK_f1cc8f73d16b367f426261a8736" PRIMARY KEY ("sharedById", "sharedWithId");`.execute(
db,
);
await sql`ALTER TABLE "sessions" ADD CONSTRAINT "PK_48cb6b5c20faa63157b3c1baf7f" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "PK_642e2b0f619e4876e5f90a43465" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "PK_9b4f3687f9b31d1e311336b05e3" PRIMARY KEY ("assetsId", "sharedLinksId");`.execute(db);
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "PK_9b4f3687f9b31d1e311336b05e3" PRIMARY KEY ("assetsId", "sharedLinksId");`.execute(
db,
);
await sql`ALTER TABLE "smart_search" ADD CONSTRAINT "smart_search_pkey" PRIMARY KEY ("assetId");`.execute(db);
await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "PK_b846ab547a702863ef7cd9412fb" PRIMARY KEY ("sessionId", "type");`.execute(db);
await sql`ALTER TABLE "system_metadata" ADD CONSTRAINT "PK_fa94f6857470fb5b81ec6084465" PRIMARY KEY ("key");`.execute(db);
await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "PK_b846ab547a702863ef7cd9412fb" PRIMARY KEY ("sessionId", "type");`.execute(
db,
);
await sql`ALTER TABLE "system_metadata" ADD CONSTRAINT "PK_fa94f6857470fb5b81ec6084465" PRIMARY KEY ("key");`.execute(
db,
);
await sql`ALTER TABLE "tags" ADD CONSTRAINT "PK_e7dc17249a1148a1970748eda99" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId");`.execute(db);
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "PK_eab38eb12a3ec6df8376c95477c" PRIMARY KEY ("id_ancestor", "id_descendant");`.execute(db);
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId");`.execute(
db,
);
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "PK_eab38eb12a3ec6df8376c95477c" PRIMARY KEY ("id_ancestor", "id_descendant");`.execute(
db,
);
await sql`ALTER TABLE "users_audit" ADD CONSTRAINT "PK_e9b2bdfd90e7eb5961091175180" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "PK_5931462150b3438cbc83277fe5a" PRIMARY KEY ("userId", "key");`.execute(db);
await sql`ALTER TABLE "version_history" ADD CONSTRAINT "PK_5db259cbb09ce82c0d13cfd1b23" PRIMARY KEY ("id");`.execute(db);
await sql`ALTER TABLE "libraries" ADD CONSTRAINT "FK_0f6fc2fb195f24d19b0fb0d57c1" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_91704e101438fd0653f582426dc" FOREIGN KEY ("primaryAssetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;`.execute(db);
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_c05079e542fd74de3b5ecb5c1c8" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" FOREIGN KEY ("livePhotoVideoId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" FOREIGN KEY ("libraryId") REFERENCES "libraries" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_1af8519996fbfb3684b58df280b" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_8091ea76b12338cb4428d33d782" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_e590fa396c6898fcd4a50e40927" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_427c350ad49bd3935a50baab737" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06" FOREIGN KEY ("usersId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_5527cc99f530a547093f9e577b6" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_2bbabe31656b6778c6b87b61023" FOREIGN KEY ("faceAssetId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE SET NULL;`.execute(db);
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_02a43fd0b3c50fb6d7f0cb7282c" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_95ad7106dd7b484275443f580f9" FOREIGN KEY ("personId") REFERENCES "person" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "FK_e3e103a5f1d8bc8402999286040" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "FK_420bec36fc02813bddf5c8b73d4" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "exif" ADD CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "face_search" ADD CONSTRAINT "face_search_faceId_fkey" FOREIGN KEY ("faceId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "memories" ADD CONSTRAINT "FK_575842846f0c28fa5da46c99b19" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e" FOREIGN KEY ("memoriesId") REFERENCES "memories" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_7e077a8b70b3530138610ff5e04" FOREIGN KEY ("sharedById") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3" FOREIGN KEY ("sharedWithId") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "sessions" ADD CONSTRAINT "FK_57de40bc620f456c7311aa3a1e6" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_66fe3837414c5a9f1c33ca49340" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab" FOREIGN KEY ("sharedLinksId") REFERENCES "shared_links" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "smart_search" ADD CONSTRAINT "smart_search_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "FK_d8ddd9d687816cc490432b3d4bc" FOREIGN KEY ("sessionId") REFERENCES "sessions" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_9f9590cc11561f1f48ff034ef99" FOREIGN KEY ("parentId") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" FOREIGN KEY ("tagsId") REFERENCES "tags" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_15fbcbc67663c6bfc07b354c22c" FOREIGN KEY ("id_ancestor") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_b1a2a7ed45c29179b5ad51548a1" FOREIGN KEY ("id_descendant") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "FK_6afb43681a21cf7815932bc38ac" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "PK_5931462150b3438cbc83277fe5a" PRIMARY KEY ("userId", "key");`.execute(
db,
);
await sql`ALTER TABLE "version_history" ADD CONSTRAINT "PK_5db259cbb09ce82c0d13cfd1b23" PRIMARY KEY ("id");`.execute(
db,
);
await sql`ALTER TABLE "libraries" ADD CONSTRAINT "FK_0f6fc2fb195f24d19b0fb0d57c1" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_91704e101438fd0653f582426dc" FOREIGN KEY ("primaryAssetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;`.execute(
db,
);
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_c05079e542fd74de3b5ecb5c1c8" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" FOREIGN KEY ("livePhotoVideoId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
db,
);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" FOREIGN KEY ("libraryId") REFERENCES "libraries" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
db,
);
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
db,
);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_1af8519996fbfb3684b58df280b" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_8091ea76b12338cb4428d33d782" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_e590fa396c6898fcd4a50e40927" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_427c350ad49bd3935a50baab737" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06" FOREIGN KEY ("usersId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_5527cc99f530a547093f9e577b6" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_2bbabe31656b6778c6b87b61023" FOREIGN KEY ("faceAssetId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE SET NULL;`.execute(
db,
);
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_02a43fd0b3c50fb6d7f0cb7282c" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_95ad7106dd7b484275443f580f9" FOREIGN KEY ("personId") REFERENCES "person" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
db,
);
await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "FK_e3e103a5f1d8bc8402999286040" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "FK_420bec36fc02813bddf5c8b73d4" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "exif" ADD CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "face_search" ADD CONSTRAINT "face_search_faceId_fkey" FOREIGN KEY ("faceId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "memories" ADD CONSTRAINT "FK_575842846f0c28fa5da46c99b19" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e" FOREIGN KEY ("memoriesId") REFERENCES "memories" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_7e077a8b70b3530138610ff5e04" FOREIGN KEY ("sharedById") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3" FOREIGN KEY ("sharedWithId") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "sessions" ADD CONSTRAINT "FK_57de40bc620f456c7311aa3a1e6" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_66fe3837414c5a9f1c33ca49340" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab" FOREIGN KEY ("sharedLinksId") REFERENCES "shared_links" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "smart_search" ADD CONSTRAINT "smart_search_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "FK_d8ddd9d687816cc490432b3d4bc" FOREIGN KEY ("sessionId") REFERENCES "sessions" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_9f9590cc11561f1f48ff034ef99" FOREIGN KEY ("parentId") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" FOREIGN KEY ("tagsId") REFERENCES "tags" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_15fbcbc67663c6bfc07b354c22c" FOREIGN KEY ("id_ancestor") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_b1a2a7ed45c29179b5ad51548a1" FOREIGN KEY ("id_descendant") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "FK_6afb43681a21cf7815932bc38ac" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
db,
);
await sql`ALTER TABLE "users" ADD CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email");`.execute(db);
await sql`ALTER TABLE "users" ADD CONSTRAINT "UQ_b309cf34fa58137c416b32cea3a" UNIQUE ("storageLabel");`.execute(db);
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "REL_91704e101438fd0653f582426d" UNIQUE ("primaryAssetId");`.execute(db);
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "REL_91704e101438fd0653f582426d" UNIQUE ("primaryAssetId");`.execute(
db,
);
await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "UQ_assetId_type" UNIQUE ("assetId", "type");`.execute(db);
await sql`ALTER TABLE "move_history" ADD CONSTRAINT "UQ_newPath" UNIQUE ("newPath");`.execute(db);
await sql`ALTER TABLE "move_history" ADD CONSTRAINT "UQ_entityId_pathType" UNIQUE ("entityId", "pathType");`.execute(db);
await sql`ALTER TABLE "move_history" ADD CONSTRAINT "UQ_entityId_pathType" UNIQUE ("entityId", "pathType");`.execute(
db,
);
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "UQ_sharedlink_key" UNIQUE ("key");`.execute(db);
await sql`ALTER TABLE "tags" ADD CONSTRAINT "UQ_79d6f16e52bb2c7130375246793" UNIQUE ("userId", "value");`.execute(db);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "CHK_2ab1e70f113f450eb40c1e3ec8" CHECK (("comment" IS NULL AND "isLiked" = true) OR ("comment" IS NOT NULL AND "isLiked" = false));`.execute(db);
await sql`ALTER TABLE "person" ADD CONSTRAINT "CHK_b0f82b0ed662bfc24fbb58bb45" CHECK ("birthDate" <= CURRENT_DATE);`.execute(db);
await sql`ALTER TABLE "activity" ADD CONSTRAINT "CHK_2ab1e70f113f450eb40c1e3ec8" CHECK (("comment" IS NULL AND "isLiked" = true) OR ("comment" IS NOT NULL AND "isLiked" = false));`.execute(
db,
);
await sql`ALTER TABLE "person" ADD CONSTRAINT "CHK_b0f82b0ed662bfc24fbb58bb45" CHECK ("birthDate" <= CURRENT_DATE);`.execute(
db,
);
await sql`CREATE INDEX "IDX_users_updated_at_asc_id_asc" ON "users" ("updatedAt", "id")`.execute(db);
await sql`CREATE INDEX "IDX_users_update_id" ON "users" ("updateId")`.execute(db);
await sql`CREATE INDEX "IDX_0f6fc2fb195f24d19b0fb0d57c" ON "libraries" ("ownerId")`.execute(db);
await sql`CREATE INDEX "IDX_libraries_update_id" ON "libraries" ("updateId")`.execute(db);
await sql`CREATE INDEX "IDX_91704e101438fd0653f582426d" ON "asset_stack" ("primaryAssetId")`.execute(db);
await sql`CREATE INDEX "IDX_c05079e542fd74de3b5ecb5c1c" ON "asset_stack" ("ownerId")`.execute(db);
await sql`CREATE INDEX "idx_originalfilename_trigram" ON "assets" USING gin (f_unaccent("originalFileName") gin_trgm_ops)`.execute(db);
await sql`CREATE INDEX "idx_originalfilename_trigram" ON "assets" USING gin (f_unaccent("originalFileName") gin_trgm_ops)`.execute(
db,
);
await sql`CREATE INDEX "IDX_asset_id_stackId" ON "assets" ("id", "stackId")`.execute(db);
await sql`CREATE INDEX "IDX_originalPath_libraryId" ON "assets" ("originalPath", "libraryId")`.execute(db);
await sql`CREATE INDEX "idx_local_date_time_month" ON "assets" ((date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text))`.execute(db);
await sql`CREATE INDEX "idx_local_date_time_month" ON "assets" ((date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text))`.execute(
db,
);
await sql`CREATE INDEX "idx_local_date_time" ON "assets" ((("localDateTime" at time zone 'UTC')::date))`.execute(db);
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_library_checksum" ON "assets" ("ownerId", "libraryId", "checksum") WHERE ("libraryId" IS NOT NULL)`.execute(db);
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_checksum" ON "assets" ("ownerId", "checksum") WHERE ("libraryId" IS NULL)`.execute(db);
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_library_checksum" ON "assets" ("ownerId", "libraryId", "checksum") WHERE ("libraryId" IS NOT NULL)`.execute(
db,
);
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_checksum" ON "assets" ("ownerId", "checksum") WHERE ("libraryId" IS NULL)`.execute(
db,
);
await sql`CREATE INDEX "IDX_2c5ac0d6fb58b238fd2068de67" ON "assets" ("ownerId")`.execute(db);
await sql`CREATE INDEX "idx_asset_file_created_at" ON "assets" ("fileCreatedAt")`.execute(db);
await sql`CREATE INDEX "IDX_8d3efe36c0755849395e6ea866" ON "assets" ("checksum")`.execute(db);
@ -266,7 +456,9 @@ export async function up(db: Kysely<any>): Promise<void> {
await sql`CREATE INDEX "IDX_b22c53f35ef20c28c21637c85f" ON "albums" ("ownerId")`.execute(db);
await sql`CREATE INDEX "IDX_05895aa505a670300d4816debc" ON "albums" ("albumThumbnailAssetId")`.execute(db);
await sql`CREATE INDEX "IDX_albums_update_id" ON "albums" ("updateId")`.execute(db);
await sql`CREATE UNIQUE INDEX "IDX_activity_like" ON "activity" ("assetId", "userId", "albumId") WHERE ("isLiked" = true)`.execute(db);
await sql`CREATE UNIQUE INDEX "IDX_activity_like" ON "activity" ("assetId", "userId", "albumId") WHERE ("isLiked" = true)`.execute(
db,
);
await sql`CREATE INDEX "IDX_1af8519996fbfb3684b58df280" ON "activity" ("albumId")`.execute(db);
await sql`CREATE INDEX "IDX_3571467bcbe021f66e2bdce96e" ON "activity" ("userId")`.execute(db);
await sql`CREATE INDEX "IDX_8091ea76b12338cb4428d33d78" ON "activity" ("assetId")`.execute(db);
@ -295,11 +487,21 @@ export async function up(db: Kysely<any>): Promise<void> {
await sql`CREATE INDEX "IDX_auto_stack_id" ON "exif" ("autoStackId")`.execute(db);
await sql`CREATE INDEX "IDX_asset_exif_update_id" ON "exif" ("updateId")`.execute(db);
await sql.raw(vectorIndexQuery({ vectorExtension, table: 'face_search', indexName: 'face_index' })).execute(db);
await sql`CREATE INDEX "IDX_geodata_gist_earthcoord" ON "geodata_places" (ll_to_earth_public(latitude, longitude))`.execute(db);
await sql`CREATE INDEX "idx_geodata_places_name" ON "geodata_places" USING gin (f_unaccent("name") gin_trgm_ops)`.execute(db);
await sql`CREATE INDEX "idx_geodata_places_admin2_name" ON "geodata_places" USING gin (f_unaccent("admin2Name") gin_trgm_ops)`.execute(db);
await sql`CREATE INDEX "idx_geodata_places_admin1_name" ON "geodata_places" USING gin (f_unaccent("admin1Name") gin_trgm_ops)`.execute(db);
await sql`CREATE INDEX "idx_geodata_places_alternate_names" ON "geodata_places" USING gin (f_unaccent("alternateNames") gin_trgm_ops)`.execute(db);
await sql`CREATE INDEX "IDX_geodata_gist_earthcoord" ON "geodata_places" (ll_to_earth_public(latitude, longitude))`.execute(
db,
);
await sql`CREATE INDEX "idx_geodata_places_name" ON "geodata_places" USING gin (f_unaccent("name") gin_trgm_ops)`.execute(
db,
);
await sql`CREATE INDEX "idx_geodata_places_admin2_name" ON "geodata_places" USING gin (f_unaccent("admin2Name") gin_trgm_ops)`.execute(
db,
);
await sql`CREATE INDEX "idx_geodata_places_admin1_name" ON "geodata_places" USING gin (f_unaccent("admin1Name") gin_trgm_ops)`.execute(
db,
);
await sql`CREATE INDEX "idx_geodata_places_alternate_names" ON "geodata_places" USING gin (f_unaccent("alternateNames") gin_trgm_ops)`.execute(
db,
);
await sql`CREATE INDEX "IDX_575842846f0c28fa5da46c99b1" ON "memories" ("ownerId")`.execute(db);
await sql`CREATE INDEX "IDX_memories_update_id" ON "memories" ("updateId")`.execute(db);
await sql`CREATE INDEX "IDX_984e5c9ab1f04d34538cd32334" ON "memories_assets_assets" ("memoriesId")`.execute(db);
@ -319,7 +521,9 @@ export async function up(db: Kysely<any>): Promise<void> {
await sql`CREATE INDEX "IDX_c9fab4aa97ffd1b034f3d6581a" ON "shared_link__asset" ("sharedLinksId")`.execute(db);
await sql.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: 'clip_index' })).execute(db);
await sql`CREATE INDEX "IDX_d8ddd9d687816cc490432b3d4b" ON "session_sync_checkpoints" ("sessionId")`.execute(db);
await sql`CREATE INDEX "IDX_session_sync_checkpoints_update_id" ON "session_sync_checkpoints" ("updateId")`.execute(db);
await sql`CREATE INDEX "IDX_session_sync_checkpoints_update_id" ON "session_sync_checkpoints" ("updateId")`.execute(
db,
);
await sql`CREATE INDEX "IDX_92e67dc508c705dd66c9461557" ON "tags" ("userId")`.execute(db);
await sql`CREATE INDEX "IDX_9f9590cc11561f1f48ff034ef9" ON "tags" ("parentId")`.execute(db);
await sql`CREATE INDEX "IDX_tags_update_id" ON "tags" ("updateId")`.execute(db);
@ -407,5 +611,5 @@ export async function up(db: Kysely<any>): Promise<void> {
}
export async function down(): Promise<void> {
// not implemented
// not implemented
}

View File

@ -2,11 +2,11 @@ import { Kysely, sql } from 'kysely';
import { UserMetadataKey } from 'src/enum';
export async function up(db: Kysely<any>): Promise<void> {
await sql`INSERT INTO user_metadata SELECT id, ${UserMetadataKey.ONBOARDING}, '{"isOnboarded": true}' FROM users
await sql`INSERT INTO user_metadata SELECT id, ${UserMetadataKey.Onboarding}, '{"isOnboarded": true}' FROM users
ON CONFLICT ("userId", key) DO NOTHING
`.execute(db);
}
export async function down(db: Kysely<any>): Promise<void> {
await sql`DELETE FROM user_metadata WHERE key = ${UserMetadataKey.ONBOARDING}`.execute(db);
await sql`DELETE FROM user_metadata WHERE key = ${UserMetadataKey.Onboarding}`.execute(db);
}

View File

@ -47,7 +47,7 @@ export class AlbumUserTable {
})
usersId!: string;
@Column({ type: 'character varying', default: AlbumUserRole.EDITOR })
@Column({ type: 'character varying', default: AlbumUserRole.Editor })
role!: Generated<AlbumUserRole>;
@CreateIdColumn({ index: true })

View File

@ -57,7 +57,7 @@ export class AlbumTable {
@Column({ type: 'boolean', default: true })
isActivityEnabled!: Generated<boolean>;
@Column({ default: AssetOrder.DESC })
@Column({ default: AssetOrder.Desc })
order!: Generated<AssetOrder>;
@UpdateIdColumn({ index: true })

View File

@ -56,7 +56,7 @@ export class AssetFaceTable {
@Column({ default: 0, type: 'integer' })
boundingBoxY2!: Generated<number>;
@Column({ default: SourceType.MACHINE_LEARNING, enum: asset_face_source_type })
@Column({ default: SourceType.MachineLearning, enum: asset_face_source_type })
sourceType!: Generated<SourceType>;
@DeleteDateColumn()

View File

@ -132,12 +132,12 @@ export class AssetTable {
@Column({ type: 'uuid', nullable: true, index: true })
duplicateId!: string | null;
@Column({ enum: assets_status_enum, default: AssetStatus.ACTIVE })
@Column({ enum: assets_status_enum, default: AssetStatus.Active })
status!: Generated<AssetStatus>;
@UpdateIdColumn({ index: true })
updateId!: Generated<string>;
@Column({ enum: asset_visibility_enum, default: AssetVisibility.TIMELINE })
@Column({ enum: asset_visibility_enum, default: AssetVisibility.Timeline })
visibility!: Generated<AssetVisibility>;
}

View File

@ -73,7 +73,7 @@ export class UserTable {
@Column({ type: 'bigint', default: 0 })
quotaUsageInBytes!: Generated<ColumnType<number>>;
@Column({ type: 'character varying', default: UserStatus.ACTIVE })
@Column({ type: 'character varying', default: UserStatus.Active })
status!: Generated<UserStatus>;
@Column({ type: 'timestamp with time zone', default: () => 'now()' })

View File

@ -18,7 +18,7 @@ import { BaseService } from 'src/services/base.service';
@Injectable()
export class ActivityService extends BaseService {
async getAll(auth: AuthDto, dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] });
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [dto.albumId] });
const activities = await this.activityRepository.search({
userId: dto.userId,
albumId: dto.albumId,
@ -30,12 +30,12 @@ export class ActivityService extends BaseService {
}
async getStatistics(auth: AuthDto, dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] });
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [dto.albumId] });
return await this.activityRepository.getStatistics({ albumId: dto.albumId, assetId: dto.assetId });
}
async create(auth: AuthDto, dto: ActivityCreateDto): Promise<MaybeDuplicate<ActivityResponseDto>> {
await this.requireAccess({ auth, permission: Permission.ACTIVITY_CREATE, ids: [dto.albumId] });
await this.requireAccess({ auth, permission: Permission.ActivityCreate, ids: [dto.albumId] });
const common = {
userId: auth.user.id,
@ -69,7 +69,7 @@ export class ActivityService extends BaseService {
}
async delete(auth: AuthDto, id: string): Promise<void> {
await this.requireAccess({ auth, permission: Permission.ACTIVITY_DELETE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.ActivityDelete, ids: [id] });
await this.activityRepository.delete(id);
}
}

View File

@ -146,7 +146,7 @@ describe(AlbumService.name, () => {
await sut.create(authStub.admin, {
albumName: 'Empty album',
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.Editor }],
description: '',
assetIds: ['123'],
});
@ -160,7 +160,7 @@ describe(AlbumService.name, () => {
albumThumbnailAssetId: '123',
},
['123'],
[{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
[{ userId: 'user-id', role: AlbumUserRole.Editor }],
);
expect(mocks.user.get).toHaveBeenCalledWith('user-id', {});
@ -177,10 +177,10 @@ describe(AlbumService.name, () => {
mocks.user.get.mockResolvedValue(userStub.user1);
mocks.user.getMetadata.mockResolvedValue([
{
key: UserMetadataKey.PREFERENCES,
key: UserMetadataKey.Preferences,
value: {
albums: {
defaultAssetOrder: AssetOrder.ASC,
defaultAssetOrder: AssetOrder.Asc,
},
},
},
@ -189,7 +189,7 @@ describe(AlbumService.name, () => {
await sut.create(authStub.admin, {
albumName: 'Empty album',
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.Editor }],
description: '',
assetIds: ['123'],
});
@ -203,7 +203,7 @@ describe(AlbumService.name, () => {
albumThumbnailAssetId: '123',
},
['123'],
[{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
[{ userId: 'user-id', role: AlbumUserRole.Editor }],
);
expect(mocks.user.get).toHaveBeenCalledWith('user-id', {});
@ -220,7 +220,7 @@ describe(AlbumService.name, () => {
await expect(
sut.create(authStub.admin, {
albumName: 'Empty album',
albumUsers: [{ userId: 'user-3', role: AlbumUserRole.EDITOR }],
albumUsers: [{ userId: 'user-3', role: AlbumUserRole.Editor }],
}),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.user.get).toHaveBeenCalledWith('user-3', {});
@ -262,7 +262,7 @@ describe(AlbumService.name, () => {
await expect(
sut.create(authStub.admin, {
albumName: 'Empty album',
albumUsers: [{ userId: userStub.admin.id, role: AlbumUserRole.EDITOR }],
albumUsers: [{ userId: userStub.admin.id, role: AlbumUserRole.Editor }],
}),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.album.create).not.toHaveBeenCalled();
@ -404,7 +404,7 @@ describe(AlbumService.name, () => {
mocks.albumUser.create.mockResolvedValue({
usersId: userStub.user2.id,
albumsId: albumStub.sharedWithAdmin.id,
role: AlbumUserRole.EDITOR,
role: AlbumUserRole.Editor,
});
await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, {
albumUsers: [{ userId: authStub.user2.user.id }],
@ -512,11 +512,11 @@ describe(AlbumService.name, () => {
mocks.albumUser.update.mockResolvedValue(null as any);
await sut.updateUser(authStub.user1, albumStub.sharedWithAdmin.id, userStub.admin.id, {
role: AlbumUserRole.EDITOR,
role: AlbumUserRole.Editor,
});
expect(mocks.albumUser.update).toHaveBeenCalledWith(
{ albumsId: albumStub.sharedWithAdmin.id, usersId: userStub.admin.id },
{ role: AlbumUserRole.EDITOR },
{ role: AlbumUserRole.Editor },
);
});
});
@ -585,7 +585,7 @@ describe(AlbumService.name, () => {
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
authStub.user1.user.id,
new Set(['album-123']),
AlbumUserRole.VIEWER,
AlbumUserRole.Viewer,
);
});
@ -596,7 +596,7 @@ describe(AlbumService.name, () => {
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
authStub.admin.user.id,
new Set(['album-123']),
AlbumUserRole.VIEWER,
AlbumUserRole.Viewer,
);
});
});

View File

@ -71,7 +71,7 @@ export class AlbumService extends BaseService {
}
async get(auth: AuthDto, id: string, dto: AlbumInfoDto): Promise<AlbumResponseDto> {
await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
await this.albumRepository.updateThumbnails();
const withAssets = dto.withoutAssets === undefined ? true : !dto.withoutAssets;
const album = await this.findOrFail(id, { withAssets });
@ -102,7 +102,7 @@ export class AlbumService extends BaseService {
const allowedAssetIdsSet = await this.checkAccess({
auth,
permission: Permission.ASSET_SHARE,
permission: Permission.AssetShare,
ids: dto.assetIds || [],
});
const assetIds = [...allowedAssetIdsSet].map((id) => id);
@ -129,7 +129,7 @@ export class AlbumService extends BaseService {
}
async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
await this.requireAccess({ auth, permission: Permission.ALBUM_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] });
const album = await this.findOrFail(id, { withAssets: true });
@ -152,13 +152,13 @@ export class AlbumService extends BaseService {
}
async delete(auth: AuthDto, id: string): Promise<void> {
await this.requireAccess({ auth, permission: Permission.ALBUM_DELETE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AlbumDelete, ids: [id] });
await this.albumRepository.delete(id);
}
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
const album = await this.findOrFail(id, { withAssets: false });
await this.requireAccess({ auth, permission: Permission.ALBUM_ADD_ASSET, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AlbumAddAsset, ids: [id] });
const results = await addAssets(
auth,
@ -187,13 +187,13 @@ export class AlbumService extends BaseService {
}
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
await this.requireAccess({ auth, permission: Permission.ALBUM_REMOVE_ASSET, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AlbumRemoveAsset, ids: [id] });
const album = await this.findOrFail(id, { withAssets: false });
const results = await removeAssets(
auth,
{ access: this.accessRepository, bulk: this.albumRepository },
{ parentId: id, assetIds: dto.ids, canAlwaysRemove: Permission.ALBUM_DELETE },
{ parentId: id, assetIds: dto.ids, canAlwaysRemove: Permission.AlbumDelete },
);
const removedIds = results.filter(({ success }) => success).map(({ id }) => id);
@ -205,7 +205,7 @@ export class AlbumService extends BaseService {
}
async addUsers(auth: AuthDto, id: string, { albumUsers }: AddUsersDto): Promise<AlbumResponseDto> {
await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
const album = await this.findOrFail(id, { withAssets: false });
@ -249,14 +249,14 @@ export class AlbumService extends BaseService {
// non-admin can remove themselves
if (auth.user.id !== userId) {
await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
}
await this.albumUserRepository.delete({ albumsId: id, usersId: userId });
}
async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise<void> {
await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
await this.albumUserRepository.update({ albumsId: id, usersId: userId }, { role: dto.role });
}

View File

@ -15,7 +15,7 @@ describe(ApiKeyService.name, () => {
describe('create', () => {
it('should create a new key', async () => {
const auth = factory.auth();
const apiKey = factory.apiKey({ userId: auth.user.id, permissions: [Permission.ALL] });
const apiKey = factory.apiKey({ userId: auth.user.id, permissions: [Permission.All] });
const key = 'super-secret';
mocks.crypto.randomBytesAsText.mockReturnValue(key);
@ -41,12 +41,12 @@ describe(ApiKeyService.name, () => {
mocks.crypto.randomBytesAsText.mockReturnValue(key);
mocks.apiKey.create.mockResolvedValue(apiKey);
await sut.create(auth, { permissions: [Permission.ALL] });
await sut.create(auth, { permissions: [Permission.All] });
expect(mocks.apiKey.create).toHaveBeenCalledWith({
key: 'super-secret (hashed)',
name: 'API Key',
permissions: [Permission.ALL],
permissions: [Permission.All],
userId: auth.user.id,
});
expect(mocks.crypto.randomBytesAsText).toHaveBeenCalled();
@ -54,9 +54,9 @@ describe(ApiKeyService.name, () => {
});
it('should throw an error if the api key does not have sufficient permissions', async () => {
const auth = factory.auth({ apiKey: { permissions: [Permission.ASSET_READ] } });
const auth = factory.auth({ apiKey: { permissions: [Permission.AssetRead] } });
await expect(sut.create(auth, { permissions: [Permission.ASSET_UPDATE] })).rejects.toBeInstanceOf(
await expect(sut.create(auth, { permissions: [Permission.AssetUpdate] })).rejects.toBeInstanceOf(
BadRequestException,
);
});
@ -69,7 +69,7 @@ describe(ApiKeyService.name, () => {
mocks.apiKey.getById.mockResolvedValue(void 0);
await expect(sut.update(auth, id, { name: 'New Name', permissions: [Permission.ALL] })).rejects.toBeInstanceOf(
await expect(sut.update(auth, id, { name: 'New Name', permissions: [Permission.All] })).rejects.toBeInstanceOf(
BadRequestException,
);
@ -84,18 +84,18 @@ describe(ApiKeyService.name, () => {
mocks.apiKey.getById.mockResolvedValue(apiKey);
mocks.apiKey.update.mockResolvedValue(apiKey);
await sut.update(auth, apiKey.id, { name: newName, permissions: [Permission.ALL] });
await sut.update(auth, apiKey.id, { name: newName, permissions: [Permission.All] });
expect(mocks.apiKey.update).toHaveBeenCalledWith(auth.user.id, apiKey.id, {
name: newName,
permissions: [Permission.ALL],
permissions: [Permission.All],
});
});
it('should update permissions', async () => {
const auth = factory.auth();
const apiKey = factory.apiKey({ userId: auth.user.id });
const newPermissions = [Permission.ACTIVITY_CREATE, Permission.ACTIVITY_READ, Permission.ACTIVITY_UPDATE];
const newPermissions = [Permission.ActivityCreate, Permission.ActivityRead, Permission.ActivityUpdate];
mocks.apiKey.getById.mockResolvedValue(apiKey);
mocks.apiKey.update.mockResolvedValue(apiKey);

View File

@ -157,7 +157,7 @@ const assetEntity = Object.freeze({
ownerId: 'user_id_1',
deviceAssetId: 'device_asset_id_1',
deviceId: 'device_id_1',
type: AssetType.VIDEO,
type: AssetType.Video,
originalPath: 'fake_path/asset_1.jpeg',
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'),
@ -177,7 +177,7 @@ const assetEntity = Object.freeze({
const existingAsset = Object.freeze({
...assetEntity,
duration: null,
type: AssetType.IMAGE,
type: AssetType.Image,
checksum: Buffer.from('_getExistingAsset', 'utf8'),
libraryId: 'libraryId',
originalFileName: 'existing-filename.jpeg',
@ -384,7 +384,7 @@ describe(AssetMediaService.name, () => {
});
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.DELETE_FILES,
name: JobName.DeleteFiles,
data: { files: ['fake_path/asset_1.jpeg', undefined] },
});
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
@ -409,7 +409,7 @@ describe(AssetMediaService.name, () => {
);
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.DELETE_FILES,
name: JobName.DeleteFiles,
data: { files: ['fake_path/asset_1.jpeg', undefined] },
});
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
@ -437,7 +437,7 @@ describe(AssetMediaService.name, () => {
it('should hide the linked motion asset', async () => {
mocks.asset.getById.mockResolvedValueOnce({
...assetStub.livePhotoMotionAsset,
visibility: AssetVisibility.TIMELINE,
visibility: AssetVisibility.Timeline,
});
mocks.asset.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset);
@ -455,7 +455,7 @@ describe(AssetMediaService.name, () => {
expect(mocks.asset.getById).toHaveBeenCalledWith('live-photo-motion-asset');
expect(mocks.asset.update).toHaveBeenCalledWith({
id: 'live-photo-motion-asset',
visibility: AssetVisibility.HIDDEN,
visibility: AssetVisibility.Hidden,
});
});
@ -506,7 +506,7 @@ describe(AssetMediaService.name, () => {
new ImmichFileResponse({
path: '/original/path.jpg',
contentType: 'image/jpeg',
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
}),
);
});
@ -546,7 +546,7 @@ describe(AssetMediaService.name, () => {
{
id: '42',
path: '/path/to/preview',
type: AssetFileType.THUMBNAIL,
type: AssetFileType.Thumbnail,
},
],
});
@ -563,7 +563,7 @@ describe(AssetMediaService.name, () => {
{
id: '42',
path: '/path/to/preview.jpg',
type: AssetFileType.PREVIEW,
type: AssetFileType.Preview,
},
],
});
@ -573,7 +573,7 @@ describe(AssetMediaService.name, () => {
).resolves.toEqual(
new ImmichFileResponse({
path: '/path/to/preview.jpg',
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
contentType: 'image/jpeg',
fileName: 'asset-id_thumbnail.jpg',
}),
@ -588,7 +588,7 @@ describe(AssetMediaService.name, () => {
).resolves.toEqual(
new ImmichFileResponse({
path: '/uploads/user-id/thumbs/path.jpg',
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
contentType: 'image/jpeg',
fileName: 'asset-id_preview.jpg',
}),
@ -603,7 +603,7 @@ describe(AssetMediaService.name, () => {
).resolves.toEqual(
new ImmichFileResponse({
path: '/uploads/user-id/webp/path.ext',
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
contentType: 'application/octet-stream',
fileName: 'asset-id_thumbnail.ext',
}),
@ -640,7 +640,7 @@ describe(AssetMediaService.name, () => {
await expect(sut.playbackVideo(authStub.admin, assetStub.hasEncodedVideo.id)).resolves.toEqual(
new ImmichFileResponse({
path: assetStub.hasEncodedVideo.encodedVideoPath!,
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
contentType: 'video/mp4',
}),
);
@ -653,7 +653,7 @@ describe(AssetMediaService.name, () => {
await expect(sut.playbackVideo(authStub.admin, assetStub.video.id)).resolves.toEqual(
new ImmichFileResponse({
path: assetStub.video.originalPath,
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
contentType: 'application/octet-stream',
}),
);
@ -723,7 +723,7 @@ describe(AssetMediaService.name, () => {
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
deletedAt: expect.any(Date),
status: AssetStatus.TRASHED,
status: AssetStatus.Trashed,
});
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
expect(mocks.storage.utimes).toHaveBeenCalledWith(
@ -754,7 +754,7 @@ describe(AssetMediaService.name, () => {
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
deletedAt: expect.any(Date),
status: AssetStatus.TRASHED,
status: AssetStatus.Trashed,
});
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
expect(mocks.storage.utimes).toHaveBeenCalledWith(
@ -783,7 +783,7 @@ describe(AssetMediaService.name, () => {
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
deletedAt: expect.any(Date),
status: AssetStatus.TRASHED,
status: AssetStatus.Trashed,
});
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
expect(mocks.storage.utimes).toHaveBeenCalledWith(
@ -815,7 +815,7 @@ describe(AssetMediaService.name, () => {
expect(mocks.asset.create).not.toHaveBeenCalled();
expect(mocks.asset.updateAll).not.toHaveBeenCalled();
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.DELETE_FILES,
name: JobName.DeleteFiles,
data: { files: [updatedFile.originalPath, undefined] },
});
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
@ -912,7 +912,7 @@ describe(AssetMediaService.name, () => {
await sut.onUploadError(request, file);
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.DELETE_FILES,
name: JobName.DeleteFiles,
data: { files: ['upload/upload/user-id/ra/nd/random-uuid.jpg'] },
});
});

View File

@ -106,9 +106,9 @@ export class AssetMediaService extends BaseService {
getUploadFolder({ auth, fieldName, file }: UploadRequest): string {
auth = requireUploadAccess(auth);
let folder = StorageCore.getNestedFolder(StorageFolder.UPLOAD, auth.user.id, file.uuid);
let folder = StorageCore.getNestedFolder(StorageFolder.Upload, auth.user.id, file.uuid);
if (fieldName === UploadFieldName.PROFILE_DATA) {
folder = StorageCore.getFolderLocation(StorageFolder.PROFILE, auth.user.id);
folder = StorageCore.getFolderLocation(StorageFolder.Profile, auth.user.id);
}
this.storageRepository.mkdirSync(folder);
@ -121,7 +121,7 @@ export class AssetMediaService extends BaseService {
const uploadFolder = this.getUploadFolder(asRequest(request, file));
const uploadPath = `${uploadFolder}/${uploadFilename}`;
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [uploadPath] } });
await this.jobRepository.queue({ name: JobName.DeleteFiles, data: { files: [uploadPath] } });
}
async uploadAsset(
@ -133,7 +133,7 @@ export class AssetMediaService extends BaseService {
try {
await this.requireAccess({
auth,
permission: Permission.ASSET_UPLOAD,
permission: Permission.AssetUpload,
// do not need an id here, but the interface requires it
ids: [auth.user.id],
});
@ -164,7 +164,7 @@ export class AssetMediaService extends BaseService {
sidecarFile?: UploadFile,
): Promise<AssetMediaResponseDto> {
try {
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
const asset = await this.assetRepository.getById(id);
if (!asset) {
@ -179,7 +179,7 @@ export class AssetMediaService extends BaseService {
// but the local variable holds the original file data paths.
const copiedPhoto = await this.createCopy(asset);
// and immediate trash it
await this.assetRepository.updateAll([copiedPhoto.id], { deletedAt: new Date(), status: AssetStatus.TRASHED });
await this.assetRepository.updateAll([copiedPhoto.id], { deletedAt: new Date(), status: AssetStatus.Trashed });
await this.eventRepository.emit('AssetTrash', { assetId: copiedPhoto.id, userId: auth.user.id });
await this.userRepository.updateUsage(auth.user.id, file.size);
@ -191,14 +191,14 @@ export class AssetMediaService extends BaseService {
}
async downloadOriginal(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AssetDownload, ids: [id] });
const asset = await this.findOrFail(id);
return new ImmichFileResponse({
path: asset.originalPath,
contentType: mimeTypes.lookup(asset.originalPath),
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
});
}
@ -207,7 +207,7 @@ export class AssetMediaService extends BaseService {
id: string,
dto: AssetMediaOptionsDto,
): Promise<ImmichFileResponse | AssetMediaRedirectResponse> {
await this.requireAccess({ auth, permission: Permission.ASSET_VIEW, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AssetView, ids: [id] });
const asset = await this.findOrFail(id);
const size = dto.size ?? AssetMediaSize.THUMBNAIL;
@ -240,16 +240,16 @@ export class AssetMediaService extends BaseService {
fileName,
path: filepath,
contentType: mimeTypes.lookup(filepath),
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
});
}
async playbackVideo(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
await this.requireAccess({ auth, permission: Permission.ASSET_VIEW, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AssetView, ids: [id] });
const asset = await this.findOrFail(id);
if (asset.type !== AssetType.VIDEO) {
if (asset.type !== AssetType.Video) {
throw new BadRequestException('Asset is not a video');
}
@ -258,7 +258,7 @@ export class AssetMediaService extends BaseService {
return new ImmichFileResponse({
path: filepath,
contentType: mimeTypes.lookup(filepath),
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
cacheControl: CacheControl.PrivateWithCache,
});
}
@ -312,7 +312,7 @@ export class AssetMediaService extends BaseService {
): Promise<AssetMediaResponseDto> {
// clean up files
await this.jobRepository.queue({
name: JobName.DELETE_FILES,
name: JobName.DeleteFiles,
data: { files: [file.originalPath, sidecarFile?.originalPath] },
});
@ -365,7 +365,7 @@ export class AssetMediaService extends BaseService {
await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt));
await this.assetRepository.upsertExif({ assetId, fileSizeInByte: file.size });
await this.jobRepository.queue({
name: JobName.METADATA_EXTRACTION,
name: JobName.MetadataExtraction,
data: { id: assetId, source: 'upload' },
});
}
@ -394,7 +394,7 @@ export class AssetMediaService extends BaseService {
const { size } = await this.storageRepository.stat(created.originalPath);
await this.assetRepository.upsertExif({ assetId: created.id, fileSizeInByte: size });
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: created.id, source: 'copy' } });
await this.jobRepository.queue({ name: JobName.MetadataExtraction, data: { id: created.id, source: 'copy' } });
return created;
}
@ -416,7 +416,7 @@ export class AssetMediaService extends BaseService {
type: mimeTypes.assetType(file.originalPath),
isFavorite: dto.isFavorite,
duration: dto.duration || null,
visibility: dto.visibility ?? AssetVisibility.TIMELINE,
visibility: dto.visibility ?? AssetVisibility.Timeline,
livePhotoVideoId: dto.livePhotoVideoId,
originalFileName: dto.filename || file.originalName,
sidecarPath: sidecarFile?.originalPath,
@ -427,7 +427,7 @@ export class AssetMediaService extends BaseService {
}
await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt));
await this.assetRepository.upsertExif({ assetId: asset.id, fileSizeInByte: file.size });
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id, source: 'upload' } });
await this.jobRepository.queue({ name: JobName.MetadataExtraction, data: { id: asset.id, source: 'upload' } });
return asset;
}

View File

@ -13,10 +13,10 @@ import { factory } from 'test/small.factory';
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
const stats: AssetStats = {
[AssetType.IMAGE]: 10,
[AssetType.VIDEO]: 23,
[AssetType.AUDIO]: 0,
[AssetType.OTHER]: 0,
[AssetType.Image]: 10,
[AssetType.Video]: 23,
[AssetType.Audio]: 0,
[AssetType.Other]: 0,
};
const statResponse: AssetStatsResponseDto = {
@ -46,21 +46,21 @@ describe(AssetService.name, () => {
describe('getStatistics', () => {
it('should get the statistics for a user, excluding archived assets', async () => {
mocks.asset.getStatistics.mockResolvedValue(stats);
await expect(sut.getStatistics(authStub.admin, { visibility: AssetVisibility.TIMELINE })).resolves.toEqual(
await expect(sut.getStatistics(authStub.admin, { visibility: AssetVisibility.Timeline })).resolves.toEqual(
statResponse,
);
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {
visibility: AssetVisibility.TIMELINE,
visibility: AssetVisibility.Timeline,
});
});
it('should get the statistics for a user for archived assets', async () => {
mocks.asset.getStatistics.mockResolvedValue(stats);
await expect(sut.getStatistics(authStub.admin, { visibility: AssetVisibility.ARCHIVE })).resolves.toEqual(
await expect(sut.getStatistics(authStub.admin, { visibility: AssetVisibility.Archive })).resolves.toEqual(
statResponse,
);
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {
visibility: AssetVisibility.ARCHIVE,
visibility: AssetVisibility.Archive,
});
});
@ -202,7 +202,7 @@ describe(AssetService.name, () => {
describe('update', () => {
it('should require asset write access for the id', async () => {
await expect(
sut.update(authStub.admin, 'asset-1', { visibility: AssetVisibility.TIMELINE }),
sut.update(authStub.admin, 'asset-1', { visibility: AssetVisibility.Timeline }),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.asset.update).not.toHaveBeenCalled();
@ -253,7 +253,7 @@ describe(AssetService.name, () => {
});
expect(mocks.asset.update).not.toHaveBeenCalledWith({
id: assetStub.livePhotoMotionAsset.id,
visibility: AssetVisibility.TIMELINE,
visibility: AssetVisibility.Timeline,
});
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
assetId: assetStub.livePhotoMotionAsset.id,
@ -277,7 +277,7 @@ describe(AssetService.name, () => {
});
expect(mocks.asset.update).not.toHaveBeenCalledWith({
id: assetStub.livePhotoMotionAsset.id,
visibility: AssetVisibility.TIMELINE,
visibility: AssetVisibility.Timeline,
});
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
assetId: assetStub.livePhotoMotionAsset.id,
@ -301,7 +301,7 @@ describe(AssetService.name, () => {
});
expect(mocks.asset.update).not.toHaveBeenCalledWith({
id: assetStub.livePhotoMotionAsset.id,
visibility: AssetVisibility.TIMELINE,
visibility: AssetVisibility.Timeline,
});
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
assetId: assetStub.livePhotoMotionAsset.id,
@ -314,7 +314,7 @@ describe(AssetService.name, () => {
mocks.asset.getById.mockResolvedValueOnce({
...assetStub.livePhotoMotionAsset,
ownerId: authStub.admin.user.id,
visibility: AssetVisibility.TIMELINE,
visibility: AssetVisibility.Timeline,
});
mocks.asset.getById.mockResolvedValueOnce(assetStub.image);
mocks.asset.update.mockResolvedValue(assetStub.image);
@ -325,7 +325,7 @@ describe(AssetService.name, () => {
expect(mocks.asset.update).toHaveBeenCalledWith({
id: assetStub.livePhotoMotionAsset.id,
visibility: AssetVisibility.HIDDEN,
visibility: AssetVisibility.Hidden,
});
expect(mocks.event.emit).toHaveBeenCalledWith('AssetHide', {
assetId: assetStub.livePhotoMotionAsset.id,
@ -392,10 +392,10 @@ describe(AssetService.name, () => {
it('should update all assets', async () => {
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], visibility: AssetVisibility.ARCHIVE });
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], visibility: AssetVisibility.Archive });
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], {
visibility: AssetVisibility.ARCHIVE,
visibility: AssetVisibility.Archive,
});
});
@ -428,7 +428,7 @@ describe(AssetService.name, () => {
expect(mocks.asset.updateAll).toHaveBeenCalled();
expect(mocks.asset.updateAllExif).toHaveBeenCalledWith(['asset-1'], { latitude: 0, longitude: 0 });
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{ name: JobName.SIDECAR_WRITE, data: { id: 'asset-1', latitude: 0, longitude: 0 } },
{ name: JobName.SidecarWrite, data: { id: 'asset-1', latitude: 0, longitude: 0 } },
]);
});
@ -451,7 +451,7 @@ describe(AssetService.name, () => {
longitude: 50,
});
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{ name: JobName.SIDECAR_WRITE, data: { id: 'asset-1', dateTimeOriginal, latitude: 30, longitude: 50 } },
{ name: JobName.SidecarWrite, data: { id: 'asset-1', dateTimeOriginal, latitude: 30, longitude: 50 } },
]);
});
@ -497,7 +497,7 @@ describe(AssetService.name, () => {
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset1', 'asset2'], {
deletedAt: expect.any(Date),
status: AssetStatus.TRASHED,
status: AssetStatus.Trashed,
});
expect(mocks.job.queue.mock.calls).toEqual([]);
});
@ -518,11 +518,11 @@ describe(AssetService.name, () => {
mocks.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset]));
mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: false } });
await expect(sut.handleAssetDeletionCheck()).resolves.toBe(JobStatus.SUCCESS);
await expect(sut.handleAssetDeletionCheck()).resolves.toBe(JobStatus.Success);
expect(mocks.assetJob.streamForDeletedJob).toHaveBeenCalledWith(new Date());
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{ name: JobName.ASSET_DELETION, data: { id: asset.id, deleteOnDisk: true } },
{ name: JobName.AssetDeletion, data: { id: asset.id, deleteOnDisk: true } },
]);
});
@ -532,11 +532,11 @@ describe(AssetService.name, () => {
mocks.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset]));
mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: true, days: 7 } });
await expect(sut.handleAssetDeletionCheck()).resolves.toBe(JobStatus.SUCCESS);
await expect(sut.handleAssetDeletionCheck()).resolves.toBe(JobStatus.Success);
expect(mocks.assetJob.streamForDeletedJob).toHaveBeenCalledWith(DateTime.now().minus({ days: 7 }).toJSDate());
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{ name: JobName.ASSET_DELETION, data: { id: asset.id, deleteOnDisk: true } },
{ name: JobName.AssetDeletion, data: { id: asset.id, deleteOnDisk: true } },
]);
});
});
@ -552,7 +552,7 @@ describe(AssetService.name, () => {
expect(mocks.job.queue.mock.calls).toEqual([
[
{
name: JobName.DELETE_FILES,
name: JobName.DeleteFiles,
data: {
files: [
'/uploads/user-id/webp/path.ext',
@ -606,7 +606,7 @@ describe(AssetService.name, () => {
expect(mocks.job.queue.mock.calls).toEqual([
[
{
name: JobName.ASSET_DELETION,
name: JobName.AssetDeletion,
data: {
id: assetStub.livePhotoMotionAsset.id,
deleteOnDisk: true,
@ -615,7 +615,7 @@ describe(AssetService.name, () => {
],
[
{
name: JobName.DELETE_FILES,
name: JobName.DeleteFiles,
data: {
files: [
'/uploads/user-id/webp/path.ext',
@ -643,7 +643,7 @@ describe(AssetService.name, () => {
expect(mocks.job.queue.mock.calls).toEqual([
[
{
name: JobName.DELETE_FILES,
name: JobName.DeleteFiles,
data: {
files: [
'/uploads/user-id/webp/path.ext',
@ -668,7 +668,7 @@ describe(AssetService.name, () => {
it('should fail if asset could not be found', async () => {
mocks.assetJob.getForAssetDeletion.mockResolvedValue(void 0);
await expect(sut.handleAssetDeletion({ id: assetStub.image.id, deleteOnDisk: true })).resolves.toBe(
JobStatus.FAILED,
JobStatus.Failed,
);
});
});
@ -679,7 +679,7 @@ describe(AssetService.name, () => {
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_FACES });
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.FACE_DETECTION, data: { id: 'asset-1' } }]);
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.FaceDetection, data: { id: 'asset-1' } }]);
});
it('should run the refresh metadata job', async () => {
@ -687,7 +687,7 @@ describe(AssetService.name, () => {
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA });
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } }]);
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.MetadataExtraction, data: { id: 'asset-1' } }]);
});
it('should run the refresh thumbnails job', async () => {
@ -695,7 +695,7 @@ describe(AssetService.name, () => {
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL });
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1' } }]);
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.GenerateThumbnails, data: { id: 'asset-1' } }]);
});
it('should run the transcode video', async () => {
@ -703,7 +703,7 @@ describe(AssetService.name, () => {
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO });
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } }]);
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.VideoConversation, data: { id: 'asset-1' } }]);
});
});

View File

@ -23,7 +23,7 @@ import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUn
@Injectable()
export class AssetService extends BaseService {
async getStatistics(auth: AuthDto, dto: AssetStatsDto) {
if (dto.visibility === AssetVisibility.LOCKED) {
if (dto.visibility === AssetVisibility.Locked) {
requireElevatedPermission(auth);
}
@ -46,7 +46,7 @@ export class AssetService extends BaseService {
}
async get(auth: AuthDto, id: string): Promise<AssetResponseDto | SanitizedAssetResponseDto> {
await this.requireAccess({ auth, permission: Permission.ASSET_READ, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] });
const asset = await this.assetRepository.getById(id, {
exifInfo: true,
@ -78,7 +78,7 @@ export class AssetService extends BaseService {
}
async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids: [id] });
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
const { description, dateTimeOriginal, latitude, longitude, rating, ...rest } = dto;
const repos = { asset: this.assetRepository, event: this.eventRepository };
@ -114,7 +114,7 @@ export class AssetService extends BaseService {
async updateAll(auth: AuthDto, dto: AssetBulkUpdateDto): Promise<void> {
const { ids, description, dateTimeOriginal, latitude, longitude, ...options } = dto;
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids });
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids });
if (
description !== undefined ||
@ -125,7 +125,7 @@ export class AssetService extends BaseService {
await this.assetRepository.updateAllExif(ids, { description, dateTimeOriginal, latitude, longitude });
await this.jobRepository.queueAll(
ids.map((id) => ({
name: JobName.SIDECAR_WRITE,
name: JobName.SidecarWrite,
data: { id, description, dateTimeOriginal, latitude, longitude },
})),
);
@ -139,13 +139,13 @@ export class AssetService extends BaseService {
) {
await this.assetRepository.updateAll(ids, options);
if (options.visibility === AssetVisibility.LOCKED) {
if (options.visibility === AssetVisibility.Locked) {
await this.albumRepository.removeAssetsFromAll(ids);
}
}
}
@OnJob({ name: JobName.ASSET_DELETION_CHECK, queue: QueueName.BACKGROUND_TASK })
@OnJob({ name: JobName.AssetDeletionCheck, queue: QueueName.BackgroundTask })
async handleAssetDeletionCheck(): Promise<JobStatus> {
const config = await this.getConfig({ withCache: false });
const trashedDays = config.trash.enabled ? config.trash.days : 0;
@ -158,7 +158,7 @@ export class AssetService extends BaseService {
if (chunk.length > 0) {
await this.jobRepository.queueAll(
chunk.map(({ id, isOffline }) => ({
name: JobName.ASSET_DELETION,
name: JobName.AssetDeletion,
data: { id, deleteOnDisk: !isOffline },
})),
);
@ -176,17 +176,17 @@ export class AssetService extends BaseService {
await queueChunk();
return JobStatus.SUCCESS;
return JobStatus.Success;
}
@OnJob({ name: JobName.ASSET_DELETION, queue: QueueName.BACKGROUND_TASK })
async handleAssetDeletion(job: JobOf<JobName.ASSET_DELETION>): Promise<JobStatus> {
@OnJob({ name: JobName.AssetDeletion, queue: QueueName.BackgroundTask })
async handleAssetDeletion(job: JobOf<JobName.AssetDeletion>): Promise<JobStatus> {
const { id, deleteOnDisk } = job;
const asset = await this.assetJobRepository.getForAssetDeletion(id);
if (!asset) {
return JobStatus.FAILED;
return JobStatus.Failed;
}
// Replace the parent of the stack children with a new asset
@ -215,7 +215,7 @@ export class AssetService extends BaseService {
const count = await this.assetRepository.getLivePhotoCount(asset.livePhotoVideoId);
if (count === 0) {
await this.jobRepository.queue({
name: JobName.ASSET_DELETION,
name: JobName.AssetDeletion,
data: { id: asset.livePhotoVideoId, deleteOnDisk },
});
}
@ -228,18 +228,18 @@ export class AssetService extends BaseService {
files.push(asset.sidecarPath, asset.originalPath);
}
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files } });
await this.jobRepository.queue({ name: JobName.DeleteFiles, data: { files } });
return JobStatus.SUCCESS;
return JobStatus.Success;
}
async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise<void> {
const { ids, force } = dto;
await this.requireAccess({ auth, permission: Permission.ASSET_DELETE, ids });
await this.requireAccess({ auth, permission: Permission.AssetDelete, ids });
await this.assetRepository.updateAll(ids, {
deletedAt: new Date(),
status: force ? AssetStatus.DELETED : AssetStatus.TRASHED,
status: force ? AssetStatus.Deleted : AssetStatus.Trashed,
});
await this.eventRepository.emit(force ? 'AssetDeleteAll' : 'AssetTrashAll', {
assetIds: ids,
@ -248,29 +248,29 @@ export class AssetService extends BaseService {
}
async run(auth: AuthDto, dto: AssetJobsDto) {
await this.requireAccess({ auth, permission: Permission.ASSET_UPDATE, ids: dto.assetIds });
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.assetIds });
const jobs: JobItem[] = [];
for (const id of dto.assetIds) {
switch (dto.name) {
case AssetJobName.REFRESH_FACES: {
jobs.push({ name: JobName.FACE_DETECTION, data: { id } });
jobs.push({ name: JobName.FaceDetection, data: { id } });
break;
}
case AssetJobName.REFRESH_METADATA: {
jobs.push({ name: JobName.METADATA_EXTRACTION, data: { id } });
jobs.push({ name: JobName.MetadataExtraction, data: { id } });
break;
}
case AssetJobName.REGENERATE_THUMBNAIL: {
jobs.push({ name: JobName.GENERATE_THUMBNAILS, data: { id } });
jobs.push({ name: JobName.GenerateThumbnails, data: { id } });
break;
}
case AssetJobName.TRANSCODE_VIDEO: {
jobs.push({ name: JobName.VIDEO_CONVERSION, data: { id } });
jobs.push({ name: JobName.VideoConversation, data: { id } });
break;
}
}
@ -292,7 +292,7 @@ export class AssetService extends BaseService {
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude, rating }, _.isUndefined);
if (Object.keys(writes).length > 0) {
await this.assetRepository.upsertExif({ assetId: id, ...writes });
await this.jobRepository.queue({ name: JobName.SIDECAR_WRITE, data: { id, ...writes } });
await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id, ...writes } });
}
}
}

View File

@ -18,7 +18,7 @@ describe(AuditService.name, () => {
it('should delete old audit entries', async () => {
mocks.audit.removeBefore.mockResolvedValue();
await expect(sut.handleCleanup()).resolves.toBe(JobStatus.SUCCESS);
await expect(sut.handleCleanup()).resolves.toBe(JobStatus.Success);
expect(mocks.audit.removeBefore).toHaveBeenCalledWith(expect.any(Date));
});

View File

@ -7,9 +7,9 @@ import { BaseService } from 'src/services/base.service';
@Injectable()
export class AuditService extends BaseService {
@OnJob({ name: JobName.CLEAN_OLD_AUDIT_LOGS, queue: QueueName.BACKGROUND_TASK })
@OnJob({ name: JobName.CleanOldAuditLogs, queue: QueueName.BackgroundTask })
async handleCleanup(): Promise<JobStatus> {
await this.auditRepository.removeBefore(DateTime.now().minus(AUDIT_LOG_MAX_DURATION).toJSDate());
return JobStatus.SUCCESS;
return JobStatus.Success;
}
}

View File

@ -154,7 +154,7 @@ describe(AuthService.name, () => {
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled);
await expect(sut.logout(auth, AuthType.OAUTH)).resolves.toEqual({
await expect(sut.logout(auth, AuthType.OAuth)).resolves.toEqual({
successful: true,
redirectUri: 'http://end-session-endpoint',
});
@ -163,7 +163,7 @@ describe(AuthService.name, () => {
it('should return the default redirect', async () => {
const auth = factory.auth();
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
await expect(sut.logout(auth, AuthType.Password)).resolves.toEqual({
successful: true,
redirectUri: '/auth/login?autoLaunch=0',
});
@ -173,7 +173,7 @@ describe(AuthService.name, () => {
const auth = { user: { id: '123' }, session: { id: 'token123' } } as AuthDto;
mocks.session.delete.mockResolvedValue();
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
await expect(sut.logout(auth, AuthType.Password)).resolves.toEqual({
successful: true,
redirectUri: '/auth/login?autoLaunch=0',
});
@ -185,7 +185,7 @@ describe(AuthService.name, () => {
it('should return the default redirect if auth type is OAUTH but oauth is not enabled', async () => {
const auth = { user: { id: '123' } } as AuthDto;
await expect(sut.logout(auth, AuthType.OAUTH)).resolves.toEqual({
await expect(sut.logout(auth, AuthType.OAuth)).resolves.toEqual({
successful: true,
redirectUri: '/auth/login?autoLaunch=0',
});
@ -463,7 +463,7 @@ describe(AuthService.name, () => {
sut.authenticate({
headers: { 'x-api-key': 'auth_token' },
queryParams: {},
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test', permission: Permission.ASSET_READ },
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test', permission: Permission.AssetRead },
}),
).rejects.toBeInstanceOf(ForbiddenException);
});

View File

@ -194,13 +194,13 @@ export class AuthService extends BaseService {
}
private async validate({ headers, queryParams }: Omit<ValidateRequest, 'metadata'>): Promise<AuthDto> {
const shareKey = (headers[ImmichHeader.SHARED_LINK_KEY] || queryParams[ImmichQuery.SHARED_LINK_KEY]) as string;
const session = (headers[ImmichHeader.USER_TOKEN] ||
headers[ImmichHeader.SESSION_TOKEN] ||
queryParams[ImmichQuery.SESSION_KEY] ||
const shareKey = (headers[ImmichHeader.SharedLinkKey] || queryParams[ImmichQuery.SharedLinkKey]) as string;
const session = (headers[ImmichHeader.UserToken] ||
headers[ImmichHeader.SessionToken] ||
queryParams[ImmichQuery.SessionKey] ||
this.getBearerToken(headers) ||
this.getCookieToken(headers)) as string;
const apiKey = (headers[ImmichHeader.API_KEY] || queryParams[ImmichQuery.API_KEY]) as string;
const apiKey = (headers[ImmichHeader.ApiKey] || queryParams[ImmichQuery.ApiKey]) as string;
if (shareKey) {
return this.validateSharedLink(shareKey);
@ -321,7 +321,7 @@ export class AuthService extends BaseService {
const { contentType, data } = await this.oauthRepository.getProfilePicture(url);
const extensionWithDot = mimeTypes.toExtension(contentType || 'image/jpeg') ?? 'jpg';
const profileImagePath = join(
StorageCore.getFolderLocation(StorageFolder.PROFILE, user.id),
StorageCore.getFolderLocation(StorageFolder.Profile, user.id),
`${this.cryptoRepository.randomUUID()}${extensionWithDot}`,
);
@ -330,7 +330,7 @@ export class AuthService extends BaseService {
await this.userRepository.update(user.id, { profileImagePath, profileChangedAt: new Date() });
if (oldPath) {
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [oldPath] } });
await this.jobRepository.queue({ name: JobName.DeleteFiles, data: { files: [oldPath] } });
}
} catch (error: Error | any) {
this.logger.warn(`Unable to sync oauth profile picture: ${error}`, error?.stack);
@ -366,7 +366,7 @@ export class AuthService extends BaseService {
}
private async getLogoutEndpoint(authType: AuthType): Promise<string> {
if (authType !== AuthType.OAUTH) {
if (authType !== AuthType.OAuth) {
return LOGIN_URL;
}
@ -389,17 +389,17 @@ export class AuthService extends BaseService {
private getCookieToken(headers: IncomingHttpHeaders): string | null {
const cookies = parse(headers.cookie || '');
return cookies[ImmichCookie.ACCESS_TOKEN] || null;
return cookies[ImmichCookie.AccessToken] || null;
}
private getCookieOauthState(headers: IncomingHttpHeaders): string | null {
const cookies = parse(headers.cookie || '');
return cookies[ImmichCookie.OAUTH_STATE] || null;
return cookies[ImmichCookie.OAuthState] || null;
}
private getCookieCodeVerifier(headers: IncomingHttpHeaders): string | null {
const cookies = parse(headers.cookie || '');
return cookies[ImmichCookie.OAUTH_CODE_VERIFIER] || null;
return cookies[ImmichCookie.OAuthCodeVerifier] || null;
}
async validateSharedLink(key: string | string[]): Promise<AuthDto> {

View File

@ -38,7 +38,7 @@ describe(BackupService.name, () => {
});
it('should not initialise backup database job when running on microservices', async () => {
mocks.config.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES);
mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices);
await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig });
expect(mocks.cron.create).not.toHaveBeenCalled();
@ -98,10 +98,10 @@ describe(BackupService.name, () => {
await sut.cleanupDatabaseBackups();
expect(mocks.storage.unlink).toHaveBeenCalledTimes(2);
expect(mocks.storage.unlink).toHaveBeenCalledWith(
`${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-123.sql.gz.tmp`,
`${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-123.sql.gz.tmp`,
);
expect(mocks.storage.unlink).toHaveBeenCalledWith(
`${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-345.sql.gz.tmp`,
`${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-345.sql.gz.tmp`,
);
});
@ -111,7 +111,7 @@ describe(BackupService.name, () => {
await sut.cleanupDatabaseBackups();
expect(mocks.storage.unlink).toHaveBeenCalledTimes(1);
expect(mocks.storage.unlink).toHaveBeenCalledWith(
`${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-1.sql.gz`,
`${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-1.sql.gz`,
);
});
@ -125,10 +125,10 @@ describe(BackupService.name, () => {
await sut.cleanupDatabaseBackups();
expect(mocks.storage.unlink).toHaveBeenCalledTimes(2);
expect(mocks.storage.unlink).toHaveBeenCalledWith(
`${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-1.sql.gz.tmp`,
`${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-1.sql.gz.tmp`,
);
expect(mocks.storage.unlink).toHaveBeenCalledWith(
`${StorageCore.getBaseFolder(StorageFolder.BACKUPS)}/immich-db-backup-2.sql.gz`,
`${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-2.sql.gz`,
);
});
});
@ -145,13 +145,13 @@ describe(BackupService.name, () => {
it('should run a database backup successfully', async () => {
const result = await sut.handleBackupDatabase();
expect(result).toBe(JobStatus.SUCCESS);
expect(result).toBe(JobStatus.Success);
expect(mocks.storage.createWriteStream).toHaveBeenCalled();
});
it('should rename file on success', async () => {
const result = await sut.handleBackupDatabase();
expect(result).toBe(JobStatus.SUCCESS);
expect(result).toBe(JobStatus.Success);
expect(mocks.storage.rename).toHaveBeenCalled();
});
@ -219,7 +219,7 @@ describe(BackupService.name, () => {
mocks.database.getPostgresVersion.mockResolvedValue(postgresVersion);
const result = await sut.handleBackupDatabase();
expect(mocks.process.spawn).not.toHaveBeenCalled();
expect(result).toBe(JobStatus.FAILED);
expect(result).toBe(JobStatus.Failed);
});
});
});

View File

@ -14,7 +14,7 @@ import { handlePromiseError } from 'src/utils/misc';
export class BackupService extends BaseService {
private backupLock = false;
@OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.MICROSERVICES] })
@OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] })
async onConfigInit({
newConfig: {
backup: { database },
@ -26,7 +26,7 @@ export class BackupService extends BaseService {
this.cronRepository.create({
name: 'backupDatabase',
expression: database.cronExpression,
onTick: () => handlePromiseError(this.jobRepository.queue({ name: JobName.BACKUP_DATABASE }), this.logger),
onTick: () => handlePromiseError(this.jobRepository.queue({ name: JobName.BackupDatabase }), this.logger),
start: database.enabled,
});
}
@ -51,7 +51,7 @@ export class BackupService extends BaseService {
backup: { database: config },
} = await this.getConfig({ withCache: false });
const backupsFolder = StorageCore.getBaseFolder(StorageFolder.BACKUPS);
const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups);
const files = await this.storageRepository.readdir(backupsFolder);
const failedBackups = files.filter((file) => file.match(/immich-db-backup-\d+\.sql\.gz\.tmp$/));
const backups = files
@ -68,7 +68,7 @@ export class BackupService extends BaseService {
this.logger.debug(`Database Backup Cleanup Finished, deleted ${toDelete.length} backups`);
}
@OnJob({ name: JobName.BACKUP_DATABASE, queue: QueueName.BACKUP_DATABASE })
@OnJob({ name: JobName.BackupDatabase, queue: QueueName.BackupDatabase })
async handleBackupDatabase(): Promise<JobStatus> {
this.logger.debug(`Database Backup Started`);
const { database } = this.configRepository.getEnv();
@ -92,7 +92,7 @@ export class BackupService extends BaseService {
databaseParams.push('--clean', '--if-exists');
const databaseVersion = await this.databaseRepository.getPostgresVersion();
const backupFilePath = path.join(
StorageCore.getBaseFolder(StorageFolder.BACKUPS),
StorageCore.getBaseFolder(StorageFolder.Backups),
`immich-db-backup-${DateTime.now().toFormat("yyyyLLdd'T'HHmmss")}-v${serverVersion.toString()}-pg${databaseVersion.split(' ')[0]}.sql.gz.tmp`,
);
const databaseSemver = semver.coerce(databaseVersion);
@ -100,7 +100,7 @@ export class BackupService extends BaseService {
if (!databaseMajorVersion || !databaseSemver || !semver.satisfies(databaseSemver, '>=14.0.0 <18.0.0')) {
this.logger.error(`Database Backup Failure: Unsupported PostgreSQL version: ${databaseVersion}`);
return JobStatus.FAILED;
return JobStatus.Failed;
}
this.logger.log(`Database Backup Starting. Database Version: ${databaseMajorVersion}`);
@ -179,6 +179,6 @@ export class BackupService extends BaseService {
this.logger.log(`Database Backup Success`);
await this.cleanupDatabaseBackups();
return JobStatus.SUCCESS;
return JobStatus.Success;
}
}

View File

@ -19,7 +19,7 @@ describe(DatabaseService.name, () => {
({ sut, mocks } = newTestService(DatabaseService));
extensionRange = '0.2.x';
mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.VECTORCHORD);
mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.VectorChord);
mocks.database.getExtensionVersionRange.mockReturnValue(extensionRange);
versionBelowRange = '0.1.0';
@ -28,7 +28,7 @@ describe(DatabaseService.name, () => {
versionAboveRange = '0.3.0';
mocks.database.getExtensionVersions.mockResolvedValue([
{
name: DatabaseExtension.VECTORCHORD,
name: DatabaseExtension.VectorChord,
installedVersion: null,
availableVersion: minVersionInRange,
},
@ -49,9 +49,9 @@ describe(DatabaseService.name, () => {
});
describe.each(<Array<{ extension: VectorExtension; extensionName: string }>>[
{ extension: DatabaseExtension.VECTOR, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTOR] },
{ extension: DatabaseExtension.VECTORS, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTORS] },
{ extension: DatabaseExtension.VECTORCHORD, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTORCHORD] },
{ extension: DatabaseExtension.Vector, extensionName: EXTENSION_NAMES[DatabaseExtension.Vector] },
{ extension: DatabaseExtension.Vectors, extensionName: EXTENSION_NAMES[DatabaseExtension.Vectors] },
{ extension: DatabaseExtension.VectorChord, extensionName: EXTENSION_NAMES[DatabaseExtension.VectorChord] },
])('should work with $extensionName', ({ extension, extensionName }) => {
beforeEach(() => {
mocks.database.getExtensionVersions.mockResolvedValue([
@ -292,8 +292,8 @@ describe(DatabaseService.name, () => {
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledExactlyOnceWith([
VectorIndex.CLIP,
VectorIndex.FACE,
VectorIndex.Clip,
VectorIndex.Face,
]);
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledTimes(1);
expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1);
@ -306,8 +306,8 @@ describe(DatabaseService.name, () => {
await expect(sut.onBootstrap()).rejects.toBeDefined();
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledExactlyOnceWith([
VectorIndex.CLIP,
VectorIndex.FACE,
VectorIndex.Clip,
VectorIndex.Face,
]);
expect(mocks.database.runMigrations).not.toHaveBeenCalled();
expect(mocks.logger.fatal).not.toHaveBeenCalled();
@ -330,7 +330,7 @@ describe(DatabaseService.name, () => {
database: 'immich',
},
skipMigrations: true,
vectorExtension: DatabaseExtension.VECTORS,
vectorExtension: DatabaseExtension.Vectors,
},
}),
);
@ -356,12 +356,12 @@ describe(DatabaseService.name, () => {
it(`should drop unused extension`, async () => {
mocks.database.getExtensionVersions.mockResolvedValue([
{
name: DatabaseExtension.VECTORS,
name: DatabaseExtension.Vectors,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},
{
name: DatabaseExtension.VECTORCHORD,
name: DatabaseExtension.VectorChord,
installedVersion: null,
availableVersion: minVersionInRange,
},
@ -369,19 +369,19 @@ describe(DatabaseService.name, () => {
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VECTORCHORD);
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VECTORS);
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord);
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors);
});
it(`should warn if unused extension could not be dropped`, async () => {
mocks.database.getExtensionVersions.mockResolvedValue([
{
name: DatabaseExtension.VECTORS,
name: DatabaseExtension.Vectors,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},
{
name: DatabaseExtension.VECTORCHORD,
name: DatabaseExtension.VectorChord,
installedVersion: null,
availableVersion: minVersionInRange,
},
@ -390,8 +390,8 @@ describe(DatabaseService.name, () => {
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VECTORCHORD);
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VECTORS);
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord);
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors);
expect(mocks.logger.warn).toHaveBeenCalledTimes(1);
expect(mocks.logger.warn.mock.calls[0][0]).toContain('DROP EXTENSION vectors');
});
@ -399,12 +399,12 @@ describe(DatabaseService.name, () => {
it(`should not try to drop pgvector when using vectorchord`, async () => {
mocks.database.getExtensionVersions.mockResolvedValue([
{
name: DatabaseExtension.VECTOR,
name: DatabaseExtension.Vector,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},
{
name: DatabaseExtension.VECTORCHORD,
name: DatabaseExtension.VectorChord,
installedVersion: minVersionInRange,
availableVersion: minVersionInRange,
},

View File

@ -100,7 +100,7 @@ export class DatabaseService extends BaseService {
}
try {
await this.databaseRepository.reindexVectorsIfNeeded([VectorIndex.CLIP, VectorIndex.FACE]);
await this.databaseRepository.reindexVectorsIfNeeded([VectorIndex.Clip, VectorIndex.Face]);
} catch (error) {
this.logger.warn(
'Could not run vector reindexing checks. If the extension was updated, please restart the Postgres instance. If you are upgrading directly from a version below 1.107.2, please upgrade to 1.107.2 first.',
@ -109,7 +109,7 @@ export class DatabaseService extends BaseService {
}
for (const { name: dbName, installedVersion } of extensionVersions) {
const isDepended = dbName === DatabaseExtension.VECTOR && extension === DatabaseExtension.VECTORCHORD;
const isDepended = dbName === DatabaseExtension.Vector && extension === DatabaseExtension.VectorChord;
if (dbName !== extension && installedVersion && !isDepended) {
await this.dropExtension(dbName);
}
@ -120,8 +120,8 @@ export class DatabaseService extends BaseService {
await this.databaseRepository.runMigrations();
}
await Promise.all([
this.databaseRepository.prewarm(VectorIndex.CLIP),
this.databaseRepository.prewarm(VectorIndex.FACE),
this.databaseRepository.prewarm(VectorIndex.Clip),
this.databaseRepository.prewarm(VectorIndex.Face),
]);
});
}

View File

@ -17,15 +17,15 @@ export class DownloadService extends BaseService {
if (dto.assetIds) {
const assetIds = dto.assetIds;
await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: assetIds });
await this.requireAccess({ auth, permission: Permission.AssetDownload, ids: assetIds });
assets = this.downloadRepository.downloadAssetIds(assetIds);
} else if (dto.albumId) {
const albumId = dto.albumId;
await this.requireAccess({ auth, permission: Permission.ALBUM_DOWNLOAD, ids: [albumId] });
await this.requireAccess({ auth, permission: Permission.AlbumDownload, ids: [albumId] });
assets = this.downloadRepository.downloadAlbumId(albumId);
} else if (dto.userId) {
const userId = dto.userId;
await this.requireAccess({ auth, permission: Permission.TIMELINE_DOWNLOAD, ids: [userId] });
await this.requireAccess({ auth, permission: Permission.TimelineDownload, ids: [userId] });
assets = this.downloadRepository.downloadUserId(userId);
} else {
throw new BadRequestException('assetIds, albumId, or userId is required');
@ -81,7 +81,7 @@ export class DownloadService extends BaseService {
}
async downloadArchive(auth: AuthDto, dto: AssetIdsDto): Promise<ImmichReadStream> {
await this.requireAccess({ auth, permission: Permission.ASSET_DOWNLOAD, ids: dto.assetIds });
await this.requireAccess({ auth, permission: Permission.AssetDownload, ids: dto.assetIds });
const zip = this.storageRepository.createZipStream();
const assets = await this.assetRepository.getByIds(dto.assetIds);

View File

@ -12,10 +12,10 @@ const hasEmbedding = {
id: 'asset-1',
ownerId: 'user-id',
stackId: null,
type: AssetType.IMAGE,
type: AssetType.Image,
duplicateId: null,
embedding: '[1, 2, 3, 4]',
visibility: AssetVisibility.TIMELINE,
visibility: AssetVisibility.Timeline,
};
const hasDupe = {
@ -78,7 +78,7 @@ describe(SearchService.name, () => {
},
});
await expect(sut.handleQueueSearchDuplicates({})).resolves.toBe(JobStatus.SKIPPED);
await expect(sut.handleQueueSearchDuplicates({})).resolves.toBe(JobStatus.Skipped);
expect(mocks.job.queue).not.toHaveBeenCalled();
expect(mocks.job.queueAll).not.toHaveBeenCalled();
expect(mocks.systemMetadata.get).toHaveBeenCalled();
@ -94,7 +94,7 @@ describe(SearchService.name, () => {
},
});
await expect(sut.handleQueueSearchDuplicates({})).resolves.toBe(JobStatus.SKIPPED);
await expect(sut.handleQueueSearchDuplicates({})).resolves.toBe(JobStatus.Skipped);
expect(mocks.job.queue).not.toHaveBeenCalled();
expect(mocks.job.queueAll).not.toHaveBeenCalled();
expect(mocks.systemMetadata.get).toHaveBeenCalled();
@ -108,7 +108,7 @@ describe(SearchService.name, () => {
expect(mocks.assetJob.streamForSearchDuplicates).toHaveBeenCalledWith(undefined);
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{
name: JobName.DUPLICATE_DETECTION,
name: JobName.DuplicateDetection,
data: { id: assetStub.image.id },
},
]);
@ -122,7 +122,7 @@ describe(SearchService.name, () => {
expect(mocks.assetJob.streamForSearchDuplicates).toHaveBeenCalledWith(true);
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{
name: JobName.DUPLICATE_DETECTION,
name: JobName.DuplicateDetection,
data: { id: assetStub.image.id },
},
]);
@ -154,7 +154,7 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id });
expect(result).toBe(JobStatus.SKIPPED);
expect(result).toBe(JobStatus.Skipped);
expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled();
});
@ -171,7 +171,7 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id });
expect(result).toBe(JobStatus.SKIPPED);
expect(result).toBe(JobStatus.Skipped);
expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled();
});
@ -180,7 +180,7 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id: assetStub.image.id });
expect(result).toBe(JobStatus.FAILED);
expect(result).toBe(JobStatus.Failed);
expect(mocks.logger.error).toHaveBeenCalledWith(`Asset ${assetStub.image.id} not found`);
});
@ -190,7 +190,7 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id });
expect(result).toBe(JobStatus.SKIPPED);
expect(result).toBe(JobStatus.Skipped);
expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${id} is part of a stack, skipping`);
});
@ -198,12 +198,12 @@ describe(SearchService.name, () => {
const id = assetStub.livePhotoMotionAsset.id;
mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue({
...hasEmbedding,
visibility: AssetVisibility.HIDDEN,
visibility: AssetVisibility.Hidden,
});
const result = await sut.handleSearchDuplicates({ id });
expect(result).toBe(JobStatus.SKIPPED);
expect(result).toBe(JobStatus.Skipped);
expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${id} is not visible, skipping`);
});
@ -212,7 +212,7 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id: assetStub.image.id });
expect(result).toBe(JobStatus.FAILED);
expect(result).toBe(JobStatus.Failed);
expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${assetStub.image.id} is missing embedding`);
});
@ -226,7 +226,7 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
expect(result).toBe(JobStatus.SUCCESS);
expect(result).toBe(JobStatus.Success);
expect(mocks.duplicateRepository.search).toHaveBeenCalledWith({
assetId: hasEmbedding.id,
embedding: hasEmbedding.embedding,
@ -253,7 +253,7 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
expect(result).toBe(JobStatus.SUCCESS);
expect(result).toBe(JobStatus.Success);
expect(mocks.duplicateRepository.search).toHaveBeenCalledWith({
assetId: hasEmbedding.id,
embedding: hasEmbedding.embedding,
@ -277,7 +277,7 @@ describe(SearchService.name, () => {
const result = await sut.handleSearchDuplicates({ id: hasDupe.id });
expect(result).toBe(JobStatus.SUCCESS);
expect(result).toBe(JobStatus.Success);
expect(mocks.asset.update).toHaveBeenCalledWith({ id: hasDupe.id, duplicateId: null });
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith({
assetId: hasDupe.id,

View File

@ -29,11 +29,11 @@ export class DuplicateService extends BaseService {
await this.duplicateRepository.deleteAll(auth.user.id, dto.ids);
}
@OnJob({ name: JobName.QUEUE_DUPLICATE_DETECTION, queue: QueueName.DUPLICATE_DETECTION })
async handleQueueSearchDuplicates({ force }: JobOf<JobName.QUEUE_DUPLICATE_DETECTION>): Promise<JobStatus> {
@OnJob({ name: JobName.QueueDuplicateDetection, queue: QueueName.DuplicateDetection })
async handleQueueSearchDuplicates({ force }: JobOf<JobName.QueueDuplicateDetection>): Promise<JobStatus> {
const { machineLearning } = await this.getConfig({ withCache: false });
if (!isDuplicateDetectionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
let jobs: JobItem[] = [];
@ -44,7 +44,7 @@ export class DuplicateService extends BaseService {
const assets = this.assetJobRepository.streamForSearchDuplicates(force);
for await (const asset of assets) {
jobs.push({ name: JobName.DUPLICATE_DETECTION, data: { id: asset.id } });
jobs.push({ name: JobName.DuplicateDetection, data: { id: asset.id } });
if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) {
await queueAll();
}
@ -52,40 +52,40 @@ export class DuplicateService extends BaseService {
await queueAll();
return JobStatus.SUCCESS;
return JobStatus.Success;
}
@OnJob({ name: JobName.DUPLICATE_DETECTION, queue: QueueName.DUPLICATE_DETECTION })
async handleSearchDuplicates({ id }: JobOf<JobName.DUPLICATE_DETECTION>): Promise<JobStatus> {
@OnJob({ name: JobName.DuplicateDetection, queue: QueueName.DuplicateDetection })
async handleSearchDuplicates({ id }: JobOf<JobName.DuplicateDetection>): Promise<JobStatus> {
const { machineLearning } = await this.getConfig({ withCache: true });
if (!isDuplicateDetectionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
const asset = await this.assetJobRepository.getForSearchDuplicatesJob(id);
if (!asset) {
this.logger.error(`Asset ${id} not found`);
return JobStatus.FAILED;
return JobStatus.Failed;
}
if (asset.stackId) {
this.logger.debug(`Asset ${id} is part of a stack, skipping`);
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
if (asset.visibility === AssetVisibility.HIDDEN) {
if (asset.visibility === AssetVisibility.Hidden) {
this.logger.debug(`Asset ${id} is not visible, skipping`);
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
if (asset.visibility === AssetVisibility.LOCKED) {
if (asset.visibility === AssetVisibility.Locked) {
this.logger.debug(`Asset ${id} is locked, skipping`);
return JobStatus.SKIPPED;
return JobStatus.Skipped;
}
if (!asset.embedding) {
this.logger.debug(`Asset ${id} is missing embedding`);
return JobStatus.FAILED;
return JobStatus.Failed;
}
const duplicateAssets = await this.duplicateRepository.search({
@ -110,7 +110,7 @@ export class DuplicateService extends BaseService {
const duplicatesDetectedAt = new Date();
await this.assetRepository.upsertJobStatus(...assetIds.map((assetId) => ({ assetId, duplicatesDetectedAt })));
return JobStatus.SUCCESS;
return JobStatus.Success;
}
private async updateDuplicates(

View File

@ -13,7 +13,7 @@ describe(JobService.name, () => {
beforeEach(() => {
({ sut, mocks } = newTestService(JobService, {}));
mocks.config.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES);
mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices);
});
it('should work', () => {
@ -25,10 +25,10 @@ describe(JobService.name, () => {
sut.onConfigUpdate({ newConfig: defaults, oldConfig: {} as SystemConfig });
expect(mocks.job.setConcurrency).toHaveBeenCalledTimes(15);
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(5, QueueName.FACIAL_RECOGNITION, 1);
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(7, QueueName.DUPLICATE_DETECTION, 1);
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(8, QueueName.BACKGROUND_TASK, 5);
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(9, QueueName.STORAGE_TEMPLATE_MIGRATION, 1);
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(5, QueueName.FacialRecognition, 1);
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(7, QueueName.DuplicateDetection, 1);
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(8, QueueName.BackgroundTask, 5);
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(9, QueueName.StorageTemplateMigration, 1);
});
});
@ -37,16 +37,16 @@ describe(JobService.name, () => {
await sut.handleNightlyJobs();
expect(mocks.job.queueAll).toHaveBeenCalledWith([
{ name: JobName.ASSET_DELETION_CHECK },
{ name: JobName.USER_DELETE_CHECK },
{ name: JobName.PERSON_CLEANUP },
{ name: JobName.MEMORIES_CLEANUP },
{ name: JobName.CLEAN_OLD_SESSION_TOKENS },
{ name: JobName.CLEAN_OLD_AUDIT_LOGS },
{ name: JobName.MEMORIES_CREATE },
{ name: JobName.USER_SYNC_USAGE },
{ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force: false } },
{ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false, nightly: true } },
{ name: JobName.AssetDeletionCheck },
{ name: JobName.UserDeleteCheck },
{ name: JobName.PersonCleanup },
{ name: JobName.MemoriesCleanup },
{ name: JobName.CleanOldSessionTokens },
{ name: JobName.CleanOldAuditLogs },
{ name: JobName.MemoriesCreate },
{ name: JobName.userSyncUsage },
{ name: JobName.QueueGenerateThumbnails, data: { force: false } },
{ name: JobName.QueueFacialRecognition, data: { force: false, nightly: true } },
]);
});
});
@ -82,49 +82,49 @@ describe(JobService.name, () => {
};
await expect(sut.getAllJobsStatus()).resolves.toEqual({
[QueueName.BACKGROUND_TASK]: expectedJobStatus,
[QueueName.DUPLICATE_DETECTION]: expectedJobStatus,
[QueueName.SMART_SEARCH]: expectedJobStatus,
[QueueName.METADATA_EXTRACTION]: expectedJobStatus,
[QueueName.SEARCH]: expectedJobStatus,
[QueueName.STORAGE_TEMPLATE_MIGRATION]: expectedJobStatus,
[QueueName.MIGRATION]: expectedJobStatus,
[QueueName.THUMBNAIL_GENERATION]: expectedJobStatus,
[QueueName.VIDEO_CONVERSION]: expectedJobStatus,
[QueueName.FACE_DETECTION]: expectedJobStatus,
[QueueName.FACIAL_RECOGNITION]: expectedJobStatus,
[QueueName.SIDECAR]: expectedJobStatus,
[QueueName.LIBRARY]: expectedJobStatus,
[QueueName.NOTIFICATION]: expectedJobStatus,
[QueueName.BACKUP_DATABASE]: expectedJobStatus,
[QueueName.BackgroundTask]: expectedJobStatus,
[QueueName.DuplicateDetection]: expectedJobStatus,
[QueueName.SmartSearch]: expectedJobStatus,
[QueueName.MetadataExtraction]: expectedJobStatus,
[QueueName.Search]: expectedJobStatus,
[QueueName.StorageTemplateMigration]: expectedJobStatus,
[QueueName.Migration]: expectedJobStatus,
[QueueName.ThumbnailGeneration]: expectedJobStatus,
[QueueName.VideoConversion]: expectedJobStatus,
[QueueName.FaceDetection]: expectedJobStatus,
[QueueName.FacialRecognition]: expectedJobStatus,
[QueueName.Sidecar]: expectedJobStatus,
[QueueName.Library]: expectedJobStatus,
[QueueName.Notification]: expectedJobStatus,
[QueueName.BackupDatabase]: expectedJobStatus,
});
});
});
describe('handleCommand', () => {
it('should handle a pause command', async () => {
await sut.handleCommand(QueueName.METADATA_EXTRACTION, { command: JobCommand.PAUSE, force: false });
await sut.handleCommand(QueueName.MetadataExtraction, { command: JobCommand.Pause, force: false });
expect(mocks.job.pause).toHaveBeenCalledWith(QueueName.METADATA_EXTRACTION);
expect(mocks.job.pause).toHaveBeenCalledWith(QueueName.MetadataExtraction);
});
it('should handle a resume command', async () => {
await sut.handleCommand(QueueName.METADATA_EXTRACTION, { command: JobCommand.RESUME, force: false });
await sut.handleCommand(QueueName.MetadataExtraction, { command: JobCommand.Resume, force: false });
expect(mocks.job.resume).toHaveBeenCalledWith(QueueName.METADATA_EXTRACTION);
expect(mocks.job.resume).toHaveBeenCalledWith(QueueName.MetadataExtraction);
});
it('should handle an empty command', async () => {
await sut.handleCommand(QueueName.METADATA_EXTRACTION, { command: JobCommand.EMPTY, force: false });
await sut.handleCommand(QueueName.MetadataExtraction, { command: JobCommand.Empty, force: false });
expect(mocks.job.empty).toHaveBeenCalledWith(QueueName.METADATA_EXTRACTION);
expect(mocks.job.empty).toHaveBeenCalledWith(QueueName.MetadataExtraction);
});
it('should not start a job that is already running', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: true, isPaused: false });
await expect(
sut.handleCommand(QueueName.VIDEO_CONVERSION, { command: JobCommand.START, force: false }),
sut.handleCommand(QueueName.VideoConversion, { command: JobCommand.Start, force: false }),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.job.queue).not.toHaveBeenCalled();
@ -134,80 +134,80 @@ describe(JobService.name, () => {
it('should handle a start video conversion command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.VIDEO_CONVERSION, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.VideoConversion, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_VIDEO_CONVERSION, data: { force: false } });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QueueVideoConversion, data: { force: false } });
});
it('should handle a start storage template migration command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.STORAGE_TEMPLATE_MIGRATION, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.StorageTemplateMigration, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.STORAGE_TEMPLATE_MIGRATION });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.StorageTemplateMigration });
});
it('should handle a start smart search command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.SMART_SEARCH, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.SmartSearch, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_SMART_SEARCH, data: { force: false } });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QueueSmartSearch, data: { force: false } });
});
it('should handle a start metadata extraction command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.METADATA_EXTRACTION, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.MetadataExtraction, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_METADATA_EXTRACTION, data: { force: false } });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QueueMetadataExtraction, data: { force: false } });
});
it('should handle a start sidecar command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.SIDECAR, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.Sidecar, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_SIDECAR, data: { force: false } });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QueueSidecar, data: { force: false } });
});
it('should handle a start thumbnail generation command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.THUMBNAIL_GENERATION, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.ThumbnailGeneration, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force: false } });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QueueGenerateThumbnails, data: { force: false } });
});
it('should handle a start face detection command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.FACE_DETECTION, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.FaceDetection, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_FACE_DETECTION, data: { force: false } });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QueueFaceDetection, data: { force: false } });
});
it('should handle a start facial recognition command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.FACIAL_RECOGNITION, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.FacialRecognition, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false } });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.QueueFacialRecognition, data: { force: false } });
});
it('should handle a start backup database command', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await sut.handleCommand(QueueName.BACKUP_DATABASE, { command: JobCommand.START, force: false });
await sut.handleCommand(QueueName.BackupDatabase, { command: JobCommand.Start, force: false });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.BACKUP_DATABASE, data: { force: false } });
expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.BackupDatabase, data: { force: false } });
});
it('should throw a bad request when an invalid queue is used', async () => {
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
await expect(
sut.handleCommand(QueueName.BACKGROUND_TASK, { command: JobCommand.START, force: false }),
sut.handleCommand(QueueName.BackgroundTask, { command: JobCommand.Start, force: false }),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.job.queue).not.toHaveBeenCalled();
@ -217,10 +217,10 @@ describe(JobService.name, () => {
describe('onJobStart', () => {
it('should process a successful job', async () => {
mocks.job.run.mockResolvedValue(JobStatus.SUCCESS);
mocks.job.run.mockResolvedValue(JobStatus.Success);
await sut.onJobStart(QueueName.BACKGROUND_TASK, {
name: JobName.DELETE_FILES,
await sut.onJobStart(QueueName.BackgroundTask, {
name: JobName.DeleteFiles,
data: { files: ['path/to/file'] },
});
@ -232,55 +232,55 @@ describe(JobService.name, () => {
const tests: Array<{ item: JobItem; jobs: JobName[]; stub?: any }> = [
{
item: { name: JobName.SIDECAR_SYNC, data: { id: 'asset-1' } },
jobs: [JobName.METADATA_EXTRACTION],
item: { name: JobName.SidecarSync, data: { id: 'asset-1' } },
jobs: [JobName.MetadataExtraction],
},
{
item: { name: JobName.SIDECAR_DISCOVERY, data: { id: 'asset-1' } },
jobs: [JobName.METADATA_EXTRACTION],
item: { name: JobName.SidecarDiscovery, data: { id: 'asset-1' } },
jobs: [JobName.MetadataExtraction],
},
{
item: { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, data: { id: 'asset-1', source: 'upload' } },
jobs: [JobName.GENERATE_THUMBNAILS],
item: { name: JobName.StorageTemplateMigrationSingle, data: { id: 'asset-1', source: 'upload' } },
jobs: [JobName.GenerateThumbnails],
},
{
item: { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, data: { id: 'asset-1' } },
item: { name: JobName.StorageTemplateMigrationSingle, data: { id: 'asset-1' } },
jobs: [],
},
{
item: { name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: 'asset-1' } },
item: { name: JobName.GeneratePersonThumbnail, data: { id: 'asset-1' } },
jobs: [],
},
{
item: { name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1' } },
item: { name: JobName.GenerateThumbnails, data: { id: 'asset-1' } },
jobs: [],
stub: [assetStub.image],
},
{
item: { name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1' } },
item: { name: JobName.GenerateThumbnails, data: { id: 'asset-1' } },
jobs: [],
stub: [assetStub.video],
},
{
item: { name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1', source: 'upload' } },
jobs: [JobName.SMART_SEARCH, JobName.FACE_DETECTION],
item: { name: JobName.GenerateThumbnails, data: { id: 'asset-1', source: 'upload' } },
jobs: [JobName.SmartSearch, JobName.FaceDetection],
stub: [assetStub.livePhotoStillAsset],
},
{
item: { name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1', source: 'upload' } },
jobs: [JobName.SMART_SEARCH, JobName.FACE_DETECTION, JobName.VIDEO_CONVERSION],
item: { name: JobName.GenerateThumbnails, data: { id: 'asset-1', source: 'upload' } },
jobs: [JobName.SmartSearch, JobName.FaceDetection, JobName.VideoConversation],
stub: [assetStub.video],
},
{
item: { name: JobName.SMART_SEARCH, data: { id: 'asset-1' } },
item: { name: JobName.SmartSearch, data: { id: 'asset-1' } },
jobs: [],
},
{
item: { name: JobName.FACE_DETECTION, data: { id: 'asset-1' } },
item: { name: JobName.FaceDetection, data: { id: 'asset-1' } },
jobs: [],
},
{
item: { name: JobName.FACIAL_RECOGNITION, data: { id: 'asset-1' } },
item: { name: JobName.FacialRecognition, data: { id: 'asset-1' } },
jobs: [],
},
];
@ -291,9 +291,9 @@ describe(JobService.name, () => {
mocks.asset.getByIdsWithAllRelationsButStacks.mockResolvedValue(stub);
}
mocks.job.run.mockResolvedValue(JobStatus.SUCCESS);
mocks.job.run.mockResolvedValue(JobStatus.Success);
await sut.onJobStart(QueueName.BACKGROUND_TASK, item);
await sut.onJobStart(QueueName.BackgroundTask, item);
if (jobs.length > 1) {
expect(mocks.job.queueAll).toHaveBeenCalledWith(
@ -308,9 +308,9 @@ describe(JobService.name, () => {
});
it(`should not queue any jobs when ${item.name} fails`, async () => {
mocks.job.run.mockResolvedValue(JobStatus.FAILED);
mocks.job.run.mockResolvedValue(JobStatus.Failed);
await sut.onJobStart(QueueName.BACKGROUND_TASK, item);
await sut.onJobStart(QueueName.BackgroundTask, item);
expect(mocks.job.queueAll).not.toHaveBeenCalled();
});

Some files were not shown because too many files have changed in this diff Show More