mirror of
https://github.com/immich-app/immich.git
synced 2025-07-31 15:08:44 -04:00
refactor: enum casing (#19946)
This commit is contained in:
parent
920d7de349
commit
e73abe0762
@ -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 {}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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']));
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -157,7 +157,7 @@ export class UserPreferencesUpdateDto {
|
||||
|
||||
class AlbumsResponse {
|
||||
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' })
|
||||
defaultAssetOrder: AssetOrder = AssetOrder.DESC;
|
||||
defaultAssetOrder: AssetOrder = AssetOrder.Desc;
|
||||
}
|
||||
|
||||
class RatingsResponse {
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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`);
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
]),
|
||||
)
|
||||
|
||||
|
@ -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),
|
||||
]),
|
||||
)
|
||||
|
@ -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')
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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[]> {
|
||||
|
@ -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]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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'),
|
||||
)
|
||||
|
@ -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 });
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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!))
|
||||
|
@ -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) {
|
||||
|
@ -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'),
|
||||
)
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 })
|
||||
|
@ -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 })
|
||||
|
@ -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()
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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()' })
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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 });
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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'] },
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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' } }]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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 } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user