diff --git a/server/src/interfaces/stack.interface.ts b/server/src/interfaces/stack.interface.ts index 378f63fd95..a9fb8cec76 100644 --- a/server/src/interfaces/stack.interface.ts +++ b/server/src/interfaces/stack.interface.ts @@ -1,3 +1,4 @@ +import { Updateable } from 'kysely'; import { StackEntity } from 'src/entities/stack.entity'; export const IStackRepository = 'IStackRepository'; @@ -10,8 +11,8 @@ export interface StackSearch { export interface IStackRepository { search(query: StackSearch): Promise<StackEntity[]>; create(stack: { ownerId: string; assetIds: string[] }): Promise<StackEntity>; - update(stack: Pick<StackEntity, 'id'> & Partial<StackEntity>): Promise<StackEntity>; + update(id: string, entity: Updateable<StackEntity>): Promise<StackEntity>; delete(id: string): Promise<void>; deleteAll(ids: string[]): Promise<void>; - getById(id: string): Promise<StackEntity | null>; + getById(id: string): Promise<StackEntity | undefined>; } diff --git a/server/src/queries/stack.repository.sql b/server/src/queries/stack.repository.sql index f7da019f05..54f86c94af 100644 --- a/server/src/queries/stack.repository.sql +++ b/server/src/queries/stack.repository.sql @@ -1,257 +1,95 @@ -- NOTE: This file is auto generated by ./sql-generator -- StackRepository.search -SELECT - "StackEntity"."id" AS "StackEntity_id", - "StackEntity"."ownerId" AS "StackEntity_ownerId", - "StackEntity"."primaryAssetId" AS "StackEntity_primaryAssetId", - "StackEntity__StackEntity_assets"."id" AS "StackEntity__StackEntity_assets_id", - "StackEntity__StackEntity_assets"."deviceAssetId" AS "StackEntity__StackEntity_assets_deviceAssetId", - "StackEntity__StackEntity_assets"."ownerId" AS "StackEntity__StackEntity_assets_ownerId", - "StackEntity__StackEntity_assets"."libraryId" AS "StackEntity__StackEntity_assets_libraryId", - "StackEntity__StackEntity_assets"."deviceId" AS "StackEntity__StackEntity_assets_deviceId", - "StackEntity__StackEntity_assets"."type" AS "StackEntity__StackEntity_assets_type", - "StackEntity__StackEntity_assets"."status" AS "StackEntity__StackEntity_assets_status", - "StackEntity__StackEntity_assets"."originalPath" AS "StackEntity__StackEntity_assets_originalPath", - "StackEntity__StackEntity_assets"."thumbhash" AS "StackEntity__StackEntity_assets_thumbhash", - "StackEntity__StackEntity_assets"."encodedVideoPath" AS "StackEntity__StackEntity_assets_encodedVideoPath", - "StackEntity__StackEntity_assets"."createdAt" AS "StackEntity__StackEntity_assets_createdAt", - "StackEntity__StackEntity_assets"."updatedAt" AS "StackEntity__StackEntity_assets_updatedAt", - "StackEntity__StackEntity_assets"."deletedAt" AS "StackEntity__StackEntity_assets_deletedAt", - "StackEntity__StackEntity_assets"."fileCreatedAt" AS "StackEntity__StackEntity_assets_fileCreatedAt", - "StackEntity__StackEntity_assets"."localDateTime" AS "StackEntity__StackEntity_assets_localDateTime", - "StackEntity__StackEntity_assets"."fileModifiedAt" AS "StackEntity__StackEntity_assets_fileModifiedAt", - "StackEntity__StackEntity_assets"."isFavorite" AS "StackEntity__StackEntity_assets_isFavorite", - "StackEntity__StackEntity_assets"."isArchived" AS "StackEntity__StackEntity_assets_isArchived", - "StackEntity__StackEntity_assets"."isExternal" AS "StackEntity__StackEntity_assets_isExternal", - "StackEntity__StackEntity_assets"."isOffline" AS "StackEntity__StackEntity_assets_isOffline", - "StackEntity__StackEntity_assets"."checksum" AS "StackEntity__StackEntity_assets_checksum", - "StackEntity__StackEntity_assets"."duration" AS "StackEntity__StackEntity_assets_duration", - "StackEntity__StackEntity_assets"."isVisible" AS "StackEntity__StackEntity_assets_isVisible", - "StackEntity__StackEntity_assets"."livePhotoVideoId" AS "StackEntity__StackEntity_assets_livePhotoVideoId", - "StackEntity__StackEntity_assets"."originalFileName" AS "StackEntity__StackEntity_assets_originalFileName", - "StackEntity__StackEntity_assets"."sidecarPath" AS "StackEntity__StackEntity_assets_sidecarPath", - "StackEntity__StackEntity_assets"."stackId" AS "StackEntity__StackEntity_assets_stackId", - "StackEntity__StackEntity_assets"."duplicateId" AS "StackEntity__StackEntity_assets_duplicateId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_assetId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."description" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_description", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageWidth" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageWidth", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageHeight" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageHeight", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fileSizeInByte" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fileSizeInByte", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."orientation" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_orientation", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."dateTimeOriginal" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_dateTimeOriginal", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."modifyDate" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_modifyDate", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."timeZone" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_timeZone", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."latitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_latitude", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."longitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_longitude", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."projectionType" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_projectionType", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."city" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_city", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."livePhotoCID" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_livePhotoCID", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."autoStackId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_autoStackId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."state" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_state", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."country" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_country", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."make" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_make", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."model" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_model", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."lensModel" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_lensModel", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fNumber" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fNumber", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."focalLength" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_focalLength", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."iso" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_iso", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exposureTime" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exposureTime", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."profileDescription" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_profileDescription", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."colorspace" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_colorspace", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."bitsPerSample" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_bitsPerSample", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."rating" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_rating", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fps" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fps" -FROM - "asset_stack" "StackEntity" - LEFT JOIN "assets" "StackEntity__StackEntity_assets" ON "StackEntity__StackEntity_assets"."stackId" = "StackEntity"."id" - AND ( - "StackEntity__StackEntity_assets"."deletedAt" IS NULL - ) - LEFT JOIN "exif" "01db479afeb88793eed8e0d1dde6ccfccf1698b9" ON "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" = "StackEntity__StackEntity_assets"."id" -WHERE - (("StackEntity"."ownerId" = $1)) +select + "asset_stack".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + * + from + "assets" + where + "assets"."deletedAt" is null + and "assets"."stackId" = "asset_stack"."id" + ) as agg + ) as "assets" +from + "asset_stack" +where + "asset_stack"."ownerId" = $1 -- StackRepository.delete -SELECT DISTINCT - "distinctAlias"."StackEntity_id" AS "ids_StackEntity_id", - "distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt" -FROM +select + *, ( - SELECT - "StackEntity"."id" AS "StackEntity_id", - "StackEntity"."ownerId" AS "StackEntity_ownerId", - "StackEntity"."primaryAssetId" AS "StackEntity_primaryAssetId", - "StackEntity__StackEntity_assets"."id" AS "StackEntity__StackEntity_assets_id", - "StackEntity__StackEntity_assets"."deviceAssetId" AS "StackEntity__StackEntity_assets_deviceAssetId", - "StackEntity__StackEntity_assets"."ownerId" AS "StackEntity__StackEntity_assets_ownerId", - "StackEntity__StackEntity_assets"."libraryId" AS "StackEntity__StackEntity_assets_libraryId", - "StackEntity__StackEntity_assets"."deviceId" AS "StackEntity__StackEntity_assets_deviceId", - "StackEntity__StackEntity_assets"."type" AS "StackEntity__StackEntity_assets_type", - "StackEntity__StackEntity_assets"."status" AS "StackEntity__StackEntity_assets_status", - "StackEntity__StackEntity_assets"."originalPath" AS "StackEntity__StackEntity_assets_originalPath", - "StackEntity__StackEntity_assets"."thumbhash" AS "StackEntity__StackEntity_assets_thumbhash", - "StackEntity__StackEntity_assets"."encodedVideoPath" AS "StackEntity__StackEntity_assets_encodedVideoPath", - "StackEntity__StackEntity_assets"."createdAt" AS "StackEntity__StackEntity_assets_createdAt", - "StackEntity__StackEntity_assets"."updatedAt" AS "StackEntity__StackEntity_assets_updatedAt", - "StackEntity__StackEntity_assets"."deletedAt" AS "StackEntity__StackEntity_assets_deletedAt", - "StackEntity__StackEntity_assets"."fileCreatedAt" AS "StackEntity__StackEntity_assets_fileCreatedAt", - "StackEntity__StackEntity_assets"."localDateTime" AS "StackEntity__StackEntity_assets_localDateTime", - "StackEntity__StackEntity_assets"."fileModifiedAt" AS "StackEntity__StackEntity_assets_fileModifiedAt", - "StackEntity__StackEntity_assets"."isFavorite" AS "StackEntity__StackEntity_assets_isFavorite", - "StackEntity__StackEntity_assets"."isArchived" AS "StackEntity__StackEntity_assets_isArchived", - "StackEntity__StackEntity_assets"."isExternal" AS "StackEntity__StackEntity_assets_isExternal", - "StackEntity__StackEntity_assets"."isOffline" AS "StackEntity__StackEntity_assets_isOffline", - "StackEntity__StackEntity_assets"."checksum" AS "StackEntity__StackEntity_assets_checksum", - "StackEntity__StackEntity_assets"."duration" AS "StackEntity__StackEntity_assets_duration", - "StackEntity__StackEntity_assets"."isVisible" AS "StackEntity__StackEntity_assets_isVisible", - "StackEntity__StackEntity_assets"."livePhotoVideoId" AS "StackEntity__StackEntity_assets_livePhotoVideoId", - "StackEntity__StackEntity_assets"."originalFileName" AS "StackEntity__StackEntity_assets_originalFileName", - "StackEntity__StackEntity_assets"."sidecarPath" AS "StackEntity__StackEntity_assets_sidecarPath", - "StackEntity__StackEntity_assets"."stackId" AS "StackEntity__StackEntity_assets_stackId", - "StackEntity__StackEntity_assets"."duplicateId" AS "StackEntity__StackEntity_assets_duplicateId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_assetId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."description" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_description", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageWidth" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageWidth", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageHeight" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageHeight", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fileSizeInByte" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fileSizeInByte", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."orientation" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_orientation", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."dateTimeOriginal" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_dateTimeOriginal", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."modifyDate" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_modifyDate", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."timeZone" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_timeZone", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."latitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_latitude", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."longitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_longitude", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."projectionType" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_projectionType", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."city" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_city", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."livePhotoCID" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_livePhotoCID", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."autoStackId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_autoStackId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."state" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_state", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."country" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_country", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."make" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_make", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."model" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_model", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."lensModel" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_lensModel", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fNumber" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fNumber", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."focalLength" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_focalLength", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."iso" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_iso", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exposureTime" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exposureTime", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."profileDescription" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_profileDescription", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."colorspace" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_colorspace", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."bitsPerSample" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_bitsPerSample", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."rating" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_rating", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fps" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fps", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."id" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_id", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."value" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_value", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."createdAt" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_createdAt", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."updatedAt" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_updatedAt", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."color" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_color", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."parentId" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_parentId", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."userId" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_userId" - FROM - "asset_stack" "StackEntity" - LEFT JOIN "assets" "StackEntity__StackEntity_assets" ON "StackEntity__StackEntity_assets"."stackId" = "StackEntity"."id" - AND ( - "StackEntity__StackEntity_assets"."deletedAt" IS NULL - ) - LEFT JOIN "exif" "01db479afeb88793eed8e0d1dde6ccfccf1698b9" ON "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" = "StackEntity__StackEntity_assets"."id" - LEFT JOIN "tag_asset" "4f1c9474d4596aede2814ee2eb938eecf7a93b95" ON "4f1c9474d4596aede2814ee2eb938eecf7a93b95"."assetsId" = "StackEntity__StackEntity_assets"."id" - LEFT JOIN "tags" "3e3064f11b97177a1e1ce3c77ecf32850343aba1" ON "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."id" = "4f1c9474d4596aede2814ee2eb938eecf7a93b95"."tagsId" - WHERE - (("StackEntity"."id" = $1)) - ) "distinctAlias" -ORDER BY - "distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt" ASC, - "StackEntity_id" ASC -LIMIT - 1 + select + coalesce(json_agg(agg), '[]') + from + ( + select + *, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "tags".* + from + "tags" + inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId" + where + "tag_asset"."assetsId" = "assets"."id" + ) as agg + ) as "tags" + from + "assets" + where + "assets"."deletedAt" is null + and "assets"."stackId" = "asset_stack"."id" + ) as agg + ) as "assets" +from + "asset_stack" +where + "id" = $1::uuid -- StackRepository.getById -SELECT DISTINCT - "distinctAlias"."StackEntity_id" AS "ids_StackEntity_id", - "distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt" -FROM +select + *, ( - SELECT - "StackEntity"."id" AS "StackEntity_id", - "StackEntity"."ownerId" AS "StackEntity_ownerId", - "StackEntity"."primaryAssetId" AS "StackEntity_primaryAssetId", - "StackEntity__StackEntity_assets"."id" AS "StackEntity__StackEntity_assets_id", - "StackEntity__StackEntity_assets"."deviceAssetId" AS "StackEntity__StackEntity_assets_deviceAssetId", - "StackEntity__StackEntity_assets"."ownerId" AS "StackEntity__StackEntity_assets_ownerId", - "StackEntity__StackEntity_assets"."libraryId" AS "StackEntity__StackEntity_assets_libraryId", - "StackEntity__StackEntity_assets"."deviceId" AS "StackEntity__StackEntity_assets_deviceId", - "StackEntity__StackEntity_assets"."type" AS "StackEntity__StackEntity_assets_type", - "StackEntity__StackEntity_assets"."status" AS "StackEntity__StackEntity_assets_status", - "StackEntity__StackEntity_assets"."originalPath" AS "StackEntity__StackEntity_assets_originalPath", - "StackEntity__StackEntity_assets"."thumbhash" AS "StackEntity__StackEntity_assets_thumbhash", - "StackEntity__StackEntity_assets"."encodedVideoPath" AS "StackEntity__StackEntity_assets_encodedVideoPath", - "StackEntity__StackEntity_assets"."createdAt" AS "StackEntity__StackEntity_assets_createdAt", - "StackEntity__StackEntity_assets"."updatedAt" AS "StackEntity__StackEntity_assets_updatedAt", - "StackEntity__StackEntity_assets"."deletedAt" AS "StackEntity__StackEntity_assets_deletedAt", - "StackEntity__StackEntity_assets"."fileCreatedAt" AS "StackEntity__StackEntity_assets_fileCreatedAt", - "StackEntity__StackEntity_assets"."localDateTime" AS "StackEntity__StackEntity_assets_localDateTime", - "StackEntity__StackEntity_assets"."fileModifiedAt" AS "StackEntity__StackEntity_assets_fileModifiedAt", - "StackEntity__StackEntity_assets"."isFavorite" AS "StackEntity__StackEntity_assets_isFavorite", - "StackEntity__StackEntity_assets"."isArchived" AS "StackEntity__StackEntity_assets_isArchived", - "StackEntity__StackEntity_assets"."isExternal" AS "StackEntity__StackEntity_assets_isExternal", - "StackEntity__StackEntity_assets"."isOffline" AS "StackEntity__StackEntity_assets_isOffline", - "StackEntity__StackEntity_assets"."checksum" AS "StackEntity__StackEntity_assets_checksum", - "StackEntity__StackEntity_assets"."duration" AS "StackEntity__StackEntity_assets_duration", - "StackEntity__StackEntity_assets"."isVisible" AS "StackEntity__StackEntity_assets_isVisible", - "StackEntity__StackEntity_assets"."livePhotoVideoId" AS "StackEntity__StackEntity_assets_livePhotoVideoId", - "StackEntity__StackEntity_assets"."originalFileName" AS "StackEntity__StackEntity_assets_originalFileName", - "StackEntity__StackEntity_assets"."sidecarPath" AS "StackEntity__StackEntity_assets_sidecarPath", - "StackEntity__StackEntity_assets"."stackId" AS "StackEntity__StackEntity_assets_stackId", - "StackEntity__StackEntity_assets"."duplicateId" AS "StackEntity__StackEntity_assets_duplicateId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_assetId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."description" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_description", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageWidth" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageWidth", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageHeight" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageHeight", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fileSizeInByte" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fileSizeInByte", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."orientation" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_orientation", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."dateTimeOriginal" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_dateTimeOriginal", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."modifyDate" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_modifyDate", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."timeZone" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_timeZone", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."latitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_latitude", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."longitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_longitude", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."projectionType" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_projectionType", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."city" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_city", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."livePhotoCID" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_livePhotoCID", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."autoStackId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_autoStackId", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."state" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_state", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."country" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_country", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."make" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_make", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."model" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_model", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."lensModel" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_lensModel", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fNumber" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fNumber", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."focalLength" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_focalLength", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."iso" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_iso", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exposureTime" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exposureTime", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."profileDescription" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_profileDescription", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."colorspace" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_colorspace", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."bitsPerSample" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_bitsPerSample", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."rating" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_rating", - "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fps" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fps", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."id" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_id", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."value" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_value", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."createdAt" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_createdAt", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."updatedAt" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_updatedAt", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."color" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_color", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."parentId" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_parentId", - "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."userId" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_userId" - FROM - "asset_stack" "StackEntity" - LEFT JOIN "assets" "StackEntity__StackEntity_assets" ON "StackEntity__StackEntity_assets"."stackId" = "StackEntity"."id" - AND ( - "StackEntity__StackEntity_assets"."deletedAt" IS NULL - ) - LEFT JOIN "exif" "01db479afeb88793eed8e0d1dde6ccfccf1698b9" ON "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" = "StackEntity__StackEntity_assets"."id" - LEFT JOIN "tag_asset" "4f1c9474d4596aede2814ee2eb938eecf7a93b95" ON "4f1c9474d4596aede2814ee2eb938eecf7a93b95"."assetsId" = "StackEntity__StackEntity_assets"."id" - LEFT JOIN "tags" "3e3064f11b97177a1e1ce3c77ecf32850343aba1" ON "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."id" = "4f1c9474d4596aede2814ee2eb938eecf7a93b95"."tagsId" - WHERE - (("StackEntity"."id" = $1)) - ) "distinctAlias" -ORDER BY - "distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt" ASC, - "StackEntity_id" ASC -LIMIT - 1 + select + coalesce(json_agg(agg), '[]') + from + ( + select + *, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "tags".* + from + "tags" + inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId" + where + "tag_asset"."assetsId" = "assets"."id" + ) as agg + ) as "tags" + from + "assets" + where + "assets"."deletedAt" is null + and "assets"."stackId" = "asset_stack"."id" + ) as agg + ) as "assets" +from + "asset_stack" +where + "id" = $1::uuid diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index 15288b94fa..4d32950d85 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -36,10 +36,7 @@ class ActivityAccess implements IActivityAccess { .where('activity.id', 'in', [...activityIds]) .where('activity.userId', '=', userId) .execute() - .then((activities) => { - console.log('activities', activities); - return new Set(activities.map((activity) => activity.id)); - }); + .then((activities) => new Set(activities.map((activity) => activity.id))); } @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) diff --git a/server/src/repositories/stack.repository.ts b/server/src/repositories/stack.repository.ts index 2887fbeb96..6a80c1f59c 100644 --- a/server/src/repositories/stack.repository.ts +++ b/server/src/repositories/stack.repository.ts @@ -1,84 +1,113 @@ import { Injectable } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { ExpressionBuilder, Kysely, Updateable } from 'kysely'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetEntity } from 'src/entities/asset.entity'; import { StackEntity } from 'src/entities/stack.entity'; import { IStackRepository, StackSearch } from 'src/interfaces/stack.interface'; -import { DataSource, In, Repository } from 'typeorm'; +import { asUuid } from 'src/utils/database'; + +const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false) => { + return jsonArrayFrom( + eb + .selectFrom('assets') + .selectAll() + .$if(withTags, (eb) => + eb.select((eb) => + jsonArrayFrom( + eb + .selectFrom('tags') + .selectAll('tags') + .innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId') + .whereRef('tag_asset.assetsId', '=', 'assets.id'), + ).as('tags'), + ), + ) + .where('assets.deletedAt', 'is', null) + .whereRef('assets.stackId', '=', 'asset_stack.id'), + ).as('assets'); +}; @Injectable() export class StackRepository implements IStackRepository { - constructor( - @InjectDataSource() private dataSource: DataSource, - @InjectRepository(StackEntity) private repository: Repository<StackEntity>, - ) {} + constructor(@InjectKysely() private db: Kysely<DB>) {} @GenerateSql({ params: [{ ownerId: DummyValue.UUID }] }) search(query: StackSearch): Promise<StackEntity[]> { - return this.repository.find({ - where: { - ownerId: query.ownerId, - primaryAssetId: query.primaryAssetId, - }, - relations: { - assets: { - exifInfo: true, - }, - }, - }); + return this.db + .selectFrom('asset_stack') + .selectAll('asset_stack') + .select(withAssets) + .where('asset_stack.ownerId', '=', query.ownerId) + .$if(!!query.primaryAssetId, (eb) => eb.where('asset_stack.primaryAssetId', '=', query.primaryAssetId!)) + .execute() as unknown as Promise<StackEntity[]>; } async create(entity: { ownerId: string; assetIds: string[] }): Promise<StackEntity> { - return this.dataSource.manager.transaction(async (manager) => { - const stackRepository = manager.getRepository(StackEntity); - - const stacks = await stackRepository.find({ - where: { - ownerId: entity.ownerId, - primaryAssetId: In(entity.assetIds), - }, - select: { - id: true, - assets: { - id: true, - }, - }, - relations: { - assets: { - exifInfo: true, - }, - }, - }); + return this.db.transaction().execute(async (tx) => { + const stacks = await tx + .selectFrom('asset_stack') + .where('asset_stack.ownerId', '=', entity.ownerId) + .where('asset_stack.primaryAssetId', 'in', entity.assetIds) + .select('asset_stack.id') + .select((eb) => + jsonArrayFrom( + eb + .selectFrom('assets') + .select('assets.id') + .whereRef('assets.stackId', '=', 'asset_stack.id') + .where('assets.deletedAt', 'is', null), + ).as('assets'), + ) + .execute(); const assetIds = new Set<string>(entity.assetIds); // children for (const stack of stacks) { - for (const asset of stack.assets) { - assetIds.add(asset.id); + if (stack.assets && stack.assets.length > 0) { + for (const asset of stack.assets) { + assetIds.add(asset.id); + } } } if (stacks.length > 0) { - await stackRepository.delete({ id: In(stacks.map((stack) => stack.id)) }); + await tx + .deleteFrom('asset_stack') + .where( + 'id', + 'in', + stacks.map((stack) => stack.id), + ) + .execute(); } - const { id } = await stackRepository.save({ - ownerId: entity.ownerId, - primaryAssetId: entity.assetIds[0], - assets: [...assetIds].map((id) => ({ id }) as AssetEntity), - }); + const newRecord = await tx + .insertInto('asset_stack') + .values({ + ownerId: entity.ownerId, + primaryAssetId: entity.assetIds[0], + }) + .returning('id') + .executeTakeFirstOrThrow(); - return stackRepository.findOneOrFail({ - where: { - id, - }, - relations: { - assets: { - exifInfo: true, - }, - }, - }); + await tx + .updateTable('assets') + .set({ + stackId: newRecord.id, + updatedAt: new Date(), + }) + .where('id', 'in', [...assetIds]) + .execute(); + + return tx + .selectFrom('asset_stack') + .selectAll('asset_stack') + .select(withAssets) + .where('id', '=', newRecord.id) + .executeTakeFirst() as unknown as Promise<StackEntity>; }); } @@ -91,12 +120,12 @@ export class StackRepository implements IStackRepository { const assetIds = stack.assets.map(({ id }) => id); - await this.repository.delete(id); - - // Update assets updatedAt - await this.dataSource.manager.update(AssetEntity, assetIds, { - updatedAt: new Date(), - }); + await this.db.deleteFrom('asset_stack').where('id', '=', asUuid(id)).execute(); + await this.db + .updateTable('assets') + .set({ stackId: null, updatedAt: new Date() }) + .where('id', 'in', assetIds) + .execute(); } async deleteAll(ids: string[]): Promise<void> { @@ -110,54 +139,31 @@ export class StackRepository implements IStackRepository { assetIds.push(...stack.assets.map(({ id }) => id)); } - await this.repository.delete(ids); - - // Update assets updatedAt - await this.dataSource.manager.update(AssetEntity, assetIds, { - updatedAt: new Date(), - }); + await this.db + .updateTable('assets') + .set({ updatedAt: new Date(), stackId: null }) + .where('id', 'in', assetIds) + .where('stackId', 'in', ids) + .execute(); } - update(entity: Partial<StackEntity>) { - return this.save(entity); + update(id: string, entity: Updateable<StackEntity>): Promise<StackEntity> { + return this.db + .updateTable('asset_stack') + .set(entity) + .where('id', '=', asUuid(id)) + .returningAll('asset_stack') + .returning((eb) => withAssets(eb, true)) + .executeTakeFirstOrThrow() as unknown as Promise<StackEntity>; } @GenerateSql({ params: [DummyValue.UUID] }) - async getById(id: string): Promise<StackEntity | null> { - return this.repository.findOne({ - where: { - id, - }, - relations: { - assets: { - exifInfo: true, - tags: true, - }, - }, - order: { - assets: { - fileCreatedAt: 'ASC', - }, - }, - }); - } - - private async save(entity: Partial<StackEntity>) { - const { id } = await this.repository.save(entity); - return this.repository.findOneOrFail({ - where: { - id, - }, - relations: { - assets: { - exifInfo: true, - }, - }, - order: { - assets: { - fileCreatedAt: 'ASC', - }, - }, - }); + getById(id: string): Promise<StackEntity | undefined> { + return this.db + .selectFrom('asset_stack') + .selectAll() + .select((eb) => withAssets(eb, true)) + .where('id', '=', asUuid(id)) + .executeTakeFirst() as Promise<StackEntity | undefined>; } } diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index cc8f0a1ab0..bf36c181fc 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -520,7 +520,7 @@ describe(AssetService.name, () => { await sut.handleAssetDeletion({ id: assetStub.primaryImage.id, deleteOnDisk: true }); - expect(stackMock.update).toHaveBeenCalledWith({ + expect(stackMock.update).toHaveBeenCalledWith('stack-1', { id: 'stack-1', primaryAssetId: 'stack-child-asset-1', }); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index de4c0fe0f1..3913c0ce4c 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -192,7 +192,7 @@ export class AssetService extends BaseService { const stackAssetIds = asset.stack.assets.map((a) => a.id); if (stackAssetIds.length > 2) { const newPrimaryAssetId = stackAssetIds.find((a) => a !== id)!; - await this.stackRepository.update({ + await this.stackRepository.update(asset.stack.id, { id: asset.stack.id, primaryAssetId: newPrimaryAssetId, }); diff --git a/server/src/services/stack.service.spec.ts b/server/src/services/stack.service.spec.ts index 4e8813145c..f37e2c4af4 100644 --- a/server/src/services/stack.service.spec.ts +++ b/server/src/services/stack.service.spec.ts @@ -141,7 +141,10 @@ describe(StackService.name, () => { await sut.update(authStub.admin, 'stack-id', { primaryAssetId: assetStub.image1.id }); expect(stackMock.getById).toHaveBeenCalledWith('stack-id'); - expect(stackMock.update).toHaveBeenCalledWith({ id: 'stack-id', primaryAssetId: assetStub.image1.id }); + expect(stackMock.update).toHaveBeenCalledWith('stack-id', { + id: 'stack-id', + primaryAssetId: assetStub.image1.id, + }); expect(eventMock.emit).toHaveBeenCalledWith('stack.update', { stackId: 'stack-id', userId: authStub.admin.user.id, diff --git a/server/src/services/stack.service.ts b/server/src/services/stack.service.ts index 58fccc8be2..29413109c5 100644 --- a/server/src/services/stack.service.ts +++ b/server/src/services/stack.service.ts @@ -39,7 +39,7 @@ export class StackService extends BaseService { throw new BadRequestException('Primary asset must be in the stack'); } - const updatedStack = await this.stackRepository.update({ id, primaryAssetId: dto.primaryAssetId }); + const updatedStack = await this.stackRepository.update(id, { id, primaryAssetId: dto.primaryAssetId }); await this.eventRepository.emit('stack.update', { stackId: id, userId: auth.user.id });