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({
|
@Module({
|
||||||
imports: [...imports, ScheduleModule.forRoot()],
|
imports: [...imports, ScheduleModule.forRoot()],
|
||||||
controllers: [...controllers],
|
controllers: [...controllers],
|
||||||
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.API }],
|
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.Api }],
|
||||||
})
|
})
|
||||||
export class ApiModule extends BaseModule {}
|
export class ApiModule extends BaseModule {}
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [...imports],
|
imports: [...imports],
|
||||||
providers: [...common, { provide: IWorker, useValue: ImmichWorker.MICROSERVICES }, SchedulerRegistry],
|
providers: [...common, { provide: IWorker, useValue: ImmichWorker.Microservices }, SchedulerRegistry],
|
||||||
})
|
})
|
||||||
export class MicroservicesModule extends BaseModule {}
|
export class MicroservicesModule extends BaseModule {}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
OAuthTokenEndpointAuthMethod,
|
OAuthTokenEndpointAuthMethod,
|
||||||
QueueName,
|
QueueName,
|
||||||
ToneMapping,
|
ToneMapping,
|
||||||
TranscodeHWAccel,
|
TranscodeHardwareAcceleration,
|
||||||
TranscodePolicy,
|
TranscodePolicy,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
VideoContainer,
|
VideoContainer,
|
||||||
@ -42,7 +42,7 @@ export interface SystemConfig {
|
|||||||
twoPass: boolean;
|
twoPass: boolean;
|
||||||
preferredHwDevice: string;
|
preferredHwDevice: string;
|
||||||
transcode: TranscodePolicy;
|
transcode: TranscodePolicy;
|
||||||
accel: TranscodeHWAccel;
|
accel: TranscodeHardwareAcceleration;
|
||||||
accelDecode: boolean;
|
accelDecode: boolean;
|
||||||
tonemap: ToneMapping;
|
tonemap: ToneMapping;
|
||||||
};
|
};
|
||||||
@ -190,39 +190,39 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
preset: 'ultrafast',
|
preset: 'ultrafast',
|
||||||
targetVideoCodec: VideoCodec.H264,
|
targetVideoCodec: VideoCodec.H264,
|
||||||
acceptedVideoCodecs: [VideoCodec.H264],
|
acceptedVideoCodecs: [VideoCodec.H264],
|
||||||
targetAudioCodec: AudioCodec.AAC,
|
targetAudioCodec: AudioCodec.Aac,
|
||||||
acceptedAudioCodecs: [AudioCodec.AAC, AudioCodec.MP3, AudioCodec.LIBOPUS, AudioCodec.PCMS16LE],
|
acceptedAudioCodecs: [AudioCodec.Aac, AudioCodec.Mp3, AudioCodec.LibOpus, AudioCodec.PcmS16le],
|
||||||
acceptedContainers: [VideoContainer.MOV, VideoContainer.OGG, VideoContainer.WEBM],
|
acceptedContainers: [VideoContainer.Mov, VideoContainer.Ogg, VideoContainer.Webm],
|
||||||
targetResolution: '720',
|
targetResolution: '720',
|
||||||
maxBitrate: '0',
|
maxBitrate: '0',
|
||||||
bframes: -1,
|
bframes: -1,
|
||||||
refs: 0,
|
refs: 0,
|
||||||
gopSize: 0,
|
gopSize: 0,
|
||||||
temporalAQ: false,
|
temporalAQ: false,
|
||||||
cqMode: CQMode.AUTO,
|
cqMode: CQMode.Auto,
|
||||||
twoPass: false,
|
twoPass: false,
|
||||||
preferredHwDevice: 'auto',
|
preferredHwDevice: 'auto',
|
||||||
transcode: TranscodePolicy.REQUIRED,
|
transcode: TranscodePolicy.Required,
|
||||||
tonemap: ToneMapping.HABLE,
|
tonemap: ToneMapping.Hable,
|
||||||
accel: TranscodeHWAccel.DISABLED,
|
accel: TranscodeHardwareAcceleration.Disabled,
|
||||||
accelDecode: false,
|
accelDecode: false,
|
||||||
},
|
},
|
||||||
job: {
|
job: {
|
||||||
[QueueName.BACKGROUND_TASK]: { concurrency: 5 },
|
[QueueName.BackgroundTask]: { concurrency: 5 },
|
||||||
[QueueName.SMART_SEARCH]: { concurrency: 2 },
|
[QueueName.SmartSearch]: { concurrency: 2 },
|
||||||
[QueueName.METADATA_EXTRACTION]: { concurrency: 5 },
|
[QueueName.MetadataExtraction]: { concurrency: 5 },
|
||||||
[QueueName.FACE_DETECTION]: { concurrency: 2 },
|
[QueueName.FaceDetection]: { concurrency: 2 },
|
||||||
[QueueName.SEARCH]: { concurrency: 5 },
|
[QueueName.Search]: { concurrency: 5 },
|
||||||
[QueueName.SIDECAR]: { concurrency: 5 },
|
[QueueName.Sidecar]: { concurrency: 5 },
|
||||||
[QueueName.LIBRARY]: { concurrency: 5 },
|
[QueueName.Library]: { concurrency: 5 },
|
||||||
[QueueName.MIGRATION]: { concurrency: 5 },
|
[QueueName.Migration]: { concurrency: 5 },
|
||||||
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 3 },
|
[QueueName.ThumbnailGeneration]: { concurrency: 3 },
|
||||||
[QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
|
[QueueName.VideoConversion]: { concurrency: 1 },
|
||||||
[QueueName.NOTIFICATION]: { concurrency: 5 },
|
[QueueName.Notification]: { concurrency: 5 },
|
||||||
},
|
},
|
||||||
logging: {
|
logging: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
level: LogLevel.LOG,
|
level: LogLevel.Log,
|
||||||
},
|
},
|
||||||
machineLearning: {
|
machineLearning: {
|
||||||
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
|
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
|
||||||
@ -273,7 +273,7 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
storageLabelClaim: 'preferred_username',
|
storageLabelClaim: 'preferred_username',
|
||||||
storageQuotaClaim: 'immich_quota',
|
storageQuotaClaim: 'immich_quota',
|
||||||
roleClaim: 'immich_role',
|
roleClaim: 'immich_role',
|
||||||
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST,
|
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.ClientSecretPost,
|
||||||
timeout: 30_000,
|
timeout: 30_000,
|
||||||
},
|
},
|
||||||
passwordLogin: {
|
passwordLogin: {
|
||||||
@ -286,12 +286,12 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
format: ImageFormat.WEBP,
|
format: ImageFormat.Webp,
|
||||||
size: 250,
|
size: 250,
|
||||||
quality: 80,
|
quality: 80,
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
format: ImageFormat.JPEG,
|
format: ImageFormat.Jpeg,
|
||||||
size: 1440,
|
size: 1440,
|
||||||
quality: 80,
|
quality: 80,
|
||||||
},
|
},
|
||||||
@ -299,7 +299,7 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
extractEmbedded: false,
|
extractEmbedded: false,
|
||||||
fullsize: {
|
fullsize: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
format: ImageFormat.JPEG,
|
format: ImageFormat.Jpeg,
|
||||||
quality: 80,
|
quality: 80,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -25,14 +25,14 @@ export const EXTENSION_NAMES: Record<DatabaseExtension, string> = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const VECTOR_EXTENSIONS = [
|
export const VECTOR_EXTENSIONS = [
|
||||||
DatabaseExtension.VECTORCHORD,
|
DatabaseExtension.VectorChord,
|
||||||
DatabaseExtension.VECTORS,
|
DatabaseExtension.Vectors,
|
||||||
DatabaseExtension.VECTOR,
|
DatabaseExtension.Vector,
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const VECTOR_INDEX_TABLES = {
|
export const VECTOR_INDEX_TABLES = {
|
||||||
[VectorIndex.CLIP]: 'smart_search',
|
[VectorIndex.Clip]: 'smart_search',
|
||||||
[VectorIndex.FACE]: 'face_search',
|
[VectorIndex.Face]: 'face_search',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const VECTORCHORD_LIST_SLACK_FACTOR = 1.2;
|
export const VECTORCHORD_LIST_SLACK_FACTOR = 1.2;
|
||||||
|
@ -20,13 +20,13 @@ export class ActivityController {
|
|||||||
constructor(private service: ActivityService) {}
|
constructor(private service: ActivityService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.ACTIVITY_READ })
|
@Authenticated({ permission: Permission.ActivityRead })
|
||||||
getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
||||||
return this.service.getAll(auth, dto);
|
return this.service.getAll(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.ACTIVITY_CREATE })
|
@Authenticated({ permission: Permission.ActivityCreate })
|
||||||
async createActivity(
|
async createActivity(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Body() dto: ActivityCreateDto,
|
@Body() dto: ActivityCreateDto,
|
||||||
@ -40,14 +40,14 @@ export class ActivityController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('statistics')
|
@Get('statistics')
|
||||||
@Authenticated({ permission: Permission.ACTIVITY_STATISTICS })
|
@Authenticated({ permission: Permission.ActivityStatistics })
|
||||||
getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
|
getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
|
||||||
return this.service.getStatistics(auth, dto);
|
return this.service.getStatistics(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.ACTIVITY_DELETE })
|
@Authenticated({ permission: Permission.ActivityDelete })
|
||||||
deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -23,24 +23,24 @@ export class AlbumController {
|
|||||||
constructor(private service: AlbumService) {}
|
constructor(private service: AlbumService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.ALBUM_READ })
|
@Authenticated({ permission: Permission.AlbumRead })
|
||||||
getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||||
return this.service.getAll(auth, query);
|
return this.service.getAll(auth, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.ALBUM_CREATE })
|
@Authenticated({ permission: Permission.AlbumCreate })
|
||||||
createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('statistics')
|
@Get('statistics')
|
||||||
@Authenticated({ permission: Permission.ALBUM_STATISTICS })
|
@Authenticated({ permission: Permission.AlbumStatistics })
|
||||||
getAlbumStatistics(@Auth() auth: AuthDto): Promise<AlbumStatisticsResponseDto> {
|
getAlbumStatistics(@Auth() auth: AuthDto): Promise<AlbumStatisticsResponseDto> {
|
||||||
return this.service.getStatistics(auth);
|
return this.service.getStatistics(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authenticated({ permission: Permission.ALBUM_READ, sharedLink: true })
|
@Authenticated({ permission: Permission.AlbumRead, sharedLink: true })
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
getAlbumInfo(
|
getAlbumInfo(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@ -51,7 +51,7 @@ export class AlbumController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Authenticated({ permission: Permission.ALBUM_UPDATE })
|
@Authenticated({ permission: Permission.AlbumUpdate })
|
||||||
updateAlbumInfo(
|
updateAlbumInfo(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -61,7 +61,7 @@ export class AlbumController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated({ permission: Permission.ALBUM_DELETE })
|
@Authenticated({ permission: Permission.AlbumDelete })
|
||||||
deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ describe(APIKeyController.name, () => {
|
|||||||
it('should require a valid uuid', async () => {
|
it('should require a valid uuid', async () => {
|
||||||
const { status, body } = await request(ctx.getHttpServer())
|
const { status, body } = await request(ctx.getHttpServer())
|
||||||
.put(`/api-keys/123`)
|
.put(`/api-keys/123`)
|
||||||
.send({ name: 'new name', permissions: [Permission.ALL] });
|
.send({ name: 'new name', permissions: [Permission.All] });
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(factory.responses.badRequest(['id must be a UUID']));
|
expect(body).toEqual(factory.responses.badRequest(['id must be a UUID']));
|
||||||
});
|
});
|
||||||
|
@ -13,25 +13,25 @@ export class APIKeyController {
|
|||||||
constructor(private service: ApiKeyService) {}
|
constructor(private service: ApiKeyService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.API_KEY_CREATE })
|
@Authenticated({ permission: Permission.ApiKeyCreate })
|
||||||
createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.API_KEY_READ })
|
@Authenticated({ permission: Permission.ApiKeyRead })
|
||||||
getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
|
getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
|
||||||
return this.service.getAll(auth);
|
return this.service.getAll(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.API_KEY_READ })
|
@Authenticated({ permission: Permission.ApiKeyRead })
|
||||||
getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
|
getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
|
||||||
return this.service.getById(auth, id);
|
return this.service.getById(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.API_KEY_UPDATE })
|
@Authenticated({ permission: Permission.ApiKeyUpdate })
|
||||||
updateApiKey(
|
updateApiKey(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -42,7 +42,7 @@ export class APIKeyController {
|
|||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.API_KEY_DELETE })
|
@Authenticated({ permission: Permission.ApiKeyDelete })
|
||||||
deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ import { ImmichFileResponse, sendFile } from 'src/utils/file';
|
|||||||
import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation';
|
import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation';
|
||||||
|
|
||||||
@ApiTags('Assets')
|
@ApiTags('Assets')
|
||||||
@Controller(RouteKey.ASSET)
|
@Controller(RouteKey.Asset)
|
||||||
export class AssetMediaController {
|
export class AssetMediaController {
|
||||||
constructor(
|
constructor(
|
||||||
private logger: LoggingRepository,
|
private logger: LoggingRepository,
|
||||||
@ -56,7 +56,7 @@ export class AssetMediaController {
|
|||||||
@UseInterceptors(AssetUploadInterceptor, FileUploadInterceptor)
|
@UseInterceptors(AssetUploadInterceptor, FileUploadInterceptor)
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiConsumes('multipart/form-data')
|
||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
name: ImmichHeader.CHECKSUM,
|
name: ImmichHeader.Checksum,
|
||||||
description: 'sha1 checksum that can be used for duplicate detection before the file is uploaded',
|
description: 'sha1 checksum that can be used for duplicate detection before the file is uploaded',
|
||||||
required: false,
|
required: false,
|
||||||
})
|
})
|
||||||
|
@ -19,7 +19,7 @@ import { AssetService } from 'src/services/asset.service';
|
|||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
|
|
||||||
@ApiTags('Assets')
|
@ApiTags('Assets')
|
||||||
@Controller(RouteKey.ASSET)
|
@Controller(RouteKey.Asset)
|
||||||
export class AssetController {
|
export class AssetController {
|
||||||
constructor(private service: AssetService) {}
|
constructor(private service: AssetService) {}
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@ export class AuthController {
|
|||||||
return respondWithCookie(res, body, {
|
return respondWithCookie(res, body, {
|
||||||
isSecure: loginDetails.isSecure,
|
isSecure: loginDetails.isSecure,
|
||||||
values: [
|
values: [
|
||||||
{ key: ImmichCookie.ACCESS_TOKEN, value: body.accessToken },
|
{ key: ImmichCookie.AccessToken, value: body.accessToken },
|
||||||
{ key: ImmichCookie.AUTH_TYPE, value: AuthType.PASSWORD },
|
{ key: ImmichCookie.AuthType, value: AuthType.Password },
|
||||||
{ key: ImmichCookie.IS_AUTHENTICATED, value: 'true' },
|
{ key: ImmichCookie.IsAuthenticated, value: 'true' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -70,13 +70,13 @@ export class AuthController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
): Promise<LogoutResponseDto> {
|
): Promise<LogoutResponseDto> {
|
||||||
const authType = (request.cookies || {})[ImmichCookie.AUTH_TYPE];
|
const authType = (request.cookies || {})[ImmichCookie.AuthType];
|
||||||
|
|
||||||
const body = await this.service.logout(auth, authType);
|
const body = await this.service.logout(auth, authType);
|
||||||
return respondWithoutCookie(res, body, [
|
return respondWithoutCookie(res, body, [
|
||||||
ImmichCookie.ACCESS_TOKEN,
|
ImmichCookie.AccessToken,
|
||||||
ImmichCookie.AUTH_TYPE,
|
ImmichCookie.AuthType,
|
||||||
ImmichCookie.IS_AUTHENTICATED,
|
ImmichCookie.IsAuthenticated,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,19 +19,19 @@ export class FaceController {
|
|||||||
constructor(private service: PersonService) {}
|
constructor(private service: PersonService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.FACE_CREATE })
|
@Authenticated({ permission: Permission.FaceCreate })
|
||||||
createFace(@Auth() auth: AuthDto, @Body() dto: AssetFaceCreateDto) {
|
createFace(@Auth() auth: AuthDto, @Body() dto: AssetFaceCreateDto) {
|
||||||
return this.service.createFace(auth, dto);
|
return this.service.createFace(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.FACE_READ })
|
@Authenticated({ permission: Permission.FaceRead })
|
||||||
getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
||||||
return this.service.getFacesById(auth, dto);
|
return this.service.getFacesById(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.FACE_UPDATE })
|
@Authenticated({ permission: Permission.FaceUpdate })
|
||||||
reassignFacesById(
|
reassignFacesById(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -41,7 +41,7 @@ export class FaceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated({ permission: Permission.FACE_DELETE })
|
@Authenticated({ permission: Permission.FaceDelete })
|
||||||
deleteFace(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: AssetFaceDeleteDto) {
|
deleteFace(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: AssetFaceDeleteDto) {
|
||||||
return this.service.deleteFace(auth, id, dto);
|
return this.service.deleteFace(auth, id, dto);
|
||||||
}
|
}
|
||||||
|
@ -19,32 +19,32 @@ export class LibraryController {
|
|||||||
constructor(private service: LibraryService) {}
|
constructor(private service: LibraryService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.LIBRARY_READ, admin: true })
|
@Authenticated({ permission: Permission.LibraryRead, admin: true })
|
||||||
getAllLibraries(): Promise<LibraryResponseDto[]> {
|
getAllLibraries(): Promise<LibraryResponseDto[]> {
|
||||||
return this.service.getAll();
|
return this.service.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.LIBRARY_CREATE, admin: true })
|
@Authenticated({ permission: Permission.LibraryCreate, admin: true })
|
||||||
createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> {
|
createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> {
|
||||||
return this.service.create(dto);
|
return this.service.create(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.LIBRARY_READ, admin: true })
|
@Authenticated({ permission: Permission.LibraryRead, admin: true })
|
||||||
getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> {
|
getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> {
|
||||||
return this.service.get(id);
|
return this.service.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':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> {
|
updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
|
||||||
return this.service.update(id, dto);
|
return this.service.update(id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.LIBRARY_DELETE, admin: true })
|
@Authenticated({ permission: Permission.LibraryDelete, admin: true })
|
||||||
deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> {
|
deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(id);
|
return this.service.delete(id);
|
||||||
}
|
}
|
||||||
@ -58,14 +58,14 @@ export class LibraryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/statistics')
|
@Get(':id/statistics')
|
||||||
@Authenticated({ permission: Permission.LIBRARY_STATISTICS, admin: true })
|
@Authenticated({ permission: Permission.LibraryStatistics, admin: true })
|
||||||
getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
|
getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
|
||||||
return this.service.getStatistics(id);
|
return this.service.getStatistics(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/scan')
|
@Post(':id/scan')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.LIBRARY_UPDATE, admin: true })
|
@Authenticated({ permission: Permission.LibraryUpdate, admin: true })
|
||||||
scanLibrary(@Param() { id }: UUIDParamDto) {
|
scanLibrary(@Param() { id }: UUIDParamDto) {
|
||||||
return this.service.queueScan(id);
|
return this.service.queueScan(id);
|
||||||
}
|
}
|
||||||
|
@ -20,31 +20,31 @@ export class MemoryController {
|
|||||||
constructor(private service: MemoryService) {}
|
constructor(private service: MemoryService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.MEMORY_READ })
|
@Authenticated({ permission: Permission.MemoryRead })
|
||||||
searchMemories(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryResponseDto[]> {
|
searchMemories(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryResponseDto[]> {
|
||||||
return this.service.search(auth, dto);
|
return this.service.search(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.MEMORY_CREATE })
|
@Authenticated({ permission: Permission.MemoryCreate })
|
||||||
createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise<MemoryResponseDto> {
|
createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise<MemoryResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('statistics')
|
@Get('statistics')
|
||||||
@Authenticated({ permission: Permission.MEMORY_READ })
|
@Authenticated({ permission: Permission.MemoryRead })
|
||||||
memoriesStatistics(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryStatisticsResponseDto> {
|
memoriesStatistics(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryStatisticsResponseDto> {
|
||||||
return this.service.statistics(auth, dto);
|
return this.service.statistics(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.MEMORY_READ })
|
@Authenticated({ permission: Permission.MemoryRead })
|
||||||
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
|
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.MEMORY_UPDATE })
|
@Authenticated({ permission: Permission.MemoryUpdate })
|
||||||
updateMemory(
|
updateMemory(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -55,7 +55,7 @@ export class MemoryController {
|
|||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.MEMORY_DELETE })
|
@Authenticated({ permission: Permission.MemoryDelete })
|
||||||
deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.remove(auth, id);
|
return this.service.remove(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -19,31 +19,31 @@ export class NotificationController {
|
|||||||
constructor(private service: NotificationService) {}
|
constructor(private service: NotificationService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.NOTIFICATION_READ })
|
@Authenticated({ permission: Permission.NotificationRead })
|
||||||
getNotifications(@Auth() auth: AuthDto, @Query() dto: NotificationSearchDto): Promise<NotificationDto[]> {
|
getNotifications(@Auth() auth: AuthDto, @Query() dto: NotificationSearchDto): Promise<NotificationDto[]> {
|
||||||
return this.service.search(auth, dto);
|
return this.service.search(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put()
|
@Put()
|
||||||
@Authenticated({ permission: Permission.NOTIFICATION_UPDATE })
|
@Authenticated({ permission: Permission.NotificationUpdate })
|
||||||
updateNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationUpdateAllDto): Promise<void> {
|
updateNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationUpdateAllDto): Promise<void> {
|
||||||
return this.service.updateAll(auth, dto);
|
return this.service.updateAll(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete()
|
@Delete()
|
||||||
@Authenticated({ permission: Permission.NOTIFICATION_DELETE })
|
@Authenticated({ permission: Permission.NotificationDelete })
|
||||||
deleteNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationDeleteAllDto): Promise<void> {
|
deleteNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationDeleteAllDto): Promise<void> {
|
||||||
return this.service.deleteAll(auth, dto);
|
return this.service.deleteAll(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.NOTIFICATION_READ })
|
@Authenticated({ permission: Permission.NotificationRead })
|
||||||
getNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<NotificationDto> {
|
getNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<NotificationDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.NOTIFICATION_UPDATE })
|
@Authenticated({ permission: Permission.NotificationUpdate })
|
||||||
updateNotification(
|
updateNotification(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -53,7 +53,7 @@ export class NotificationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated({ permission: Permission.NOTIFICATION_DELETE })
|
@Authenticated({ permission: Permission.NotificationDelete })
|
||||||
deleteNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,8 @@ export class OAuthController {
|
|||||||
{
|
{
|
||||||
isSecure: loginDetails.isSecure,
|
isSecure: loginDetails.isSecure,
|
||||||
values: [
|
values: [
|
||||||
{ key: ImmichCookie.OAUTH_STATE, value: state },
|
{ key: ImmichCookie.OAuthState, value: state },
|
||||||
{ key: ImmichCookie.OAUTH_CODE_VERIFIER, value: codeVerifier },
|
{ key: ImmichCookie.OAuthCodeVerifier, value: codeVerifier },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -56,14 +56,14 @@ export class OAuthController {
|
|||||||
@GetLoginDetails() loginDetails: LoginDetails,
|
@GetLoginDetails() loginDetails: LoginDetails,
|
||||||
): Promise<LoginResponseDto> {
|
): Promise<LoginResponseDto> {
|
||||||
const body = await this.service.callback(dto, request.headers, loginDetails);
|
const body = await this.service.callback(dto, request.headers, loginDetails);
|
||||||
res.clearCookie(ImmichCookie.OAUTH_STATE);
|
res.clearCookie(ImmichCookie.OAuthState);
|
||||||
res.clearCookie(ImmichCookie.OAUTH_CODE_VERIFIER);
|
res.clearCookie(ImmichCookie.OAuthCodeVerifier);
|
||||||
return respondWithCookie(res, body, {
|
return respondWithCookie(res, body, {
|
||||||
isSecure: loginDetails.isSecure,
|
isSecure: loginDetails.isSecure,
|
||||||
values: [
|
values: [
|
||||||
{ key: ImmichCookie.ACCESS_TOKEN, value: body.accessToken },
|
{ key: ImmichCookie.AccessToken, value: body.accessToken },
|
||||||
{ key: ImmichCookie.AUTH_TYPE, value: AuthType.OAUTH },
|
{ key: ImmichCookie.AuthType, value: AuthType.OAuth },
|
||||||
{ key: ImmichCookie.IS_AUTHENTICATED, value: 'true' },
|
{ key: ImmichCookie.IsAuthenticated, value: 'true' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,19 +13,19 @@ export class PartnerController {
|
|||||||
constructor(private service: PartnerService) {}
|
constructor(private service: PartnerService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.PARTNER_READ })
|
@Authenticated({ permission: Permission.PartnerRead })
|
||||||
getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise<PartnerResponseDto[]> {
|
getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise<PartnerResponseDto[]> {
|
||||||
return this.service.search(auth, dto);
|
return this.service.search(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id')
|
@Post(':id')
|
||||||
@Authenticated({ permission: Permission.PARTNER_CREATE })
|
@Authenticated({ permission: Permission.PartnerCreate })
|
||||||
createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
|
createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
|
||||||
return this.service.create(auth, id);
|
return this.service.create(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.PARTNER_UPDATE })
|
@Authenticated({ permission: Permission.PartnerUpdate })
|
||||||
updatePartner(
|
updatePartner(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -35,7 +35,7 @@ export class PartnerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated({ permission: Permission.PARTNER_DELETE })
|
@Authenticated({ permission: Permission.PartnerDelete })
|
||||||
removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.remove(auth, id);
|
return this.service.remove(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -45,38 +45,38 @@ export class PersonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.PERSON_READ })
|
@Authenticated({ permission: Permission.PersonRead })
|
||||||
getAllPeople(@Auth() auth: AuthDto, @Query() options: PersonSearchDto): Promise<PeopleResponseDto> {
|
getAllPeople(@Auth() auth: AuthDto, @Query() options: PersonSearchDto): Promise<PeopleResponseDto> {
|
||||||
return this.service.getAll(auth, options);
|
return this.service.getAll(auth, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.PERSON_CREATE })
|
@Authenticated({ permission: Permission.PersonCreate })
|
||||||
createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
|
createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put()
|
@Put()
|
||||||
@Authenticated({ permission: Permission.PERSON_UPDATE })
|
@Authenticated({ permission: Permission.PersonUpdate })
|
||||||
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||||
return this.service.updateAll(auth, dto);
|
return this.service.updateAll(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete()
|
@Delete()
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.PERSON_DELETE })
|
@Authenticated({ permission: Permission.PersonDelete })
|
||||||
deletePeople(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
|
deletePeople(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
|
||||||
return this.service.deleteAll(auth, dto);
|
return this.service.deleteAll(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.PERSON_READ })
|
@Authenticated({ permission: Permission.PersonRead })
|
||||||
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
|
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
|
||||||
return this.service.getById(auth, id);
|
return this.service.getById(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.PERSON_UPDATE })
|
@Authenticated({ permission: Permission.PersonUpdate })
|
||||||
updatePerson(
|
updatePerson(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -87,20 +87,20 @@ export class PersonController {
|
|||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.PERSON_DELETE })
|
@Authenticated({ permission: Permission.PersonDelete })
|
||||||
deletePerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deletePerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/statistics')
|
@Get(':id/statistics')
|
||||||
@Authenticated({ permission: Permission.PERSON_STATISTICS })
|
@Authenticated({ permission: Permission.PersonStatistics })
|
||||||
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
|
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
|
||||||
return this.service.getStatistics(auth, id);
|
return this.service.getStatistics(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/thumbnail')
|
@Get(':id/thumbnail')
|
||||||
@FileResponse()
|
@FileResponse()
|
||||||
@Authenticated({ permission: Permission.PERSON_READ })
|
@Authenticated({ permission: Permission.PersonRead })
|
||||||
async getPersonThumbnail(
|
async getPersonThumbnail(
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
@Next() next: NextFunction,
|
@Next() next: NextFunction,
|
||||||
@ -111,7 +111,7 @@ export class PersonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id/reassign')
|
@Put(':id/reassign')
|
||||||
@Authenticated({ permission: Permission.PERSON_REASSIGN })
|
@Authenticated({ permission: Permission.PersonReassign })
|
||||||
reassignFaces(
|
reassignFaces(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -121,7 +121,7 @@ export class PersonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/merge')
|
@Post(':id/merge')
|
||||||
@Authenticated({ permission: Permission.PERSON_MERGE })
|
@Authenticated({ permission: Permission.PersonMerge })
|
||||||
mergePerson(
|
mergePerson(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
|
@ -13,26 +13,26 @@ export class SessionController {
|
|||||||
constructor(private service: SessionService) {}
|
constructor(private service: SessionService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.SESSION_CREATE })
|
@Authenticated({ permission: Permission.SessionCreate })
|
||||||
createSession(@Auth() auth: AuthDto, @Body() dto: SessionCreateDto): Promise<SessionCreateResponseDto> {
|
createSession(@Auth() auth: AuthDto, @Body() dto: SessionCreateDto): Promise<SessionCreateResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.SESSION_READ })
|
@Authenticated({ permission: Permission.SessionRead })
|
||||||
getSessions(@Auth() auth: AuthDto): Promise<SessionResponseDto[]> {
|
getSessions(@Auth() auth: AuthDto): Promise<SessionResponseDto[]> {
|
||||||
return this.service.getAll(auth);
|
return this.service.getAll(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete()
|
@Delete()
|
||||||
@Authenticated({ permission: Permission.SESSION_DELETE })
|
@Authenticated({ permission: Permission.SessionDelete })
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
deleteAllSessions(@Auth() auth: AuthDto): Promise<void> {
|
deleteAllSessions(@Auth() auth: AuthDto): Promise<void> {
|
||||||
return this.service.deleteAll(auth);
|
return this.service.deleteAll(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.SESSION_UPDATE })
|
@Authenticated({ permission: Permission.SessionUpdate })
|
||||||
updateSession(
|
updateSession(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -42,14 +42,14 @@ export class SessionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated({ permission: Permission.SESSION_DELETE })
|
@Authenticated({ permission: Permission.SessionDelete })
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
deleteSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/lock')
|
@Post(':id/lock')
|
||||||
@Authenticated({ permission: Permission.SESSION_LOCK })
|
@Authenticated({ permission: Permission.SessionLock })
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
lockSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
lockSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.lock(auth, id);
|
return this.service.lock(auth, id);
|
||||||
|
@ -24,7 +24,7 @@ export class SharedLinkController {
|
|||||||
constructor(private service: SharedLinkService) {}
|
constructor(private service: SharedLinkService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.SHARED_LINK_READ })
|
@Authenticated({ permission: Permission.SharedLinkRead })
|
||||||
getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> {
|
getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> {
|
||||||
return this.service.getAll(auth, dto);
|
return this.service.getAll(auth, dto);
|
||||||
}
|
}
|
||||||
@ -38,31 +38,31 @@ export class SharedLinkController {
|
|||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@GetLoginDetails() loginDetails: LoginDetails,
|
@GetLoginDetails() loginDetails: LoginDetails,
|
||||||
): Promise<SharedLinkResponseDto> {
|
): Promise<SharedLinkResponseDto> {
|
||||||
const sharedLinkToken = request.cookies?.[ImmichCookie.SHARED_LINK_TOKEN];
|
const sharedLinkToken = request.cookies?.[ImmichCookie.SharedLinkToken];
|
||||||
if (sharedLinkToken) {
|
if (sharedLinkToken) {
|
||||||
dto.token = sharedLinkToken;
|
dto.token = sharedLinkToken;
|
||||||
}
|
}
|
||||||
const body = await this.service.getMine(auth, dto);
|
const body = await this.service.getMine(auth, dto);
|
||||||
return respondWithCookie(res, body, {
|
return respondWithCookie(res, body, {
|
||||||
isSecure: loginDetails.isSecure,
|
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')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.SHARED_LINK_READ })
|
@Authenticated({ permission: Permission.SharedLinkRead })
|
||||||
getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
|
getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.SHARED_LINK_CREATE })
|
@Authenticated({ permission: Permission.SharedLinkCreate })
|
||||||
createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
|
createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Authenticated({ permission: Permission.SHARED_LINK_UPDATE })
|
@Authenticated({ permission: Permission.SharedLinkUpdate })
|
||||||
updateSharedLink(
|
updateSharedLink(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -72,7 +72,7 @@ export class SharedLinkController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated({ permission: Permission.SHARED_LINK_DELETE })
|
@Authenticated({ permission: Permission.SharedLinkDelete })
|
||||||
removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.remove(auth, id);
|
return this.service.remove(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -14,32 +14,32 @@ export class StackController {
|
|||||||
constructor(private service: StackService) {}
|
constructor(private service: StackService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.STACK_READ })
|
@Authenticated({ permission: Permission.StackRead })
|
||||||
searchStacks(@Auth() auth: AuthDto, @Query() query: StackSearchDto): Promise<StackResponseDto[]> {
|
searchStacks(@Auth() auth: AuthDto, @Query() query: StackSearchDto): Promise<StackResponseDto[]> {
|
||||||
return this.service.search(auth, query);
|
return this.service.search(auth, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.STACK_CREATE })
|
@Authenticated({ permission: Permission.StackCreate })
|
||||||
createStack(@Auth() auth: AuthDto, @Body() dto: StackCreateDto): Promise<StackResponseDto> {
|
createStack(@Auth() auth: AuthDto, @Body() dto: StackCreateDto): Promise<StackResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete()
|
@Delete()
|
||||||
@Authenticated({ permission: Permission.STACK_DELETE })
|
@Authenticated({ permission: Permission.StackDelete })
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
deleteStacks(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
|
deleteStacks(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
|
||||||
return this.service.deleteAll(auth, dto);
|
return this.service.deleteAll(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.STACK_READ })
|
@Authenticated({ permission: Permission.StackRead })
|
||||||
getStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<StackResponseDto> {
|
getStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<StackResponseDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.STACK_UPDATE })
|
@Authenticated({ permission: Permission.StackUpdate })
|
||||||
updateStack(
|
updateStack(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -50,7 +50,7 @@ export class StackController {
|
|||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.STACK_DELETE })
|
@Authenticated({ permission: Permission.StackDelete })
|
||||||
deleteStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -15,25 +15,25 @@ export class SystemConfigController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
|
||||||
getConfig(): Promise<SystemConfigDto> {
|
getConfig(): Promise<SystemConfigDto> {
|
||||||
return this.service.getSystemConfig();
|
return this.service.getSystemConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('defaults')
|
@Get('defaults')
|
||||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
|
||||||
getConfigDefaults(): SystemConfigDto {
|
getConfigDefaults(): SystemConfigDto {
|
||||||
return this.service.getDefaults();
|
return this.service.getDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put()
|
@Put()
|
||||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_UPDATE, admin: true })
|
@Authenticated({ permission: Permission.SystemConfigUpdate, admin: true })
|
||||||
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
|
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
|
||||||
return this.service.updateSystemConfig(dto);
|
return this.service.updateSystemConfig(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('storage-template-options')
|
@Get('storage-template-options')
|
||||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
|
||||||
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
|
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
|
||||||
return this.storageTemplateService.getStorageTemplateOptions();
|
return this.storageTemplateService.getStorageTemplateOptions();
|
||||||
}
|
}
|
||||||
|
@ -15,26 +15,26 @@ export class SystemMetadataController {
|
|||||||
constructor(private service: SystemMetadataService) {}
|
constructor(private service: SystemMetadataService) {}
|
||||||
|
|
||||||
@Get('admin-onboarding')
|
@Get('admin-onboarding')
|
||||||
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
|
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
|
||||||
getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> {
|
getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> {
|
||||||
return this.service.getAdminOnboarding();
|
return this.service.getAdminOnboarding();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('admin-onboarding')
|
@Post('admin-onboarding')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.SYSTEM_METADATA_UPDATE, admin: true })
|
@Authenticated({ permission: Permission.SystemMetadataUpdate, admin: true })
|
||||||
updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> {
|
updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> {
|
||||||
return this.service.updateAdminOnboarding(dto);
|
return this.service.updateAdminOnboarding(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('reverse-geocoding-state')
|
@Get('reverse-geocoding-state')
|
||||||
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
|
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
|
||||||
getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
|
getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
|
||||||
return this.service.getReverseGeocodingState();
|
return this.service.getReverseGeocodingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('version-check-state')
|
@Get('version-check-state')
|
||||||
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
|
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
|
||||||
getVersionCheckState(): Promise<VersionCheckStateResponseDto> {
|
getVersionCheckState(): Promise<VersionCheckStateResponseDto> {
|
||||||
return this.service.getVersionCheckState();
|
return this.service.getVersionCheckState();
|
||||||
}
|
}
|
||||||
|
@ -21,50 +21,50 @@ export class TagController {
|
|||||||
constructor(private service: TagService) {}
|
constructor(private service: TagService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.TAG_CREATE })
|
@Authenticated({ permission: Permission.TagCreate })
|
||||||
createTag(@Auth() auth: AuthDto, @Body() dto: TagCreateDto): Promise<TagResponseDto> {
|
createTag(@Auth() auth: AuthDto, @Body() dto: TagCreateDto): Promise<TagResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.TAG_READ })
|
@Authenticated({ permission: Permission.TagRead })
|
||||||
getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
|
getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
|
||||||
return this.service.getAll(auth);
|
return this.service.getAll(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put()
|
@Put()
|
||||||
@Authenticated({ permission: Permission.TAG_CREATE })
|
@Authenticated({ permission: Permission.TagCreate })
|
||||||
upsertTags(@Auth() auth: AuthDto, @Body() dto: TagUpsertDto): Promise<TagResponseDto[]> {
|
upsertTags(@Auth() auth: AuthDto, @Body() dto: TagUpsertDto): Promise<TagResponseDto[]> {
|
||||||
return this.service.upsert(auth, dto);
|
return this.service.upsert(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('assets')
|
@Put('assets')
|
||||||
@Authenticated({ permission: Permission.TAG_ASSET })
|
@Authenticated({ permission: Permission.TagAsset })
|
||||||
bulkTagAssets(@Auth() auth: AuthDto, @Body() dto: TagBulkAssetsDto): Promise<TagBulkAssetsResponseDto> {
|
bulkTagAssets(@Auth() auth: AuthDto, @Body() dto: TagBulkAssetsDto): Promise<TagBulkAssetsResponseDto> {
|
||||||
return this.service.bulkTagAssets(auth, dto);
|
return this.service.bulkTagAssets(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.TAG_READ })
|
@Authenticated({ permission: Permission.TagRead })
|
||||||
getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
|
getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.TAG_UPDATE })
|
@Authenticated({ permission: Permission.TagUpdate })
|
||||||
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: TagUpdateDto): Promise<TagResponseDto> {
|
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: TagUpdateDto): Promise<TagResponseDto> {
|
||||||
return this.service.update(auth, id, dto);
|
return this.service.update(auth, id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ permission: Permission.TAG_DELETE })
|
@Authenticated({ permission: Permission.TagDelete })
|
||||||
deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.remove(auth, id);
|
return this.service.remove(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id/assets')
|
@Put(':id/assets')
|
||||||
@Authenticated({ permission: Permission.TAG_ASSET })
|
@Authenticated({ permission: Permission.TagAsset })
|
||||||
tagAssets(
|
tagAssets(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -74,7 +74,7 @@ export class TagController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id/assets')
|
@Delete(':id/assets')
|
||||||
@Authenticated({ permission: Permission.TAG_ASSET })
|
@Authenticated({ permission: Permission.TagAsset })
|
||||||
untagAssets(
|
untagAssets(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Body() dto: BulkIdsDto,
|
@Body() dto: BulkIdsDto,
|
||||||
|
@ -12,13 +12,13 @@ export class TimelineController {
|
|||||||
constructor(private service: TimelineService) {}
|
constructor(private service: TimelineService) {}
|
||||||
|
|
||||||
@Get('buckets')
|
@Get('buckets')
|
||||||
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
|
@Authenticated({ permission: Permission.AssetRead, sharedLink: true })
|
||||||
getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto) {
|
getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto) {
|
||||||
return this.service.getTimeBuckets(auth, dto);
|
return this.service.getTimeBuckets(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('bucket')
|
@Get('bucket')
|
||||||
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
|
@Authenticated({ permission: Permission.AssetRead, sharedLink: true })
|
||||||
@ApiOkResponse({ type: TimeBucketAssetResponseDto })
|
@ApiOkResponse({ type: TimeBucketAssetResponseDto })
|
||||||
@Header('Content-Type', 'application/json')
|
@Header('Content-Type', 'application/json')
|
||||||
getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto) {
|
getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto) {
|
||||||
|
@ -14,21 +14,21 @@ export class TrashController {
|
|||||||
|
|
||||||
@Post('empty')
|
@Post('empty')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Authenticated({ permission: Permission.ASSET_DELETE })
|
@Authenticated({ permission: Permission.AssetDelete })
|
||||||
emptyTrash(@Auth() auth: AuthDto): Promise<TrashResponseDto> {
|
emptyTrash(@Auth() auth: AuthDto): Promise<TrashResponseDto> {
|
||||||
return this.service.empty(auth);
|
return this.service.empty(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('restore')
|
@Post('restore')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Authenticated({ permission: Permission.ASSET_DELETE })
|
@Authenticated({ permission: Permission.AssetDelete })
|
||||||
restoreTrash(@Auth() auth: AuthDto): Promise<TrashResponseDto> {
|
restoreTrash(@Auth() auth: AuthDto): Promise<TrashResponseDto> {
|
||||||
return this.service.restore(auth);
|
return this.service.restore(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('restore/assets')
|
@Post('restore/assets')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Authenticated({ permission: Permission.ASSET_DELETE })
|
@Authenticated({ permission: Permission.AssetDelete })
|
||||||
restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<TrashResponseDto> {
|
restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<TrashResponseDto> {
|
||||||
return this.service.restoreAssets(auth, dto);
|
return this.service.restoreAssets(auth, dto);
|
||||||
}
|
}
|
||||||
|
@ -21,25 +21,25 @@ export class UserAdminController {
|
|||||||
constructor(private service: UserAdminService) {}
|
constructor(private service: UserAdminService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
|
||||||
searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
|
searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
|
||||||
return this.service.search(auth, dto);
|
return this.service.search(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ permission: Permission.ADMIN_USER_CREATE, admin: true })
|
@Authenticated({ permission: Permission.AdminUserCreate, admin: true })
|
||||||
createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
|
createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
|
||||||
return this.service.create(createUserDto);
|
return this.service.create(createUserDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@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> {
|
getUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ permission: Permission.ADMIN_USER_UPDATE, admin: true })
|
@Authenticated({ permission: Permission.AdminUserUpdate, admin: true })
|
||||||
updateUserAdmin(
|
updateUserAdmin(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -49,7 +49,7 @@ export class UserAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
@Authenticated({ permission: Permission.AdminUserDelete, admin: true })
|
||||||
deleteUserAdmin(
|
deleteUserAdmin(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -59,7 +59,7 @@ export class UserAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/statistics')
|
@Get(':id/statistics')
|
||||||
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
|
||||||
getUserStatisticsAdmin(
|
getUserStatisticsAdmin(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -69,13 +69,13 @@ export class UserAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/preferences')
|
@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> {
|
getUserPreferencesAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserPreferencesResponseDto> {
|
||||||
return this.service.getPreferences(auth, id);
|
return this.service.getPreferences(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id/preferences')
|
@Put(':id/preferences')
|
||||||
@Authenticated({ permission: Permission.ADMIN_USER_UPDATE, admin: true })
|
@Authenticated({ permission: Permission.AdminUserUpdate, admin: true })
|
||||||
updateUserPreferencesAdmin(
|
updateUserPreferencesAdmin(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -85,7 +85,7 @@ export class UserAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/restore')
|
@Post(':id/restore')
|
||||||
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
@Authenticated({ permission: Permission.AdminUserDelete, admin: true })
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
||||||
return this.service.restore(auth, id);
|
return this.service.restore(auth, id);
|
||||||
|
@ -30,7 +30,7 @@ import { sendFile } from 'src/utils/file';
|
|||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
|
|
||||||
@ApiTags('Users')
|
@ApiTags('Users')
|
||||||
@Controller(RouteKey.USER)
|
@Controller(RouteKey.User)
|
||||||
export class UserController {
|
export class UserController {
|
||||||
constructor(
|
constructor(
|
||||||
private service: UserService,
|
private service: UserService,
|
||||||
|
@ -25,8 +25,8 @@ export interface MoveRequest {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL | AssetPathType.FULLSIZE;
|
export type GeneratedImageType = AssetPathType.Preview | AssetPathType.Thumbnail | AssetPathType.FullSize;
|
||||||
export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO;
|
export type GeneratedAssetType = GeneratedImageType | AssetPathType.EncodedVideo;
|
||||||
|
|
||||||
export type ThumbnailPathEntity = { id: string; ownerId: string };
|
export type ThumbnailPathEntity = { id: string; ownerId: string };
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ export class StorageCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getLibraryFolder(user: { storageLabel: string | null; id: string }) {
|
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) {
|
static getBaseFolder(folder: StorageFolder) {
|
||||||
@ -87,23 +87,23 @@ export class StorageCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getPersonThumbnailPath(person: ThumbnailPathEntity) {
|
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') {
|
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) {
|
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) {
|
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) {
|
static isAndroidMotionPath(originalPath: string) {
|
||||||
return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.ENCODED_VIDEO));
|
return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.EncodedVideo));
|
||||||
}
|
}
|
||||||
|
|
||||||
static isImmichPath(path: string) {
|
static isImmichPath(path: string) {
|
||||||
@ -130,7 +130,7 @@ export class StorageCore {
|
|||||||
async moveAssetVideo(asset: StorageAsset) {
|
async moveAssetVideo(asset: StorageAsset) {
|
||||||
return this.moveFile({
|
return this.moveFile({
|
||||||
entityId: asset.id,
|
entityId: asset.id,
|
||||||
pathType: AssetPathType.ENCODED_VIDEO,
|
pathType: AssetPathType.EncodedVideo,
|
||||||
oldPath: asset.encodedVideoPath,
|
oldPath: asset.encodedVideoPath,
|
||||||
newPath: StorageCore.getEncodedVideoPath(asset),
|
newPath: StorageCore.getEncodedVideoPath(asset),
|
||||||
});
|
});
|
||||||
@ -139,7 +139,7 @@ export class StorageCore {
|
|||||||
async movePersonFile(person: { id: string; ownerId: string; thumbnailPath: string }, pathType: PersonPathType) {
|
async movePersonFile(person: { id: string; ownerId: string; thumbnailPath: string }, pathType: PersonPathType) {
|
||||||
const { id: entityId, thumbnailPath } = person;
|
const { id: entityId, thumbnailPath } = person;
|
||||||
switch (pathType) {
|
switch (pathType) {
|
||||||
case PersonPathType.FACE: {
|
case PersonPathType.Face: {
|
||||||
await this.moveFile({
|
await this.moveFile({
|
||||||
entityId,
|
entityId,
|
||||||
pathType,
|
pathType,
|
||||||
@ -188,7 +188,7 @@ export class StorageCore {
|
|||||||
move = await this.moveRepository.create({ entityId, pathType, oldPath, newPath });
|
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}`);
|
this.logger.warn(`Unable to complete move. Missing asset info for ${entityId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -274,25 +274,25 @@ export class StorageCore {
|
|||||||
|
|
||||||
private savePath(pathType: PathType, id: string, newPath: string) {
|
private savePath(pathType: PathType, id: string, newPath: string) {
|
||||||
switch (pathType) {
|
switch (pathType) {
|
||||||
case AssetPathType.ORIGINAL: {
|
case AssetPathType.Original: {
|
||||||
return this.assetRepository.update({ id, originalPath: newPath });
|
return this.assetRepository.update({ id, originalPath: newPath });
|
||||||
}
|
}
|
||||||
case AssetPathType.FULLSIZE: {
|
case AssetPathType.FullSize: {
|
||||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.FULLSIZE, path: newPath });
|
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.FullSize, path: newPath });
|
||||||
}
|
}
|
||||||
case AssetPathType.PREVIEW: {
|
case AssetPathType.Preview: {
|
||||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.PREVIEW, path: newPath });
|
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Preview, path: newPath });
|
||||||
}
|
}
|
||||||
case AssetPathType.THUMBNAIL: {
|
case AssetPathType.Thumbnail: {
|
||||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.THUMBNAIL, path: newPath });
|
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 });
|
return this.assetRepository.update({ id, encodedVideoPath: newPath });
|
||||||
}
|
}
|
||||||
case AssetPathType.SIDECAR: {
|
case AssetPathType.Sidecar: {
|
||||||
return this.assetRepository.update({ id, sidecarPath: newPath });
|
return this.assetRepository.update({ id, sidecarPath: newPath });
|
||||||
}
|
}
|
||||||
case PersonPathType.FACE: {
|
case PersonPathType.Face: {
|
||||||
return this.personRepository.update({ id, thumbnailPath: newPath });
|
return this.personRepository.update({ id, thumbnailPath: newPath });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ export interface GenerateSqlQueries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Telemetry = (options: { enabled?: boolean }) =>
|
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 */
|
/** Decorator to enable versioning/tracking of generated Sql */
|
||||||
export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options);
|
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 */
|
/** register events for these workers, defaults to all workers */
|
||||||
workers?: ImmichWorker[];
|
workers?: ImmichWorker[];
|
||||||
};
|
};
|
||||||
export const OnEvent = (config: EventConfig) => SetMetadata(MetadataKey.EVENT_CONFIG, config);
|
export const OnEvent = (config: EventConfig) => SetMetadata(MetadataKey.EventConfig, config);
|
||||||
|
|
||||||
export type JobConfig = {
|
export type JobConfig = {
|
||||||
name: JobName;
|
name: JobName;
|
||||||
queue: QueueName;
|
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 LifecycleRelease = 'NEXT_RELEASE' | string;
|
||||||
type LifecycleMetadata = {
|
type LifecycleMetadata = {
|
||||||
|
@ -18,7 +18,7 @@ export class AlbumUserAddDto {
|
|||||||
@ValidateUUID()
|
@ValidateUUID()
|
||||||
userId!: string;
|
userId!: string;
|
||||||
|
|
||||||
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', default: AlbumUserRole.EDITOR })
|
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', default: AlbumUserRole.Editor })
|
||||||
role?: AlbumUserRole;
|
role?: AlbumUserRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
|
|||||||
localDateTime: entity.localDateTime,
|
localDateTime: entity.localDateTime,
|
||||||
updatedAt: entity.updatedAt,
|
updatedAt: entity.updatedAt,
|
||||||
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
|
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
|
||||||
isArchived: entity.visibility === AssetVisibility.ARCHIVE,
|
isArchived: entity.visibility === AssetVisibility.Archive,
|
||||||
isTrashed: !!entity.deletedAt,
|
isTrashed: !!entity.deletedAt,
|
||||||
visibility: entity.visibility,
|
visibility: entity.visibility,
|
||||||
duration: entity.duration ?? '0:00:00.00000',
|
duration: entity.duration ?? '0:00:00.00000',
|
||||||
|
@ -126,8 +126,8 @@ export class AssetStatsResponseDto {
|
|||||||
|
|
||||||
export const mapStats = (stats: AssetStats): AssetStatsResponseDto => {
|
export const mapStats = (stats: AssetStats): AssetStatsResponseDto => {
|
||||||
return {
|
return {
|
||||||
images: stats[AssetType.IMAGE],
|
images: stats[AssetType.Image],
|
||||||
videos: stats[AssetType.VIDEO],
|
videos: stats[AssetType.Video],
|
||||||
total: Object.values(stats).reduce((total, value) => total + value, 0),
|
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 {
|
export function mapLoginResponse(entity: UserAdmin, accessToken: string): LoginResponseDto {
|
||||||
const onboardingMetadata = entity.metadata.find(
|
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;
|
)?.value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -50,47 +50,47 @@ export class JobStatusDto {
|
|||||||
|
|
||||||
export class AllJobStatusResponseDto implements Record<QueueName, JobStatusDto> {
|
export class AllJobStatusResponseDto implements Record<QueueName, JobStatusDto> {
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.THUMBNAIL_GENERATION]!: JobStatusDto;
|
[QueueName.ThumbnailGeneration]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.METADATA_EXTRACTION]!: JobStatusDto;
|
[QueueName.MetadataExtraction]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.VIDEO_CONVERSION]!: JobStatusDto;
|
[QueueName.VideoConversion]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.SMART_SEARCH]!: JobStatusDto;
|
[QueueName.SmartSearch]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.STORAGE_TEMPLATE_MIGRATION]!: JobStatusDto;
|
[QueueName.StorageTemplateMigration]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.MIGRATION]!: JobStatusDto;
|
[QueueName.Migration]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.BACKGROUND_TASK]!: JobStatusDto;
|
[QueueName.BackgroundTask]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.SEARCH]!: JobStatusDto;
|
[QueueName.Search]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.DUPLICATE_DETECTION]!: JobStatusDto;
|
[QueueName.DuplicateDetection]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.FACE_DETECTION]!: JobStatusDto;
|
[QueueName.FaceDetection]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.FACIAL_RECOGNITION]!: JobStatusDto;
|
[QueueName.FacialRecognition]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.SIDECAR]!: JobStatusDto;
|
[QueueName.Sidecar]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.LIBRARY]!: JobStatusDto;
|
[QueueName.Library]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.NOTIFICATION]!: JobStatusDto;
|
[QueueName.Notification]!: JobStatusDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobStatusDto })
|
@ApiProperty({ type: JobStatusDto })
|
||||||
[QueueName.BACKUP_DATABASE]!: JobStatusDto;
|
[QueueName.BackupDatabase]!: JobStatusDto;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ export class MemoryCreateDto extends MemoryBaseDto {
|
|||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type((options) => {
|
@Type((options) => {
|
||||||
switch (options?.object.type) {
|
switch (options?.object.type) {
|
||||||
case MemoryType.ON_THIS_DAY: {
|
case MemoryType.OnThisDay: {
|
||||||
return OnThisDayDto;
|
return OnThisDayDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ export class MetadataSearchDto extends RandomSearchDto {
|
|||||||
@Optional()
|
@Optional()
|
||||||
encodedVideoPath?: string;
|
encodedVideoPath?: string;
|
||||||
|
|
||||||
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.DESC })
|
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.Desc })
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
@ -26,7 +26,7 @@ import {
|
|||||||
OAuthTokenEndpointAuthMethod,
|
OAuthTokenEndpointAuthMethod,
|
||||||
QueueName,
|
QueueName,
|
||||||
ToneMapping,
|
ToneMapping,
|
||||||
TranscodeHWAccel,
|
TranscodeHardwareAcceleration,
|
||||||
TranscodePolicy,
|
TranscodePolicy,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
VideoContainer,
|
VideoContainer,
|
||||||
@ -136,8 +136,8 @@ export class SystemConfigFFmpegDto {
|
|||||||
@ValidateEnum({ enum: TranscodePolicy, name: 'TranscodePolicy' })
|
@ValidateEnum({ enum: TranscodePolicy, name: 'TranscodePolicy' })
|
||||||
transcode!: TranscodePolicy;
|
transcode!: TranscodePolicy;
|
||||||
|
|
||||||
@ValidateEnum({ enum: TranscodeHWAccel, name: 'TranscodeHWAccel' })
|
@ValidateEnum({ enum: TranscodeHardwareAcceleration, name: 'TranscodeHWAccel' })
|
||||||
accel!: TranscodeHWAccel;
|
accel!: TranscodeHardwareAcceleration;
|
||||||
|
|
||||||
@ValidateBoolean()
|
@ValidateBoolean()
|
||||||
accelDecode!: boolean;
|
accelDecode!: boolean;
|
||||||
@ -158,67 +158,67 @@ class SystemConfigJobDto implements Record<ConcurrentQueueName, JobSettingsDto>
|
|||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.THUMBNAIL_GENERATION]!: JobSettingsDto;
|
[QueueName.ThumbnailGeneration]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.METADATA_EXTRACTION]!: JobSettingsDto;
|
[QueueName.MetadataExtraction]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.VIDEO_CONVERSION]!: JobSettingsDto;
|
[QueueName.VideoConversion]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.SMART_SEARCH]!: JobSettingsDto;
|
[QueueName.SmartSearch]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.MIGRATION]!: JobSettingsDto;
|
[QueueName.Migration]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.BACKGROUND_TASK]!: JobSettingsDto;
|
[QueueName.BackgroundTask]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.SEARCH]!: JobSettingsDto;
|
[QueueName.Search]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.FACE_DETECTION]!: JobSettingsDto;
|
[QueueName.FaceDetection]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.SIDECAR]!: JobSettingsDto;
|
[QueueName.Sidecar]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.LIBRARY]!: JobSettingsDto;
|
[QueueName.Library]!: JobSettingsDto;
|
||||||
|
|
||||||
@ApiProperty({ type: JobSettingsDto })
|
@ApiProperty({ type: JobSettingsDto })
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@Type(() => JobSettingsDto)
|
@Type(() => JobSettingsDto)
|
||||||
[QueueName.NOTIFICATION]!: JobSettingsDto;
|
[QueueName.Notification]!: JobSettingsDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SystemConfigLibraryScanDto {
|
class SystemConfigLibraryScanDto {
|
||||||
|
@ -157,7 +157,7 @@ export class UserPreferencesUpdateDto {
|
|||||||
|
|
||||||
class AlbumsResponse {
|
class AlbumsResponse {
|
||||||
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' })
|
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' })
|
||||||
defaultAssetOrder: AssetOrder = AssetOrder.DESC;
|
defaultAssetOrder: AssetOrder = AssetOrder.Desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RatingsResponse {
|
class RatingsResponse {
|
||||||
|
@ -171,7 +171,7 @@ export class UserAdminResponseDto extends UserResponseDto {
|
|||||||
export function mapUserAdmin(entity: UserAdmin): UserAdminResponseDto {
|
export function mapUserAdmin(entity: UserAdmin): UserAdminResponseDto {
|
||||||
const metadata = entity.metadata || [];
|
const metadata = entity.metadata || [];
|
||||||
const license = metadata.find(
|
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;
|
)?.value;
|
||||||
return {
|
return {
|
||||||
...mapUser(entity),
|
...mapUser(entity),
|
||||||
|
@ -1,402 +1,397 @@
|
|||||||
export enum AuthType {
|
export enum AuthType {
|
||||||
PASSWORD = 'password',
|
Password = 'password',
|
||||||
OAUTH = 'oauth',
|
OAuth = 'oauth',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImmichCookie {
|
export enum ImmichCookie {
|
||||||
ACCESS_TOKEN = 'immich_access_token',
|
AccessToken = 'immich_access_token',
|
||||||
AUTH_TYPE = 'immich_auth_type',
|
AuthType = 'immich_auth_type',
|
||||||
IS_AUTHENTICATED = 'immich_is_authenticated',
|
IsAuthenticated = 'immich_is_authenticated',
|
||||||
SHARED_LINK_TOKEN = 'immich_shared_link_token',
|
SharedLinkToken = 'immich_shared_link_token',
|
||||||
OAUTH_STATE = 'immich_oauth_state',
|
OAuthState = 'immich_oauth_state',
|
||||||
OAUTH_CODE_VERIFIER = 'immich_oauth_code_verifier',
|
OAuthCodeVerifier = 'immich_oauth_code_verifier',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImmichHeader {
|
export enum ImmichHeader {
|
||||||
API_KEY = 'x-api-key',
|
ApiKey = 'x-api-key',
|
||||||
USER_TOKEN = 'x-immich-user-token',
|
UserToken = 'x-immich-user-token',
|
||||||
SESSION_TOKEN = 'x-immich-session-token',
|
SessionToken = 'x-immich-session-token',
|
||||||
SHARED_LINK_KEY = 'x-immich-share-key',
|
SharedLinkKey = 'x-immich-share-key',
|
||||||
CHECKSUM = 'x-immich-checksum',
|
Checksum = 'x-immich-checksum',
|
||||||
CID = 'x-immich-cid',
|
Cid = 'x-immich-cid',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImmichQuery {
|
export enum ImmichQuery {
|
||||||
SHARED_LINK_KEY = 'key',
|
SharedLinkKey = 'key',
|
||||||
API_KEY = 'apiKey',
|
ApiKey = 'apiKey',
|
||||||
SESSION_KEY = 'sessionKey',
|
SessionKey = 'sessionKey',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetType {
|
export enum AssetType {
|
||||||
IMAGE = 'IMAGE',
|
Image = 'IMAGE',
|
||||||
VIDEO = 'VIDEO',
|
Video = 'VIDEO',
|
||||||
AUDIO = 'AUDIO',
|
Audio = 'AUDIO',
|
||||||
OTHER = 'OTHER',
|
Other = 'OTHER',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetFileType {
|
export enum AssetFileType {
|
||||||
/**
|
/**
|
||||||
* An full/large-size image extracted/converted from RAW photos
|
* An full/large-size image extracted/converted from RAW photos
|
||||||
*/
|
*/
|
||||||
FULLSIZE = 'fullsize',
|
FullSize = 'fullsize',
|
||||||
PREVIEW = 'preview',
|
Preview = 'preview',
|
||||||
THUMBNAIL = 'thumbnail',
|
Thumbnail = 'thumbnail',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AlbumUserRole {
|
export enum AlbumUserRole {
|
||||||
EDITOR = 'editor',
|
Editor = 'editor',
|
||||||
VIEWER = 'viewer',
|
Viewer = 'viewer',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetOrder {
|
export enum AssetOrder {
|
||||||
ASC = 'asc',
|
Asc = 'asc',
|
||||||
DESC = 'desc',
|
Desc = 'desc',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DatabaseAction {
|
export enum DatabaseAction {
|
||||||
CREATE = 'CREATE',
|
Create = 'CREATE',
|
||||||
UPDATE = 'UPDATE',
|
Update = 'UPDATE',
|
||||||
DELETE = 'DELETE',
|
Delete = 'DELETE',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EntityType {
|
export enum EntityType {
|
||||||
ASSET = 'ASSET',
|
Asset = 'ASSET',
|
||||||
ALBUM = 'ALBUM',
|
Album = 'ALBUM',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MemoryType {
|
export enum MemoryType {
|
||||||
/** pictures taken on this day X years ago */
|
/** pictures taken on this day X years ago */
|
||||||
ON_THIS_DAY = 'on_this_day',
|
OnThisDay = 'on_this_day',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
ALL = 'all',
|
All = 'all',
|
||||||
|
|
||||||
ACTIVITY_CREATE = 'activity.create',
|
ActivityCreate = 'activity.create',
|
||||||
ACTIVITY_READ = 'activity.read',
|
ActivityRead = 'activity.read',
|
||||||
ACTIVITY_UPDATE = 'activity.update',
|
ActivityUpdate = 'activity.update',
|
||||||
ACTIVITY_DELETE = 'activity.delete',
|
ActivityDelete = 'activity.delete',
|
||||||
ACTIVITY_STATISTICS = 'activity.statistics',
|
ActivityStatistics = 'activity.statistics',
|
||||||
|
|
||||||
API_KEY_CREATE = 'apiKey.create',
|
ApiKeyCreate = 'apiKey.create',
|
||||||
API_KEY_READ = 'apiKey.read',
|
ApiKeyRead = 'apiKey.read',
|
||||||
API_KEY_UPDATE = 'apiKey.update',
|
ApiKeyUpdate = 'apiKey.update',
|
||||||
API_KEY_DELETE = 'apiKey.delete',
|
ApiKeyDelete = 'apiKey.delete',
|
||||||
|
|
||||||
// ASSET_CREATE = 'asset.create',
|
// ASSET_CREATE = 'asset.create',
|
||||||
ASSET_READ = 'asset.read',
|
AssetRead = 'asset.read',
|
||||||
ASSET_UPDATE = 'asset.update',
|
AssetUpdate = 'asset.update',
|
||||||
ASSET_DELETE = 'asset.delete',
|
AssetDelete = 'asset.delete',
|
||||||
ASSET_SHARE = 'asset.share',
|
AssetShare = 'asset.share',
|
||||||
ASSET_VIEW = 'asset.view',
|
AssetView = 'asset.view',
|
||||||
ASSET_DOWNLOAD = 'asset.download',
|
AssetDownload = 'asset.download',
|
||||||
ASSET_UPLOAD = 'asset.upload',
|
AssetUpload = 'asset.upload',
|
||||||
|
|
||||||
ALBUM_CREATE = 'album.create',
|
AlbumCreate = 'album.create',
|
||||||
ALBUM_READ = 'album.read',
|
AlbumRead = 'album.read',
|
||||||
ALBUM_UPDATE = 'album.update',
|
AlbumUpdate = 'album.update',
|
||||||
ALBUM_DELETE = 'album.delete',
|
AlbumDelete = 'album.delete',
|
||||||
ALBUM_STATISTICS = 'album.statistics',
|
AlbumStatistics = 'album.statistics',
|
||||||
|
|
||||||
ALBUM_ADD_ASSET = 'album.addAsset',
|
AlbumAddAsset = 'album.addAsset',
|
||||||
ALBUM_REMOVE_ASSET = 'album.removeAsset',
|
AlbumRemoveAsset = 'album.removeAsset',
|
||||||
ALBUM_SHARE = 'album.share',
|
AlbumShare = 'album.share',
|
||||||
ALBUM_DOWNLOAD = 'album.download',
|
AlbumDownload = 'album.download',
|
||||||
|
|
||||||
AUTH_DEVICE_DELETE = 'authDevice.delete',
|
AuthDeviceDelete = 'authDevice.delete',
|
||||||
|
|
||||||
ARCHIVE_READ = 'archive.read',
|
ArchiveRead = 'archive.read',
|
||||||
|
|
||||||
FACE_CREATE = 'face.create',
|
FaceCreate = 'face.create',
|
||||||
FACE_READ = 'face.read',
|
FaceRead = 'face.read',
|
||||||
FACE_UPDATE = 'face.update',
|
FaceUpdate = 'face.update',
|
||||||
FACE_DELETE = 'face.delete',
|
FaceDelete = 'face.delete',
|
||||||
|
|
||||||
LIBRARY_CREATE = 'library.create',
|
LibraryCreate = 'library.create',
|
||||||
LIBRARY_READ = 'library.read',
|
LibraryRead = 'library.read',
|
||||||
LIBRARY_UPDATE = 'library.update',
|
LibraryUpdate = 'library.update',
|
||||||
LIBRARY_DELETE = 'library.delete',
|
LibraryDelete = 'library.delete',
|
||||||
LIBRARY_STATISTICS = 'library.statistics',
|
LibraryStatistics = 'library.statistics',
|
||||||
|
|
||||||
TIMELINE_READ = 'timeline.read',
|
TimelineRead = 'timeline.read',
|
||||||
TIMELINE_DOWNLOAD = 'timeline.download',
|
TimelineDownload = 'timeline.download',
|
||||||
|
|
||||||
MEMORY_CREATE = 'memory.create',
|
MemoryCreate = 'memory.create',
|
||||||
MEMORY_READ = 'memory.read',
|
MemoryRead = 'memory.read',
|
||||||
MEMORY_UPDATE = 'memory.update',
|
MemoryUpdate = 'memory.update',
|
||||||
MEMORY_DELETE = 'memory.delete',
|
MemoryDelete = 'memory.delete',
|
||||||
|
|
||||||
NOTIFICATION_CREATE = 'notification.create',
|
NotificationCreate = 'notification.create',
|
||||||
NOTIFICATION_READ = 'notification.read',
|
NotificationRead = 'notification.read',
|
||||||
NOTIFICATION_UPDATE = 'notification.update',
|
NotificationUpdate = 'notification.update',
|
||||||
NOTIFICATION_DELETE = 'notification.delete',
|
NotificationDelete = 'notification.delete',
|
||||||
|
|
||||||
PARTNER_CREATE = 'partner.create',
|
PartnerCreate = 'partner.create',
|
||||||
PARTNER_READ = 'partner.read',
|
PartnerRead = 'partner.read',
|
||||||
PARTNER_UPDATE = 'partner.update',
|
PartnerUpdate = 'partner.update',
|
||||||
PARTNER_DELETE = 'partner.delete',
|
PartnerDelete = 'partner.delete',
|
||||||
|
|
||||||
PERSON_CREATE = 'person.create',
|
PersonCreate = 'person.create',
|
||||||
PERSON_READ = 'person.read',
|
PersonRead = 'person.read',
|
||||||
PERSON_UPDATE = 'person.update',
|
PersonUpdate = 'person.update',
|
||||||
PERSON_DELETE = 'person.delete',
|
PersonDelete = 'person.delete',
|
||||||
PERSON_STATISTICS = 'person.statistics',
|
PersonStatistics = 'person.statistics',
|
||||||
PERSON_MERGE = 'person.merge',
|
PersonMerge = 'person.merge',
|
||||||
PERSON_REASSIGN = 'person.reassign',
|
PersonReassign = 'person.reassign',
|
||||||
|
|
||||||
SESSION_CREATE = 'session.create',
|
SessionCreate = 'session.create',
|
||||||
SESSION_READ = 'session.read',
|
SessionRead = 'session.read',
|
||||||
SESSION_UPDATE = 'session.update',
|
SessionUpdate = 'session.update',
|
||||||
SESSION_DELETE = 'session.delete',
|
SessionDelete = 'session.delete',
|
||||||
SESSION_LOCK = 'session.lock',
|
SessionLock = 'session.lock',
|
||||||
|
|
||||||
SHARED_LINK_CREATE = 'sharedLink.create',
|
SharedLinkCreate = 'sharedLink.create',
|
||||||
SHARED_LINK_READ = 'sharedLink.read',
|
SharedLinkRead = 'sharedLink.read',
|
||||||
SHARED_LINK_UPDATE = 'sharedLink.update',
|
SharedLinkUpdate = 'sharedLink.update',
|
||||||
SHARED_LINK_DELETE = 'sharedLink.delete',
|
SharedLinkDelete = 'sharedLink.delete',
|
||||||
|
|
||||||
STACK_CREATE = 'stack.create',
|
StackCreate = 'stack.create',
|
||||||
STACK_READ = 'stack.read',
|
StackRead = 'stack.read',
|
||||||
STACK_UPDATE = 'stack.update',
|
StackUpdate = 'stack.update',
|
||||||
STACK_DELETE = 'stack.delete',
|
StackDelete = 'stack.delete',
|
||||||
|
|
||||||
SYSTEM_CONFIG_READ = 'systemConfig.read',
|
SystemConfigRead = 'systemConfig.read',
|
||||||
SYSTEM_CONFIG_UPDATE = 'systemConfig.update',
|
SystemConfigUpdate = 'systemConfig.update',
|
||||||
|
|
||||||
SYSTEM_METADATA_READ = 'systemMetadata.read',
|
SystemMetadataRead = 'systemMetadata.read',
|
||||||
SYSTEM_METADATA_UPDATE = 'systemMetadata.update',
|
SystemMetadataUpdate = 'systemMetadata.update',
|
||||||
|
|
||||||
TAG_CREATE = 'tag.create',
|
TagCreate = 'tag.create',
|
||||||
TAG_READ = 'tag.read',
|
TagRead = 'tag.read',
|
||||||
TAG_UPDATE = 'tag.update',
|
TagUpdate = 'tag.update',
|
||||||
TAG_DELETE = 'tag.delete',
|
TagDelete = 'tag.delete',
|
||||||
TAG_ASSET = 'tag.asset',
|
TagAsset = 'tag.asset',
|
||||||
|
|
||||||
ADMIN_USER_CREATE = 'admin.user.create',
|
AdminUserCreate = 'admin.user.create',
|
||||||
ADMIN_USER_READ = 'admin.user.read',
|
AdminUserRead = 'admin.user.read',
|
||||||
ADMIN_USER_UPDATE = 'admin.user.update',
|
AdminUserUpdate = 'admin.user.update',
|
||||||
ADMIN_USER_DELETE = 'admin.user.delete',
|
AdminUserDelete = 'admin.user.delete',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SharedLinkType {
|
export enum SharedLinkType {
|
||||||
ALBUM = 'ALBUM',
|
Album = 'ALBUM',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Individual asset
|
* Individual asset
|
||||||
* or group of assets that are not in an album
|
* or group of assets that are not in an album
|
||||||
*/
|
*/
|
||||||
INDIVIDUAL = 'INDIVIDUAL',
|
Individual = 'INDIVIDUAL',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum StorageFolder {
|
export enum StorageFolder {
|
||||||
ENCODED_VIDEO = 'encoded-video',
|
EncodedVideo = 'encoded-video',
|
||||||
LIBRARY = 'library',
|
Library = 'library',
|
||||||
UPLOAD = 'upload',
|
Upload = 'upload',
|
||||||
PROFILE = 'profile',
|
Profile = 'profile',
|
||||||
THUMBNAILS = 'thumbs',
|
Thumbnails = 'thumbs',
|
||||||
BACKUPS = 'backups',
|
Backups = 'backups',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SystemMetadataKey {
|
export enum SystemMetadataKey {
|
||||||
REVERSE_GEOCODING_STATE = 'reverse-geocoding-state',
|
ReverseGeocodingState = 'reverse-geocoding-state',
|
||||||
FACIAL_RECOGNITION_STATE = 'facial-recognition-state',
|
FacialRecognitionState = 'facial-recognition-state',
|
||||||
MEMORIES_STATE = 'memories-state',
|
MemoriesState = 'memories-state',
|
||||||
ADMIN_ONBOARDING = 'admin-onboarding',
|
AdminOnboarding = 'admin-onboarding',
|
||||||
SYSTEM_CONFIG = 'system-config',
|
SystemConfig = 'system-config',
|
||||||
SYSTEM_FLAGS = 'system-flags',
|
SystemFlags = 'system-flags',
|
||||||
VERSION_CHECK_STATE = 'version-check-state',
|
VersionCheckState = 'version-check-state',
|
||||||
LICENSE = 'license',
|
License = 'license',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserMetadataKey {
|
export enum UserMetadataKey {
|
||||||
PREFERENCES = 'preferences',
|
Preferences = 'preferences',
|
||||||
LICENSE = 'license',
|
License = 'license',
|
||||||
ONBOARDING = 'onboarding',
|
Onboarding = 'onboarding',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserAvatarColor {
|
export enum UserAvatarColor {
|
||||||
PRIMARY = 'primary',
|
Primary = 'primary',
|
||||||
PINK = 'pink',
|
Pink = 'pink',
|
||||||
RED = 'red',
|
Red = 'red',
|
||||||
YELLOW = 'yellow',
|
Yellow = 'yellow',
|
||||||
BLUE = 'blue',
|
Blue = 'blue',
|
||||||
GREEN = 'green',
|
Green = 'green',
|
||||||
PURPLE = 'purple',
|
Purple = 'purple',
|
||||||
ORANGE = 'orange',
|
Orange = 'orange',
|
||||||
GRAY = 'gray',
|
Gray = 'gray',
|
||||||
AMBER = 'amber',
|
Amber = 'amber',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserStatus {
|
export enum UserStatus {
|
||||||
ACTIVE = 'active',
|
Active = 'active',
|
||||||
REMOVING = 'removing',
|
Removing = 'removing',
|
||||||
DELETED = 'deleted',
|
Deleted = 'deleted',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetStatus {
|
export enum AssetStatus {
|
||||||
ACTIVE = 'active',
|
Active = 'active',
|
||||||
TRASHED = 'trashed',
|
Trashed = 'trashed',
|
||||||
DELETED = 'deleted',
|
Deleted = 'deleted',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SourceType {
|
export enum SourceType {
|
||||||
MACHINE_LEARNING = 'machine-learning',
|
MachineLearning = 'machine-learning',
|
||||||
EXIF = 'exif',
|
Exif = 'exif',
|
||||||
MANUAL = 'manual',
|
Manual = 'manual',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ManualJobName {
|
export enum ManualJobName {
|
||||||
PERSON_CLEANUP = 'person-cleanup',
|
PersonCleanup = 'person-cleanup',
|
||||||
TAG_CLEANUP = 'tag-cleanup',
|
TagCleanup = 'tag-cleanup',
|
||||||
USER_CLEANUP = 'user-cleanup',
|
UserCleanup = 'user-cleanup',
|
||||||
MEMORY_CLEANUP = 'memory-cleanup',
|
MemoryCleanup = 'memory-cleanup',
|
||||||
MEMORY_CREATE = 'memory-create',
|
MemoryCreate = 'memory-create',
|
||||||
BACKUP_DATABASE = 'backup-database',
|
BackupDatabase = 'backup-database',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetPathType {
|
export enum AssetPathType {
|
||||||
ORIGINAL = 'original',
|
Original = 'original',
|
||||||
FULLSIZE = 'fullsize',
|
FullSize = 'fullsize',
|
||||||
PREVIEW = 'preview',
|
Preview = 'preview',
|
||||||
THUMBNAIL = 'thumbnail',
|
Thumbnail = 'thumbnail',
|
||||||
ENCODED_VIDEO = 'encoded_video',
|
EncodedVideo = 'encoded_video',
|
||||||
SIDECAR = 'sidecar',
|
Sidecar = 'sidecar',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PersonPathType {
|
export enum PersonPathType {
|
||||||
FACE = 'face',
|
Face = 'face',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserPathType {
|
export enum UserPathType {
|
||||||
PROFILE = 'profile',
|
Profile = 'profile',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PathType = AssetPathType | PersonPathType | UserPathType;
|
export type PathType = AssetPathType | PersonPathType | UserPathType;
|
||||||
|
|
||||||
export enum TranscodePolicy {
|
export enum TranscodePolicy {
|
||||||
ALL = 'all',
|
All = 'all',
|
||||||
OPTIMAL = 'optimal',
|
Optimal = 'optimal',
|
||||||
BITRATE = 'bitrate',
|
Bitrate = 'bitrate',
|
||||||
REQUIRED = 'required',
|
Required = 'required',
|
||||||
DISABLED = 'disabled',
|
Disabled = 'disabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TranscodeTarget {
|
export enum TranscodeTarget {
|
||||||
NONE,
|
None = 'NONE',
|
||||||
AUDIO,
|
Audio = 'AUDIO',
|
||||||
VIDEO,
|
Video = 'VIDEO',
|
||||||
ALL,
|
All = 'ALL',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum VideoCodec {
|
export enum VideoCodec {
|
||||||
H264 = 'h264',
|
H264 = 'h264',
|
||||||
HEVC = 'hevc',
|
Hevc = 'hevc',
|
||||||
VP9 = 'vp9',
|
Vp9 = 'vp9',
|
||||||
AV1 = 'av1',
|
Av1 = 'av1',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AudioCodec {
|
export enum AudioCodec {
|
||||||
MP3 = 'mp3',
|
Mp3 = 'mp3',
|
||||||
AAC = 'aac',
|
Aac = 'aac',
|
||||||
LIBOPUS = 'libopus',
|
LibOpus = 'libopus',
|
||||||
PCMS16LE = 'pcm_s16le',
|
PcmS16le = 'pcm_s16le',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum VideoContainer {
|
export enum VideoContainer {
|
||||||
MOV = 'mov',
|
Mov = 'mov',
|
||||||
MP4 = 'mp4',
|
Mp4 = 'mp4',
|
||||||
OGG = 'ogg',
|
Ogg = 'ogg',
|
||||||
WEBM = 'webm',
|
Webm = 'webm',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TranscodeHWAccel {
|
export enum TranscodeHardwareAcceleration {
|
||||||
NVENC = 'nvenc',
|
Nvenc = 'nvenc',
|
||||||
QSV = 'qsv',
|
Qsv = 'qsv',
|
||||||
VAAPI = 'vaapi',
|
Vaapi = 'vaapi',
|
||||||
RKMPP = 'rkmpp',
|
Rkmpp = 'rkmpp',
|
||||||
DISABLED = 'disabled',
|
Disabled = 'disabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ToneMapping {
|
export enum ToneMapping {
|
||||||
HABLE = 'hable',
|
Hable = 'hable',
|
||||||
MOBIUS = 'mobius',
|
Mobius = 'mobius',
|
||||||
REINHARD = 'reinhard',
|
Reinhard = 'reinhard',
|
||||||
DISABLED = 'disabled',
|
Disabled = 'disabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CQMode {
|
export enum CQMode {
|
||||||
AUTO = 'auto',
|
Auto = 'auto',
|
||||||
CQP = 'cqp',
|
Cqp = 'cqp',
|
||||||
ICQ = 'icq',
|
Icq = 'icq',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Colorspace {
|
export enum Colorspace {
|
||||||
SRGB = 'srgb',
|
Srgb = 'srgb',
|
||||||
P3 = 'p3',
|
P3 = 'p3',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImageFormat {
|
export enum ImageFormat {
|
||||||
JPEG = 'jpeg',
|
Jpeg = 'jpeg',
|
||||||
WEBP = 'webp',
|
Webp = 'webp',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RawExtractedFormat {
|
export enum RawExtractedFormat {
|
||||||
JPEG = 'jpeg',
|
Jpeg = 'jpeg',
|
||||||
JXL = 'jxl',
|
Jxl = 'jxl',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LogLevel {
|
export enum LogLevel {
|
||||||
VERBOSE = 'verbose',
|
Verbose = 'verbose',
|
||||||
DEBUG = 'debug',
|
Debug = 'debug',
|
||||||
LOG = 'log',
|
Log = 'log',
|
||||||
WARN = 'warn',
|
Warn = 'warn',
|
||||||
ERROR = 'error',
|
Error = 'error',
|
||||||
FATAL = 'fatal',
|
Fatal = 'fatal',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MetadataKey {
|
export enum MetadataKey {
|
||||||
AUTH_ROUTE = 'auth_route',
|
AuthRoute = 'auth_route',
|
||||||
ADMIN_ROUTE = 'admin_route',
|
AdminRoute = 'admin_route',
|
||||||
SHARED_ROUTE = 'shared_route',
|
SharedRoute = 'shared_route',
|
||||||
API_KEY_SECURITY = 'api_key',
|
ApiKeySecurity = 'api_key',
|
||||||
EVENT_CONFIG = 'event_config',
|
EventConfig = 'event_config',
|
||||||
JOB_CONFIG = 'job_config',
|
JobConfig = 'job_config',
|
||||||
TELEMETRY_ENABLED = 'telemetry_enabled',
|
TelemetryEnabled = 'telemetry_enabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RouteKey {
|
export enum RouteKey {
|
||||||
ASSET = 'assets',
|
Asset = 'assets',
|
||||||
USER = 'users',
|
User = 'users',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CacheControl {
|
export enum CacheControl {
|
||||||
PRIVATE_WITH_CACHE = 'private_with_cache',
|
PrivateWithCache = 'private_with_cache',
|
||||||
PRIVATE_WITHOUT_CACHE = 'private_without_cache',
|
PrivateWithoutCache = 'private_without_cache',
|
||||||
NONE = 'none',
|
None = 'none',
|
||||||
}
|
|
||||||
|
|
||||||
export enum PaginationMode {
|
|
||||||
LIMIT_OFFSET = 'limit-offset',
|
|
||||||
SKIP_TAKE = 'skip-take',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImmichEnvironment {
|
export enum ImmichEnvironment {
|
||||||
DEVELOPMENT = 'development',
|
Development = 'development',
|
||||||
TESTING = 'testing',
|
Testing = 'testing',
|
||||||
PRODUCTION = 'production',
|
Production = 'production',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImmichWorker {
|
export enum ImmichWorker {
|
||||||
API = 'api',
|
Api = 'api',
|
||||||
MICROSERVICES = 'microservices',
|
Microservices = 'microservices',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImmichTelemetry {
|
export enum ImmichTelemetry {
|
||||||
HOST = 'host',
|
Host = 'host',
|
||||||
API = 'api',
|
Api = 'api',
|
||||||
IO = 'io',
|
Io = 'io',
|
||||||
REPO = 'repo',
|
Repo = 'repo',
|
||||||
JOB = 'job',
|
Job = 'job',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ExifOrientation {
|
export enum ExifOrientation {
|
||||||
@ -411,11 +406,11 @@ export enum ExifOrientation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum DatabaseExtension {
|
export enum DatabaseExtension {
|
||||||
CUBE = 'cube',
|
Cube = 'cube',
|
||||||
EARTH_DISTANCE = 'earthdistance',
|
EarthDistance = 'earthdistance',
|
||||||
VECTOR = 'vector',
|
Vector = 'vector',
|
||||||
VECTORS = 'vectors',
|
Vectors = 'vectors',
|
||||||
VECTORCHORD = 'vchord',
|
VectorChord = 'vchord',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BootstrapEventPriority {
|
export enum BootstrapEventPriority {
|
||||||
@ -428,135 +423,135 @@ export enum BootstrapEventPriority {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueName {
|
export enum QueueName {
|
||||||
THUMBNAIL_GENERATION = 'thumbnailGeneration',
|
ThumbnailGeneration = 'thumbnailGeneration',
|
||||||
METADATA_EXTRACTION = 'metadataExtraction',
|
MetadataExtraction = 'metadataExtraction',
|
||||||
VIDEO_CONVERSION = 'videoConversion',
|
VideoConversion = 'videoConversion',
|
||||||
FACE_DETECTION = 'faceDetection',
|
FaceDetection = 'faceDetection',
|
||||||
FACIAL_RECOGNITION = 'facialRecognition',
|
FacialRecognition = 'facialRecognition',
|
||||||
SMART_SEARCH = 'smartSearch',
|
SmartSearch = 'smartSearch',
|
||||||
DUPLICATE_DETECTION = 'duplicateDetection',
|
DuplicateDetection = 'duplicateDetection',
|
||||||
BACKGROUND_TASK = 'backgroundTask',
|
BackgroundTask = 'backgroundTask',
|
||||||
STORAGE_TEMPLATE_MIGRATION = 'storageTemplateMigration',
|
StorageTemplateMigration = 'storageTemplateMigration',
|
||||||
MIGRATION = 'migration',
|
Migration = 'migration',
|
||||||
SEARCH = 'search',
|
Search = 'search',
|
||||||
SIDECAR = 'sidecar',
|
Sidecar = 'sidecar',
|
||||||
LIBRARY = 'library',
|
Library = 'library',
|
||||||
NOTIFICATION = 'notifications',
|
Notification = 'notifications',
|
||||||
BACKUP_DATABASE = 'backupDatabase',
|
BackupDatabase = 'backupDatabase',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum JobName {
|
export enum JobName {
|
||||||
//backups
|
//backups
|
||||||
BACKUP_DATABASE = 'database-backup',
|
BackupDatabase = 'database-backup',
|
||||||
|
|
||||||
// conversion
|
// conversion
|
||||||
QUEUE_VIDEO_CONVERSION = 'queue-video-conversion',
|
QueueVideoConversion = 'queue-video-conversion',
|
||||||
VIDEO_CONVERSION = 'video-conversion',
|
VideoConversation = 'video-conversion',
|
||||||
|
|
||||||
// thumbnails
|
// thumbnails
|
||||||
QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails',
|
QueueGenerateThumbnails = 'queue-generate-thumbnails',
|
||||||
GENERATE_THUMBNAILS = 'generate-thumbnails',
|
GenerateThumbnails = 'generate-thumbnails',
|
||||||
GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail',
|
GeneratePersonThumbnail = 'generate-person-thumbnail',
|
||||||
|
|
||||||
// metadata
|
// metadata
|
||||||
QUEUE_METADATA_EXTRACTION = 'queue-metadata-extraction',
|
QueueMetadataExtraction = 'queue-metadata-extraction',
|
||||||
METADATA_EXTRACTION = 'metadata-extraction',
|
MetadataExtraction = 'metadata-extraction',
|
||||||
|
|
||||||
// user
|
// user
|
||||||
USER_DELETION = 'user-deletion',
|
UserDeletion = 'user-deletion',
|
||||||
USER_DELETE_CHECK = 'user-delete-check',
|
UserDeleteCheck = 'user-delete-check',
|
||||||
USER_SYNC_USAGE = 'user-sync-usage',
|
userSyncUsage = 'user-sync-usage',
|
||||||
|
|
||||||
// asset
|
// asset
|
||||||
ASSET_DELETION = 'asset-deletion',
|
AssetDeletion = 'asset-deletion',
|
||||||
ASSET_DELETION_CHECK = 'asset-deletion-check',
|
AssetDeletionCheck = 'asset-deletion-check',
|
||||||
|
|
||||||
// storage template
|
// storage template
|
||||||
STORAGE_TEMPLATE_MIGRATION = 'storage-template-migration',
|
StorageTemplateMigration = 'storage-template-migration',
|
||||||
STORAGE_TEMPLATE_MIGRATION_SINGLE = 'storage-template-migration-single',
|
StorageTemplateMigrationSingle = 'storage-template-migration-single',
|
||||||
|
|
||||||
// tags
|
// tags
|
||||||
TAG_CLEANUP = 'tag-cleanup',
|
TagCleanup = 'tag-cleanup',
|
||||||
|
|
||||||
// migration
|
// migration
|
||||||
QUEUE_MIGRATION = 'queue-migration',
|
QueueMigration = 'queue-migration',
|
||||||
MIGRATE_ASSET = 'migrate-asset',
|
MigrateAsset = 'migrate-asset',
|
||||||
MIGRATE_PERSON = 'migrate-person',
|
MigratePerson = 'migrate-person',
|
||||||
|
|
||||||
// facial recognition
|
// facial recognition
|
||||||
PERSON_CLEANUP = 'person-cleanup',
|
PersonCleanup = 'person-cleanup',
|
||||||
QUEUE_FACE_DETECTION = 'queue-face-detection',
|
QueueFaceDetection = 'queue-face-detection',
|
||||||
FACE_DETECTION = 'face-detection',
|
FaceDetection = 'face-detection',
|
||||||
QUEUE_FACIAL_RECOGNITION = 'queue-facial-recognition',
|
QueueFacialRecognition = 'queue-facial-recognition',
|
||||||
FACIAL_RECOGNITION = 'facial-recognition',
|
FacialRecognition = 'facial-recognition',
|
||||||
|
|
||||||
// library management
|
// library management
|
||||||
LIBRARY_QUEUE_SYNC_FILES = 'library-queue-sync-files',
|
LibraryQueueSyncFiles = 'library-queue-sync-files',
|
||||||
LIBRARY_QUEUE_SYNC_ASSETS = 'library-queue-sync-assets',
|
LibraryQueueSyncAssets = 'library-queue-sync-assets',
|
||||||
LIBRARY_SYNC_FILES = 'library-sync-files',
|
LibrarySyncFiles = 'library-sync-files',
|
||||||
LIBRARY_SYNC_ASSETS = 'library-sync-assets',
|
LibrarySyncAssets = 'library-sync-assets',
|
||||||
LIBRARY_ASSET_REMOVAL = 'handle-library-file-deletion',
|
LibraryAssetRemoval = 'handle-library-file-deletion',
|
||||||
LIBRARY_DELETE = 'library-delete',
|
LibraryDelete = 'library-delete',
|
||||||
LIBRARY_QUEUE_SCAN_ALL = 'library-queue-scan-all',
|
LibraryQueueScanAll = 'library-queue-scan-all',
|
||||||
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
|
LibraryQueueCleanup = 'library-queue-cleanup',
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
DELETE_FILES = 'delete-files',
|
DeleteFiles = 'delete-files',
|
||||||
CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
|
CleanOldAuditLogs = 'clean-old-audit-logs',
|
||||||
CLEAN_OLD_SESSION_TOKENS = 'clean-old-session-tokens',
|
CleanOldSessionTokens = 'clean-old-session-tokens',
|
||||||
|
|
||||||
// memories
|
// memories
|
||||||
MEMORIES_CLEANUP = 'memories-cleanup',
|
MemoriesCleanup = 'memories-cleanup',
|
||||||
MEMORIES_CREATE = 'memories-create',
|
MemoriesCreate = 'memories-create',
|
||||||
|
|
||||||
// smart search
|
// smart search
|
||||||
QUEUE_SMART_SEARCH = 'queue-smart-search',
|
QueueSmartSearch = 'queue-smart-search',
|
||||||
SMART_SEARCH = 'smart-search',
|
SmartSearch = 'smart-search',
|
||||||
|
|
||||||
QUEUE_TRASH_EMPTY = 'queue-trash-empty',
|
QueueTrashEmpty = 'queue-trash-empty',
|
||||||
|
|
||||||
// duplicate detection
|
// duplicate detection
|
||||||
QUEUE_DUPLICATE_DETECTION = 'queue-duplicate-detection',
|
QueueDuplicateDetection = 'queue-duplicate-detection',
|
||||||
DUPLICATE_DETECTION = 'duplicate-detection',
|
DuplicateDetection = 'duplicate-detection',
|
||||||
|
|
||||||
// XMP sidecars
|
// XMP sidecars
|
||||||
QUEUE_SIDECAR = 'queue-sidecar',
|
QueueSidecar = 'queue-sidecar',
|
||||||
SIDECAR_DISCOVERY = 'sidecar-discovery',
|
SidecarDiscovery = 'sidecar-discovery',
|
||||||
SIDECAR_SYNC = 'sidecar-sync',
|
SidecarSync = 'sidecar-sync',
|
||||||
SIDECAR_WRITE = 'sidecar-write',
|
SidecarWrite = 'sidecar-write',
|
||||||
|
|
||||||
// Notification
|
// Notification
|
||||||
NOTIFY_SIGNUP = 'notify-signup',
|
NotifySignup = 'notify-signup',
|
||||||
NOTIFY_ALBUM_INVITE = 'notify-album-invite',
|
NotifyAlbumInvite = 'notify-album-invite',
|
||||||
NOTIFY_ALBUM_UPDATE = 'notify-album-update',
|
NotifyAlbumUpdate = 'notify-album-update',
|
||||||
NOTIFICATIONS_CLEANUP = 'notifications-cleanup',
|
NotificationsCleanup = 'notifications-cleanup',
|
||||||
SEND_EMAIL = 'notification-send-email',
|
SendMail = 'notification-send-email',
|
||||||
|
|
||||||
// Version check
|
// Version check
|
||||||
VERSION_CHECK = 'version-check',
|
VersionCheck = 'version-check',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum JobCommand {
|
export enum JobCommand {
|
||||||
START = 'start',
|
Start = 'start',
|
||||||
PAUSE = 'pause',
|
Pause = 'pause',
|
||||||
RESUME = 'resume',
|
Resume = 'resume',
|
||||||
EMPTY = 'empty',
|
Empty = 'empty',
|
||||||
CLEAR_FAILED = 'clear-failed',
|
ClearFailed = 'clear-failed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum JobStatus {
|
export enum JobStatus {
|
||||||
SUCCESS = 'success',
|
Success = 'success',
|
||||||
FAILED = 'failed',
|
Failed = 'failed',
|
||||||
SKIPPED = 'skipped',
|
Skipped = 'skipped',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueCleanType {
|
export enum QueueCleanType {
|
||||||
FAILED = 'failed',
|
Failed = 'failed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum VectorIndex {
|
export enum VectorIndex {
|
||||||
CLIP = 'clip_index',
|
Clip = 'clip_index',
|
||||||
FACE = 'face_index',
|
Face = 'face_index',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DatabaseLock {
|
export enum DatabaseLock {
|
||||||
@ -663,8 +658,8 @@ export enum NotificationType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum OAuthTokenEndpointAuthMethod {
|
export enum OAuthTokenEndpointAuthMethod {
|
||||||
CLIENT_SECRET_POST = 'client_secret_post',
|
ClientSecretPost = 'client_secret_post',
|
||||||
CLIENT_SECRET_BASIC = 'client_secret_basic',
|
ClientSecretBasic = 'client_secret_basic',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DatabaseSslMode {
|
export enum DatabaseSslMode {
|
||||||
@ -676,14 +671,14 @@ export enum DatabaseSslMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetVisibility {
|
export enum AssetVisibility {
|
||||||
ARCHIVE = 'archive',
|
Archive = 'archive',
|
||||||
TIMELINE = 'timeline',
|
Timeline = 'timeline',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Video part of the LivePhotos and MotionPhotos
|
* Video part of the LivePhotos and MotionPhotos
|
||||||
*/
|
*/
|
||||||
HIDDEN = 'hidden',
|
Hidden = 'hidden',
|
||||||
LOCKED = 'locked',
|
Locked = 'locked',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CronJob {
|
export enum CronJob {
|
||||||
|
@ -20,7 +20,7 @@ const onExit = (name: string, exitCode: number | null) => {
|
|||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
console.error(`${name} worker exited with code ${exitCode}`);
|
console.error(`${name} worker exited with code ${exitCode}`);
|
||||||
|
|
||||||
if (apiProcess && name !== ImmichWorker.API) {
|
if (apiProcess && name !== ImmichWorker.Api) {
|
||||||
console.error('Killing api process');
|
console.error('Killing api process');
|
||||||
apiProcess.kill('SIGTERM');
|
apiProcess.kill('SIGTERM');
|
||||||
apiProcess = undefined;
|
apiProcess = undefined;
|
||||||
@ -34,7 +34,7 @@ function bootstrapWorker(name: ImmichWorker) {
|
|||||||
console.log(`Starting ${name} worker`);
|
console.log(`Starting ${name} worker`);
|
||||||
|
|
||||||
let worker: Worker | ChildProcess;
|
let worker: Worker | ChildProcess;
|
||||||
if (name === ImmichWorker.API) {
|
if (name === ImmichWorker.Api) {
|
||||||
worker = fork(`./dist/workers/${name}.js`, [], {
|
worker = fork(`./dist/workers/${name}.js`, [], {
|
||||||
execArgv: process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)),
|
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() {
|
function bootstrap() {
|
||||||
if (immichApp === 'immich-admin') {
|
if (immichApp === 'immich-admin') {
|
||||||
process.title = 'immich_admin_cli';
|
process.title = 'immich_admin_cli';
|
||||||
process.env.IMMICH_LOG_LEVEL = LogLevel.WARN;
|
process.env.IMMICH_LOG_LEVEL = LogLevel.Warn;
|
||||||
return CommandFactory.run(ImmichAdminModule);
|
return CommandFactory.run(ImmichAdminModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export class AssetUploadInterceptor implements NestInterceptor {
|
|||||||
const req = context.switchToHttp().getRequest<AuthenticatedRequest>();
|
const req = context.switchToHttp().getRequest<AuthenticatedRequest>();
|
||||||
const res = context.switchToHttp().getResponse<Response<AssetMediaResponseDto>>();
|
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);
|
const response = await this.service.getUploadAssetIdByChecksum(req.user, checksum);
|
||||||
if (response) {
|
if (response) {
|
||||||
res.status(200);
|
res.status(200);
|
||||||
|
@ -23,12 +23,12 @@ export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator =
|
|||||||
const decorators: MethodDecorator[] = [
|
const decorators: MethodDecorator[] = [
|
||||||
ApiBearerAuth(),
|
ApiBearerAuth(),
|
||||||
ApiCookieAuth(),
|
ApiCookieAuth(),
|
||||||
ApiSecurity(MetadataKey.API_KEY_SECURITY),
|
ApiSecurity(MetadataKey.ApiKeySecurity),
|
||||||
SetMetadata(MetadataKey.AUTH_ROUTE, options || {}),
|
SetMetadata(MetadataKey.AuthRoute, options || {}),
|
||||||
];
|
];
|
||||||
|
|
||||||
if ((options as SharedLinkRoute)?.sharedLink) {
|
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);
|
return applyDecorators(...decorators);
|
||||||
@ -76,7 +76,7 @@ export class AuthGuard implements CanActivate {
|
|||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const targets = [context.getHandler()];
|
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) {
|
if (!options) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -154,11 +154,11 @@ export class FileUploadInterceptor implements NestInterceptor {
|
|||||||
|
|
||||||
private getHandler(route: RouteKey) {
|
private getHandler(route: RouteKey) {
|
||||||
switch (route) {
|
switch (route) {
|
||||||
case RouteKey.ASSET: {
|
case RouteKey.Asset: {
|
||||||
return this.handlers.assetUpload;
|
return this.handlers.assetUpload;
|
||||||
}
|
}
|
||||||
|
|
||||||
case RouteKey.USER: {
|
case RouteKey.User: {
|
||||||
return this.handlers.userProfile;
|
return this.handlers.userProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
|
|||||||
export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
|
export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
const vectorExtension = await getVectorExtension(queryRunner);
|
const vectorExtension = await getVectorExtension(queryRunner);
|
||||||
if (vectorExtension === DatabaseExtension.VECTORS) {
|
if (vectorExtension === DatabaseExtension.Vectors) {
|
||||||
await queryRunner.query(`SET search_path TO "$user", public, 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> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
const vectorExtension = await getVectorExtension(queryRunner);
|
const vectorExtension = await getVectorExtension(queryRunner);
|
||||||
if (vectorExtension === DatabaseExtension.VECTORS) {
|
if (vectorExtension === DatabaseExtension.Vectors) {
|
||||||
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
|
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ class AlbumAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accessRole =
|
const accessRole =
|
||||||
access === AlbumUserRole.EDITOR ? [AlbumUserRole.EDITOR] : [AlbumUserRole.EDITOR, AlbumUserRole.VIEWER];
|
access === AlbumUserRole.Editor ? [AlbumUserRole.Editor] : [AlbumUserRole.Editor, AlbumUserRole.Viewer];
|
||||||
|
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
@ -178,7 +178,7 @@ class AssetAccess {
|
|||||||
.select('asset.id')
|
.select('asset.id')
|
||||||
.where('asset.id', 'in', [...assetIds])
|
.where('asset.id', 'in', [...assetIds])
|
||||||
.where('asset.ownerId', '=', userId)
|
.where('asset.ownerId', '=', userId)
|
||||||
.$if(!hasElevatedPermission, (eb) => eb.where('asset.visibility', '!=', AssetVisibility.LOCKED))
|
.$if(!hasElevatedPermission, (eb) => eb.where('asset.visibility', '!=', AssetVisibility.Locked))
|
||||||
.execute()
|
.execute()
|
||||||
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
||||||
}
|
}
|
||||||
@ -200,8 +200,8 @@ class AssetAccess {
|
|||||||
.where('partner.sharedWithId', '=', userId)
|
.where('partner.sharedWithId', '=', userId)
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.or([
|
eb.or([
|
||||||
eb('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE)),
|
eb('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)),
|
||||||
eb('asset.visibility', '=', sql.lit(AssetVisibility.HIDDEN)),
|
eb('asset.visibility', '=', sql.lit(AssetVisibility.Hidden)),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ export class ActivityRepository {
|
|||||||
.where('activity.albumId', '=', albumId)
|
.where('activity.albumId', '=', albumId)
|
||||||
.where(({ or, and, eb }) =>
|
.where(({ or, and, eb }) =>
|
||||||
or([
|
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),
|
eb('asset.id', 'is', null),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
@ -24,7 +24,7 @@ export class AlbumUserRepository {
|
|||||||
.executeTakeFirstOrThrow();
|
.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>) {
|
update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable<AlbumUserTable>) {
|
||||||
return this.db
|
return this.db
|
||||||
.updateTable('album_user')
|
.updateTable('album_user')
|
||||||
|
@ -62,7 +62,7 @@ export class AssetJobRepository {
|
|||||||
.select(['asset.id', 'asset.thumbhash'])
|
.select(['asset.id', 'asset.thumbhash'])
|
||||||
.select(withFiles)
|
.select(withFiles)
|
||||||
.where('asset.deletedAt', 'is', null)
|
.where('asset.deletedAt', 'is', null)
|
||||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||||
.$if(!force, (qb) =>
|
.$if(!force, (qb) =>
|
||||||
qb
|
qb
|
||||||
// If there aren't any entries, metadata extraction hasn't run yet which is required for thumbnails
|
// 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();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, AssetFileType.THUMBNAIL] })
|
@GenerateSql({ params: [DummyValue.UUID, AssetFileType.Thumbnail] })
|
||||||
getAlbumThumbnailFiles(id: string, fileType?: AssetFileType) {
|
getAlbumThumbnailFiles(id: string, fileType?: AssetFileType) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset_file')
|
.selectFrom('asset_file')
|
||||||
@ -130,7 +130,7 @@ export class AssetJobRepository {
|
|||||||
private assetsWithPreviews() {
|
private assetsWithPreviews() {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||||
.where('asset.deletedAt', 'is', null)
|
.where('asset.deletedAt', 'is', null)
|
||||||
.innerJoin('asset_job_status as job_status', 'assetId', 'asset.id')
|
.innerJoin('asset_job_status as job_status', 'assetId', 'asset.id')
|
||||||
.where('job_status.previewAt', 'is not', null);
|
.where('job_status.previewAt', 'is not', null);
|
||||||
@ -167,7 +167,7 @@ export class AssetJobRepository {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.select(['asset.id', 'asset.visibility'])
|
.select(['asset.id', 'asset.visibility'])
|
||||||
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
|
.select((eb) => withFiles(eb, AssetFileType.Preview))
|
||||||
.where('asset.id', '=', id)
|
.where('asset.id', '=', id)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
@ -179,7 +179,7 @@ export class AssetJobRepository {
|
|||||||
.select(['asset.id', 'asset.visibility'])
|
.select(['asset.id', 'asset.visibility'])
|
||||||
.$call(withExifInner)
|
.$call(withExifInner)
|
||||||
.select((eb) => withFaces(eb, true))
|
.select((eb) => withFaces(eb, true))
|
||||||
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
|
.select((eb) => withFiles(eb, AssetFileType.Preview))
|
||||||
.where('asset.id', '=', id)
|
.where('asset.id', '=', id)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
@ -225,7 +225,7 @@ export class AssetJobRepository {
|
|||||||
.select(['stack.id', 'stack.primaryAssetId'])
|
.select(['stack.id', 'stack.primaryAssetId'])
|
||||||
.select((eb) => eb.fn<Asset[]>('array_agg', [eb.table('stacked')]).as('assets'))
|
.select((eb) => eb.fn<Asset[]>('array_agg', [eb.table('stacked')]).as('assets'))
|
||||||
.where('stacked.deletedAt', 'is not', null)
|
.where('stacked.deletedAt', 'is not', null)
|
||||||
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
|
.where('stacked.visibility', '=', AssetVisibility.Timeline)
|
||||||
.whereRef('stacked.stackId', '=', 'stack.id')
|
.whereRef('stacked.stackId', '=', 'stack.id')
|
||||||
.groupBy('stack.id')
|
.groupBy('stack.id')
|
||||||
.as('stacked_assets'),
|
.as('stacked_assets'),
|
||||||
@ -241,11 +241,11 @@ export class AssetJobRepository {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.select(['asset.id'])
|
.select(['asset.id'])
|
||||||
.where('asset.type', '=', AssetType.VIDEO)
|
.where('asset.type', '=', AssetType.Video)
|
||||||
.$if(!force, (qb) =>
|
.$if(!force, (qb) =>
|
||||||
qb
|
qb
|
||||||
.where((eb) => eb.or([eb('asset.encodedVideoPath', 'is', null), eb('asset.encodedVideoPath', '=', '')]))
|
.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)
|
.where('asset.deletedAt', 'is', null)
|
||||||
.stream();
|
.stream();
|
||||||
@ -257,7 +257,7 @@ export class AssetJobRepository {
|
|||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.select(['asset.id', 'asset.ownerId', 'asset.originalPath', 'asset.encodedVideoPath'])
|
.select(['asset.id', 'asset.ownerId', 'asset.originalPath', 'asset.encodedVideoPath'])
|
||||||
.where('asset.id', '=', id)
|
.where('asset.id', '=', id)
|
||||||
.where('asset.type', '=', AssetType.VIDEO)
|
.where('asset.type', '=', AssetType.Video)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ export class AssetJobRepository {
|
|||||||
.$if(!force, (qb) =>
|
.$if(!force, (qb) =>
|
||||||
qb.where((eb) => eb.or([eb('asset.sidecarPath', '=', ''), eb('asset.sidecarPath', 'is', null)])),
|
qb.where((eb) => eb.or([eb('asset.sidecarPath', '=', ''), eb('asset.sidecarPath', 'is', null)])),
|
||||||
)
|
)
|
||||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,13 +230,13 @@ export class AssetRepository {
|
|||||||
.where('asset_job_status.previewAt', 'is not', null)
|
.where('asset_job_status.previewAt', 'is not', null)
|
||||||
.where(sql`(asset."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`)
|
.where(sql`(asset."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`)
|
||||||
.where('asset.ownerId', '=', anyUuid(ownerIds))
|
.where('asset.ownerId', '=', anyUuid(ownerIds))
|
||||||
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
|
.where('asset.visibility', '=', AssetVisibility.Timeline)
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.exists((qb) =>
|
eb.exists((qb) =>
|
||||||
qb
|
qb
|
||||||
.selectFrom('asset_file')
|
.selectFrom('asset_file')
|
||||||
.whereRef('assetId', '=', 'asset.id')
|
.whereRef('assetId', '=', 'asset.id')
|
||||||
.where('asset_file.type', '=', AssetFileType.PREVIEW),
|
.where('asset_file.type', '=', AssetFileType.Preview),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.where('asset.deletedAt', 'is', null)
|
.where('asset.deletedAt', 'is', null)
|
||||||
@ -318,7 +318,7 @@ export class AssetRepository {
|
|||||||
.select(['deviceAssetId'])
|
.select(['deviceAssetId'])
|
||||||
.where('ownerId', '=', asUuid(ownerId))
|
.where('ownerId', '=', asUuid(ownerId))
|
||||||
.where('deviceId', '=', deviceId)
|
.where('deviceId', '=', deviceId)
|
||||||
.where('visibility', '!=', AssetVisibility.HIDDEN)
|
.where('visibility', '!=', AssetVisibility.Hidden)
|
||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
@ -363,7 +363,7 @@ export class AssetRepository {
|
|||||||
.whereRef('stacked.stackId', '=', 'stack.id')
|
.whereRef('stacked.stackId', '=', 'stack.id')
|
||||||
.whereRef('stacked.id', '!=', 'stack.primaryAssetId')
|
.whereRef('stacked.id', '!=', 'stack.primaryAssetId')
|
||||||
.where('stacked.deletedAt', 'is', null)
|
.where('stacked.deletedAt', 'is', null)
|
||||||
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
|
.where('stacked.visibility', '=', AssetVisibility.Timeline)
|
||||||
.groupBy('stack.id')
|
.groupBy('stack.id')
|
||||||
.as('stacked_assets'),
|
.as('stacked_assets'),
|
||||||
(join) => join.on('stack.id', 'is not', null),
|
(join) => join.on('stack.id', 'is not', null),
|
||||||
@ -463,15 +463,15 @@ export class AssetRepository {
|
|||||||
getStatistics(ownerId: string, { visibility, isFavorite, isTrashed }: AssetStatsOptions): Promise<AssetStats> {
|
getStatistics(ownerId: string, { visibility, isFavorite, isTrashed }: AssetStatsOptions): Promise<AssetStats> {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.AUDIO).as(AssetType.AUDIO))
|
.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.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.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.Other).as(AssetType.Other))
|
||||||
.where('ownerId', '=', asUuid(ownerId))
|
.where('ownerId', '=', asUuid(ownerId))
|
||||||
.$if(visibility === undefined, withDefaultVisibility)
|
.$if(visibility === undefined, withDefaultVisibility)
|
||||||
.$if(!!visibility, (qb) => qb.where('asset.visibility', '=', visibility!))
|
.$if(!!visibility, (qb) => qb.where('asset.visibility', '=', visibility!))
|
||||||
.$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!))
|
.$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)
|
.where('deletedAt', isTrashed ? 'is not' : 'is', null)
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
}
|
}
|
||||||
@ -496,7 +496,7 @@ export class AssetRepository {
|
|||||||
qb
|
qb
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.select(truncatedDate<Date>().as('timeBucket'))
|
.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)
|
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||||
.$if(options.visibility === undefined, withDefaultVisibility)
|
.$if(options.visibility === undefined, withDefaultVisibility)
|
||||||
.$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!))
|
.$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'))
|
.select(sql`array[stacked."stackId"::text, count('stacked')::text]`.as('stack'))
|
||||||
.whereRef('stacked.stackId', '=', 'asset.stackId')
|
.whereRef('stacked.stackId', '=', 'asset.stackId')
|
||||||
.where('stacked.deletedAt', 'is', null)
|
.where('stacked.deletedAt', 'is', null)
|
||||||
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
|
.where('stacked.visibility', '=', AssetVisibility.Timeline)
|
||||||
.groupBy('stacked.stackId')
|
.groupBy('stacked.stackId')
|
||||||
.as('stacked_assets'),
|
.as('stacked_assets'),
|
||||||
(join) => join.onTrue(),
|
(join) => join.onTrue(),
|
||||||
@ -617,7 +617,7 @@ export class AssetRepository {
|
|||||||
.$if(options.isDuplicate !== undefined, (qb) =>
|
.$if(options.isDuplicate !== undefined, (qb) =>
|
||||||
qb.where('asset.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
|
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!))
|
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
|
||||||
.orderBy('asset.fileCreatedAt', options.order ?? 'desc'),
|
.orderBy('asset.fileCreatedAt', options.order ?? 'desc'),
|
||||||
)
|
)
|
||||||
@ -671,8 +671,8 @@ export class AssetRepository {
|
|||||||
.select(['assetId as data', 'asset_exif.city as value'])
|
.select(['assetId as data', 'asset_exif.city as value'])
|
||||||
.$narrowType<{ value: NotNull }>()
|
.$narrowType<{ value: NotNull }>()
|
||||||
.where('ownerId', '=', asUuid(ownerId))
|
.where('ownerId', '=', asUuid(ownerId))
|
||||||
.where('visibility', '=', AssetVisibility.TIMELINE)
|
.where('visibility', '=', AssetVisibility.Timeline)
|
||||||
.where('type', '=', AssetType.IMAGE)
|
.where('type', '=', AssetType.Image)
|
||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.limit(maxFields)
|
.limit(maxFields)
|
||||||
.execute();
|
.execute();
|
||||||
@ -710,7 +710,7 @@ export class AssetRepository {
|
|||||||
)
|
)
|
||||||
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo<Stack | null>().as('stack'))
|
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo<Stack | null>().as('stack'))
|
||||||
.where('asset.ownerId', '=', asUuid(ownerId))
|
.where('asset.ownerId', '=', asUuid(ownerId))
|
||||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||||
.where('asset.updatedAt', '<=', updatedUntil)
|
.where('asset.updatedAt', '<=', updatedUntil)
|
||||||
.$if(!!lastId, (qb) => qb.where('asset.id', '>', lastId!))
|
.$if(!!lastId, (qb) => qb.where('asset.id', '>', lastId!))
|
||||||
.orderBy('asset.id')
|
.orderBy('asset.id')
|
||||||
@ -738,7 +738,7 @@ export class AssetRepository {
|
|||||||
)
|
)
|
||||||
.select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo<Stack | null>()).as('stack'))
|
.select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo<Stack | null>()).as('stack'))
|
||||||
.where('asset.ownerId', '=', anyUuid(options.userIds))
|
.where('asset.ownerId', '=', anyUuid(options.userIds))
|
||||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||||
.where('asset.updatedAt', '>', options.updatedAfter)
|
.where('asset.updatedAt', '>', options.updatedAfter)
|
||||||
.limit(options.limit)
|
.limit(options.limit)
|
||||||
.execute();
|
.execute();
|
||||||
|
@ -18,7 +18,7 @@ export class AuditRepository {
|
|||||||
@GenerateSql({
|
@GenerateSql({
|
||||||
params: [
|
params: [
|
||||||
DummyValue.DATE,
|
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[]> {
|
async getAfter(since: Date, options: AuditSearch): Promise<string[]> {
|
||||||
|
@ -275,14 +275,14 @@ describe('getEnv', () => {
|
|||||||
process.env.IMMICH_TELEMETRY_EXCLUDE = 'job';
|
process.env.IMMICH_TELEMETRY_EXCLUDE = 'job';
|
||||||
const { telemetry } = getEnv();
|
const { telemetry } = getEnv();
|
||||||
expect(telemetry.metrics).toEqual(
|
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', () => {
|
it('should run with specific telemetry metrics', () => {
|
||||||
process.env.IMMICH_TELEMETRY_INCLUDE = 'io, host, api';
|
process.env.IMMICH_TELEMETRY_INCLUDE = 'io, host, api';
|
||||||
const { telemetry } = getEnv();
|
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 excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
|
||||||
const workers = [...setDifference(includedWorkers, excludedWorkers)];
|
const workers = [...setDifference(includedWorkers, excludedWorkers)];
|
||||||
for (const worker of workers) {
|
for (const worker of workers) {
|
||||||
@ -145,8 +145,8 @@ const getEnv = (): EnvData => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const environment = dto.IMMICH_ENV || ImmichEnvironment.PRODUCTION;
|
const environment = dto.IMMICH_ENV || ImmichEnvironment.Production;
|
||||||
const isProd = environment === ImmichEnvironment.PRODUCTION;
|
const isProd = environment === ImmichEnvironment.Production;
|
||||||
const buildFolder = dto.IMMICH_BUILD_DATA || '/build';
|
const buildFolder = dto.IMMICH_BUILD_DATA || '/build';
|
||||||
const folders = {
|
const folders = {
|
||||||
geodata: join(buildFolder, 'geodata'),
|
geodata: join(buildFolder, 'geodata'),
|
||||||
@ -199,15 +199,15 @@ const getEnv = (): EnvData => {
|
|||||||
let vectorExtension: VectorExtension | undefined;
|
let vectorExtension: VectorExtension | undefined;
|
||||||
switch (dto.DB_VECTOR_EXTENSION) {
|
switch (dto.DB_VECTOR_EXTENSION) {
|
||||||
case 'pgvector': {
|
case 'pgvector': {
|
||||||
vectorExtension = DatabaseExtension.VECTOR;
|
vectorExtension = DatabaseExtension.Vector;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'pgvecto.rs': {
|
case 'pgvecto.rs': {
|
||||||
vectorExtension = DatabaseExtension.VECTORS;
|
vectorExtension = DatabaseExtension.Vectors;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'vectorchord': {
|
case 'vectorchord': {
|
||||||
vectorExtension = DatabaseExtension.VECTORCHORD;
|
vectorExtension = DatabaseExtension.VectorChord;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -254,11 +254,11 @@ const getEnv = (): EnvData => {
|
|||||||
mount: true,
|
mount: true,
|
||||||
generateId: true,
|
generateId: true,
|
||||||
setup: (cls, req: Request, res: Response) => {
|
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 headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues;
|
||||||
const cid = headerValue || cls.get(CLS_ID);
|
const cid = headerValue || cls.get(CLS_ID);
|
||||||
cls.set(CLS_ID, cid);
|
cls.set(CLS_ID, cid);
|
||||||
res.header(ImmichHeader.CID, cid);
|
res.header(ImmichHeader.Cid, cid);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -278,9 +278,9 @@ const getEnv = (): EnvData => {
|
|||||||
|
|
||||||
otel: {
|
otel: {
|
||||||
metrics: {
|
metrics: {
|
||||||
hostMetrics: telemetries.has(ImmichTelemetry.HOST),
|
hostMetrics: telemetries.has(ImmichTelemetry.Host),
|
||||||
apiMetrics: {
|
apiMetrics: {
|
||||||
enable: telemetries.has(ImmichTelemetry.API),
|
enable: telemetries.has(ImmichTelemetry.Api),
|
||||||
ignoreRoutes: excludePaths,
|
ignoreRoutes: excludePaths,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -335,7 +335,7 @@ export class ConfigRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isDev() {
|
isDev() {
|
||||||
return this.getEnv().environment === ImmichEnvironment.DEVELOPMENT;
|
return this.getEnv().environment === ImmichEnvironment.Development;
|
||||||
}
|
}
|
||||||
|
|
||||||
getWorker() {
|
getWorker() {
|
||||||
|
@ -53,8 +53,8 @@ export async function getVectorExtension(runner: Kysely<DB> | QueryRunner): Prom
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const probes: Record<VectorIndex, number> = {
|
export const probes: Record<VectorIndex, number> = {
|
||||||
[VectorIndex.CLIP]: 1,
|
[VectorIndex.Clip]: 1,
|
||||||
[VectorIndex.FACE]: 1,
|
[VectorIndex.Face]: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -77,7 +77,7 @@ export class DatabaseRepository {
|
|||||||
return getVectorExtension(this.db);
|
return getVectorExtension(this.db);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [[DatabaseExtension.VECTORS]] })
|
@GenerateSql({ params: [[DatabaseExtension.Vectors]] })
|
||||||
async getExtensionVersions(extensions: readonly DatabaseExtension[]): Promise<ExtensionVersion[]> {
|
async getExtensionVersions(extensions: readonly DatabaseExtension[]): Promise<ExtensionVersion[]> {
|
||||||
const { rows } = await sql<ExtensionVersion>`
|
const { rows } = await sql<ExtensionVersion>`
|
||||||
SELECT name, default_version as "availableVersion", installed_version as "installedVersion"
|
SELECT name, default_version as "availableVersion", installed_version as "installedVersion"
|
||||||
@ -89,13 +89,13 @@ export class DatabaseRepository {
|
|||||||
|
|
||||||
getExtensionVersionRange(extension: VectorExtension): string {
|
getExtensionVersionRange(extension: VectorExtension): string {
|
||||||
switch (extension) {
|
switch (extension) {
|
||||||
case DatabaseExtension.VECTORCHORD: {
|
case DatabaseExtension.VectorChord: {
|
||||||
return VECTORCHORD_VERSION_RANGE;
|
return VECTORCHORD_VERSION_RANGE;
|
||||||
}
|
}
|
||||||
case DatabaseExtension.VECTORS: {
|
case DatabaseExtension.Vectors: {
|
||||||
return VECTORS_VERSION_RANGE;
|
return VECTORS_VERSION_RANGE;
|
||||||
}
|
}
|
||||||
case DatabaseExtension.VECTOR: {
|
case DatabaseExtension.Vector: {
|
||||||
return VECTOR_VERSION_RANGE;
|
return VECTOR_VERSION_RANGE;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -117,7 +117,7 @@ export class DatabaseRepository {
|
|||||||
async createExtension(extension: DatabaseExtension): Promise<void> {
|
async createExtension(extension: DatabaseExtension): Promise<void> {
|
||||||
this.logger.log(`Creating ${EXTENSION_NAMES[extension]} extension`);
|
this.logger.log(`Creating ${EXTENSION_NAMES[extension]} extension`);
|
||||||
await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db);
|
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());
|
const dbName = sql.id(await this.getDatabaseName());
|
||||||
await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db);
|
await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db);
|
||||||
await sql`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([
|
await Promise.all([
|
||||||
this.db.schema.dropIndex(VectorIndex.CLIP).ifExists().execute(),
|
this.db.schema.dropIndex(VectorIndex.Clip).ifExists().execute(),
|
||||||
this.db.schema.dropIndex(VectorIndex.FACE).ifExists().execute(),
|
this.db.schema.dropIndex(VectorIndex.Face).ifExists().execute(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await this.db.transaction().execute(async (tx) => {
|
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);
|
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);
|
await sql`SELECT pgvectors_upgrade()`.execute(tx);
|
||||||
restartRequired = true;
|
restartRequired = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!restartRequired) {
|
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 };
|
return { restartRequired };
|
||||||
@ -171,7 +171,7 @@ export class DatabaseRepository {
|
|||||||
|
|
||||||
async prewarm(index: VectorIndex): Promise<void> {
|
async prewarm(index: VectorIndex): Promise<void> {
|
||||||
const vectorExtension = await getVectorExtension(this.db);
|
const vectorExtension = await getVectorExtension(this.db);
|
||||||
if (vectorExtension !== DatabaseExtension.VECTORCHORD) {
|
if (vectorExtension !== DatabaseExtension.VectorChord) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.logger.debug(`Prewarming ${index}`);
|
this.logger.debug(`Prewarming ${index}`);
|
||||||
@ -196,19 +196,19 @@ export class DatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (vectorExtension) {
|
switch (vectorExtension) {
|
||||||
case DatabaseExtension.VECTOR: {
|
case DatabaseExtension.Vector: {
|
||||||
if (!row.indexdef.toLowerCase().includes('using hnsw')) {
|
if (!row.indexdef.toLowerCase().includes('using hnsw')) {
|
||||||
promises.push(this.reindexVectors(indexName));
|
promises.push(this.reindexVectors(indexName));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DatabaseExtension.VECTORS: {
|
case DatabaseExtension.Vectors: {
|
||||||
if (!row.indexdef.toLowerCase().includes('using vectors')) {
|
if (!row.indexdef.toLowerCase().includes('using vectors')) {
|
||||||
promises.push(this.reindexVectors(indexName));
|
promises.push(this.reindexVectors(indexName));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DatabaseExtension.VECTORCHORD: {
|
case DatabaseExtension.VectorChord: {
|
||||||
const matches = row.indexdef.match(/(?<=lists = \[)\d+/g);
|
const matches = row.indexdef.match(/(?<=lists = \[)\d+/g);
|
||||||
const lists = matches && matches.length > 0 ? Number(matches[0]) : 1;
|
const lists = matches && matches.length > 0 ? Number(matches[0]) : 1;
|
||||||
promises.push(
|
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)} ADD COLUMN embedding real[] NOT NULL`.execute(tx);
|
||||||
}
|
}
|
||||||
await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE real[]`.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`
|
await sql`
|
||||||
ALTER TABLE ${sql.raw(table)}
|
ALTER TABLE ${sql.raw(table)}
|
||||||
ALTER COLUMN embedding
|
ALTER COLUMN embedding
|
||||||
@ -329,11 +329,11 @@ export class DatabaseRepository {
|
|||||||
.alterColumn('embedding', (col) => col.setDataType(sql.raw(`vector(${dimSize})`)))
|
.alterColumn('embedding', (col) => col.setDataType(sql.raw(`vector(${dimSize})`)))
|
||||||
.execute();
|
.execute();
|
||||||
await sql
|
await sql
|
||||||
.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: VectorIndex.CLIP }))
|
.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: VectorIndex.Clip }))
|
||||||
.execute(trx);
|
.execute(trx);
|
||||||
await trx.schema.alterTable('smart_search').dropConstraint('dim_size_constraint').ifExists().execute();
|
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);
|
await sql`vacuum analyze ${sql.table('smart_search')}`.execute(this.db);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ export class DownloadRepository {
|
|||||||
downloadUserId(userId: string) {
|
downloadUserId(userId: string) {
|
||||||
return builder(this.db)
|
return builder(this.db)
|
||||||
.where('asset.ownerId', '=', userId)
|
.where('asset.ownerId', '=', userId)
|
||||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,14 +109,14 @@ export class DuplicateRepository {
|
|||||||
assetId: DummyValue.UUID,
|
assetId: DummyValue.UUID,
|
||||||
embedding: DummyValue.VECTOR,
|
embedding: DummyValue.VECTOR,
|
||||||
maxDistance: 0.6,
|
maxDistance: 0.6,
|
||||||
type: AssetType.IMAGE,
|
type: AssetType.Image,
|
||||||
userIds: [DummyValue.UUID],
|
userIds: [DummyValue.UUID],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
search({ assetId, embedding, maxDistance, type, userIds }: DuplicateSearch) {
|
search({ assetId, embedding, maxDistance, type, userIds }: DuplicateSearch) {
|
||||||
return this.db.transaction().execute(async (trx) => {
|
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
|
return await trx
|
||||||
.with('cte', (qb) =>
|
.with('cte', (qb) =>
|
||||||
qb
|
qb
|
||||||
|
@ -166,7 +166,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = reflector.get<EventConfig>(MetadataKey.EVENT_CONFIG, handler);
|
const event = reflector.get<EventConfig>(MetadataKey.EventConfig, handler);
|
||||||
if (!event) {
|
if (!event) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ export class JobRepository {
|
|||||||
const instance = this.moduleRef.get<any>(Service);
|
const instance = this.moduleRef.get<any>(Service);
|
||||||
for (const methodName of getMethodNames(instance)) {
|
for (const methodName of getMethodNames(instance)) {
|
||||||
const handler = instance[methodName];
|
const handler = instance[methodName];
|
||||||
const config = reflector.get<JobConfig>(MetadataKey.JOB_CONFIG, handler);
|
const config = reflector.get<JobConfig>(MetadataKey.JobConfig, handler);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ export class JobRepository {
|
|||||||
const item = this.handlers[name as JobName];
|
const item = this.handlers[name as JobName];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
this.logger.warn(`Skipping unknown job: "${name}"`);
|
this.logger.warn(`Skipping unknown job: "${name}"`);
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.handler(data);
|
return item.handler(data);
|
||||||
@ -205,20 +205,20 @@ export class JobRepository {
|
|||||||
|
|
||||||
private getJobOptions(item: JobItem): JobsOptions | null {
|
private getJobOptions(item: JobItem): JobsOptions | null {
|
||||||
switch (item.name) {
|
switch (item.name) {
|
||||||
case JobName.NOTIFY_ALBUM_UPDATE: {
|
case JobName.NotifyAlbumUpdate: {
|
||||||
return {
|
return {
|
||||||
jobId: `${item.data.id}/${item.data.recipientId}`,
|
jobId: `${item.data.id}/${item.data.recipientId}`,
|
||||||
delay: item.data?.delay,
|
delay: item.data?.delay,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE: {
|
case JobName.StorageTemplateMigrationSingle: {
|
||||||
return { jobId: item.data.id };
|
return { jobId: item.data.id };
|
||||||
}
|
}
|
||||||
case JobName.GENERATE_PERSON_THUMBNAIL: {
|
case JobName.GeneratePersonThumbnail: {
|
||||||
return { priority: 1 };
|
return { priority: 1 };
|
||||||
}
|
}
|
||||||
case JobName.QUEUE_FACIAL_RECOGNITION: {
|
case JobName.QueueFacialRecognition: {
|
||||||
return { jobId: JobName.QUEUE_FACIAL_RECOGNITION };
|
return { jobId: JobName.QueueFacialRecognition };
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return null;
|
return null;
|
||||||
|
@ -79,7 +79,7 @@ export class LibraryRepository {
|
|||||||
eb.fn
|
eb.fn
|
||||||
.countAll<number>()
|
.countAll<number>()
|
||||||
.filterWhere((eb) =>
|
.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'),
|
.as('photos'),
|
||||||
)
|
)
|
||||||
@ -87,7 +87,7 @@ export class LibraryRepository {
|
|||||||
eb.fn
|
eb.fn
|
||||||
.countAll<number>()
|
.countAll<number>()
|
||||||
.filterWhere((eb) =>
|
.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'),
|
.as('videos'),
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,7 @@ describe(LoggingRepository.name, () => {
|
|||||||
describe('formatContext', () => {
|
describe('formatContext', () => {
|
||||||
it('should use colors', () => {
|
it('should use colors', () => {
|
||||||
sut = new LoggingRepository(clsMock, configMock);
|
sut = new LoggingRepository(clsMock, configMock);
|
||||||
sut.setAppName(ImmichWorker.API);
|
sut.setAppName(ImmichWorker.Api);
|
||||||
|
|
||||||
const logger = new MyConsoleLogger(clsMock, { color: true });
|
const logger = new MyConsoleLogger(clsMock, { color: true });
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ describe(LoggingRepository.name, () => {
|
|||||||
|
|
||||||
it('should not use colors when color is false', () => {
|
it('should not use colors when color is false', () => {
|
||||||
sut = new LoggingRepository(clsMock, configMock);
|
sut = new LoggingRepository(clsMock, configMock);
|
||||||
sut.setAppName(ImmichWorker.API);
|
sut.setAppName(ImmichWorker.Api);
|
||||||
|
|
||||||
const logger = new MyConsoleLogger(clsMock, { color: false });
|
const logger = new MyConsoleLogger(clsMock, { color: false });
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { ConfigRepository } from 'src/repositories/config.repository';
|
|||||||
type LogDetails = any;
|
type LogDetails = any;
|
||||||
type LogFunction = () => string;
|
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 {
|
enum LogColor {
|
||||||
RED = 31,
|
RED = 31,
|
||||||
@ -20,7 +20,7 @@ enum LogColor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let appName: string | undefined;
|
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 {
|
export class MyConsoleLogger extends ConsoleLogger {
|
||||||
private isColorEnabled: boolean;
|
private isColorEnabled: boolean;
|
||||||
@ -106,35 +106,35 @@ export class LoggingRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
verbose(message: string, ...details: LogDetails) {
|
verbose(message: string, ...details: LogDetails) {
|
||||||
this.handleMessage(LogLevel.VERBOSE, message, details);
|
this.handleMessage(LogLevel.Verbose, message, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseFn(message: LogFunction, ...details: LogDetails) {
|
verboseFn(message: LogFunction, ...details: LogDetails) {
|
||||||
this.handleFunction(LogLevel.VERBOSE, message, details);
|
this.handleFunction(LogLevel.Verbose, message, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(message: string, ...details: LogDetails) {
|
debug(message: string, ...details: LogDetails) {
|
||||||
this.handleMessage(LogLevel.DEBUG, message, details);
|
this.handleMessage(LogLevel.Debug, message, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
debugFn(message: LogFunction, ...details: LogDetails) {
|
debugFn(message: LogFunction, ...details: LogDetails) {
|
||||||
this.handleFunction(LogLevel.DEBUG, message, details);
|
this.handleFunction(LogLevel.Debug, message, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
log(message: string, ...details: LogDetails) {
|
log(message: string, ...details: LogDetails) {
|
||||||
this.handleMessage(LogLevel.LOG, message, details);
|
this.handleMessage(LogLevel.Log, message, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
warn(message: string, ...details: LogDetails) {
|
warn(message: string, ...details: LogDetails) {
|
||||||
this.handleMessage(LogLevel.WARN, message, details);
|
this.handleMessage(LogLevel.Warn, message, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message: string | Error, ...details: LogDetails) {
|
error(message: string | Error, ...details: LogDetails) {
|
||||||
this.handleMessage(LogLevel.ERROR, message, details);
|
this.handleMessage(LogLevel.Error, message, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
fatal(message: string, ...details: LogDetails) {
|
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[]) {
|
private handleFunction(level: LogLevel, message: LogFunction, details: LogDetails[]) {
|
||||||
@ -145,32 +145,32 @@ export class LoggingRepository {
|
|||||||
|
|
||||||
private handleMessage(level: LogLevel, message: string | Error, details: LogDetails[]) {
|
private handleMessage(level: LogLevel, message: string | Error, details: LogDetails[]) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LogLevel.VERBOSE: {
|
case LogLevel.Verbose: {
|
||||||
this.logger.verbose(message, ...details);
|
this.logger.verbose(message, ...details);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case LogLevel.DEBUG: {
|
case LogLevel.Debug: {
|
||||||
this.logger.debug(message, ...details);
|
this.logger.debug(message, ...details);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case LogLevel.LOG: {
|
case LogLevel.Log: {
|
||||||
this.logger.log(message, ...details);
|
this.logger.log(message, ...details);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case LogLevel.WARN: {
|
case LogLevel.Warn: {
|
||||||
this.logger.warn(message, ...details);
|
this.logger.warn(message, ...details);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case LogLevel.ERROR: {
|
case LogLevel.Error: {
|
||||||
this.logger.error(message, ...details);
|
this.logger.error(message, ...details);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case LogLevel.FATAL: {
|
case LogLevel.Fatal: {
|
||||||
this.logger.fatal(message, ...details);
|
this.logger.fatal(message, ...details);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -61,14 +61,14 @@ export class MapRepository {
|
|||||||
const geodataDate = await readFile(resourcePaths.geodata.dateFile, 'utf8');
|
const geodataDate = await readFile(resourcePaths.geodata.dateFile, 'utf8');
|
||||||
|
|
||||||
// TODO move to service init
|
// 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) {
|
if (geocodingMetadata?.lastUpdate === geodataDate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([this.importGeodata(), this.importNaturalEarthCountries()]);
|
await Promise.all([this.importGeodata(), this.importNaturalEarthCountries()]);
|
||||||
|
|
||||||
await this.metadataRepository.set(SystemMetadataKey.REVERSE_GEOCODING_STATE, {
|
await this.metadataRepository.set(SystemMetadataKey.ReverseGeocodingState, {
|
||||||
lastUpdate: geodataDate,
|
lastUpdate: geodataDate,
|
||||||
lastImportFileName: citiesFile,
|
lastImportFileName: citiesFile,
|
||||||
});
|
});
|
||||||
@ -102,13 +102,13 @@ export class MapRepository {
|
|||||||
.$if(isArchived === true, (qb) =>
|
.$if(isArchived === true, (qb) =>
|
||||||
qb.where((eb) =>
|
qb.where((eb) =>
|
||||||
eb.or([
|
eb.or([
|
||||||
eb('asset.visibility', '=', AssetVisibility.TIMELINE),
|
eb('asset.visibility', '=', AssetVisibility.Timeline),
|
||||||
eb('asset.visibility', '=', AssetVisibility.ARCHIVE),
|
eb('asset.visibility', '=', AssetVisibility.Archive),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.$if(isArchived === false || isArchived === undefined, (qb) =>
|
.$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(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!))
|
||||||
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))
|
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))
|
||||||
|
@ -55,28 +55,28 @@ export class MediaRepository {
|
|||||||
async extract(input: string): Promise<ExtractResult | null> {
|
async extract(input: string): Promise<ExtractResult | null> {
|
||||||
try {
|
try {
|
||||||
const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw2', input);
|
const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw2', input);
|
||||||
return { buffer, format: RawExtractedFormat.JPEG };
|
return { buffer, format: RawExtractedFormat.Jpeg };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.debug('Could not extract JpgFromRaw2 buffer from image, trying JPEG from RAW next', error.message);
|
this.logger.debug('Could not extract JpgFromRaw2 buffer from image, trying JPEG from RAW next', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw', input);
|
const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw', input);
|
||||||
return { buffer, format: RawExtractedFormat.JPEG };
|
return { buffer, format: RawExtractedFormat.Jpeg };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.debug('Could not extract JPEG buffer from image, trying PreviewJXL next', error.message);
|
this.logger.debug('Could not extract JPEG buffer from image, trying PreviewJXL next', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewJXL', input);
|
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewJXL', input);
|
||||||
return { buffer, format: RawExtractedFormat.JXL };
|
return { buffer, format: RawExtractedFormat.Jxl };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.debug('Could not extract PreviewJXL buffer from image, trying PreviewImage next', error.message);
|
this.logger.debug('Could not extract PreviewJXL buffer from image, trying PreviewImage next', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewImage', input);
|
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewImage', input);
|
||||||
return { buffer, format: RawExtractedFormat.JPEG };
|
return { buffer, format: RawExtractedFormat.Jpeg };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.debug('Could not extract preview buffer from image', error.message);
|
this.logger.debug('Could not extract preview buffer from image', error.message);
|
||||||
return null;
|
return null;
|
||||||
@ -142,7 +142,7 @@ export class MediaRepository {
|
|||||||
limitInputPixels: false,
|
limitInputPixels: false,
|
||||||
raw: options.raw,
|
raw: options.raw,
|
||||||
})
|
})
|
||||||
.pipelineColorspace(options.colorspace === Colorspace.SRGB ? 'srgb' : 'rgb16')
|
.pipelineColorspace(options.colorspace === Colorspace.Srgb ? 'srgb' : 'rgb16')
|
||||||
.withIccProfile(options.colorspace);
|
.withIccProfile(options.colorspace);
|
||||||
|
|
||||||
if (!options.raw) {
|
if (!options.raw) {
|
||||||
@ -267,7 +267,7 @@ export class MediaRepository {
|
|||||||
|
|
||||||
const { frameCount, percentInterval } = options.progress;
|
const { frameCount, percentInterval } = options.progress;
|
||||||
const frameInterval = Math.ceil(frameCount / (100 / percentInterval));
|
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;
|
let lastProgressFrame: number = 0;
|
||||||
ffmpegCall.on('progress', (progress: ProgressEvent) => {
|
ffmpegCall.on('progress', (progress: ProgressEvent) => {
|
||||||
if (progress.frames - lastProgressFrame < frameInterval) {
|
if (progress.frames - lastProgressFrame < frameInterval) {
|
||||||
|
@ -19,7 +19,7 @@ export class MemoryRepository implements IBulkAsset {
|
|||||||
.deleteFrom('memory_asset')
|
.deleteFrom('memory_asset')
|
||||||
.using('asset')
|
.using('asset')
|
||||||
.whereRef('memory_asset.assetsId', '=', 'asset.id')
|
.whereRef('memory_asset.assetsId', '=', 'asset.id')
|
||||||
.where('asset.visibility', '!=', AssetVisibility.TIMELINE)
|
.where('asset.visibility', '!=', AssetVisibility.Timeline)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return this.db
|
return this.db
|
||||||
@ -67,7 +67,7 @@ export class MemoryRepository implements IBulkAsset {
|
|||||||
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
|
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
|
||||||
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
|
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
|
||||||
.orderBy('asset.fileCreatedAt', 'asc')
|
.orderBy('asset.fileCreatedAt', 'asc')
|
||||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
|
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||||
.where('asset.deletedAt', 'is', null),
|
.where('asset.deletedAt', 'is', null),
|
||||||
).as('assets'),
|
).as('assets'),
|
||||||
)
|
)
|
||||||
@ -158,7 +158,7 @@ export class MemoryRepository implements IBulkAsset {
|
|||||||
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
|
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
|
||||||
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
|
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
|
||||||
.orderBy('asset.fileCreatedAt', 'asc')
|
.orderBy('asset.fileCreatedAt', 'asc')
|
||||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
|
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||||
.where('asset.deletedAt', 'is', null),
|
.where('asset.deletedAt', 'is', null),
|
||||||
).as('assets'),
|
).as('assets'),
|
||||||
)
|
)
|
||||||
|
@ -48,7 +48,7 @@ export class MoveRepository {
|
|||||||
eb.selectFrom('asset').select('id').whereRef('asset.id', '=', 'move_history.entityId'),
|
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();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ export class MoveRepository {
|
|||||||
async cleanMoveHistorySingle(assetId: string): Promise<void> {
|
async cleanMoveHistorySingle(assetId: string): Promise<void> {
|
||||||
await this.db
|
await this.db
|
||||||
.deleteFrom('move_history')
|
.deleteFrom('move_history')
|
||||||
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
|
.where('move_history.pathType', '=', sql.lit(AssetPathType.Original))
|
||||||
.where('entityId', '=', assetId)
|
.where('entityId', '=', assetId)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
@ -138,11 +138,11 @@ export class OAuthRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (tokenEndpointAuthMethod) {
|
switch (tokenEndpointAuthMethod) {
|
||||||
case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST: {
|
case OAuthTokenEndpointAuthMethod.ClientSecretPost: {
|
||||||
return ClientSecretPost(clientSecret);
|
return ClientSecretPost(clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_BASIC: {
|
case OAuthTokenEndpointAuthMethod.ClientSecretBasic: {
|
||||||
return ClientSecretBasic(clientSecret);
|
return ClientSecretBasic(clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ export class PersonRepository {
|
|||||||
.innerJoin('asset', (join) =>
|
.innerJoin('asset', (join) =>
|
||||||
join
|
join
|
||||||
.onRef('asset_face.assetId', '=', 'asset.id')
|
.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),
|
.on('asset.deletedAt', 'is', null),
|
||||||
)
|
)
|
||||||
.where('person.ownerId', '=', userId)
|
.where('person.ownerId', '=', userId)
|
||||||
@ -276,7 +276,7 @@ export class PersonRepository {
|
|||||||
.selectFrom('asset_file')
|
.selectFrom('asset_file')
|
||||||
.select('asset_file.path')
|
.select('asset_file.path')
|
||||||
.whereRef('asset_file.assetId', '=', 'asset.id')
|
.whereRef('asset_file.assetId', '=', 'asset.id')
|
||||||
.where('asset_file.type', '=', sql.lit(AssetFileType.PREVIEW))
|
.where('asset_file.type', '=', sql.lit(AssetFileType.Preview))
|
||||||
.as('previewPath'),
|
.as('previewPath'),
|
||||||
)
|
)
|
||||||
.where('person.id', '=', id)
|
.where('person.id', '=', id)
|
||||||
@ -341,7 +341,7 @@ export class PersonRepository {
|
|||||||
join
|
join
|
||||||
.onRef('asset.id', '=', 'asset_face.assetId')
|
.onRef('asset.id', '=', 'asset_face.assetId')
|
||||||
.on('asset_face.personId', '=', personId)
|
.on('asset_face.personId', '=', personId)
|
||||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
|
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||||
.on('asset.deletedAt', 'is', null),
|
.on('asset.deletedAt', 'is', null),
|
||||||
)
|
)
|
||||||
.select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'))
|
.select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'))
|
||||||
@ -369,7 +369,7 @@ export class PersonRepository {
|
|||||||
eb
|
eb
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.whereRef('asset.id', '=', 'asset_face.assetId')
|
.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),
|
.where('asset.deletedAt', 'is', null),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -256,7 +256,7 @@ export class SearchRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.db.transaction().execute(async (trx) => {
|
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)
|
const items = await searchAssetBuilder(trx, options)
|
||||||
.selectAll('asset')
|
.selectAll('asset')
|
||||||
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
|
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
|
||||||
@ -284,7 +284,7 @@ export class SearchRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.db.transaction().execute(async (trx) => {
|
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
|
return await trx
|
||||||
.with('cte', (qb) =>
|
.with('cte', (qb) =>
|
||||||
qb
|
qb
|
||||||
@ -351,8 +351,8 @@ export class SearchRepository {
|
|||||||
.select(['city', 'assetId'])
|
.select(['city', 'assetId'])
|
||||||
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
||||||
.where('asset.ownerId', '=', anyUuid(userIds))
|
.where('asset.ownerId', '=', anyUuid(userIds))
|
||||||
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
|
.where('asset.visibility', '=', AssetVisibility.Timeline)
|
||||||
.where('asset.type', '=', AssetType.IMAGE)
|
.where('asset.type', '=', AssetType.Image)
|
||||||
.where('asset.deletedAt', 'is', null)
|
.where('asset.deletedAt', 'is', null)
|
||||||
.orderBy('city')
|
.orderBy('city')
|
||||||
.limit(1);
|
.limit(1);
|
||||||
@ -367,8 +367,8 @@ export class SearchRepository {
|
|||||||
.select(['city', 'assetId'])
|
.select(['city', 'assetId'])
|
||||||
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
||||||
.where('asset.ownerId', '=', anyUuid(userIds))
|
.where('asset.ownerId', '=', anyUuid(userIds))
|
||||||
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
|
.where('asset.visibility', '=', AssetVisibility.Timeline)
|
||||||
.where('asset.type', '=', AssetType.IMAGE)
|
.where('asset.type', '=', AssetType.Image)
|
||||||
.where('asset.deletedAt', 'is', null)
|
.where('asset.deletedAt', 'is', null)
|
||||||
.whereRef('asset_exif.city', '>', 'cte.city')
|
.whereRef('asset_exif.city', '>', 'cte.city')
|
||||||
.orderBy('city')
|
.orderBy('city')
|
||||||
@ -450,7 +450,7 @@ export class SearchRepository {
|
|||||||
.distinctOn(field)
|
.distinctOn(field)
|
||||||
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
||||||
.where('ownerId', '=', anyUuid(userIds))
|
.where('ownerId', '=', anyUuid(userIds))
|
||||||
.where('visibility', '=', AssetVisibility.TIMELINE)
|
.where('visibility', '=', AssetVisibility.Timeline)
|
||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.where(field, 'is not', null);
|
.where(field, 'is not', null);
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ export class SharedLinkRepository {
|
|||||||
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
|
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
|
||||||
.where('shared_link.id', '=', id)
|
.where('shared_link.id', '=', id)
|
||||||
.where('shared_link.userId', '=', userId)
|
.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')
|
.orderBy('shared_link.createdAt', 'desc')
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ export class SharedLinkRepository {
|
|||||||
(join) => join.onTrue(),
|
(join) => join.onTrue(),
|
||||||
)
|
)
|
||||||
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
|
.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!))
|
.$if(!!albumId, (eb) => eb.where('shared_link.albumId', '=', albumId!))
|
||||||
.orderBy('shared_link.createdAt', 'desc')
|
.orderBy('shared_link.createdAt', 'desc')
|
||||||
.distinctOn(['shared_link.createdAt'])
|
.distinctOn(['shared_link.createdAt'])
|
||||||
@ -185,7 +185,7 @@ export class SharedLinkRepository {
|
|||||||
eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'),
|
eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'),
|
||||||
).as('user'),
|
).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();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,21 +112,21 @@ export class TelemetryRepository {
|
|||||||
const { telemetry } = this.configRepository.getEnv();
|
const { telemetry } = this.configRepository.getEnv();
|
||||||
const { metrics } = telemetry;
|
const { metrics } = telemetry;
|
||||||
|
|
||||||
this.api = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.API) });
|
this.api = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Api) });
|
||||||
this.host = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.HOST) });
|
this.host = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Host) });
|
||||||
this.jobs = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.JOB) });
|
this.jobs = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Job) });
|
||||||
this.repo = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.REPO) });
|
this.repo = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Repo) });
|
||||||
}
|
}
|
||||||
|
|
||||||
setup({ repositories }: { repositories: ClassConstructor<unknown>[] }) {
|
setup({ repositories }: { repositories: ClassConstructor<unknown>[] }) {
|
||||||
const { telemetry } = this.configRepository.getEnv();
|
const { telemetry } = this.configRepository.getEnv();
|
||||||
const { metrics } = telemetry;
|
const { metrics } = telemetry;
|
||||||
if (!metrics.has(ImmichTelemetry.REPO)) {
|
if (!metrics.has(ImmichTelemetry.Repo)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const Repository of repositories) {
|
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) {
|
if (!isEnabled) {
|
||||||
this.logger.debug(`Telemetry disabled for ${Repository.name}`);
|
this.logger.debug(`Telemetry disabled for ${Repository.name}`);
|
||||||
continue;
|
continue;
|
||||||
|
@ -8,7 +8,7 @@ export class TrashRepository {
|
|||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
|
|
||||||
getDeletedIds(): AsyncIterableIterator<{ id: string }> {
|
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] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
@ -16,8 +16,8 @@ export class TrashRepository {
|
|||||||
const { numUpdatedRows } = await this.db
|
const { numUpdatedRows } = await this.db
|
||||||
.updateTable('asset')
|
.updateTable('asset')
|
||||||
.where('ownerId', '=', userId)
|
.where('ownerId', '=', userId)
|
||||||
.where('status', '=', AssetStatus.TRASHED)
|
.where('status', '=', AssetStatus.Trashed)
|
||||||
.set({ status: AssetStatus.ACTIVE, deletedAt: null })
|
.set({ status: AssetStatus.Active, deletedAt: null })
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
return Number(numUpdatedRows);
|
return Number(numUpdatedRows);
|
||||||
@ -28,8 +28,8 @@ export class TrashRepository {
|
|||||||
const { numUpdatedRows } = await this.db
|
const { numUpdatedRows } = await this.db
|
||||||
.updateTable('asset')
|
.updateTable('asset')
|
||||||
.where('ownerId', '=', userId)
|
.where('ownerId', '=', userId)
|
||||||
.where('status', '=', AssetStatus.TRASHED)
|
.where('status', '=', AssetStatus.Trashed)
|
||||||
.set({ status: AssetStatus.DELETED })
|
.set({ status: AssetStatus.Deleted })
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
return Number(numUpdatedRows);
|
return Number(numUpdatedRows);
|
||||||
@ -43,9 +43,9 @@ export class TrashRepository {
|
|||||||
|
|
||||||
const { numUpdatedRows } = await this.db
|
const { numUpdatedRows } = await this.db
|
||||||
.updateTable('asset')
|
.updateTable('asset')
|
||||||
.where('status', '=', AssetStatus.TRASHED)
|
.where('status', '=', AssetStatus.Trashed)
|
||||||
.where('id', 'in', ids)
|
.where('id', 'in', ids)
|
||||||
.set({ status: AssetStatus.ACTIVE, deletedAt: null })
|
.set({ status: AssetStatus.Active, deletedAt: null })
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
return Number(numUpdatedRows);
|
return Number(numUpdatedRows);
|
||||||
|
@ -187,7 +187,7 @@ export class UserRepository {
|
|||||||
restore(id: string) {
|
restore(id: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.updateTable('user')
|
.updateTable('user')
|
||||||
.set({ status: UserStatus.ACTIVE, deletedAt: null })
|
.set({ status: UserStatus.Active, deletedAt: null })
|
||||||
.where('user.id', '=', asUuid(id))
|
.where('user.id', '=', asUuid(id))
|
||||||
.returning(columns.userAdmin)
|
.returning(columns.userAdmin)
|
||||||
.returning(withMetadata)
|
.returning(withMetadata)
|
||||||
@ -229,8 +229,8 @@ export class UserRepository {
|
|||||||
.countAll<number>()
|
.countAll<number>()
|
||||||
.filterWhere((eb) =>
|
.filterWhere((eb) =>
|
||||||
eb.and([
|
eb.and([
|
||||||
eb('asset.type', '=', sql.lit(AssetType.IMAGE)),
|
eb('asset.type', '=', sql.lit(AssetType.Image)),
|
||||||
eb('asset.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
|
eb('asset.visibility', '!=', sql.lit(AssetVisibility.Hidden)),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.as('photos'),
|
.as('photos'),
|
||||||
@ -238,8 +238,8 @@ export class UserRepository {
|
|||||||
.countAll<number>()
|
.countAll<number>()
|
||||||
.filterWhere((eb) =>
|
.filterWhere((eb) =>
|
||||||
eb.and([
|
eb.and([
|
||||||
eb('asset.type', '=', sql.lit(AssetType.VIDEO)),
|
eb('asset.type', '=', sql.lit(AssetType.Video)),
|
||||||
eb('asset.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
|
eb('asset.visibility', '!=', sql.lit(AssetVisibility.Hidden)),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.as('videos'),
|
.as('videos'),
|
||||||
@ -254,7 +254,7 @@ export class UserRepository {
|
|||||||
eb.fn
|
eb.fn
|
||||||
.sum<number>('asset_exif.fileSizeInByte')
|
.sum<number>('asset_exif.fileSizeInByte')
|
||||||
.filterWhere((eb) =>
|
.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),
|
eb.lit(0),
|
||||||
)
|
)
|
||||||
@ -264,7 +264,7 @@ export class UserRepository {
|
|||||||
eb.fn
|
eb.fn
|
||||||
.sum<number>('asset_exif.fileSizeInByte')
|
.sum<number>('asset_exif.fileSizeInByte')
|
||||||
.filterWhere((eb) =>
|
.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),
|
eb.lit(0),
|
||||||
)
|
)
|
||||||
|
@ -15,7 +15,7 @@ export class ViewRepository {
|
|||||||
.select((eb) => eb.fn<string>('substring', ['asset.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath'))
|
.select((eb) => eb.fn<string>('substring', ['asset.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath'))
|
||||||
.distinct()
|
.distinct()
|
||||||
.where('ownerId', '=', asUuid(userId))
|
.where('ownerId', '=', asUuid(userId))
|
||||||
.where('visibility', '=', AssetVisibility.TIMELINE)
|
.where('visibility', '=', AssetVisibility.Timeline)
|
||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.where('fileCreatedAt', 'is not', null)
|
.where('fileCreatedAt', 'is not', null)
|
||||||
.where('fileModifiedAt', 'is not', null)
|
.where('fileModifiedAt', 'is not', null)
|
||||||
@ -34,7 +34,7 @@ export class ViewRepository {
|
|||||||
.selectAll('asset')
|
.selectAll('asset')
|
||||||
.$call(withExif)
|
.$call(withExif)
|
||||||
.where('ownerId', '=', asUuid(userId))
|
.where('ownerId', '=', asUuid(userId))
|
||||||
.where('visibility', '=', AssetVisibility.TIMELINE)
|
.where('visibility', '=', AssetVisibility.Timeline)
|
||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.where('fileCreatedAt', 'is not', null)
|
.where('fileCreatedAt', 'is not', null)
|
||||||
.where('fileModifiedAt', 'is not', null)
|
.where('fileModifiedAt', 'is not', null)
|
||||||
|
@ -16,9 +16,7 @@ export async function up(db: Kysely<any>): Promise<void> {
|
|||||||
rows: [lastMigration],
|
rows: [lastMigration],
|
||||||
} = await lastMigrationSql.execute(db);
|
} = await lastMigrationSql.execute(db);
|
||||||
if (lastMigration?.name !== 'AddMissingIndex1744910873956') {
|
if (lastMigration?.name !== 'AddMissingIndex1744910873956') {
|
||||||
throw new Error(
|
throw new Error('Invalid upgrade path. For more information, see https://immich.app/errors#typeorm-upgrade');
|
||||||
'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');
|
logger.log('Database has up to date TypeORM migrations, skipping initial Kysely migration');
|
||||||
return;
|
return;
|
||||||
@ -108,152 +106,344 @@ export async function up(db: Kysely<any>): Promise<void> {
|
|||||||
RETURN NULL;
|
RETURN NULL;
|
||||||
END;
|
END;
|
||||||
$$;`.execute(db);
|
$$;`.execute(db);
|
||||||
if (vectorExtension === DatabaseExtension.VECTORS) {
|
if (vectorExtension === DatabaseExtension.Vectors) {
|
||||||
await sql`SET search_path TO "$user", public, vectors`.execute(db);
|
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 "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 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 "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(
|
||||||
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);
|
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 "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(
|
||||||
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);
|
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`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 "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(
|
||||||
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);
|
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 "albums_assets_assets" ("albumsId" uuid NOT NULL, "assetsId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(
|
||||||
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);
|
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 "albums_shared_users_users" ("albumsId" uuid NOT NULL, "usersId" uuid NOT NULL, "role" character varying NOT NULL DEFAULT 'editor');`.execute(
|
||||||
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);
|
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 "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(
|
||||||
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);
|
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 "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 "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(
|
||||||
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);
|
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 "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 "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(
|
||||||
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);
|
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 "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(
|
||||||
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);
|
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 "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 "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`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`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 "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 "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 "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 "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(
|
||||||
await sql`CREATE TABLE "user_metadata" ("userId" uuid NOT NULL, "key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(db);
|
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 "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 "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 "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 "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 "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 "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 "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_assets_assets" ADD CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180" PRIMARY KEY ("albumsId", "assetsId");`.execute(
|
||||||
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8" PRIMARY KEY ("albumsId", "usersId");`.execute(db);
|
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 "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 "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 "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_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_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 "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 "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 "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" 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 "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 "naturalearth_countries" ADD CONSTRAINT "PK_21a6d86d1ab5d841648212e5353" PRIMARY KEY ("id");`.execute(
|
||||||
await sql`ALTER TABLE "partners_audit" ADD CONSTRAINT "PK_952b50217ff78198a7e380f0359" PRIMARY KEY ("id");`.execute(db);
|
db,
|
||||||
await sql`ALTER TABLE "partners" ADD CONSTRAINT "PK_f1cc8f73d16b367f426261a8736" PRIMARY KEY ("sharedById", "sharedWithId");`.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 "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_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 "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 "session_sync_checkpoints" ADD CONSTRAINT "PK_b846ab547a702863ef7cd9412fb" PRIMARY KEY ("sessionId", "type");`.execute(
|
||||||
await sql`ALTER TABLE "system_metadata" ADD CONSTRAINT "PK_fa94f6857470fb5b81ec6084465" PRIMARY KEY ("key");`.execute(db);
|
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 "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 "tag_asset" ADD CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId");`.execute(
|
||||||
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "PK_eab38eb12a3ec6df8376c95477c" PRIMARY KEY ("id_ancestor", "id_descendant");`.execute(db);
|
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 "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 "user_metadata" ADD CONSTRAINT "PK_5931462150b3438cbc83277fe5a" PRIMARY KEY ("userId", "key");`.execute(
|
||||||
await sql`ALTER TABLE "version_history" ADD CONSTRAINT "PK_5db259cbb09ce82c0d13cfd1b23" PRIMARY KEY ("id");`.execute(db);
|
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 "version_history" ADD CONSTRAINT "PK_5db259cbb09ce82c0d13cfd1b23" PRIMARY KEY ("id");`.execute(
|
||||||
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_c05079e542fd74de3b5ecb5c1c8" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
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 "libraries" ADD CONSTRAINT "FK_0f6fc2fb195f24d19b0fb0d57c1" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" FOREIGN KEY ("libraryId") REFERENCES "libraries" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
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 "asset_stack" ADD CONSTRAINT "FK_91704e101438fd0653f582426dc" FOREIGN KEY ("primaryAssetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;`.execute(
|
||||||
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
|
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 "asset_stack" ADD CONSTRAINT "FK_c05079e542fd74de3b5ecb5c1c8" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_8091ea76b12338cb4428d33d782" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
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 "assets" ADD CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
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);
|
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 "assets" ADD CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" FOREIGN KEY ("livePhotoVideoId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
|
||||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_5527cc99f530a547093f9e577b6" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
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 "assets" ADD CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" FOREIGN KEY ("libraryId") REFERENCES "libraries" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
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);
|
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 "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
|
||||||
await sql`ALTER TABLE "exif" ADD CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
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 "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
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);
|
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 "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
|
||||||
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3" FOREIGN KEY ("sharedWithId") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
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 "activity" ADD CONSTRAINT "FK_1af8519996fbfb3684b58df280b" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
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 "activity" ADD CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
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);
|
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 "activity" ADD CONSTRAINT "FK_8091ea76b12338cb4428d33d782" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_9f9590cc11561f1f48ff034ef99" FOREIGN KEY ("parentId") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
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 "albums_assets_assets" ADD CONSTRAINT "FK_e590fa396c6898fcd4a50e40927" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||||
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);
|
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 "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_97672ac88f789774dd47f7c8be3" UNIQUE ("email");`.execute(db);
|
||||||
await sql`ALTER TABLE "users" ADD CONSTRAINT "UQ_b309cf34fa58137c416b32cea3a" UNIQUE ("storageLabel");`.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 "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_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 "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 "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 "activity" ADD CONSTRAINT "CHK_2ab1e70f113f450eb40c1e3ec8" CHECK (("comment" IS NULL AND "isLiked" = true) OR ("comment" IS NOT NULL AND "isLiked" = false));`.execute(
|
||||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "CHK_b0f82b0ed662bfc24fbb58bb45" CHECK ("birthDate" <= CURRENT_DATE);`.execute(db);
|
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_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_users_update_id" ON "users" ("updateId")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_0f6fc2fb195f24d19b0fb0d57c" ON "libraries" ("ownerId")`.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_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_91704e101438fd0653f582426d" ON "asset_stack" ("primaryAssetId")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_c05079e542fd74de3b5ecb5c1c" ON "asset_stack" ("ownerId")`.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_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_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 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_library_checksum" ON "assets" ("ownerId", "libraryId", "checksum") WHERE ("libraryId" IS NOT NULL)`.execute(
|
||||||
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_checksum" ON "assets" ("ownerId", "checksum") WHERE ("libraryId" IS NULL)`.execute(db);
|
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_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_asset_file_created_at" ON "assets" ("fileCreatedAt")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_8d3efe36c0755849395e6ea866" ON "assets" ("checksum")`.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_b22c53f35ef20c28c21637c85f" ON "albums" ("ownerId")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_05895aa505a670300d4816debc" ON "albums" ("albumThumbnailAssetId")`.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 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_1af8519996fbfb3684b58df280" ON "activity" ("albumId")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_3571467bcbe021f66e2bdce96e" ON "activity" ("userId")`.execute(db);
|
await sql`CREATE INDEX "IDX_3571467bcbe021f66e2bdce96e" ON "activity" ("userId")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_8091ea76b12338cb4428d33d78" ON "activity" ("assetId")`.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_auto_stack_id" ON "exif" ("autoStackId")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_asset_exif_update_id" ON "exif" ("updateId")`.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.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_gist_earthcoord" ON "geodata_places" (ll_to_earth_public(latitude, longitude))`.execute(
|
||||||
await sql`CREATE INDEX "idx_geodata_places_name" ON "geodata_places" USING gin (f_unaccent("name") gin_trgm_ops)`.execute(db);
|
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_name" ON "geodata_places" USING gin (f_unaccent("name") gin_trgm_ops)`.execute(
|
||||||
await sql`CREATE INDEX "idx_geodata_places_alternate_names" ON "geodata_places" USING gin (f_unaccent("alternateNames") gin_trgm_ops)`.execute(db);
|
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_575842846f0c28fa5da46c99b1" ON "memories" ("ownerId")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_memories_update_id" ON "memories" ("updateId")`.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);
|
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`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.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_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_92e67dc508c705dd66c9461557" ON "tags" ("userId")`.execute(db);
|
||||||
await sql`CREATE INDEX "IDX_9f9590cc11561f1f48ff034ef9" ON "tags" ("parentId")`.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);
|
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> {
|
export async function down(): Promise<void> {
|
||||||
// not implemented
|
// not implemented
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ import { Kysely, sql } from 'kysely';
|
|||||||
import { UserMetadataKey } from 'src/enum';
|
import { UserMetadataKey } from 'src/enum';
|
||||||
|
|
||||||
export async function up(db: Kysely<any>): Promise<void> {
|
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
|
ON CONFLICT ("userId", key) DO NOTHING
|
||||||
`.execute(db);
|
`.execute(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(db: Kysely<any>): Promise<void> {
|
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;
|
usersId!: string;
|
||||||
|
|
||||||
@Column({ type: 'character varying', default: AlbumUserRole.EDITOR })
|
@Column({ type: 'character varying', default: AlbumUserRole.Editor })
|
||||||
role!: Generated<AlbumUserRole>;
|
role!: Generated<AlbumUserRole>;
|
||||||
|
|
||||||
@CreateIdColumn({ index: true })
|
@CreateIdColumn({ index: true })
|
||||||
|
@ -57,7 +57,7 @@ export class AlbumTable {
|
|||||||
@Column({ type: 'boolean', default: true })
|
@Column({ type: 'boolean', default: true })
|
||||||
isActivityEnabled!: Generated<boolean>;
|
isActivityEnabled!: Generated<boolean>;
|
||||||
|
|
||||||
@Column({ default: AssetOrder.DESC })
|
@Column({ default: AssetOrder.Desc })
|
||||||
order!: Generated<AssetOrder>;
|
order!: Generated<AssetOrder>;
|
||||||
|
|
||||||
@UpdateIdColumn({ index: true })
|
@UpdateIdColumn({ index: true })
|
||||||
|
@ -56,7 +56,7 @@ export class AssetFaceTable {
|
|||||||
@Column({ default: 0, type: 'integer' })
|
@Column({ default: 0, type: 'integer' })
|
||||||
boundingBoxY2!: Generated<number>;
|
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>;
|
sourceType!: Generated<SourceType>;
|
||||||
|
|
||||||
@DeleteDateColumn()
|
@DeleteDateColumn()
|
||||||
|
@ -132,12 +132,12 @@ export class AssetTable {
|
|||||||
@Column({ type: 'uuid', nullable: true, index: true })
|
@Column({ type: 'uuid', nullable: true, index: true })
|
||||||
duplicateId!: string | null;
|
duplicateId!: string | null;
|
||||||
|
|
||||||
@Column({ enum: assets_status_enum, default: AssetStatus.ACTIVE })
|
@Column({ enum: assets_status_enum, default: AssetStatus.Active })
|
||||||
status!: Generated<AssetStatus>;
|
status!: Generated<AssetStatus>;
|
||||||
|
|
||||||
@UpdateIdColumn({ index: true })
|
@UpdateIdColumn({ index: true })
|
||||||
updateId!: Generated<string>;
|
updateId!: Generated<string>;
|
||||||
|
|
||||||
@Column({ enum: asset_visibility_enum, default: AssetVisibility.TIMELINE })
|
@Column({ enum: asset_visibility_enum, default: AssetVisibility.Timeline })
|
||||||
visibility!: Generated<AssetVisibility>;
|
visibility!: Generated<AssetVisibility>;
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ export class UserTable {
|
|||||||
@Column({ type: 'bigint', default: 0 })
|
@Column({ type: 'bigint', default: 0 })
|
||||||
quotaUsageInBytes!: Generated<ColumnType<number>>;
|
quotaUsageInBytes!: Generated<ColumnType<number>>;
|
||||||
|
|
||||||
@Column({ type: 'character varying', default: UserStatus.ACTIVE })
|
@Column({ type: 'character varying', default: UserStatus.Active })
|
||||||
status!: Generated<UserStatus>;
|
status!: Generated<UserStatus>;
|
||||||
|
|
||||||
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
|
||||||
|
@ -18,7 +18,7 @@ import { BaseService } from 'src/services/base.service';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ActivityService extends BaseService {
|
export class ActivityService extends BaseService {
|
||||||
async getAll(auth: AuthDto, dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
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({
|
const activities = await this.activityRepository.search({
|
||||||
userId: dto.userId,
|
userId: dto.userId,
|
||||||
albumId: dto.albumId,
|
albumId: dto.albumId,
|
||||||
@ -30,12 +30,12 @@ export class ActivityService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getStatistics(auth: AuthDto, dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
|
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 });
|
return await this.activityRepository.getStatistics({ albumId: dto.albumId, assetId: dto.assetId });
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(auth: AuthDto, dto: ActivityCreateDto): Promise<MaybeDuplicate<ActivityResponseDto>> {
|
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 = {
|
const common = {
|
||||||
userId: auth.user.id,
|
userId: auth.user.id,
|
||||||
@ -69,7 +69,7 @@ export class ActivityService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(auth: AuthDto, id: string): Promise<void> {
|
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);
|
await this.activityRepository.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
await sut.create(authStub.admin, {
|
await sut.create(authStub.admin, {
|
||||||
albumName: 'Empty album',
|
albumName: 'Empty album',
|
||||||
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
|
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.Editor }],
|
||||||
description: '',
|
description: '',
|
||||||
assetIds: ['123'],
|
assetIds: ['123'],
|
||||||
});
|
});
|
||||||
@ -160,7 +160,7 @@ describe(AlbumService.name, () => {
|
|||||||
albumThumbnailAssetId: '123',
|
albumThumbnailAssetId: '123',
|
||||||
},
|
},
|
||||||
['123'],
|
['123'],
|
||||||
[{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
|
[{ userId: 'user-id', role: AlbumUserRole.Editor }],
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mocks.user.get).toHaveBeenCalledWith('user-id', {});
|
expect(mocks.user.get).toHaveBeenCalledWith('user-id', {});
|
||||||
@ -177,10 +177,10 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.user.get.mockResolvedValue(userStub.user1);
|
mocks.user.get.mockResolvedValue(userStub.user1);
|
||||||
mocks.user.getMetadata.mockResolvedValue([
|
mocks.user.getMetadata.mockResolvedValue([
|
||||||
{
|
{
|
||||||
key: UserMetadataKey.PREFERENCES,
|
key: UserMetadataKey.Preferences,
|
||||||
value: {
|
value: {
|
||||||
albums: {
|
albums: {
|
||||||
defaultAssetOrder: AssetOrder.ASC,
|
defaultAssetOrder: AssetOrder.Asc,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -189,7 +189,7 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
await sut.create(authStub.admin, {
|
await sut.create(authStub.admin, {
|
||||||
albumName: 'Empty album',
|
albumName: 'Empty album',
|
||||||
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
|
albumUsers: [{ userId: 'user-id', role: AlbumUserRole.Editor }],
|
||||||
description: '',
|
description: '',
|
||||||
assetIds: ['123'],
|
assetIds: ['123'],
|
||||||
});
|
});
|
||||||
@ -203,7 +203,7 @@ describe(AlbumService.name, () => {
|
|||||||
albumThumbnailAssetId: '123',
|
albumThumbnailAssetId: '123',
|
||||||
},
|
},
|
||||||
['123'],
|
['123'],
|
||||||
[{ userId: 'user-id', role: AlbumUserRole.EDITOR }],
|
[{ userId: 'user-id', role: AlbumUserRole.Editor }],
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mocks.user.get).toHaveBeenCalledWith('user-id', {});
|
expect(mocks.user.get).toHaveBeenCalledWith('user-id', {});
|
||||||
@ -220,7 +220,7 @@ describe(AlbumService.name, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
sut.create(authStub.admin, {
|
sut.create(authStub.admin, {
|
||||||
albumName: 'Empty album',
|
albumName: 'Empty album',
|
||||||
albumUsers: [{ userId: 'user-3', role: AlbumUserRole.EDITOR }],
|
albumUsers: [{ userId: 'user-3', role: AlbumUserRole.Editor }],
|
||||||
}),
|
}),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
expect(mocks.user.get).toHaveBeenCalledWith('user-3', {});
|
expect(mocks.user.get).toHaveBeenCalledWith('user-3', {});
|
||||||
@ -262,7 +262,7 @@ describe(AlbumService.name, () => {
|
|||||||
await expect(
|
await expect(
|
||||||
sut.create(authStub.admin, {
|
sut.create(authStub.admin, {
|
||||||
albumName: 'Empty album',
|
albumName: 'Empty album',
|
||||||
albumUsers: [{ userId: userStub.admin.id, role: AlbumUserRole.EDITOR }],
|
albumUsers: [{ userId: userStub.admin.id, role: AlbumUserRole.Editor }],
|
||||||
}),
|
}),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
expect(mocks.album.create).not.toHaveBeenCalled();
|
expect(mocks.album.create).not.toHaveBeenCalled();
|
||||||
@ -404,7 +404,7 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.albumUser.create.mockResolvedValue({
|
mocks.albumUser.create.mockResolvedValue({
|
||||||
usersId: userStub.user2.id,
|
usersId: userStub.user2.id,
|
||||||
albumsId: albumStub.sharedWithAdmin.id,
|
albumsId: albumStub.sharedWithAdmin.id,
|
||||||
role: AlbumUserRole.EDITOR,
|
role: AlbumUserRole.Editor,
|
||||||
});
|
});
|
||||||
await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, {
|
await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, {
|
||||||
albumUsers: [{ userId: authStub.user2.user.id }],
|
albumUsers: [{ userId: authStub.user2.user.id }],
|
||||||
@ -512,11 +512,11 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.albumUser.update.mockResolvedValue(null as any);
|
mocks.albumUser.update.mockResolvedValue(null as any);
|
||||||
|
|
||||||
await sut.updateUser(authStub.user1, albumStub.sharedWithAdmin.id, userStub.admin.id, {
|
await sut.updateUser(authStub.user1, albumStub.sharedWithAdmin.id, userStub.admin.id, {
|
||||||
role: AlbumUserRole.EDITOR,
|
role: AlbumUserRole.Editor,
|
||||||
});
|
});
|
||||||
expect(mocks.albumUser.update).toHaveBeenCalledWith(
|
expect(mocks.albumUser.update).toHaveBeenCalledWith(
|
||||||
{ albumsId: albumStub.sharedWithAdmin.id, usersId: userStub.admin.id },
|
{ 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(
|
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
||||||
authStub.user1.user.id,
|
authStub.user1.user.id,
|
||||||
new Set(['album-123']),
|
new Set(['album-123']),
|
||||||
AlbumUserRole.VIEWER,
|
AlbumUserRole.Viewer,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -596,7 +596,7 @@ describe(AlbumService.name, () => {
|
|||||||
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
||||||
authStub.admin.user.id,
|
authStub.admin.user.id,
|
||||||
new Set(['album-123']),
|
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> {
|
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();
|
await this.albumRepository.updateThumbnails();
|
||||||
const withAssets = dto.withoutAssets === undefined ? true : !dto.withoutAssets;
|
const withAssets = dto.withoutAssets === undefined ? true : !dto.withoutAssets;
|
||||||
const album = await this.findOrFail(id, { withAssets });
|
const album = await this.findOrFail(id, { withAssets });
|
||||||
@ -102,7 +102,7 @@ export class AlbumService extends BaseService {
|
|||||||
|
|
||||||
const allowedAssetIdsSet = await this.checkAccess({
|
const allowedAssetIdsSet = await this.checkAccess({
|
||||||
auth,
|
auth,
|
||||||
permission: Permission.ASSET_SHARE,
|
permission: Permission.AssetShare,
|
||||||
ids: dto.assetIds || [],
|
ids: dto.assetIds || [],
|
||||||
});
|
});
|
||||||
const assetIds = [...allowedAssetIdsSet].map((id) => id);
|
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> {
|
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 });
|
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> {
|
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);
|
await this.albumRepository.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||||
const album = await this.findOrFail(id, { withAssets: false });
|
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(
|
const results = await addAssets(
|
||||||
auth,
|
auth,
|
||||||
@ -187,13 +187,13 @@ export class AlbumService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
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 album = await this.findOrFail(id, { withAssets: false });
|
||||||
const results = await removeAssets(
|
const results = await removeAssets(
|
||||||
auth,
|
auth,
|
||||||
{ access: this.accessRepository, bulk: this.albumRepository },
|
{ 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);
|
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> {
|
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 });
|
const album = await this.findOrFail(id, { withAssets: false });
|
||||||
|
|
||||||
@ -249,14 +249,14 @@ export class AlbumService extends BaseService {
|
|||||||
|
|
||||||
// non-admin can remove themselves
|
// non-admin can remove themselves
|
||||||
if (auth.user.id !== userId) {
|
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 });
|
await this.albumUserRepository.delete({ albumsId: id, usersId: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise<void> {
|
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 });
|
await this.albumUserRepository.update({ albumsId: id, usersId: userId }, { role: dto.role });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ describe(ApiKeyService.name, () => {
|
|||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
it('should create a new key', async () => {
|
it('should create a new key', async () => {
|
||||||
const auth = factory.auth();
|
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';
|
const key = 'super-secret';
|
||||||
|
|
||||||
mocks.crypto.randomBytesAsText.mockReturnValue(key);
|
mocks.crypto.randomBytesAsText.mockReturnValue(key);
|
||||||
@ -41,12 +41,12 @@ describe(ApiKeyService.name, () => {
|
|||||||
mocks.crypto.randomBytesAsText.mockReturnValue(key);
|
mocks.crypto.randomBytesAsText.mockReturnValue(key);
|
||||||
mocks.apiKey.create.mockResolvedValue(apiKey);
|
mocks.apiKey.create.mockResolvedValue(apiKey);
|
||||||
|
|
||||||
await sut.create(auth, { permissions: [Permission.ALL] });
|
await sut.create(auth, { permissions: [Permission.All] });
|
||||||
|
|
||||||
expect(mocks.apiKey.create).toHaveBeenCalledWith({
|
expect(mocks.apiKey.create).toHaveBeenCalledWith({
|
||||||
key: 'super-secret (hashed)',
|
key: 'super-secret (hashed)',
|
||||||
name: 'API Key',
|
name: 'API Key',
|
||||||
permissions: [Permission.ALL],
|
permissions: [Permission.All],
|
||||||
userId: auth.user.id,
|
userId: auth.user.id,
|
||||||
});
|
});
|
||||||
expect(mocks.crypto.randomBytesAsText).toHaveBeenCalled();
|
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 () => {
|
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,
|
BadRequestException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -69,7 +69,7 @@ describe(ApiKeyService.name, () => {
|
|||||||
|
|
||||||
mocks.apiKey.getById.mockResolvedValue(void 0);
|
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,
|
BadRequestException,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -84,18 +84,18 @@ describe(ApiKeyService.name, () => {
|
|||||||
mocks.apiKey.getById.mockResolvedValue(apiKey);
|
mocks.apiKey.getById.mockResolvedValue(apiKey);
|
||||||
mocks.apiKey.update.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, {
|
expect(mocks.apiKey.update).toHaveBeenCalledWith(auth.user.id, apiKey.id, {
|
||||||
name: newName,
|
name: newName,
|
||||||
permissions: [Permission.ALL],
|
permissions: [Permission.All],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update permissions', async () => {
|
it('should update permissions', async () => {
|
||||||
const auth = factory.auth();
|
const auth = factory.auth();
|
||||||
const apiKey = factory.apiKey({ userId: auth.user.id });
|
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.getById.mockResolvedValue(apiKey);
|
||||||
mocks.apiKey.update.mockResolvedValue(apiKey);
|
mocks.apiKey.update.mockResolvedValue(apiKey);
|
||||||
|
@ -157,7 +157,7 @@ const assetEntity = Object.freeze({
|
|||||||
ownerId: 'user_id_1',
|
ownerId: 'user_id_1',
|
||||||
deviceAssetId: 'device_asset_id_1',
|
deviceAssetId: 'device_asset_id_1',
|
||||||
deviceId: 'device_id_1',
|
deviceId: 'device_id_1',
|
||||||
type: AssetType.VIDEO,
|
type: AssetType.Video,
|
||||||
originalPath: 'fake_path/asset_1.jpeg',
|
originalPath: 'fake_path/asset_1.jpeg',
|
||||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||||
fileCreatedAt: 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({
|
const existingAsset = Object.freeze({
|
||||||
...assetEntity,
|
...assetEntity,
|
||||||
duration: null,
|
duration: null,
|
||||||
type: AssetType.IMAGE,
|
type: AssetType.Image,
|
||||||
checksum: Buffer.from('_getExistingAsset', 'utf8'),
|
checksum: Buffer.from('_getExistingAsset', 'utf8'),
|
||||||
libraryId: 'libraryId',
|
libraryId: 'libraryId',
|
||||||
originalFileName: 'existing-filename.jpeg',
|
originalFileName: 'existing-filename.jpeg',
|
||||||
@ -384,7 +384,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mocks.job.queue).toHaveBeenCalledWith({
|
expect(mocks.job.queue).toHaveBeenCalledWith({
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: { files: ['fake_path/asset_1.jpeg', undefined] },
|
data: { files: ['fake_path/asset_1.jpeg', undefined] },
|
||||||
});
|
});
|
||||||
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
|
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
|
||||||
@ -409,7 +409,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(mocks.job.queue).toHaveBeenCalledWith({
|
expect(mocks.job.queue).toHaveBeenCalledWith({
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: { files: ['fake_path/asset_1.jpeg', undefined] },
|
data: { files: ['fake_path/asset_1.jpeg', undefined] },
|
||||||
});
|
});
|
||||||
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
|
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
|
||||||
@ -437,7 +437,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
it('should hide the linked motion asset', async () => {
|
it('should hide the linked motion asset', async () => {
|
||||||
mocks.asset.getById.mockResolvedValueOnce({
|
mocks.asset.getById.mockResolvedValueOnce({
|
||||||
...assetStub.livePhotoMotionAsset,
|
...assetStub.livePhotoMotionAsset,
|
||||||
visibility: AssetVisibility.TIMELINE,
|
visibility: AssetVisibility.Timeline,
|
||||||
});
|
});
|
||||||
mocks.asset.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset);
|
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.getById).toHaveBeenCalledWith('live-photo-motion-asset');
|
||||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||||
id: 'live-photo-motion-asset',
|
id: 'live-photo-motion-asset',
|
||||||
visibility: AssetVisibility.HIDDEN,
|
visibility: AssetVisibility.Hidden,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -506,7 +506,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
new ImmichFileResponse({
|
new ImmichFileResponse({
|
||||||
path: '/original/path.jpg',
|
path: '/original/path.jpg',
|
||||||
contentType: 'image/jpeg',
|
contentType: 'image/jpeg',
|
||||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
cacheControl: CacheControl.PrivateWithCache,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -546,7 +546,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
{
|
{
|
||||||
id: '42',
|
id: '42',
|
||||||
path: '/path/to/preview',
|
path: '/path/to/preview',
|
||||||
type: AssetFileType.THUMBNAIL,
|
type: AssetFileType.Thumbnail,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -563,7 +563,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
{
|
{
|
||||||
id: '42',
|
id: '42',
|
||||||
path: '/path/to/preview.jpg',
|
path: '/path/to/preview.jpg',
|
||||||
type: AssetFileType.PREVIEW,
|
type: AssetFileType.Preview,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -573,7 +573,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
new ImmichFileResponse({
|
new ImmichFileResponse({
|
||||||
path: '/path/to/preview.jpg',
|
path: '/path/to/preview.jpg',
|
||||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
cacheControl: CacheControl.PrivateWithCache,
|
||||||
contentType: 'image/jpeg',
|
contentType: 'image/jpeg',
|
||||||
fileName: 'asset-id_thumbnail.jpg',
|
fileName: 'asset-id_thumbnail.jpg',
|
||||||
}),
|
}),
|
||||||
@ -588,7 +588,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
new ImmichFileResponse({
|
new ImmichFileResponse({
|
||||||
path: '/uploads/user-id/thumbs/path.jpg',
|
path: '/uploads/user-id/thumbs/path.jpg',
|
||||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
cacheControl: CacheControl.PrivateWithCache,
|
||||||
contentType: 'image/jpeg',
|
contentType: 'image/jpeg',
|
||||||
fileName: 'asset-id_preview.jpg',
|
fileName: 'asset-id_preview.jpg',
|
||||||
}),
|
}),
|
||||||
@ -603,7 +603,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
new ImmichFileResponse({
|
new ImmichFileResponse({
|
||||||
path: '/uploads/user-id/webp/path.ext',
|
path: '/uploads/user-id/webp/path.ext',
|
||||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
cacheControl: CacheControl.PrivateWithCache,
|
||||||
contentType: 'application/octet-stream',
|
contentType: 'application/octet-stream',
|
||||||
fileName: 'asset-id_thumbnail.ext',
|
fileName: 'asset-id_thumbnail.ext',
|
||||||
}),
|
}),
|
||||||
@ -640,7 +640,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
await expect(sut.playbackVideo(authStub.admin, assetStub.hasEncodedVideo.id)).resolves.toEqual(
|
await expect(sut.playbackVideo(authStub.admin, assetStub.hasEncodedVideo.id)).resolves.toEqual(
|
||||||
new ImmichFileResponse({
|
new ImmichFileResponse({
|
||||||
path: assetStub.hasEncodedVideo.encodedVideoPath!,
|
path: assetStub.hasEncodedVideo.encodedVideoPath!,
|
||||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
cacheControl: CacheControl.PrivateWithCache,
|
||||||
contentType: 'video/mp4',
|
contentType: 'video/mp4',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -653,7 +653,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
await expect(sut.playbackVideo(authStub.admin, assetStub.video.id)).resolves.toEqual(
|
await expect(sut.playbackVideo(authStub.admin, assetStub.video.id)).resolves.toEqual(
|
||||||
new ImmichFileResponse({
|
new ImmichFileResponse({
|
||||||
path: assetStub.video.originalPath,
|
path: assetStub.video.originalPath,
|
||||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
cacheControl: CacheControl.PrivateWithCache,
|
||||||
contentType: 'application/octet-stream',
|
contentType: 'application/octet-stream',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -723,7 +723,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
|
|
||||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
|
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
|
||||||
deletedAt: expect.any(Date),
|
deletedAt: expect.any(Date),
|
||||||
status: AssetStatus.TRASHED,
|
status: AssetStatus.Trashed,
|
||||||
});
|
});
|
||||||
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
|
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
|
||||||
expect(mocks.storage.utimes).toHaveBeenCalledWith(
|
expect(mocks.storage.utimes).toHaveBeenCalledWith(
|
||||||
@ -754,7 +754,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
|
|
||||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
|
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
|
||||||
deletedAt: expect.any(Date),
|
deletedAt: expect.any(Date),
|
||||||
status: AssetStatus.TRASHED,
|
status: AssetStatus.Trashed,
|
||||||
});
|
});
|
||||||
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
|
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
|
||||||
expect(mocks.storage.utimes).toHaveBeenCalledWith(
|
expect(mocks.storage.utimes).toHaveBeenCalledWith(
|
||||||
@ -783,7 +783,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
|
|
||||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
|
expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], {
|
||||||
deletedAt: expect.any(Date),
|
deletedAt: expect.any(Date),
|
||||||
status: AssetStatus.TRASHED,
|
status: AssetStatus.Trashed,
|
||||||
});
|
});
|
||||||
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
|
expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size);
|
||||||
expect(mocks.storage.utimes).toHaveBeenCalledWith(
|
expect(mocks.storage.utimes).toHaveBeenCalledWith(
|
||||||
@ -815,7 +815,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
expect(mocks.asset.create).not.toHaveBeenCalled();
|
expect(mocks.asset.create).not.toHaveBeenCalled();
|
||||||
expect(mocks.asset.updateAll).not.toHaveBeenCalled();
|
expect(mocks.asset.updateAll).not.toHaveBeenCalled();
|
||||||
expect(mocks.job.queue).toHaveBeenCalledWith({
|
expect(mocks.job.queue).toHaveBeenCalledWith({
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: { files: [updatedFile.originalPath, undefined] },
|
data: { files: [updatedFile.originalPath, undefined] },
|
||||||
});
|
});
|
||||||
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
|
expect(mocks.user.updateUsage).not.toHaveBeenCalled();
|
||||||
@ -912,7 +912,7 @@ describe(AssetMediaService.name, () => {
|
|||||||
await sut.onUploadError(request, file);
|
await sut.onUploadError(request, file);
|
||||||
|
|
||||||
expect(mocks.job.queue).toHaveBeenCalledWith({
|
expect(mocks.job.queue).toHaveBeenCalledWith({
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: { files: ['upload/upload/user-id/ra/nd/random-uuid.jpg'] },
|
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 {
|
getUploadFolder({ auth, fieldName, file }: UploadRequest): string {
|
||||||
auth = requireUploadAccess(auth);
|
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) {
|
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);
|
this.storageRepository.mkdirSync(folder);
|
||||||
@ -121,7 +121,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
const uploadFolder = this.getUploadFolder(asRequest(request, file));
|
const uploadFolder = this.getUploadFolder(asRequest(request, file));
|
||||||
const uploadPath = `${uploadFolder}/${uploadFilename}`;
|
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(
|
async uploadAsset(
|
||||||
@ -133,7 +133,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
try {
|
try {
|
||||||
await this.requireAccess({
|
await this.requireAccess({
|
||||||
auth,
|
auth,
|
||||||
permission: Permission.ASSET_UPLOAD,
|
permission: Permission.AssetUpload,
|
||||||
// do not need an id here, but the interface requires it
|
// do not need an id here, but the interface requires it
|
||||||
ids: [auth.user.id],
|
ids: [auth.user.id],
|
||||||
});
|
});
|
||||||
@ -164,7 +164,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
sidecarFile?: UploadFile,
|
sidecarFile?: UploadFile,
|
||||||
): Promise<AssetMediaResponseDto> {
|
): Promise<AssetMediaResponseDto> {
|
||||||
try {
|
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);
|
const asset = await this.assetRepository.getById(id);
|
||||||
|
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
@ -179,7 +179,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
// but the local variable holds the original file data paths.
|
// but the local variable holds the original file data paths.
|
||||||
const copiedPhoto = await this.createCopy(asset);
|
const copiedPhoto = await this.createCopy(asset);
|
||||||
// and immediate trash it
|
// 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.eventRepository.emit('AssetTrash', { assetId: copiedPhoto.id, userId: auth.user.id });
|
||||||
|
|
||||||
await this.userRepository.updateUsage(auth.user.id, file.size);
|
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> {
|
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);
|
const asset = await this.findOrFail(id);
|
||||||
|
|
||||||
return new ImmichFileResponse({
|
return new ImmichFileResponse({
|
||||||
path: asset.originalPath,
|
path: asset.originalPath,
|
||||||
contentType: mimeTypes.lookup(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,
|
id: string,
|
||||||
dto: AssetMediaOptionsDto,
|
dto: AssetMediaOptionsDto,
|
||||||
): Promise<ImmichFileResponse | AssetMediaRedirectResponse> {
|
): 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 asset = await this.findOrFail(id);
|
||||||
const size = dto.size ?? AssetMediaSize.THUMBNAIL;
|
const size = dto.size ?? AssetMediaSize.THUMBNAIL;
|
||||||
@ -240,16 +240,16 @@ export class AssetMediaService extends BaseService {
|
|||||||
fileName,
|
fileName,
|
||||||
path: filepath,
|
path: filepath,
|
||||||
contentType: mimeTypes.lookup(filepath),
|
contentType: mimeTypes.lookup(filepath),
|
||||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
cacheControl: CacheControl.PrivateWithCache,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async playbackVideo(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
|
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);
|
const asset = await this.findOrFail(id);
|
||||||
|
|
||||||
if (asset.type !== AssetType.VIDEO) {
|
if (asset.type !== AssetType.Video) {
|
||||||
throw new BadRequestException('Asset is not a video');
|
throw new BadRequestException('Asset is not a video');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +258,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
return new ImmichFileResponse({
|
return new ImmichFileResponse({
|
||||||
path: filepath,
|
path: filepath,
|
||||||
contentType: mimeTypes.lookup(filepath),
|
contentType: mimeTypes.lookup(filepath),
|
||||||
cacheControl: CacheControl.PRIVATE_WITH_CACHE,
|
cacheControl: CacheControl.PrivateWithCache,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +312,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
): Promise<AssetMediaResponseDto> {
|
): Promise<AssetMediaResponseDto> {
|
||||||
// clean up files
|
// clean up files
|
||||||
await this.jobRepository.queue({
|
await this.jobRepository.queue({
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: { files: [file.originalPath, sidecarFile?.originalPath] },
|
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.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt));
|
||||||
await this.assetRepository.upsertExif({ assetId, fileSizeInByte: file.size });
|
await this.assetRepository.upsertExif({ assetId, fileSizeInByte: file.size });
|
||||||
await this.jobRepository.queue({
|
await this.jobRepository.queue({
|
||||||
name: JobName.METADATA_EXTRACTION,
|
name: JobName.MetadataExtraction,
|
||||||
data: { id: assetId, source: 'upload' },
|
data: { id: assetId, source: 'upload' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -394,7 +394,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
|
|
||||||
const { size } = await this.storageRepository.stat(created.originalPath);
|
const { size } = await this.storageRepository.stat(created.originalPath);
|
||||||
await this.assetRepository.upsertExif({ assetId: created.id, fileSizeInByte: size });
|
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;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,7 +416,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
type: mimeTypes.assetType(file.originalPath),
|
type: mimeTypes.assetType(file.originalPath),
|
||||||
isFavorite: dto.isFavorite,
|
isFavorite: dto.isFavorite,
|
||||||
duration: dto.duration || null,
|
duration: dto.duration || null,
|
||||||
visibility: dto.visibility ?? AssetVisibility.TIMELINE,
|
visibility: dto.visibility ?? AssetVisibility.Timeline,
|
||||||
livePhotoVideoId: dto.livePhotoVideoId,
|
livePhotoVideoId: dto.livePhotoVideoId,
|
||||||
originalFileName: dto.filename || file.originalName,
|
originalFileName: dto.filename || file.originalName,
|
||||||
sidecarPath: sidecarFile?.originalPath,
|
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.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt));
|
||||||
await this.assetRepository.upsertExif({ assetId: asset.id, fileSizeInByte: file.size });
|
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;
|
return asset;
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,10 @@ import { factory } from 'test/small.factory';
|
|||||||
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
|
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
|
||||||
|
|
||||||
const stats: AssetStats = {
|
const stats: AssetStats = {
|
||||||
[AssetType.IMAGE]: 10,
|
[AssetType.Image]: 10,
|
||||||
[AssetType.VIDEO]: 23,
|
[AssetType.Video]: 23,
|
||||||
[AssetType.AUDIO]: 0,
|
[AssetType.Audio]: 0,
|
||||||
[AssetType.OTHER]: 0,
|
[AssetType.Other]: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const statResponse: AssetStatsResponseDto = {
|
const statResponse: AssetStatsResponseDto = {
|
||||||
@ -46,21 +46,21 @@ describe(AssetService.name, () => {
|
|||||||
describe('getStatistics', () => {
|
describe('getStatistics', () => {
|
||||||
it('should get the statistics for a user, excluding archived assets', async () => {
|
it('should get the statistics for a user, excluding archived assets', async () => {
|
||||||
mocks.asset.getStatistics.mockResolvedValue(stats);
|
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,
|
statResponse,
|
||||||
);
|
);
|
||||||
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {
|
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 () => {
|
it('should get the statistics for a user for archived assets', async () => {
|
||||||
mocks.asset.getStatistics.mockResolvedValue(stats);
|
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,
|
statResponse,
|
||||||
);
|
);
|
||||||
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {
|
expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {
|
||||||
visibility: AssetVisibility.ARCHIVE,
|
visibility: AssetVisibility.Archive,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ describe(AssetService.name, () => {
|
|||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
it('should require asset write access for the id', async () => {
|
it('should require asset write access for the id', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
sut.update(authStub.admin, 'asset-1', { visibility: AssetVisibility.TIMELINE }),
|
sut.update(authStub.admin, 'asset-1', { visibility: AssetVisibility.Timeline }),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||||
@ -253,7 +253,7 @@ describe(AssetService.name, () => {
|
|||||||
});
|
});
|
||||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
||||||
id: assetStub.livePhotoMotionAsset.id,
|
id: assetStub.livePhotoMotionAsset.id,
|
||||||
visibility: AssetVisibility.TIMELINE,
|
visibility: AssetVisibility.Timeline,
|
||||||
});
|
});
|
||||||
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
|
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
|
||||||
assetId: assetStub.livePhotoMotionAsset.id,
|
assetId: assetStub.livePhotoMotionAsset.id,
|
||||||
@ -277,7 +277,7 @@ describe(AssetService.name, () => {
|
|||||||
});
|
});
|
||||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
||||||
id: assetStub.livePhotoMotionAsset.id,
|
id: assetStub.livePhotoMotionAsset.id,
|
||||||
visibility: AssetVisibility.TIMELINE,
|
visibility: AssetVisibility.Timeline,
|
||||||
});
|
});
|
||||||
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
|
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
|
||||||
assetId: assetStub.livePhotoMotionAsset.id,
|
assetId: assetStub.livePhotoMotionAsset.id,
|
||||||
@ -301,7 +301,7 @@ describe(AssetService.name, () => {
|
|||||||
});
|
});
|
||||||
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
expect(mocks.asset.update).not.toHaveBeenCalledWith({
|
||||||
id: assetStub.livePhotoMotionAsset.id,
|
id: assetStub.livePhotoMotionAsset.id,
|
||||||
visibility: AssetVisibility.TIMELINE,
|
visibility: AssetVisibility.Timeline,
|
||||||
});
|
});
|
||||||
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
|
expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', {
|
||||||
assetId: assetStub.livePhotoMotionAsset.id,
|
assetId: assetStub.livePhotoMotionAsset.id,
|
||||||
@ -314,7 +314,7 @@ describe(AssetService.name, () => {
|
|||||||
mocks.asset.getById.mockResolvedValueOnce({
|
mocks.asset.getById.mockResolvedValueOnce({
|
||||||
...assetStub.livePhotoMotionAsset,
|
...assetStub.livePhotoMotionAsset,
|
||||||
ownerId: authStub.admin.user.id,
|
ownerId: authStub.admin.user.id,
|
||||||
visibility: AssetVisibility.TIMELINE,
|
visibility: AssetVisibility.Timeline,
|
||||||
});
|
});
|
||||||
mocks.asset.getById.mockResolvedValueOnce(assetStub.image);
|
mocks.asset.getById.mockResolvedValueOnce(assetStub.image);
|
||||||
mocks.asset.update.mockResolvedValue(assetStub.image);
|
mocks.asset.update.mockResolvedValue(assetStub.image);
|
||||||
@ -325,7 +325,7 @@ describe(AssetService.name, () => {
|
|||||||
|
|
||||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||||
id: assetStub.livePhotoMotionAsset.id,
|
id: assetStub.livePhotoMotionAsset.id,
|
||||||
visibility: AssetVisibility.HIDDEN,
|
visibility: AssetVisibility.Hidden,
|
||||||
});
|
});
|
||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AssetHide', {
|
expect(mocks.event.emit).toHaveBeenCalledWith('AssetHide', {
|
||||||
assetId: assetStub.livePhotoMotionAsset.id,
|
assetId: assetStub.livePhotoMotionAsset.id,
|
||||||
@ -392,10 +392,10 @@ describe(AssetService.name, () => {
|
|||||||
it('should update all assets', async () => {
|
it('should update all assets', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
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'], {
|
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.updateAll).toHaveBeenCalled();
|
||||||
expect(mocks.asset.updateAllExif).toHaveBeenCalledWith(['asset-1'], { latitude: 0, longitude: 0 });
|
expect(mocks.asset.updateAllExif).toHaveBeenCalledWith(['asset-1'], { latitude: 0, longitude: 0 });
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
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,
|
longitude: 50,
|
||||||
});
|
});
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
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'], {
|
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset1', 'asset2'], {
|
||||||
deletedAt: expect.any(Date),
|
deletedAt: expect.any(Date),
|
||||||
status: AssetStatus.TRASHED,
|
status: AssetStatus.Trashed,
|
||||||
});
|
});
|
||||||
expect(mocks.job.queue.mock.calls).toEqual([]);
|
expect(mocks.job.queue.mock.calls).toEqual([]);
|
||||||
});
|
});
|
||||||
@ -518,11 +518,11 @@ describe(AssetService.name, () => {
|
|||||||
mocks.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset]));
|
mocks.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset]));
|
||||||
mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: false } });
|
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.assetJob.streamForDeletedJob).toHaveBeenCalledWith(new Date());
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
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.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset]));
|
||||||
mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: true, days: 7 } });
|
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.assetJob.streamForDeletedJob).toHaveBeenCalledWith(DateTime.now().minus({ days: 7 }).toJSDate());
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
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([
|
expect(mocks.job.queue.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: {
|
data: {
|
||||||
files: [
|
files: [
|
||||||
'/uploads/user-id/webp/path.ext',
|
'/uploads/user-id/webp/path.ext',
|
||||||
@ -606,7 +606,7 @@ describe(AssetService.name, () => {
|
|||||||
expect(mocks.job.queue.mock.calls).toEqual([
|
expect(mocks.job.queue.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: JobName.ASSET_DELETION,
|
name: JobName.AssetDeletion,
|
||||||
data: {
|
data: {
|
||||||
id: assetStub.livePhotoMotionAsset.id,
|
id: assetStub.livePhotoMotionAsset.id,
|
||||||
deleteOnDisk: true,
|
deleteOnDisk: true,
|
||||||
@ -615,7 +615,7 @@ describe(AssetService.name, () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: {
|
data: {
|
||||||
files: [
|
files: [
|
||||||
'/uploads/user-id/webp/path.ext',
|
'/uploads/user-id/webp/path.ext',
|
||||||
@ -643,7 +643,7 @@ describe(AssetService.name, () => {
|
|||||||
expect(mocks.job.queue.mock.calls).toEqual([
|
expect(mocks.job.queue.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: {
|
data: {
|
||||||
files: [
|
files: [
|
||||||
'/uploads/user-id/webp/path.ext',
|
'/uploads/user-id/webp/path.ext',
|
||||||
@ -668,7 +668,7 @@ describe(AssetService.name, () => {
|
|||||||
it('should fail if asset could not be found', async () => {
|
it('should fail if asset could not be found', async () => {
|
||||||
mocks.assetJob.getForAssetDeletion.mockResolvedValue(void 0);
|
mocks.assetJob.getForAssetDeletion.mockResolvedValue(void 0);
|
||||||
await expect(sut.handleAssetDeletion({ id: assetStub.image.id, deleteOnDisk: true })).resolves.toBe(
|
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 });
|
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 () => {
|
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 });
|
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 () => {
|
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 });
|
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 () => {
|
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 });
|
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()
|
@Injectable()
|
||||||
export class AssetService extends BaseService {
|
export class AssetService extends BaseService {
|
||||||
async getStatistics(auth: AuthDto, dto: AssetStatsDto) {
|
async getStatistics(auth: AuthDto, dto: AssetStatsDto) {
|
||||||
if (dto.visibility === AssetVisibility.LOCKED) {
|
if (dto.visibility === AssetVisibility.Locked) {
|
||||||
requireElevatedPermission(auth);
|
requireElevatedPermission(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export class AssetService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async get(auth: AuthDto, id: string): Promise<AssetResponseDto | SanitizedAssetResponseDto> {
|
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, {
|
const asset = await this.assetRepository.getById(id, {
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
@ -78,7 +78,7 @@ export class AssetService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
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 { description, dateTimeOriginal, latitude, longitude, rating, ...rest } = dto;
|
||||||
const repos = { asset: this.assetRepository, event: this.eventRepository };
|
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> {
|
async updateAll(auth: AuthDto, dto: AssetBulkUpdateDto): Promise<void> {
|
||||||
const { ids, description, dateTimeOriginal, latitude, longitude, ...options } = dto;
|
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 (
|
if (
|
||||||
description !== undefined ||
|
description !== undefined ||
|
||||||
@ -125,7 +125,7 @@ export class AssetService extends BaseService {
|
|||||||
await this.assetRepository.updateAllExif(ids, { description, dateTimeOriginal, latitude, longitude });
|
await this.assetRepository.updateAllExif(ids, { description, dateTimeOriginal, latitude, longitude });
|
||||||
await this.jobRepository.queueAll(
|
await this.jobRepository.queueAll(
|
||||||
ids.map((id) => ({
|
ids.map((id) => ({
|
||||||
name: JobName.SIDECAR_WRITE,
|
name: JobName.SidecarWrite,
|
||||||
data: { id, description, dateTimeOriginal, latitude, longitude },
|
data: { id, description, dateTimeOriginal, latitude, longitude },
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
@ -139,13 +139,13 @@ export class AssetService extends BaseService {
|
|||||||
) {
|
) {
|
||||||
await this.assetRepository.updateAll(ids, options);
|
await this.assetRepository.updateAll(ids, options);
|
||||||
|
|
||||||
if (options.visibility === AssetVisibility.LOCKED) {
|
if (options.visibility === AssetVisibility.Locked) {
|
||||||
await this.albumRepository.removeAssetsFromAll(ids);
|
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> {
|
async handleAssetDeletionCheck(): Promise<JobStatus> {
|
||||||
const config = await this.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
const trashedDays = config.trash.enabled ? config.trash.days : 0;
|
const trashedDays = config.trash.enabled ? config.trash.days : 0;
|
||||||
@ -158,7 +158,7 @@ export class AssetService extends BaseService {
|
|||||||
if (chunk.length > 0) {
|
if (chunk.length > 0) {
|
||||||
await this.jobRepository.queueAll(
|
await this.jobRepository.queueAll(
|
||||||
chunk.map(({ id, isOffline }) => ({
|
chunk.map(({ id, isOffline }) => ({
|
||||||
name: JobName.ASSET_DELETION,
|
name: JobName.AssetDeletion,
|
||||||
data: { id, deleteOnDisk: !isOffline },
|
data: { id, deleteOnDisk: !isOffline },
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
@ -176,17 +176,17 @@ export class AssetService extends BaseService {
|
|||||||
|
|
||||||
await queueChunk();
|
await queueChunk();
|
||||||
|
|
||||||
return JobStatus.SUCCESS;
|
return JobStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnJob({ name: JobName.ASSET_DELETION, queue: QueueName.BACKGROUND_TASK })
|
@OnJob({ name: JobName.AssetDeletion, queue: QueueName.BackgroundTask })
|
||||||
async handleAssetDeletion(job: JobOf<JobName.ASSET_DELETION>): Promise<JobStatus> {
|
async handleAssetDeletion(job: JobOf<JobName.AssetDeletion>): Promise<JobStatus> {
|
||||||
const { id, deleteOnDisk } = job;
|
const { id, deleteOnDisk } = job;
|
||||||
|
|
||||||
const asset = await this.assetJobRepository.getForAssetDeletion(id);
|
const asset = await this.assetJobRepository.getForAssetDeletion(id);
|
||||||
|
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return JobStatus.FAILED;
|
return JobStatus.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the parent of the stack children with a new asset
|
// 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);
|
const count = await this.assetRepository.getLivePhotoCount(asset.livePhotoVideoId);
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
await this.jobRepository.queue({
|
await this.jobRepository.queue({
|
||||||
name: JobName.ASSET_DELETION,
|
name: JobName.AssetDeletion,
|
||||||
data: { id: asset.livePhotoVideoId, deleteOnDisk },
|
data: { id: asset.livePhotoVideoId, deleteOnDisk },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -228,18 +228,18 @@ export class AssetService extends BaseService {
|
|||||||
files.push(asset.sidecarPath, asset.originalPath);
|
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> {
|
async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise<void> {
|
||||||
const { ids, force } = dto;
|
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, {
|
await this.assetRepository.updateAll(ids, {
|
||||||
deletedAt: new Date(),
|
deletedAt: new Date(),
|
||||||
status: force ? AssetStatus.DELETED : AssetStatus.TRASHED,
|
status: force ? AssetStatus.Deleted : AssetStatus.Trashed,
|
||||||
});
|
});
|
||||||
await this.eventRepository.emit(force ? 'AssetDeleteAll' : 'AssetTrashAll', {
|
await this.eventRepository.emit(force ? 'AssetDeleteAll' : 'AssetTrashAll', {
|
||||||
assetIds: ids,
|
assetIds: ids,
|
||||||
@ -248,29 +248,29 @@ export class AssetService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async run(auth: AuthDto, dto: AssetJobsDto) {
|
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[] = [];
|
const jobs: JobItem[] = [];
|
||||||
|
|
||||||
for (const id of dto.assetIds) {
|
for (const id of dto.assetIds) {
|
||||||
switch (dto.name) {
|
switch (dto.name) {
|
||||||
case AssetJobName.REFRESH_FACES: {
|
case AssetJobName.REFRESH_FACES: {
|
||||||
jobs.push({ name: JobName.FACE_DETECTION, data: { id } });
|
jobs.push({ name: JobName.FaceDetection, data: { id } });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssetJobName.REFRESH_METADATA: {
|
case AssetJobName.REFRESH_METADATA: {
|
||||||
jobs.push({ name: JobName.METADATA_EXTRACTION, data: { id } });
|
jobs.push({ name: JobName.MetadataExtraction, data: { id } });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssetJobName.REGENERATE_THUMBNAIL: {
|
case AssetJobName.REGENERATE_THUMBNAIL: {
|
||||||
jobs.push({ name: JobName.GENERATE_THUMBNAILS, data: { id } });
|
jobs.push({ name: JobName.GenerateThumbnails, data: { id } });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssetJobName.TRANSCODE_VIDEO: {
|
case AssetJobName.TRANSCODE_VIDEO: {
|
||||||
jobs.push({ name: JobName.VIDEO_CONVERSION, data: { id } });
|
jobs.push({ name: JobName.VideoConversation, data: { id } });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -292,7 +292,7 @@ export class AssetService extends BaseService {
|
|||||||
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude, rating }, _.isUndefined);
|
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude, rating }, _.isUndefined);
|
||||||
if (Object.keys(writes).length > 0) {
|
if (Object.keys(writes).length > 0) {
|
||||||
await this.assetRepository.upsertExif({ assetId: id, ...writes });
|
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 () => {
|
it('should delete old audit entries', async () => {
|
||||||
mocks.audit.removeBefore.mockResolvedValue();
|
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));
|
expect(mocks.audit.removeBefore).toHaveBeenCalledWith(expect.any(Date));
|
||||||
});
|
});
|
||||||
|
@ -7,9 +7,9 @@ import { BaseService } from 'src/services/base.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuditService extends BaseService {
|
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> {
|
async handleCleanup(): Promise<JobStatus> {
|
||||||
await this.auditRepository.removeBefore(DateTime.now().minus(AUDIT_LOG_MAX_DURATION).toJSDate());
|
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);
|
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,
|
successful: true,
|
||||||
redirectUri: 'http://end-session-endpoint',
|
redirectUri: 'http://end-session-endpoint',
|
||||||
});
|
});
|
||||||
@ -163,7 +163,7 @@ describe(AuthService.name, () => {
|
|||||||
it('should return the default redirect', async () => {
|
it('should return the default redirect', async () => {
|
||||||
const auth = factory.auth();
|
const auth = factory.auth();
|
||||||
|
|
||||||
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
|
await expect(sut.logout(auth, AuthType.Password)).resolves.toEqual({
|
||||||
successful: true,
|
successful: true,
|
||||||
redirectUri: '/auth/login?autoLaunch=0',
|
redirectUri: '/auth/login?autoLaunch=0',
|
||||||
});
|
});
|
||||||
@ -173,7 +173,7 @@ describe(AuthService.name, () => {
|
|||||||
const auth = { user: { id: '123' }, session: { id: 'token123' } } as AuthDto;
|
const auth = { user: { id: '123' }, session: { id: 'token123' } } as AuthDto;
|
||||||
mocks.session.delete.mockResolvedValue();
|
mocks.session.delete.mockResolvedValue();
|
||||||
|
|
||||||
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
|
await expect(sut.logout(auth, AuthType.Password)).resolves.toEqual({
|
||||||
successful: true,
|
successful: true,
|
||||||
redirectUri: '/auth/login?autoLaunch=0',
|
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 () => {
|
it('should return the default redirect if auth type is OAUTH but oauth is not enabled', async () => {
|
||||||
const auth = { user: { id: '123' } } as AuthDto;
|
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,
|
successful: true,
|
||||||
redirectUri: '/auth/login?autoLaunch=0',
|
redirectUri: '/auth/login?autoLaunch=0',
|
||||||
});
|
});
|
||||||
@ -463,7 +463,7 @@ describe(AuthService.name, () => {
|
|||||||
sut.authenticate({
|
sut.authenticate({
|
||||||
headers: { 'x-api-key': 'auth_token' },
|
headers: { 'x-api-key': 'auth_token' },
|
||||||
queryParams: {},
|
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);
|
).rejects.toBeInstanceOf(ForbiddenException);
|
||||||
});
|
});
|
||||||
|
@ -194,13 +194,13 @@ export class AuthService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async validate({ headers, queryParams }: Omit<ValidateRequest, 'metadata'>): Promise<AuthDto> {
|
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 shareKey = (headers[ImmichHeader.SharedLinkKey] || queryParams[ImmichQuery.SharedLinkKey]) as string;
|
||||||
const session = (headers[ImmichHeader.USER_TOKEN] ||
|
const session = (headers[ImmichHeader.UserToken] ||
|
||||||
headers[ImmichHeader.SESSION_TOKEN] ||
|
headers[ImmichHeader.SessionToken] ||
|
||||||
queryParams[ImmichQuery.SESSION_KEY] ||
|
queryParams[ImmichQuery.SessionKey] ||
|
||||||
this.getBearerToken(headers) ||
|
this.getBearerToken(headers) ||
|
||||||
this.getCookieToken(headers)) as string;
|
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) {
|
if (shareKey) {
|
||||||
return this.validateSharedLink(shareKey);
|
return this.validateSharedLink(shareKey);
|
||||||
@ -321,7 +321,7 @@ export class AuthService extends BaseService {
|
|||||||
const { contentType, data } = await this.oauthRepository.getProfilePicture(url);
|
const { contentType, data } = await this.oauthRepository.getProfilePicture(url);
|
||||||
const extensionWithDot = mimeTypes.toExtension(contentType || 'image/jpeg') ?? 'jpg';
|
const extensionWithDot = mimeTypes.toExtension(contentType || 'image/jpeg') ?? 'jpg';
|
||||||
const profileImagePath = join(
|
const profileImagePath = join(
|
||||||
StorageCore.getFolderLocation(StorageFolder.PROFILE, user.id),
|
StorageCore.getFolderLocation(StorageFolder.Profile, user.id),
|
||||||
`${this.cryptoRepository.randomUUID()}${extensionWithDot}`,
|
`${this.cryptoRepository.randomUUID()}${extensionWithDot}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -330,7 +330,7 @@ export class AuthService extends BaseService {
|
|||||||
await this.userRepository.update(user.id, { profileImagePath, profileChangedAt: new Date() });
|
await this.userRepository.update(user.id, { profileImagePath, profileChangedAt: new Date() });
|
||||||
|
|
||||||
if (oldPath) {
|
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) {
|
} catch (error: Error | any) {
|
||||||
this.logger.warn(`Unable to sync oauth profile picture: ${error}`, error?.stack);
|
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> {
|
private async getLogoutEndpoint(authType: AuthType): Promise<string> {
|
||||||
if (authType !== AuthType.OAUTH) {
|
if (authType !== AuthType.OAuth) {
|
||||||
return LOGIN_URL;
|
return LOGIN_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,17 +389,17 @@ export class AuthService extends BaseService {
|
|||||||
|
|
||||||
private getCookieToken(headers: IncomingHttpHeaders): string | null {
|
private getCookieToken(headers: IncomingHttpHeaders): string | null {
|
||||||
const cookies = parse(headers.cookie || '');
|
const cookies = parse(headers.cookie || '');
|
||||||
return cookies[ImmichCookie.ACCESS_TOKEN] || null;
|
return cookies[ImmichCookie.AccessToken] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCookieOauthState(headers: IncomingHttpHeaders): string | null {
|
private getCookieOauthState(headers: IncomingHttpHeaders): string | null {
|
||||||
const cookies = parse(headers.cookie || '');
|
const cookies = parse(headers.cookie || '');
|
||||||
return cookies[ImmichCookie.OAUTH_STATE] || null;
|
return cookies[ImmichCookie.OAuthState] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCookieCodeVerifier(headers: IncomingHttpHeaders): string | null {
|
private getCookieCodeVerifier(headers: IncomingHttpHeaders): string | null {
|
||||||
const cookies = parse(headers.cookie || '');
|
const cookies = parse(headers.cookie || '');
|
||||||
return cookies[ImmichCookie.OAUTH_CODE_VERIFIER] || null;
|
return cookies[ImmichCookie.OAuthCodeVerifier] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateSharedLink(key: string | string[]): Promise<AuthDto> {
|
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 () => {
|
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 });
|
await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig });
|
||||||
|
|
||||||
expect(mocks.cron.create).not.toHaveBeenCalled();
|
expect(mocks.cron.create).not.toHaveBeenCalled();
|
||||||
@ -98,10 +98,10 @@ describe(BackupService.name, () => {
|
|||||||
await sut.cleanupDatabaseBackups();
|
await sut.cleanupDatabaseBackups();
|
||||||
expect(mocks.storage.unlink).toHaveBeenCalledTimes(2);
|
expect(mocks.storage.unlink).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.storage.unlink).toHaveBeenCalledWith(
|
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(
|
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();
|
await sut.cleanupDatabaseBackups();
|
||||||
expect(mocks.storage.unlink).toHaveBeenCalledTimes(1);
|
expect(mocks.storage.unlink).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.storage.unlink).toHaveBeenCalledWith(
|
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();
|
await sut.cleanupDatabaseBackups();
|
||||||
expect(mocks.storage.unlink).toHaveBeenCalledTimes(2);
|
expect(mocks.storage.unlink).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.storage.unlink).toHaveBeenCalledWith(
|
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(
|
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 () => {
|
it('should run a database backup successfully', async () => {
|
||||||
const result = await sut.handleBackupDatabase();
|
const result = await sut.handleBackupDatabase();
|
||||||
expect(result).toBe(JobStatus.SUCCESS);
|
expect(result).toBe(JobStatus.Success);
|
||||||
expect(mocks.storage.createWriteStream).toHaveBeenCalled();
|
expect(mocks.storage.createWriteStream).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rename file on success', async () => {
|
it('should rename file on success', async () => {
|
||||||
const result = await sut.handleBackupDatabase();
|
const result = await sut.handleBackupDatabase();
|
||||||
expect(result).toBe(JobStatus.SUCCESS);
|
expect(result).toBe(JobStatus.Success);
|
||||||
expect(mocks.storage.rename).toHaveBeenCalled();
|
expect(mocks.storage.rename).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -219,7 +219,7 @@ describe(BackupService.name, () => {
|
|||||||
mocks.database.getPostgresVersion.mockResolvedValue(postgresVersion);
|
mocks.database.getPostgresVersion.mockResolvedValue(postgresVersion);
|
||||||
const result = await sut.handleBackupDatabase();
|
const result = await sut.handleBackupDatabase();
|
||||||
expect(mocks.process.spawn).not.toHaveBeenCalled();
|
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 {
|
export class BackupService extends BaseService {
|
||||||
private backupLock = false;
|
private backupLock = false;
|
||||||
|
|
||||||
@OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.MICROSERVICES] })
|
@OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] })
|
||||||
async onConfigInit({
|
async onConfigInit({
|
||||||
newConfig: {
|
newConfig: {
|
||||||
backup: { database },
|
backup: { database },
|
||||||
@ -26,7 +26,7 @@ export class BackupService extends BaseService {
|
|||||||
this.cronRepository.create({
|
this.cronRepository.create({
|
||||||
name: 'backupDatabase',
|
name: 'backupDatabase',
|
||||||
expression: database.cronExpression,
|
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,
|
start: database.enabled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ export class BackupService extends BaseService {
|
|||||||
backup: { database: config },
|
backup: { database: config },
|
||||||
} = await this.getConfig({ withCache: false });
|
} = 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 files = await this.storageRepository.readdir(backupsFolder);
|
||||||
const failedBackups = files.filter((file) => file.match(/immich-db-backup-\d+\.sql\.gz\.tmp$/));
|
const failedBackups = files.filter((file) => file.match(/immich-db-backup-\d+\.sql\.gz\.tmp$/));
|
||||||
const backups = files
|
const backups = files
|
||||||
@ -68,7 +68,7 @@ export class BackupService extends BaseService {
|
|||||||
this.logger.debug(`Database Backup Cleanup Finished, deleted ${toDelete.length} backups`);
|
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> {
|
async handleBackupDatabase(): Promise<JobStatus> {
|
||||||
this.logger.debug(`Database Backup Started`);
|
this.logger.debug(`Database Backup Started`);
|
||||||
const { database } = this.configRepository.getEnv();
|
const { database } = this.configRepository.getEnv();
|
||||||
@ -92,7 +92,7 @@ export class BackupService extends BaseService {
|
|||||||
databaseParams.push('--clean', '--if-exists');
|
databaseParams.push('--clean', '--if-exists');
|
||||||
const databaseVersion = await this.databaseRepository.getPostgresVersion();
|
const databaseVersion = await this.databaseRepository.getPostgresVersion();
|
||||||
const backupFilePath = path.join(
|
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`,
|
`immich-db-backup-${DateTime.now().toFormat("yyyyLLdd'T'HHmmss")}-v${serverVersion.toString()}-pg${databaseVersion.split(' ')[0]}.sql.gz.tmp`,
|
||||||
);
|
);
|
||||||
const databaseSemver = semver.coerce(databaseVersion);
|
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')) {
|
if (!databaseMajorVersion || !databaseSemver || !semver.satisfies(databaseSemver, '>=14.0.0 <18.0.0')) {
|
||||||
this.logger.error(`Database Backup Failure: Unsupported PostgreSQL version: ${databaseVersion}`);
|
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}`);
|
this.logger.log(`Database Backup Starting. Database Version: ${databaseMajorVersion}`);
|
||||||
@ -179,6 +179,6 @@ export class BackupService extends BaseService {
|
|||||||
|
|
||||||
this.logger.log(`Database Backup Success`);
|
this.logger.log(`Database Backup Success`);
|
||||||
await this.cleanupDatabaseBackups();
|
await this.cleanupDatabaseBackups();
|
||||||
return JobStatus.SUCCESS;
|
return JobStatus.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ describe(DatabaseService.name, () => {
|
|||||||
({ sut, mocks } = newTestService(DatabaseService));
|
({ sut, mocks } = newTestService(DatabaseService));
|
||||||
|
|
||||||
extensionRange = '0.2.x';
|
extensionRange = '0.2.x';
|
||||||
mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.VECTORCHORD);
|
mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.VectorChord);
|
||||||
mocks.database.getExtensionVersionRange.mockReturnValue(extensionRange);
|
mocks.database.getExtensionVersionRange.mockReturnValue(extensionRange);
|
||||||
|
|
||||||
versionBelowRange = '0.1.0';
|
versionBelowRange = '0.1.0';
|
||||||
@ -28,7 +28,7 @@ describe(DatabaseService.name, () => {
|
|||||||
versionAboveRange = '0.3.0';
|
versionAboveRange = '0.3.0';
|
||||||
mocks.database.getExtensionVersions.mockResolvedValue([
|
mocks.database.getExtensionVersions.mockResolvedValue([
|
||||||
{
|
{
|
||||||
name: DatabaseExtension.VECTORCHORD,
|
name: DatabaseExtension.VectorChord,
|
||||||
installedVersion: null,
|
installedVersion: null,
|
||||||
availableVersion: minVersionInRange,
|
availableVersion: minVersionInRange,
|
||||||
},
|
},
|
||||||
@ -49,9 +49,9 @@ describe(DatabaseService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe.each(<Array<{ extension: VectorExtension; extensionName: string }>>[
|
describe.each(<Array<{ extension: VectorExtension; extensionName: string }>>[
|
||||||
{ extension: DatabaseExtension.VECTOR, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTOR] },
|
{ extension: DatabaseExtension.Vector, extensionName: EXTENSION_NAMES[DatabaseExtension.Vector] },
|
||||||
{ extension: DatabaseExtension.VECTORS, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTORS] },
|
{ extension: DatabaseExtension.Vectors, extensionName: EXTENSION_NAMES[DatabaseExtension.Vectors] },
|
||||||
{ extension: DatabaseExtension.VECTORCHORD, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTORCHORD] },
|
{ extension: DatabaseExtension.VectorChord, extensionName: EXTENSION_NAMES[DatabaseExtension.VectorChord] },
|
||||||
])('should work with $extensionName', ({ extension, extensionName }) => {
|
])('should work with $extensionName', ({ extension, extensionName }) => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocks.database.getExtensionVersions.mockResolvedValue([
|
mocks.database.getExtensionVersions.mockResolvedValue([
|
||||||
@ -292,8 +292,8 @@ describe(DatabaseService.name, () => {
|
|||||||
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
||||||
|
|
||||||
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledExactlyOnceWith([
|
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledExactlyOnceWith([
|
||||||
VectorIndex.CLIP,
|
VectorIndex.Clip,
|
||||||
VectorIndex.FACE,
|
VectorIndex.Face,
|
||||||
]);
|
]);
|
||||||
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledTimes(1);
|
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1);
|
expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1);
|
||||||
@ -306,8 +306,8 @@ describe(DatabaseService.name, () => {
|
|||||||
await expect(sut.onBootstrap()).rejects.toBeDefined();
|
await expect(sut.onBootstrap()).rejects.toBeDefined();
|
||||||
|
|
||||||
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledExactlyOnceWith([
|
expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledExactlyOnceWith([
|
||||||
VectorIndex.CLIP,
|
VectorIndex.Clip,
|
||||||
VectorIndex.FACE,
|
VectorIndex.Face,
|
||||||
]);
|
]);
|
||||||
expect(mocks.database.runMigrations).not.toHaveBeenCalled();
|
expect(mocks.database.runMigrations).not.toHaveBeenCalled();
|
||||||
expect(mocks.logger.fatal).not.toHaveBeenCalled();
|
expect(mocks.logger.fatal).not.toHaveBeenCalled();
|
||||||
@ -330,7 +330,7 @@ describe(DatabaseService.name, () => {
|
|||||||
database: 'immich',
|
database: 'immich',
|
||||||
},
|
},
|
||||||
skipMigrations: true,
|
skipMigrations: true,
|
||||||
vectorExtension: DatabaseExtension.VECTORS,
|
vectorExtension: DatabaseExtension.Vectors,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -356,12 +356,12 @@ describe(DatabaseService.name, () => {
|
|||||||
it(`should drop unused extension`, async () => {
|
it(`should drop unused extension`, async () => {
|
||||||
mocks.database.getExtensionVersions.mockResolvedValue([
|
mocks.database.getExtensionVersions.mockResolvedValue([
|
||||||
{
|
{
|
||||||
name: DatabaseExtension.VECTORS,
|
name: DatabaseExtension.Vectors,
|
||||||
installedVersion: minVersionInRange,
|
installedVersion: minVersionInRange,
|
||||||
availableVersion: minVersionInRange,
|
availableVersion: minVersionInRange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: DatabaseExtension.VECTORCHORD,
|
name: DatabaseExtension.VectorChord,
|
||||||
installedVersion: null,
|
installedVersion: null,
|
||||||
availableVersion: minVersionInRange,
|
availableVersion: minVersionInRange,
|
||||||
},
|
},
|
||||||
@ -369,19 +369,19 @@ describe(DatabaseService.name, () => {
|
|||||||
|
|
||||||
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
||||||
|
|
||||||
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VECTORCHORD);
|
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord);
|
||||||
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VECTORS);
|
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should warn if unused extension could not be dropped`, async () => {
|
it(`should warn if unused extension could not be dropped`, async () => {
|
||||||
mocks.database.getExtensionVersions.mockResolvedValue([
|
mocks.database.getExtensionVersions.mockResolvedValue([
|
||||||
{
|
{
|
||||||
name: DatabaseExtension.VECTORS,
|
name: DatabaseExtension.Vectors,
|
||||||
installedVersion: minVersionInRange,
|
installedVersion: minVersionInRange,
|
||||||
availableVersion: minVersionInRange,
|
availableVersion: minVersionInRange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: DatabaseExtension.VECTORCHORD,
|
name: DatabaseExtension.VectorChord,
|
||||||
installedVersion: null,
|
installedVersion: null,
|
||||||
availableVersion: minVersionInRange,
|
availableVersion: minVersionInRange,
|
||||||
},
|
},
|
||||||
@ -390,8 +390,8 @@ describe(DatabaseService.name, () => {
|
|||||||
|
|
||||||
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
||||||
|
|
||||||
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VECTORCHORD);
|
expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord);
|
||||||
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VECTORS);
|
expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors);
|
||||||
expect(mocks.logger.warn).toHaveBeenCalledTimes(1);
|
expect(mocks.logger.warn).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.logger.warn.mock.calls[0][0]).toContain('DROP EXTENSION vectors');
|
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 () => {
|
it(`should not try to drop pgvector when using vectorchord`, async () => {
|
||||||
mocks.database.getExtensionVersions.mockResolvedValue([
|
mocks.database.getExtensionVersions.mockResolvedValue([
|
||||||
{
|
{
|
||||||
name: DatabaseExtension.VECTOR,
|
name: DatabaseExtension.Vector,
|
||||||
installedVersion: minVersionInRange,
|
installedVersion: minVersionInRange,
|
||||||
availableVersion: minVersionInRange,
|
availableVersion: minVersionInRange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: DatabaseExtension.VECTORCHORD,
|
name: DatabaseExtension.VectorChord,
|
||||||
installedVersion: minVersionInRange,
|
installedVersion: minVersionInRange,
|
||||||
availableVersion: minVersionInRange,
|
availableVersion: minVersionInRange,
|
||||||
},
|
},
|
||||||
|
@ -100,7 +100,7 @@ export class DatabaseService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.databaseRepository.reindexVectorsIfNeeded([VectorIndex.CLIP, VectorIndex.FACE]);
|
await this.databaseRepository.reindexVectorsIfNeeded([VectorIndex.Clip, VectorIndex.Face]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(
|
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.',
|
'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) {
|
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) {
|
if (dbName !== extension && installedVersion && !isDepended) {
|
||||||
await this.dropExtension(dbName);
|
await this.dropExtension(dbName);
|
||||||
}
|
}
|
||||||
@ -120,8 +120,8 @@ export class DatabaseService extends BaseService {
|
|||||||
await this.databaseRepository.runMigrations();
|
await this.databaseRepository.runMigrations();
|
||||||
}
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.databaseRepository.prewarm(VectorIndex.CLIP),
|
this.databaseRepository.prewarm(VectorIndex.Clip),
|
||||||
this.databaseRepository.prewarm(VectorIndex.FACE),
|
this.databaseRepository.prewarm(VectorIndex.Face),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,15 @@ export class DownloadService extends BaseService {
|
|||||||
|
|
||||||
if (dto.assetIds) {
|
if (dto.assetIds) {
|
||||||
const assetIds = 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);
|
assets = this.downloadRepository.downloadAssetIds(assetIds);
|
||||||
} else if (dto.albumId) {
|
} else if (dto.albumId) {
|
||||||
const albumId = 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);
|
assets = this.downloadRepository.downloadAlbumId(albumId);
|
||||||
} else if (dto.userId) {
|
} else if (dto.userId) {
|
||||||
const userId = 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);
|
assets = this.downloadRepository.downloadUserId(userId);
|
||||||
} else {
|
} else {
|
||||||
throw new BadRequestException('assetIds, albumId, or userId is required');
|
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> {
|
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 zip = this.storageRepository.createZipStream();
|
||||||
const assets = await this.assetRepository.getByIds(dto.assetIds);
|
const assets = await this.assetRepository.getByIds(dto.assetIds);
|
||||||
|
@ -12,10 +12,10 @@ const hasEmbedding = {
|
|||||||
id: 'asset-1',
|
id: 'asset-1',
|
||||||
ownerId: 'user-id',
|
ownerId: 'user-id',
|
||||||
stackId: null,
|
stackId: null,
|
||||||
type: AssetType.IMAGE,
|
type: AssetType.Image,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
embedding: '[1, 2, 3, 4]',
|
embedding: '[1, 2, 3, 4]',
|
||||||
visibility: AssetVisibility.TIMELINE,
|
visibility: AssetVisibility.Timeline,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasDupe = {
|
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.queue).not.toHaveBeenCalled();
|
||||||
expect(mocks.job.queueAll).not.toHaveBeenCalled();
|
expect(mocks.job.queueAll).not.toHaveBeenCalled();
|
||||||
expect(mocks.systemMetadata.get).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.queue).not.toHaveBeenCalled();
|
||||||
expect(mocks.job.queueAll).not.toHaveBeenCalled();
|
expect(mocks.job.queueAll).not.toHaveBeenCalled();
|
||||||
expect(mocks.systemMetadata.get).toHaveBeenCalled();
|
expect(mocks.systemMetadata.get).toHaveBeenCalled();
|
||||||
@ -108,7 +108,7 @@ describe(SearchService.name, () => {
|
|||||||
expect(mocks.assetJob.streamForSearchDuplicates).toHaveBeenCalledWith(undefined);
|
expect(mocks.assetJob.streamForSearchDuplicates).toHaveBeenCalledWith(undefined);
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
name: JobName.DUPLICATE_DETECTION,
|
name: JobName.DuplicateDetection,
|
||||||
data: { id: assetStub.image.id },
|
data: { id: assetStub.image.id },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@ -122,7 +122,7 @@ describe(SearchService.name, () => {
|
|||||||
expect(mocks.assetJob.streamForSearchDuplicates).toHaveBeenCalledWith(true);
|
expect(mocks.assetJob.streamForSearchDuplicates).toHaveBeenCalledWith(true);
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
name: JobName.DUPLICATE_DETECTION,
|
name: JobName.DuplicateDetection,
|
||||||
data: { id: assetStub.image.id },
|
data: { id: assetStub.image.id },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@ -154,7 +154,7 @@ describe(SearchService.name, () => {
|
|||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id });
|
const result = await sut.handleSearchDuplicates({ id });
|
||||||
|
|
||||||
expect(result).toBe(JobStatus.SKIPPED);
|
expect(result).toBe(JobStatus.Skipped);
|
||||||
expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled();
|
expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ describe(SearchService.name, () => {
|
|||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id });
|
const result = await sut.handleSearchDuplicates({ id });
|
||||||
|
|
||||||
expect(result).toBe(JobStatus.SKIPPED);
|
expect(result).toBe(JobStatus.Skipped);
|
||||||
expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled();
|
expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -180,7 +180,7 @@ describe(SearchService.name, () => {
|
|||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id: assetStub.image.id });
|
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`);
|
expect(mocks.logger.error).toHaveBeenCalledWith(`Asset ${assetStub.image.id} not found`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -190,7 +190,7 @@ describe(SearchService.name, () => {
|
|||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id });
|
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`);
|
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;
|
const id = assetStub.livePhotoMotionAsset.id;
|
||||||
mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue({
|
mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue({
|
||||||
...hasEmbedding,
|
...hasEmbedding,
|
||||||
visibility: AssetVisibility.HIDDEN,
|
visibility: AssetVisibility.Hidden,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id });
|
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`);
|
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 });
|
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`);
|
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 });
|
const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
|
||||||
|
|
||||||
expect(result).toBe(JobStatus.SUCCESS);
|
expect(result).toBe(JobStatus.Success);
|
||||||
expect(mocks.duplicateRepository.search).toHaveBeenCalledWith({
|
expect(mocks.duplicateRepository.search).toHaveBeenCalledWith({
|
||||||
assetId: hasEmbedding.id,
|
assetId: hasEmbedding.id,
|
||||||
embedding: hasEmbedding.embedding,
|
embedding: hasEmbedding.embedding,
|
||||||
@ -253,7 +253,7 @@ describe(SearchService.name, () => {
|
|||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
|
const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id });
|
||||||
|
|
||||||
expect(result).toBe(JobStatus.SUCCESS);
|
expect(result).toBe(JobStatus.Success);
|
||||||
expect(mocks.duplicateRepository.search).toHaveBeenCalledWith({
|
expect(mocks.duplicateRepository.search).toHaveBeenCalledWith({
|
||||||
assetId: hasEmbedding.id,
|
assetId: hasEmbedding.id,
|
||||||
embedding: hasEmbedding.embedding,
|
embedding: hasEmbedding.embedding,
|
||||||
@ -277,7 +277,7 @@ describe(SearchService.name, () => {
|
|||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id: hasDupe.id });
|
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.update).toHaveBeenCalledWith({ id: hasDupe.id, duplicateId: null });
|
||||||
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith({
|
expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith({
|
||||||
assetId: hasDupe.id,
|
assetId: hasDupe.id,
|
||||||
|
@ -29,11 +29,11 @@ export class DuplicateService extends BaseService {
|
|||||||
await this.duplicateRepository.deleteAll(auth.user.id, dto.ids);
|
await this.duplicateRepository.deleteAll(auth.user.id, dto.ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnJob({ name: JobName.QUEUE_DUPLICATE_DETECTION, queue: QueueName.DUPLICATE_DETECTION })
|
@OnJob({ name: JobName.QueueDuplicateDetection, queue: QueueName.DuplicateDetection })
|
||||||
async handleQueueSearchDuplicates({ force }: JobOf<JobName.QUEUE_DUPLICATE_DETECTION>): Promise<JobStatus> {
|
async handleQueueSearchDuplicates({ force }: JobOf<JobName.QueueDuplicateDetection>): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.getConfig({ withCache: false });
|
const { machineLearning } = await this.getConfig({ withCache: false });
|
||||||
if (!isDuplicateDetectionEnabled(machineLearning)) {
|
if (!isDuplicateDetectionEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
let jobs: JobItem[] = [];
|
let jobs: JobItem[] = [];
|
||||||
@ -44,7 +44,7 @@ export class DuplicateService extends BaseService {
|
|||||||
|
|
||||||
const assets = this.assetJobRepository.streamForSearchDuplicates(force);
|
const assets = this.assetJobRepository.streamForSearchDuplicates(force);
|
||||||
for await (const asset of assets) {
|
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) {
|
if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) {
|
||||||
await queueAll();
|
await queueAll();
|
||||||
}
|
}
|
||||||
@ -52,40 +52,40 @@ export class DuplicateService extends BaseService {
|
|||||||
|
|
||||||
await queueAll();
|
await queueAll();
|
||||||
|
|
||||||
return JobStatus.SUCCESS;
|
return JobStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnJob({ name: JobName.DUPLICATE_DETECTION, queue: QueueName.DUPLICATE_DETECTION })
|
@OnJob({ name: JobName.DuplicateDetection, queue: QueueName.DuplicateDetection })
|
||||||
async handleSearchDuplicates({ id }: JobOf<JobName.DUPLICATE_DETECTION>): Promise<JobStatus> {
|
async handleSearchDuplicates({ id }: JobOf<JobName.DuplicateDetection>): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.getConfig({ withCache: true });
|
const { machineLearning } = await this.getConfig({ withCache: true });
|
||||||
if (!isDuplicateDetectionEnabled(machineLearning)) {
|
if (!isDuplicateDetectionEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
const asset = await this.assetJobRepository.getForSearchDuplicatesJob(id);
|
const asset = await this.assetJobRepository.getForSearchDuplicatesJob(id);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
this.logger.error(`Asset ${id} not found`);
|
this.logger.error(`Asset ${id} not found`);
|
||||||
return JobStatus.FAILED;
|
return JobStatus.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asset.stackId) {
|
if (asset.stackId) {
|
||||||
this.logger.debug(`Asset ${id} is part of a stack, skipping`);
|
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`);
|
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`);
|
this.logger.debug(`Asset ${id} is locked, skipping`);
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!asset.embedding) {
|
if (!asset.embedding) {
|
||||||
this.logger.debug(`Asset ${id} is missing embedding`);
|
this.logger.debug(`Asset ${id} is missing embedding`);
|
||||||
return JobStatus.FAILED;
|
return JobStatus.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateAssets = await this.duplicateRepository.search({
|
const duplicateAssets = await this.duplicateRepository.search({
|
||||||
@ -110,7 +110,7 @@ export class DuplicateService extends BaseService {
|
|||||||
const duplicatesDetectedAt = new Date();
|
const duplicatesDetectedAt = new Date();
|
||||||
await this.assetRepository.upsertJobStatus(...assetIds.map((assetId) => ({ assetId, duplicatesDetectedAt })));
|
await this.assetRepository.upsertJobStatus(...assetIds.map((assetId) => ({ assetId, duplicatesDetectedAt })));
|
||||||
|
|
||||||
return JobStatus.SUCCESS;
|
return JobStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateDuplicates(
|
private async updateDuplicates(
|
||||||
|
@ -13,7 +13,7 @@ describe(JobService.name, () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
({ sut, mocks } = newTestService(JobService, {}));
|
({ sut, mocks } = newTestService(JobService, {}));
|
||||||
|
|
||||||
mocks.config.getWorker.mockReturnValue(ImmichWorker.MICROSERVICES);
|
mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
@ -25,10 +25,10 @@ describe(JobService.name, () => {
|
|||||||
sut.onConfigUpdate({ newConfig: defaults, oldConfig: {} as SystemConfig });
|
sut.onConfigUpdate({ newConfig: defaults, oldConfig: {} as SystemConfig });
|
||||||
|
|
||||||
expect(mocks.job.setConcurrency).toHaveBeenCalledTimes(15);
|
expect(mocks.job.setConcurrency).toHaveBeenCalledTimes(15);
|
||||||
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(5, QueueName.FACIAL_RECOGNITION, 1);
|
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(5, QueueName.FacialRecognition, 1);
|
||||||
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(7, QueueName.DUPLICATE_DETECTION, 1);
|
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(7, QueueName.DuplicateDetection, 1);
|
||||||
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(8, QueueName.BACKGROUND_TASK, 5);
|
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(8, QueueName.BackgroundTask, 5);
|
||||||
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(9, QueueName.STORAGE_TEMPLATE_MIGRATION, 1);
|
expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(9, QueueName.StorageTemplateMigration, 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -37,16 +37,16 @@ describe(JobService.name, () => {
|
|||||||
await sut.handleNightlyJobs();
|
await sut.handleNightlyJobs();
|
||||||
|
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
expect(mocks.job.queueAll).toHaveBeenCalledWith([
|
||||||
{ name: JobName.ASSET_DELETION_CHECK },
|
{ name: JobName.AssetDeletionCheck },
|
||||||
{ name: JobName.USER_DELETE_CHECK },
|
{ name: JobName.UserDeleteCheck },
|
||||||
{ name: JobName.PERSON_CLEANUP },
|
{ name: JobName.PersonCleanup },
|
||||||
{ name: JobName.MEMORIES_CLEANUP },
|
{ name: JobName.MemoriesCleanup },
|
||||||
{ name: JobName.CLEAN_OLD_SESSION_TOKENS },
|
{ name: JobName.CleanOldSessionTokens },
|
||||||
{ name: JobName.CLEAN_OLD_AUDIT_LOGS },
|
{ name: JobName.CleanOldAuditLogs },
|
||||||
{ name: JobName.MEMORIES_CREATE },
|
{ name: JobName.MemoriesCreate },
|
||||||
{ name: JobName.USER_SYNC_USAGE },
|
{ name: JobName.userSyncUsage },
|
||||||
{ name: JobName.QUEUE_GENERATE_THUMBNAILS, data: { force: false } },
|
{ name: JobName.QueueGenerateThumbnails, data: { force: false } },
|
||||||
{ name: JobName.QUEUE_FACIAL_RECOGNITION, data: { force: false, nightly: true } },
|
{ name: JobName.QueueFacialRecognition, data: { force: false, nightly: true } },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -82,49 +82,49 @@ describe(JobService.name, () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await expect(sut.getAllJobsStatus()).resolves.toEqual({
|
await expect(sut.getAllJobsStatus()).resolves.toEqual({
|
||||||
[QueueName.BACKGROUND_TASK]: expectedJobStatus,
|
[QueueName.BackgroundTask]: expectedJobStatus,
|
||||||
[QueueName.DUPLICATE_DETECTION]: expectedJobStatus,
|
[QueueName.DuplicateDetection]: expectedJobStatus,
|
||||||
[QueueName.SMART_SEARCH]: expectedJobStatus,
|
[QueueName.SmartSearch]: expectedJobStatus,
|
||||||
[QueueName.METADATA_EXTRACTION]: expectedJobStatus,
|
[QueueName.MetadataExtraction]: expectedJobStatus,
|
||||||
[QueueName.SEARCH]: expectedJobStatus,
|
[QueueName.Search]: expectedJobStatus,
|
||||||
[QueueName.STORAGE_TEMPLATE_MIGRATION]: expectedJobStatus,
|
[QueueName.StorageTemplateMigration]: expectedJobStatus,
|
||||||
[QueueName.MIGRATION]: expectedJobStatus,
|
[QueueName.Migration]: expectedJobStatus,
|
||||||
[QueueName.THUMBNAIL_GENERATION]: expectedJobStatus,
|
[QueueName.ThumbnailGeneration]: expectedJobStatus,
|
||||||
[QueueName.VIDEO_CONVERSION]: expectedJobStatus,
|
[QueueName.VideoConversion]: expectedJobStatus,
|
||||||
[QueueName.FACE_DETECTION]: expectedJobStatus,
|
[QueueName.FaceDetection]: expectedJobStatus,
|
||||||
[QueueName.FACIAL_RECOGNITION]: expectedJobStatus,
|
[QueueName.FacialRecognition]: expectedJobStatus,
|
||||||
[QueueName.SIDECAR]: expectedJobStatus,
|
[QueueName.Sidecar]: expectedJobStatus,
|
||||||
[QueueName.LIBRARY]: expectedJobStatus,
|
[QueueName.Library]: expectedJobStatus,
|
||||||
[QueueName.NOTIFICATION]: expectedJobStatus,
|
[QueueName.Notification]: expectedJobStatus,
|
||||||
[QueueName.BACKUP_DATABASE]: expectedJobStatus,
|
[QueueName.BackupDatabase]: expectedJobStatus,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleCommand', () => {
|
describe('handleCommand', () => {
|
||||||
it('should handle a pause command', async () => {
|
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 () => {
|
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 () => {
|
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 () => {
|
it('should not start a job that is already running', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: true, isPaused: false });
|
mocks.job.getQueueStatus.mockResolvedValue({ isActive: true, isPaused: false });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.handleCommand(QueueName.VIDEO_CONVERSION, { command: JobCommand.START, force: false }),
|
sut.handleCommand(QueueName.VideoConversion, { command: JobCommand.Start, force: false }),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.job.queue).not.toHaveBeenCalled();
|
expect(mocks.job.queue).not.toHaveBeenCalled();
|
||||||
@ -134,80 +134,80 @@ describe(JobService.name, () => {
|
|||||||
it('should handle a start video conversion command', async () => {
|
it('should handle a start video conversion command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should handle a start storage template migration command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should handle a start smart search command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should handle a start metadata extraction command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should handle a start sidecar command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should handle a start thumbnail generation command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should handle a start face detection command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should handle a start facial recognition command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should handle a start backup database command', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
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 () => {
|
it('should throw a bad request when an invalid queue is used', async () => {
|
||||||
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
mocks.job.getQueueStatus.mockResolvedValue({ isActive: false, isPaused: false });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.handleCommand(QueueName.BACKGROUND_TASK, { command: JobCommand.START, force: false }),
|
sut.handleCommand(QueueName.BackgroundTask, { command: JobCommand.Start, force: false }),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.job.queue).not.toHaveBeenCalled();
|
expect(mocks.job.queue).not.toHaveBeenCalled();
|
||||||
@ -217,10 +217,10 @@ describe(JobService.name, () => {
|
|||||||
|
|
||||||
describe('onJobStart', () => {
|
describe('onJobStart', () => {
|
||||||
it('should process a successful job', async () => {
|
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, {
|
await sut.onJobStart(QueueName.BackgroundTask, {
|
||||||
name: JobName.DELETE_FILES,
|
name: JobName.DeleteFiles,
|
||||||
data: { files: ['path/to/file'] },
|
data: { files: ['path/to/file'] },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -232,55 +232,55 @@ describe(JobService.name, () => {
|
|||||||
|
|
||||||
const tests: Array<{ item: JobItem; jobs: JobName[]; stub?: any }> = [
|
const tests: Array<{ item: JobItem; jobs: JobName[]; stub?: any }> = [
|
||||||
{
|
{
|
||||||
item: { name: JobName.SIDECAR_SYNC, data: { id: 'asset-1' } },
|
item: { name: JobName.SidecarSync, data: { id: 'asset-1' } },
|
||||||
jobs: [JobName.METADATA_EXTRACTION],
|
jobs: [JobName.MetadataExtraction],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.SIDECAR_DISCOVERY, data: { id: 'asset-1' } },
|
item: { name: JobName.SidecarDiscovery, data: { id: 'asset-1' } },
|
||||||
jobs: [JobName.METADATA_EXTRACTION],
|
jobs: [JobName.MetadataExtraction],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, data: { id: 'asset-1', source: 'upload' } },
|
item: { name: JobName.StorageTemplateMigrationSingle, data: { id: 'asset-1', source: 'upload' } },
|
||||||
jobs: [JobName.GENERATE_THUMBNAILS],
|
jobs: [JobName.GenerateThumbnails],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE, data: { id: 'asset-1' } },
|
item: { name: JobName.StorageTemplateMigrationSingle, data: { id: 'asset-1' } },
|
||||||
jobs: [],
|
jobs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: 'asset-1' } },
|
item: { name: JobName.GeneratePersonThumbnail, data: { id: 'asset-1' } },
|
||||||
jobs: [],
|
jobs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1' } },
|
item: { name: JobName.GenerateThumbnails, data: { id: 'asset-1' } },
|
||||||
jobs: [],
|
jobs: [],
|
||||||
stub: [assetStub.image],
|
stub: [assetStub.image],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1' } },
|
item: { name: JobName.GenerateThumbnails, data: { id: 'asset-1' } },
|
||||||
jobs: [],
|
jobs: [],
|
||||||
stub: [assetStub.video],
|
stub: [assetStub.video],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1', source: 'upload' } },
|
item: { name: JobName.GenerateThumbnails, data: { id: 'asset-1', source: 'upload' } },
|
||||||
jobs: [JobName.SMART_SEARCH, JobName.FACE_DETECTION],
|
jobs: [JobName.SmartSearch, JobName.FaceDetection],
|
||||||
stub: [assetStub.livePhotoStillAsset],
|
stub: [assetStub.livePhotoStillAsset],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1', source: 'upload' } },
|
item: { name: JobName.GenerateThumbnails, data: { id: 'asset-1', source: 'upload' } },
|
||||||
jobs: [JobName.SMART_SEARCH, JobName.FACE_DETECTION, JobName.VIDEO_CONVERSION],
|
jobs: [JobName.SmartSearch, JobName.FaceDetection, JobName.VideoConversation],
|
||||||
stub: [assetStub.video],
|
stub: [assetStub.video],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.SMART_SEARCH, data: { id: 'asset-1' } },
|
item: { name: JobName.SmartSearch, data: { id: 'asset-1' } },
|
||||||
jobs: [],
|
jobs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.FACE_DETECTION, data: { id: 'asset-1' } },
|
item: { name: JobName.FaceDetection, data: { id: 'asset-1' } },
|
||||||
jobs: [],
|
jobs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
item: { name: JobName.FACIAL_RECOGNITION, data: { id: 'asset-1' } },
|
item: { name: JobName.FacialRecognition, data: { id: 'asset-1' } },
|
||||||
jobs: [],
|
jobs: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -291,9 +291,9 @@ describe(JobService.name, () => {
|
|||||||
mocks.asset.getByIdsWithAllRelationsButStacks.mockResolvedValue(stub);
|
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) {
|
if (jobs.length > 1) {
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith(
|
expect(mocks.job.queueAll).toHaveBeenCalledWith(
|
||||||
@ -308,9 +308,9 @@ describe(JobService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it(`should not queue any jobs when ${item.name} fails`, async () => {
|
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();
|
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