diff --git a/server/src/app.module.ts b/server/src/app.module.ts index f7aa5f5949..ded08a96ab 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -1,233 +1,28 @@ import { BullModule } from '@nestjs/bullmq'; -import { Module, OnModuleInit, Provider, ValidationPipe } from '@nestjs/common'; +import { Module, OnModuleInit, ValidationPipe } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OpenTelemetryModule } from 'nestjs-otel'; -import { ListUsersCommand } from 'src/commands/list-users.command'; -import { DisableOAuthLogin, EnableOAuthLogin } from 'src/commands/oauth-login'; -import { DisablePasswordLoginCommand, EnablePasswordLoginCommand } from 'src/commands/password-login'; -import { PromptPasswordQuestions, ResetAdminPasswordCommand } from 'src/commands/reset-admin-password.command'; +import { commands } from 'src/commands'; import { bullConfig, bullQueues, immichAppConfig } from 'src/config'; -import { ActivityController } from 'src/controllers/activity.controller'; -import { AlbumController } from 'src/controllers/album.controller'; -import { APIKeyController } from 'src/controllers/api-key.controller'; -import { AppController } from 'src/controllers/app.controller'; -import { AssetControllerV1 } from 'src/controllers/asset-v1.controller'; -import { AssetController, AssetsController } from 'src/controllers/asset.controller'; -import { AuditController } from 'src/controllers/audit.controller'; -import { AuthController } from 'src/controllers/auth.controller'; -import { DownloadController } from 'src/controllers/download.controller'; -import { FaceController } from 'src/controllers/face.controller'; -import { JobController } from 'src/controllers/job.controller'; -import { LibraryController } from 'src/controllers/library.controller'; -import { OAuthController } from 'src/controllers/oauth.controller'; -import { PartnerController } from 'src/controllers/partner.controller'; -import { PersonController } from 'src/controllers/person.controller'; -import { SearchController } from 'src/controllers/search.controller'; -import { ServerInfoController } from 'src/controllers/server-info.controller'; -import { SharedLinkController } from 'src/controllers/shared-link.controller'; -import { SystemConfigController } from 'src/controllers/system-config.controller'; -import { TagController } from 'src/controllers/tag.controller'; -import { TimelineController } from 'src/controllers/timeline.controller'; -import { TrashController } from 'src/controllers/trash.controller'; -import { UserController } from 'src/controllers/user.controller'; +import { controllers } from 'src/controllers'; import { databaseConfig } from 'src/database.config'; -import { databaseEntities } from 'src/entities'; -import { IAccessRepository } from 'src/interfaces/access.interface'; -import { IActivityRepository } from 'src/interfaces/activity.interface'; -import { IAlbumRepository } from 'src/interfaces/album.interface'; -import { IKeyRepository } from 'src/interfaces/api-key.interface'; -import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface'; -import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface'; -import { IAssetRepository } from 'src/interfaces/asset.interface'; -import { IAuditRepository } from 'src/interfaces/audit.interface'; -import { ICryptoRepository } from 'src/interfaces/crypto.interface'; -import { IDatabaseRepository } from 'src/interfaces/database.interface'; -import { IEventRepository } from 'src/interfaces/event.interface'; -import { IJobRepository } from 'src/interfaces/job.interface'; -import { ILibraryRepository } from 'src/interfaces/library.interface'; -import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; -import { IMediaRepository } from 'src/interfaces/media.interface'; -import { IMetadataRepository } from 'src/interfaces/metadata.interface'; -import { IMetricRepository } from 'src/interfaces/metric.interface'; -import { IMoveRepository } from 'src/interfaces/move.interface'; -import { IPartnerRepository } from 'src/interfaces/partner.interface'; -import { IPersonRepository } from 'src/interfaces/person.interface'; -import { ISearchRepository } from 'src/interfaces/search.interface'; -import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; -import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; -import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; -import { ITagRepository } from 'src/interfaces/tag.interface'; -import { IUserTokenRepository } from 'src/interfaces/user-token.interface'; -import { IUserRepository } from 'src/interfaces/user.interface'; +import { entities } from 'src/entities'; import { AuthGuard } from 'src/middleware/auth.guard'; import { ErrorInterceptor } from 'src/middleware/error.interceptor'; import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; -import { AccessRepository } from 'src/repositories/access.repository'; -import { ActivityRepository } from 'src/repositories/activity.repository'; -import { AlbumRepository } from 'src/repositories/album.repository'; -import { ApiKeyRepository } from 'src/repositories/api-key.repository'; -import { AssetStackRepository } from 'src/repositories/asset-stack.repository'; -import { AssetRepositoryV1 } from 'src/repositories/asset-v1.repository'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { AuditRepository } from 'src/repositories/audit.repository'; -import { CryptoRepository } from 'src/repositories/crypto.repository'; -import { DatabaseRepository } from 'src/repositories/database.repository'; -import { EventRepository } from 'src/repositories/event.repository'; -import { JobRepository } from 'src/repositories/job.repository'; -import { LibraryRepository } from 'src/repositories/library.repository'; -import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; -import { MediaRepository } from 'src/repositories/media.repository'; -import { MetadataRepository } from 'src/repositories/metadata.repository'; -import { MetricRepository } from 'src/repositories/metric.repository'; -import { MoveRepository } from 'src/repositories/move.repository'; -import { PartnerRepository } from 'src/repositories/partner.repository'; -import { PersonRepository } from 'src/repositories/person.repository'; -import { SearchRepository } from 'src/repositories/search.repository'; -import { ServerInfoRepository } from 'src/repositories/server-info.repository'; -import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; -import { SystemConfigRepository } from 'src/repositories/system-config.repository'; -import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { TagRepository } from 'src/repositories/tag.repository'; -import { UserTokenRepository } from 'src/repositories/user-token.repository'; -import { UserRepository } from 'src/repositories/user.repository'; -import { ActivityService } from 'src/services/activity.service'; -import { AlbumService } from 'src/services/album.service'; -import { APIKeyService } from 'src/services/api-key.service'; +import { repositories } from 'src/repositories'; +import { services } from 'src/services'; import { ApiService } from 'src/services/api.service'; -import { AssetServiceV1 } from 'src/services/asset-v1.service'; -import { AssetService } from 'src/services/asset.service'; -import { AuditService } from 'src/services/audit.service'; -import { AuthService } from 'src/services/auth.service'; -import { DatabaseService } from 'src/services/database.service'; -import { DownloadService } from 'src/services/download.service'; -import { JobService } from 'src/services/job.service'; -import { LibraryService } from 'src/services/library.service'; -import { MediaService } from 'src/services/media.service'; -import { MetadataService } from 'src/services/metadata.service'; import { MicroservicesService } from 'src/services/microservices.service'; -import { PartnerService } from 'src/services/partner.service'; -import { PersonService } from 'src/services/person.service'; -import { SearchService } from 'src/services/search.service'; -import { ServerInfoService } from 'src/services/server-info.service'; -import { SharedLinkService } from 'src/services/shared-link.service'; -import { SmartInfoService } from 'src/services/smart-info.service'; -import { StorageTemplateService } from 'src/services/storage-template.service'; -import { StorageService } from 'src/services/storage.service'; -import { SystemConfigService } from 'src/services/system-config.service'; -import { TagService } from 'src/services/tag.service'; -import { TimelineService } from 'src/services/timeline.service'; -import { TrashService } from 'src/services/trash.service'; -import { UserService } from 'src/services/user.service'; import { otelConfig } from 'src/utils/instrumentation'; import { ImmichLogger } from 'src/utils/logger'; -const commands = [ - ResetAdminPasswordCommand, - PromptPasswordQuestions, - EnablePasswordLoginCommand, - DisablePasswordLoginCommand, - EnableOAuthLogin, - DisableOAuthLogin, - ListUsersCommand, -]; - -const controllers = [ - ActivityController, - AssetsController, - AssetControllerV1, - AssetController, - AppController, - AlbumController, - APIKeyController, - AuditController, - AuthController, - DownloadController, - FaceController, - JobController, - LibraryController, - OAuthController, - PartnerController, - SearchController, - ServerInfoController, - SharedLinkController, - SystemConfigController, - TagController, - TimelineController, - TrashController, - UserController, - PersonController, -]; - -const services: Provider[] = [ - ApiService, - MicroservicesService, - APIKeyService, - ActivityService, - AlbumService, - AssetService, - AssetServiceV1, - AuditService, - AuthService, - DatabaseService, - DownloadService, - ImmichLogger, - JobService, - LibraryService, - MediaService, - MetadataService, - PartnerService, - PersonService, - SearchService, - ServerInfoService, - SharedLinkService, - SmartInfoService, - StorageService, - StorageTemplateService, - SystemConfigService, - TagService, - TimelineService, - TrashService, - UserService, -]; - -const repositories: Provider[] = [ - { provide: IActivityRepository, useClass: ActivityRepository }, - { provide: IAccessRepository, useClass: AccessRepository }, - { provide: IAlbumRepository, useClass: AlbumRepository }, - { provide: IAssetRepository, useClass: AssetRepository }, - { provide: IAssetRepositoryV1, useClass: AssetRepositoryV1 }, - { provide: IAssetStackRepository, useClass: AssetStackRepository }, - { provide: IAuditRepository, useClass: AuditRepository }, - { provide: ICryptoRepository, useClass: CryptoRepository }, - { provide: IDatabaseRepository, useClass: DatabaseRepository }, - { provide: IEventRepository, useClass: EventRepository }, - { provide: IJobRepository, useClass: JobRepository }, - { provide: ILibraryRepository, useClass: LibraryRepository }, - { provide: IKeyRepository, useClass: ApiKeyRepository }, - { provide: IMachineLearningRepository, useClass: MachineLearningRepository }, - { provide: IMetadataRepository, useClass: MetadataRepository }, - { provide: IMetricRepository, useClass: MetricRepository }, - { provide: IMoveRepository, useClass: MoveRepository }, - { provide: IPartnerRepository, useClass: PartnerRepository }, - { provide: IPersonRepository, useClass: PersonRepository }, - { provide: IServerInfoRepository, useClass: ServerInfoRepository }, - { provide: ISharedLinkRepository, useClass: SharedLinkRepository }, - { provide: ISearchRepository, useClass: SearchRepository }, - { provide: IStorageRepository, useClass: StorageRepository }, - { provide: ISystemConfigRepository, useClass: SystemConfigRepository }, - { provide: ISystemMetadataRepository, useClass: SystemMetadataRepository }, - { provide: ITagRepository, useClass: TagRepository }, - { provide: IMediaRepository, useClass: MediaRepository }, - { provide: IUserRepository, useClass: UserRepository }, - { provide: IUserTokenRepository, useClass: UserTokenRepository }, -]; +const providers = [ImmichLogger]; +const common = [...services, ...providers, ...repositories]; const middleware = [ FileUploadInterceptor, @@ -243,13 +38,13 @@ const imports = [ EventEmitterModule.forRoot(), OpenTelemetryModule.forRoot(otelConfig), TypeOrmModule.forRoot(databaseConfig), - TypeOrmModule.forFeature(databaseEntities), + TypeOrmModule.forFeature(entities), ]; @Module({ imports: [...imports, ScheduleModule.forRoot()], controllers: [...controllers], - providers: [...services, ...repositories, ...middleware], + providers: [...common, ...middleware], }) export class ApiModule implements OnModuleInit { constructor(private service: ApiService) {} @@ -261,7 +56,7 @@ export class ApiModule implements OnModuleInit { @Module({ imports: [...imports], - providers: [...services, ...repositories, SchedulerRegistry], + providers: [...common, SchedulerRegistry], }) export class MicroservicesModule implements OnModuleInit { constructor(private service: MicroservicesService) {} @@ -273,7 +68,7 @@ export class MicroservicesModule implements OnModuleInit { @Module({ imports: [...imports], - providers: [...services, ...repositories, ...commands, SchedulerRegistry], + providers: [...common, ...commands, SchedulerRegistry], }) export class ImmichAdminModule {} @@ -282,10 +77,10 @@ export class ImmichAdminModule {} ConfigModule.forRoot(immichAppConfig), EventEmitterModule.forRoot(), TypeOrmModule.forRoot(databaseConfig), - TypeOrmModule.forFeature(databaseEntities), + TypeOrmModule.forFeature(entities), OpenTelemetryModule.forRoot(otelConfig), ], controllers: [...controllers], - providers: [...services, ...repositories, ...middleware, SchedulerRegistry], + providers: [...common, ...middleware, SchedulerRegistry], }) export class AppTestModule {} diff --git a/server/src/commands/index.ts b/server/src/commands/index.ts new file mode 100644 index 0000000000..016a26cb34 --- /dev/null +++ b/server/src/commands/index.ts @@ -0,0 +1,14 @@ +import { ListUsersCommand } from 'src/commands/list-users.command'; +import { DisableOAuthLogin, EnableOAuthLogin } from 'src/commands/oauth-login'; +import { DisablePasswordLoginCommand, EnablePasswordLoginCommand } from 'src/commands/password-login'; +import { PromptPasswordQuestions, ResetAdminPasswordCommand } from 'src/commands/reset-admin-password.command'; + +export const commands = [ + ResetAdminPasswordCommand, + PromptPasswordQuestions, + EnablePasswordLoginCommand, + DisablePasswordLoginCommand, + EnableOAuthLogin, + DisableOAuthLogin, + ListUsersCommand, +]; diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts new file mode 100644 index 0000000000..00cf7bbab7 --- /dev/null +++ b/server/src/controllers/index.ts @@ -0,0 +1,50 @@ +import { ActivityController } from 'src/controllers/activity.controller'; +import { AlbumController } from 'src/controllers/album.controller'; +import { APIKeyController } from 'src/controllers/api-key.controller'; +import { AppController } from 'src/controllers/app.controller'; +import { AssetControllerV1 } from 'src/controllers/asset-v1.controller'; +import { AssetController, AssetsController } from 'src/controllers/asset.controller'; +import { AuditController } from 'src/controllers/audit.controller'; +import { AuthController } from 'src/controllers/auth.controller'; +import { DownloadController } from 'src/controllers/download.controller'; +import { FaceController } from 'src/controllers/face.controller'; +import { JobController } from 'src/controllers/job.controller'; +import { LibraryController } from 'src/controllers/library.controller'; +import { OAuthController } from 'src/controllers/oauth.controller'; +import { PartnerController } from 'src/controllers/partner.controller'; +import { PersonController } from 'src/controllers/person.controller'; +import { SearchController } from 'src/controllers/search.controller'; +import { ServerInfoController } from 'src/controllers/server-info.controller'; +import { SharedLinkController } from 'src/controllers/shared-link.controller'; +import { SystemConfigController } from 'src/controllers/system-config.controller'; +import { TagController } from 'src/controllers/tag.controller'; +import { TimelineController } from 'src/controllers/timeline.controller'; +import { TrashController } from 'src/controllers/trash.controller'; +import { UserController } from 'src/controllers/user.controller'; + +export const controllers = [ + ActivityController, + AssetsController, + AssetControllerV1, + AssetController, + AppController, + AlbumController, + APIKeyController, + AuditController, + AuthController, + DownloadController, + FaceController, + JobController, + LibraryController, + OAuthController, + PartnerController, + SearchController, + ServerInfoController, + SharedLinkController, + SystemConfigController, + TagController, + TimelineController, + TrashController, + UserController, + PersonController, +]; diff --git a/server/src/entities/index.ts b/server/src/entities/index.ts index 33b5f00135..4b568cd9c1 100644 --- a/server/src/entities/index.ts +++ b/server/src/entities/index.ts @@ -21,7 +21,7 @@ import { TagEntity } from 'src/entities/tag.entity'; import { UserTokenEntity } from 'src/entities/user-token.entity'; import { UserEntity } from 'src/entities/user.entity'; -export const databaseEntities = [ +export const entities = [ ActivityEntity, AlbumEntity, APIKeyEntity, diff --git a/server/src/queries/activity.repository.sql b/server/src/queries/activity.repository.sql new file mode 100644 index 0000000000..306f71920a --- /dev/null +++ b/server/src/queries/activity.repository.sql @@ -0,0 +1,54 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- ActivityRepository.search +SELECT + "ActivityEntity"."id" AS "ActivityEntity_id", + "ActivityEntity"."createdAt" AS "ActivityEntity_createdAt", + "ActivityEntity"."updatedAt" AS "ActivityEntity_updatedAt", + "ActivityEntity"."albumId" AS "ActivityEntity_albumId", + "ActivityEntity"."userId" AS "ActivityEntity_userId", + "ActivityEntity"."assetId" AS "ActivityEntity_assetId", + "ActivityEntity"."comment" AS "ActivityEntity_comment", + "ActivityEntity"."isLiked" AS "ActivityEntity_isLiked", + "ActivityEntity__ActivityEntity_user"."id" AS "ActivityEntity__ActivityEntity_user_id", + "ActivityEntity__ActivityEntity_user"."name" AS "ActivityEntity__ActivityEntity_user_name", + "ActivityEntity__ActivityEntity_user"."avatarColor" AS "ActivityEntity__ActivityEntity_user_avatarColor", + "ActivityEntity__ActivityEntity_user"."isAdmin" AS "ActivityEntity__ActivityEntity_user_isAdmin", + "ActivityEntity__ActivityEntity_user"."email" AS "ActivityEntity__ActivityEntity_user_email", + "ActivityEntity__ActivityEntity_user"."storageLabel" AS "ActivityEntity__ActivityEntity_user_storageLabel", + "ActivityEntity__ActivityEntity_user"."oauthId" AS "ActivityEntity__ActivityEntity_user_oauthId", + "ActivityEntity__ActivityEntity_user"."profileImagePath" AS "ActivityEntity__ActivityEntity_user_profileImagePath", + "ActivityEntity__ActivityEntity_user"."shouldChangePassword" AS "ActivityEntity__ActivityEntity_user_shouldChangePassword", + "ActivityEntity__ActivityEntity_user"."createdAt" AS "ActivityEntity__ActivityEntity_user_createdAt", + "ActivityEntity__ActivityEntity_user"."deletedAt" AS "ActivityEntity__ActivityEntity_user_deletedAt", + "ActivityEntity__ActivityEntity_user"."status" AS "ActivityEntity__ActivityEntity_user_status", + "ActivityEntity__ActivityEntity_user"."updatedAt" AS "ActivityEntity__ActivityEntity_user_updatedAt", + "ActivityEntity__ActivityEntity_user"."memoriesEnabled" AS "ActivityEntity__ActivityEntity_user_memoriesEnabled", + "ActivityEntity__ActivityEntity_user"."quotaSizeInBytes" AS "ActivityEntity__ActivityEntity_user_quotaSizeInBytes", + "ActivityEntity__ActivityEntity_user"."quotaUsageInBytes" AS "ActivityEntity__ActivityEntity_user_quotaUsageInBytes" +FROM + "activity" "ActivityEntity" + LEFT JOIN "users" "ActivityEntity__ActivityEntity_user" ON "ActivityEntity__ActivityEntity_user"."id" = "ActivityEntity"."userId" + AND ( + "ActivityEntity__ActivityEntity_user"."deletedAt" IS NULL + ) +WHERE + (("ActivityEntity"."albumId" = $1)) +ORDER BY + "ActivityEntity"."createdAt" ASC + +-- ActivityRepository.getStatistics +SELECT + COUNT(DISTINCT ("ActivityEntity"."id")) AS "cnt" +FROM + "activity" "ActivityEntity" + LEFT JOIN "users" "ActivityEntity__ActivityEntity_user" ON "ActivityEntity__ActivityEntity_user"."id" = "ActivityEntity"."userId" + AND ( + "ActivityEntity__ActivityEntity_user"."deletedAt" IS NULL + ) +WHERE + ( + ("ActivityEntity"."assetId" = $1) + AND ("ActivityEntity"."albumId" = $2) + AND ("ActivityEntity"."isLiked" = $3) + ) diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index de7d061cd3..fab0f5376f 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -1,5 +1,88 @@ -- NOTE: This file is auto generated by ./sql-generator +-- AssetRepository.getByDayOfYear +SELECT + "entity"."id" AS "entity_id", + "entity"."deviceAssetId" AS "entity_deviceAssetId", + "entity"."ownerId" AS "entity_ownerId", + "entity"."libraryId" AS "entity_libraryId", + "entity"."deviceId" AS "entity_deviceId", + "entity"."type" AS "entity_type", + "entity"."originalPath" AS "entity_originalPath", + "entity"."resizePath" AS "entity_resizePath", + "entity"."webpPath" AS "entity_webpPath", + "entity"."thumbhash" AS "entity_thumbhash", + "entity"."encodedVideoPath" AS "entity_encodedVideoPath", + "entity"."createdAt" AS "entity_createdAt", + "entity"."updatedAt" AS "entity_updatedAt", + "entity"."deletedAt" AS "entity_deletedAt", + "entity"."fileCreatedAt" AS "entity_fileCreatedAt", + "entity"."localDateTime" AS "entity_localDateTime", + "entity"."fileModifiedAt" AS "entity_fileModifiedAt", + "entity"."isFavorite" AS "entity_isFavorite", + "entity"."isArchived" AS "entity_isArchived", + "entity"."isExternal" AS "entity_isExternal", + "entity"."isReadOnly" AS "entity_isReadOnly", + "entity"."isOffline" AS "entity_isOffline", + "entity"."checksum" AS "entity_checksum", + "entity"."duration" AS "entity_duration", + "entity"."isVisible" AS "entity_isVisible", + "entity"."livePhotoVideoId" AS "entity_livePhotoVideoId", + "entity"."originalFileName" AS "entity_originalFileName", + "entity"."sidecarPath" AS "entity_sidecarPath", + "entity"."stackId" AS "entity_stackId", + "exifInfo"."assetId" AS "exifInfo_assetId", + "exifInfo"."description" AS "exifInfo_description", + "exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth", + "exifInfo"."exifImageHeight" AS "exifInfo_exifImageHeight", + "exifInfo"."fileSizeInByte" AS "exifInfo_fileSizeInByte", + "exifInfo"."orientation" AS "exifInfo_orientation", + "exifInfo"."dateTimeOriginal" AS "exifInfo_dateTimeOriginal", + "exifInfo"."modifyDate" AS "exifInfo_modifyDate", + "exifInfo"."timeZone" AS "exifInfo_timeZone", + "exifInfo"."latitude" AS "exifInfo_latitude", + "exifInfo"."longitude" AS "exifInfo_longitude", + "exifInfo"."projectionType" AS "exifInfo_projectionType", + "exifInfo"."city" AS "exifInfo_city", + "exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID", + "exifInfo"."autoStackId" AS "exifInfo_autoStackId", + "exifInfo"."state" AS "exifInfo_state", + "exifInfo"."country" AS "exifInfo_country", + "exifInfo"."make" AS "exifInfo_make", + "exifInfo"."model" AS "exifInfo_model", + "exifInfo"."lensModel" AS "exifInfo_lensModel", + "exifInfo"."fNumber" AS "exifInfo_fNumber", + "exifInfo"."focalLength" AS "exifInfo_focalLength", + "exifInfo"."iso" AS "exifInfo_iso", + "exifInfo"."exposureTime" AS "exifInfo_exposureTime", + "exifInfo"."profileDescription" AS "exifInfo_profileDescription", + "exifInfo"."colorspace" AS "exifInfo_colorspace", + "exifInfo"."bitsPerSample" AS "exifInfo_bitsPerSample", + "exifInfo"."fps" AS "exifInfo_fps" +FROM + "assets" "entity" + LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "entity"."id" +WHERE + ( + "entity"."ownerId" IN ($1) + AND "entity"."isVisible" = true + AND "entity"."isArchived" = false + AND "entity"."resizePath" IS NOT NULL + AND EXTRACT( + DAY + FROM + "entity"."localDateTime" AT TIME ZONE 'UTC' + ) = $2 + AND EXTRACT( + MONTH + FROM + "entity"."localDateTime" AT TIME ZONE 'UTC' + ) = $3 + ) + AND ("entity"."deletedAt" IS NULL) +ORDER BY + "entity"."localDateTime" ASC + -- AssetRepository.getByIds SELECT "AssetEntity"."id" AS "AssetEntity_id", @@ -170,6 +253,34 @@ DELETE FROM "assets" WHERE "ownerId" = $1 +-- AssetRepository.getLibraryAssetPaths +SELECT DISTINCT + "distinctAlias"."AssetEntity_id" AS "ids_AssetEntity_id" +FROM + ( + SELECT + "AssetEntity"."id" AS "AssetEntity_id", + "AssetEntity"."originalPath" AS "AssetEntity_originalPath", + "AssetEntity"."isOffline" AS "AssetEntity_isOffline" + FROM + "assets" "AssetEntity" + LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId" + AND ( + "AssetEntity__AssetEntity_library"."deletedAt" IS NULL + ) + WHERE + ( + ( + ((("AssetEntity__AssetEntity_library"."id" = $1))) + ) + ) + AND ("AssetEntity"."deletedAt" IS NULL) + ) "distinctAlias" +ORDER BY + "AssetEntity_id" ASC +LIMIT + 2 + -- AssetRepository.getByLibraryIdAndOriginalPath SELECT DISTINCT "distinctAlias"."AssetEntity_id" AS "ids_AssetEntity_id" diff --git a/server/src/queries/audit.repository.sql b/server/src/queries/audit.repository.sql deleted file mode 100644 index 21f9f116bd..0000000000 --- a/server/src/queries/audit.repository.sql +++ /dev/null @@ -1 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator diff --git a/server/src/queries/metadata.repository.sql b/server/src/queries/metadata.repository.sql new file mode 100644 index 0000000000..bed7d59ab6 --- /dev/null +++ b/server/src/queries/metadata.repository.sql @@ -0,0 +1,66 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- MetadataRepository.getCountries +SELECT DISTINCT + ON ("exif"."country") "exif"."country" AS "exif_country", + "exif"."assetId" AS "exif_assetId" +FROM + "exif" "exif" + LEFT JOIN "assets" "asset" ON "asset"."id" = "exif"."assetId" + AND ("asset"."deletedAt" IS NULL) +WHERE + "asset"."ownerId" = $1 + AND "exif"."country" IS NOT NULL + +-- MetadataRepository.getStates +SELECT DISTINCT + ON ("exif"."state") "exif"."state" AS "exif_state", + "exif"."assetId" AS "exif_assetId" +FROM + "exif" "exif" + LEFT JOIN "assets" "asset" ON "asset"."id" = "exif"."assetId" + AND ("asset"."deletedAt" IS NULL) +WHERE + "asset"."ownerId" = $1 + AND "exif"."state" IS NOT NULL + AND "exif"."country" = $2 + +-- MetadataRepository.getCities +SELECT DISTINCT + ON ("exif"."city") "exif"."city" AS "exif_city", + "exif"."assetId" AS "exif_assetId" +FROM + "exif" "exif" + LEFT JOIN "assets" "asset" ON "asset"."id" = "exif"."assetId" + AND ("asset"."deletedAt" IS NULL) +WHERE + "asset"."ownerId" = $1 + AND "exif"."city" IS NOT NULL + AND "exif"."country" = $2 + AND "exif"."state" = $3 + +-- MetadataRepository.getCameraMakes +SELECT DISTINCT + ON ("exif"."make") "exif"."make" AS "exif_make", + "exif"."assetId" AS "exif_assetId" +FROM + "exif" "exif" + LEFT JOIN "assets" "asset" ON "asset"."id" = "exif"."assetId" + AND ("asset"."deletedAt" IS NULL) +WHERE + "asset"."ownerId" = $1 + AND "exif"."make" IS NOT NULL + AND "exif"."model" = $2 + +-- MetadataRepository.getCameraModels +SELECT DISTINCT + ON ("exif"."model") "exif"."model" AS "exif_model", + "exif"."assetId" AS "exif_assetId" +FROM + "exif" "exif" + LEFT JOIN "assets" "asset" ON "asset"."id" = "exif"."assetId" + AND ("asset"."deletedAt" IS NULL) +WHERE + "asset"."ownerId" = $1 + AND "exif"."model" IS NOT NULL + AND "exif"."make" = $2 diff --git a/server/src/queries/partner.repository.sql b/server/src/queries/partner.repository.sql deleted file mode 100644 index 21f9f116bd..0000000000 --- a/server/src/queries/partner.repository.sql +++ /dev/null @@ -1 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql index 68a5918c4c..c7b6600681 100644 --- a/server/src/queries/search.repository.sql +++ b/server/src/queries/search.repository.sql @@ -278,7 +278,7 @@ WITH RECURSIVE exif INNER JOIN assets ON exif."assetId" = assets.id WHERE - "ownerId" = ANY ('$1'::uuid []) + "ownerId" = ANY ($1::uuid []) AND "isVisible" = $2 AND "isArchived" = $3 AND type = $4 @@ -302,7 +302,7 @@ WITH RECURSIVE INNER JOIN assets ON exif."assetId" = assets.id WHERE city > c.city - AND "ownerId" = ANY ('$1'::uuid []) + AND "ownerId" = ANY ($1::uuid []) AND "isVisible" = $2 AND "isArchived" = $3 AND type = $4 diff --git a/server/src/queries/system.metadata.repository.sql b/server/src/queries/system.metadata.repository.sql deleted file mode 100644 index 21f9f116bd..0000000000 --- a/server/src/queries/system.metadata.repository.sql +++ /dev/null @@ -1 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator diff --git a/server/src/queries/tag.repository.sql b/server/src/queries/tag.repository.sql deleted file mode 100644 index 21f9f116bd..0000000000 --- a/server/src/queries/tag.repository.sql +++ /dev/null @@ -1 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 735cfa325d..907cddb893 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -75,7 +75,7 @@ export class AssetRepository implements IAssetRepository { return this.repository.save(asset); } - @GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] }) + @GenerateSql({ params: [[DummyValue.UUID], { day: 1, month: 1 }] }) getByDayOfYear(ownerIds: string[], { day, month }: MonthDay): Promise { return this.repository .createQueryBuilder('entity') @@ -159,7 +159,7 @@ export class AssetRepository implements IAssetRepository { return this.getAll(pagination, { ...options, userIds: [userId] }); } - @GenerateSql({ params: [[DummyValue.UUID]] }) + @GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] }) getLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated { return paginate(this.repository, pagination, { select: { id: true, originalPath: true, isOffline: true }, diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts new file mode 100644 index 0000000000..4ed114216b --- /dev/null +++ b/server/src/repositories/index.ts @@ -0,0 +1,90 @@ +import { IAccessRepository } from 'src/interfaces/access.interface'; +import { IActivityRepository } from 'src/interfaces/activity.interface'; +import { IAlbumRepository } from 'src/interfaces/album.interface'; +import { IKeyRepository } from 'src/interfaces/api-key.interface'; +import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface'; +import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface'; +import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { IAuditRepository } from 'src/interfaces/audit.interface'; +import { ICryptoRepository } from 'src/interfaces/crypto.interface'; +import { IDatabaseRepository } from 'src/interfaces/database.interface'; +import { IEventRepository } from 'src/interfaces/event.interface'; +import { IJobRepository } from 'src/interfaces/job.interface'; +import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; +import { IMediaRepository } from 'src/interfaces/media.interface'; +import { IMetadataRepository } from 'src/interfaces/metadata.interface'; +import { IMetricRepository } from 'src/interfaces/metric.interface'; +import { IMoveRepository } from 'src/interfaces/move.interface'; +import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { IPersonRepository } from 'src/interfaces/person.interface'; +import { ISearchRepository } from 'src/interfaces/search.interface'; +import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; +import { IStorageRepository } from 'src/interfaces/storage.interface'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { ITagRepository } from 'src/interfaces/tag.interface'; +import { IUserTokenRepository } from 'src/interfaces/user-token.interface'; +import { IUserRepository } from 'src/interfaces/user.interface'; +import { AccessRepository } from 'src/repositories/access.repository'; +import { ActivityRepository } from 'src/repositories/activity.repository'; +import { AlbumRepository } from 'src/repositories/album.repository'; +import { ApiKeyRepository } from 'src/repositories/api-key.repository'; +import { AssetStackRepository } from 'src/repositories/asset-stack.repository'; +import { AssetRepositoryV1 } from 'src/repositories/asset-v1.repository'; +import { AssetRepository } from 'src/repositories/asset.repository'; +import { AuditRepository } from 'src/repositories/audit.repository'; +import { CryptoRepository } from 'src/repositories/crypto.repository'; +import { DatabaseRepository } from 'src/repositories/database.repository'; +import { EventRepository } from 'src/repositories/event.repository'; +import { JobRepository } from 'src/repositories/job.repository'; +import { LibraryRepository } from 'src/repositories/library.repository'; +import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; +import { MediaRepository } from 'src/repositories/media.repository'; +import { MetadataRepository } from 'src/repositories/metadata.repository'; +import { MetricRepository } from 'src/repositories/metric.repository'; +import { MoveRepository } from 'src/repositories/move.repository'; +import { PartnerRepository } from 'src/repositories/partner.repository'; +import { PersonRepository } from 'src/repositories/person.repository'; +import { SearchRepository } from 'src/repositories/search.repository'; +import { ServerInfoRepository } from 'src/repositories/server-info.repository'; +import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; +import { StorageRepository } from 'src/repositories/storage.repository'; +import { SystemConfigRepository } from 'src/repositories/system-config.repository'; +import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; +import { TagRepository } from 'src/repositories/tag.repository'; +import { UserTokenRepository } from 'src/repositories/user-token.repository'; +import { UserRepository } from 'src/repositories/user.repository'; + +export const repositories = [ + { provide: IActivityRepository, useClass: ActivityRepository }, + { provide: IAccessRepository, useClass: AccessRepository }, + { provide: IAlbumRepository, useClass: AlbumRepository }, + { provide: IAssetRepository, useClass: AssetRepository }, + { provide: IAssetRepositoryV1, useClass: AssetRepositoryV1 }, + { provide: IAssetStackRepository, useClass: AssetStackRepository }, + { provide: IAuditRepository, useClass: AuditRepository }, + { provide: ICryptoRepository, useClass: CryptoRepository }, + { provide: IDatabaseRepository, useClass: DatabaseRepository }, + { provide: IEventRepository, useClass: EventRepository }, + { provide: IJobRepository, useClass: JobRepository }, + { provide: ILibraryRepository, useClass: LibraryRepository }, + { provide: IKeyRepository, useClass: ApiKeyRepository }, + { provide: IMachineLearningRepository, useClass: MachineLearningRepository }, + { provide: IMetadataRepository, useClass: MetadataRepository }, + { provide: IMetricRepository, useClass: MetricRepository }, + { provide: IMoveRepository, useClass: MoveRepository }, + { provide: IPartnerRepository, useClass: PartnerRepository }, + { provide: IPersonRepository, useClass: PersonRepository }, + { provide: IServerInfoRepository, useClass: ServerInfoRepository }, + { provide: ISharedLinkRepository, useClass: SharedLinkRepository }, + { provide: ISearchRepository, useClass: SearchRepository }, + { provide: IStorageRepository, useClass: StorageRepository }, + { provide: ISystemConfigRepository, useClass: SystemConfigRepository }, + { provide: ISystemMetadataRepository, useClass: SystemMetadataRepository }, + { provide: ITagRepository, useClass: TagRepository }, + { provide: IMediaRepository, useClass: MediaRepository }, + { provide: IUserRepository, useClass: UserRepository }, + { provide: IUserTokenRepository, useClass: UserTokenRepository }, +]; diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts index 485e11541d..5cfa25eff4 100644 --- a/server/src/repositories/library.repository.ts +++ b/server/src/repositories/library.repository.ts @@ -5,7 +5,7 @@ import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; import { LibraryEntity, LibraryType } from 'src/entities/library.entity'; import { ILibraryRepository } from 'src/interfaces/library.interface'; import { Instrumentation } from 'src/utils/instrumentation'; -import { IsNull, Not } from 'typeorm'; +import { EntityNotFoundError, IsNull, Not } from 'typeorm'; import { Repository } from 'typeorm/repository/Repository.js'; @Instrumentation() @@ -139,6 +139,10 @@ export class LibraryRepository implements ILibraryRepository { .where('libraries.id = :id', { id }) .getRawOne(); + if (!stats) { + throw new EntityNotFoundError(LibraryEntity, { where: { id } }); + } + return { photos: Number(stats.photos), videos: Number(stats.videos), diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index 867d6b1749..0acbafe699 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -107,6 +107,7 @@ export class PersonRepository implements IPersonRepository { @GenerateSql({ params: [DummyValue.UUID] }) getFaceById(id: string): Promise { + // TODO return null instead of find or fail return this.assetFaceRepository.findOneOrFail({ where: { id }, relations: { diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 1642cca4ff..2de48b7415 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -225,7 +225,7 @@ export class SearchRepository implements ISearchRepository { .getMany(); } - @GenerateSql({ params: [[DummyValue.UUID, DummyValue.UUID]] }) + @GenerateSql({ params: [[DummyValue.UUID]] }) async getAssetsByCity(userIds: string[]): Promise { const parameters = [userIds, true, false, AssetType.IMAGE]; const rawRes = await this.repository.query(this.assetsByCityQuery, parameters); @@ -315,7 +315,7 @@ WITH RECURSIVE cte AS ( SELECT city, "assetId" FROM exif INNER JOIN assets ON exif."assetId" = assets.id - WHERE "ownerId" = ANY('$1'::uuid[]) AND "isVisible" = $2 AND "isArchived" = $3 AND type = $4 + WHERE "ownerId" = ANY($1::uuid[]) AND "isVisible" = $2 AND "isArchived" = $3 AND type = $4 ORDER BY city LIMIT 1 ) @@ -328,7 +328,7 @@ WITH RECURSIVE cte AS ( SELECT city, "assetId" FROM exif INNER JOIN assets ON exif."assetId" = assets.id - WHERE city > c.city AND "ownerId" = ANY('$1'::uuid[]) AND "isVisible" = $2 AND "isArchived" = $3 AND type = $4 + WHERE city > c.city AND "ownerId" = ANY($1::uuid[]) AND "isVisible" = $2 AND "isArchived" = $3 AND type = $4 ORDER BY city LIMIT 1 ) l diff --git a/server/src/services/index.ts b/server/src/services/index.ts new file mode 100644 index 0000000000..5a97d16fec --- /dev/null +++ b/server/src/services/index.ts @@ -0,0 +1,59 @@ +import { ActivityService } from 'src/services/activity.service'; +import { AlbumService } from 'src/services/album.service'; +import { APIKeyService } from 'src/services/api-key.service'; +import { ApiService } from 'src/services/api.service'; +import { AssetServiceV1 } from 'src/services/asset-v1.service'; +import { AssetService } from 'src/services/asset.service'; +import { AuditService } from 'src/services/audit.service'; +import { AuthService } from 'src/services/auth.service'; +import { DatabaseService } from 'src/services/database.service'; +import { DownloadService } from 'src/services/download.service'; +import { JobService } from 'src/services/job.service'; +import { LibraryService } from 'src/services/library.service'; +import { MediaService } from 'src/services/media.service'; +import { MetadataService } from 'src/services/metadata.service'; +import { MicroservicesService } from 'src/services/microservices.service'; +import { PartnerService } from 'src/services/partner.service'; +import { PersonService } from 'src/services/person.service'; +import { SearchService } from 'src/services/search.service'; +import { ServerInfoService } from 'src/services/server-info.service'; +import { SharedLinkService } from 'src/services/shared-link.service'; +import { SmartInfoService } from 'src/services/smart-info.service'; +import { StorageTemplateService } from 'src/services/storage-template.service'; +import { StorageService } from 'src/services/storage.service'; +import { SystemConfigService } from 'src/services/system-config.service'; +import { TagService } from 'src/services/tag.service'; +import { TimelineService } from 'src/services/timeline.service'; +import { TrashService } from 'src/services/trash.service'; +import { UserService } from 'src/services/user.service'; + +export const services = [ + ApiService, + MicroservicesService, + APIKeyService, + ActivityService, + AlbumService, + AssetService, + AssetServiceV1, + AuditService, + AuthService, + DatabaseService, + DownloadService, + JobService, + LibraryService, + MediaService, + MetadataService, + PartnerService, + PersonService, + SearchService, + ServerInfoService, + SharedLinkService, + SmartInfoService, + StorageService, + StorageTemplateService, + SystemConfigService, + TagService, + TimelineService, + TrashService, + UserService, +]; diff --git a/server/src/utils/sql.ts b/server/src/utils/sql.ts index 1afe4d5a84..662c40fcba 100644 --- a/server/src/utils/sql.ts +++ b/server/src/utils/sql.ts @@ -1,31 +1,21 @@ #!/usr/bin/env node import { INestApplication } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { SchedulerRegistry } from '@nestjs/schedule'; import { Test } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { OpenTelemetryModule } from 'nestjs-otel'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { format } from 'sql-formatter'; import { databaseConfig } from 'src/database.config'; import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; -import { databaseEntities } from 'src/entities'; -import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; +import { entities } from 'src/entities'; +import { repositories } from 'src/repositories'; import { AccessRepository } from 'src/repositories/access.repository'; -import { AlbumRepository } from 'src/repositories/album.repository'; -import { ApiKeyRepository } from 'src/repositories/api-key.repository'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { AuditRepository } from 'src/repositories/audit.repository'; -import { LibraryRepository } from 'src/repositories/library.repository'; -import { MoveRepository } from 'src/repositories/move.repository'; -import { PartnerRepository } from 'src/repositories/partner.repository'; -import { PersonRepository } from 'src/repositories/person.repository'; -import { SearchRepository } from 'src/repositories/search.repository'; -import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; -import { SystemConfigRepository } from 'src/repositories/system-config.repository'; -import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { TagRepository } from 'src/repositories/tag.repository'; -import { UserTokenRepository } from 'src/repositories/user-token.repository'; -import { UserRepository } from 'src/repositories/user.repository'; +import { AuthService } from 'src/services/auth.service'; +import { otelConfig } from 'src/utils/instrumentation'; import { Logger } from 'typeorm'; export class SqlLogger implements Logger { @@ -52,26 +42,9 @@ export class SqlLogger implements Logger { } const reflector = new Reflector(); -const repositories = [ - AccessRepository, - AlbumRepository, - ApiKeyRepository, - AssetRepository, - AuditRepository, - LibraryRepository, - MoveRepository, - PartnerRepository, - PersonRepository, - SharedLinkRepository, - SearchRepository, - SystemConfigRepository, - SystemMetadataRepository, - TagRepository, - UserTokenRepository, - UserRepository, -]; -type Repository = (typeof repositories)[0]; +type Repository = (typeof repositories)[0]['useClass']; +type Provider = { provide: any; useClass: Repository }; type SqlGeneratorOptions = { targetDir: string }; class SqlGenerator { @@ -84,8 +57,8 @@ class SqlGenerator { async run() { try { await this.setup(); - for (const Repository of repositories) { - await this.process(Repository); + for (const repository of repositories) { + await this.process(repository); } await this.write(); this.stats(); @@ -102,25 +75,27 @@ class SqlGenerator { imports: [ TypeOrmModule.forRoot({ ...databaseConfig, - entities: databaseEntities, + entities, logging: ['query'], logger: this.sqlLogger, }), - TypeOrmModule.forFeature(databaseEntities), + TypeOrmModule.forFeature(entities), + EventEmitterModule.forRoot(), + OpenTelemetryModule.forRoot(otelConfig), ], - providers: [{ provide: ISystemConfigRepository, useClass: SystemConfigRepository }, ...repositories], + providers: [...repositories, AuthService, SchedulerRegistry], }).compile(); this.app = await moduleFixture.createNestApplication().init(); } - async process(Repository: Repository) { + async process({ provide: token, useClass: Repository }: Provider) { if (!this.app) { throw new Error('Not initialized'); } const data: string[] = [`-- NOTE: This file is auto generated by ./sql-generator`]; - const instance = this.app.get(Repository); + const instance = this.app.get(token); // normal repositories data.push(...(await this.runTargets(instance, `${Repository.name}`))); @@ -180,6 +155,10 @@ class SqlGenerator { private async write() { for (const [repoName, data] of Object.entries(this.results)) { + // only contains the header + if (data.length === 1) { + continue; + } const filename = repoName.replaceAll(/[A-Z]/g, (letter) => `.${letter.toLowerCase()}`).replace('.', ''); const file = join(this.options.targetDir, `${filename}.sql`); await writeFile(file, data.join('\n\n') + '\n');