diff --git a/server/src/interfaces/library.interface.ts b/server/src/interfaces/library.interface.ts index d8f1a1303116e5..00ce4da376f3ad 100644 --- a/server/src/interfaces/library.interface.ts +++ b/server/src/interfaces/library.interface.ts @@ -6,10 +6,10 @@ export const ILibraryRepository = 'ILibraryRepository'; export interface ILibraryRepository { getAll(withDeleted?: boolean): Promise; getAllDeleted(): Promise; - get(id: string, withDeleted?: boolean): Promise; + get(id: string, withDeleted?: boolean): Promise; create(library: Partial): Promise; delete(id: string): Promise; softDelete(id: string): Promise; - update(library: Partial): Promise; + update(library: Partial & { id: string }): Promise; getStatistics(id: string): Promise; } diff --git a/server/src/queries/library.repository.sql b/server/src/queries/library.repository.sql index a5d6ba05dba1ff..680a8554fe700e 100644 --- a/server/src/queries/library.repository.sql +++ b/server/src/queries/library.repository.sql @@ -1,150 +1,137 @@ -- NOTE: This file is auto generated by ./sql-generator -- LibraryRepository.get -SELECT DISTINCT - "distinctAlias"."LibraryEntity_id" AS "ids_LibraryEntity_id" -FROM +select + "libraries".*, ( - SELECT - "LibraryEntity"."id" AS "LibraryEntity_id", - "LibraryEntity"."name" AS "LibraryEntity_name", - "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", - "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", - "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", - "LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt", - "LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt", - "LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt", - "LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id", - "LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name", - "LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin", - "LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email", - "LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel", - "LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId", - "LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath", - "LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword", - "LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt", - "LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt", - "LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status", - "LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt", - "LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes", - "LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes", - "LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt" - FROM - "libraries" "LibraryEntity" - LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId" - AND ( - "LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL - ) - WHERE - ((("LibraryEntity"."id" = $1))) - AND ("LibraryEntity"."deletedAt" IS NULL) - ) "distinctAlias" -ORDER BY - "LibraryEntity_id" ASC -LIMIT - 1 + select + to_json(obj) + from + ( + select + "users"."id", + "users"."email", + "users"."createdAt", + "users"."profileImagePath", + "users"."isAdmin", + "users"."shouldChangePassword", + "users"."deletedAt", + "users"."oauthId", + "users"."updatedAt", + "users"."storageLabel", + "users"."name", + "users"."quotaSizeInBytes", + "users"."quotaUsageInBytes", + "users"."status", + "users"."profileChangedAt" + from + "users" + where + "users"."id" = "libraries"."ownerId" + ) as obj + ) as "owner" +from + "libraries" +where + "libraries"."id" = $1 + and "libraries"."deletedAt" is null -- LibraryRepository.getAll -SELECT - "LibraryEntity"."id" AS "LibraryEntity_id", - "LibraryEntity"."name" AS "LibraryEntity_name", - "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", - "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", - "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", - "LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt", - "LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt", - "LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt", - "LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id", - "LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name", - "LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin", - "LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email", - "LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel", - "LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId", - "LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath", - "LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword", - "LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt", - "LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt", - "LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status", - "LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt", - "LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes", - "LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes", - "LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt" -FROM - "libraries" "LibraryEntity" - LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId" - AND ( - "LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL - ) -WHERE - "LibraryEntity"."deletedAt" IS NULL -ORDER BY - "LibraryEntity"."createdAt" ASC +select + "libraries".*, + ( + select + to_json(obj) + from + ( + select + "users"."id", + "users"."email", + "users"."createdAt", + "users"."profileImagePath", + "users"."isAdmin", + "users"."shouldChangePassword", + "users"."deletedAt", + "users"."oauthId", + "users"."updatedAt", + "users"."storageLabel", + "users"."name", + "users"."quotaSizeInBytes", + "users"."quotaUsageInBytes", + "users"."status", + "users"."profileChangedAt" + from + "users" + where + "users"."id" = "libraries"."ownerId" + ) as obj + ) as "owner" +from + "libraries" +where + "libraries"."deletedAt" is null +order by + "createdAt" asc -- LibraryRepository.getAllDeleted -SELECT - "LibraryEntity"."id" AS "LibraryEntity_id", - "LibraryEntity"."name" AS "LibraryEntity_name", - "LibraryEntity"."ownerId" AS "LibraryEntity_ownerId", - "LibraryEntity"."importPaths" AS "LibraryEntity_importPaths", - "LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns", - "LibraryEntity"."createdAt" AS "LibraryEntity_createdAt", - "LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt", - "LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt", - "LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt", - "LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id", - "LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name", - "LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin", - "LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email", - "LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel", - "LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId", - "LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath", - "LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword", - "LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt", - "LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt", - "LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status", - "LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt", - "LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes", - "LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes", - "LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt" -FROM - "libraries" "LibraryEntity" - LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId" -WHERE - ((NOT ("LibraryEntity"."deletedAt" IS NULL))) -ORDER BY - "LibraryEntity"."createdAt" ASC +select + "libraries".*, + ( + select + to_json(obj) + from + ( + select + "users"."id", + "users"."email", + "users"."createdAt", + "users"."profileImagePath", + "users"."isAdmin", + "users"."shouldChangePassword", + "users"."deletedAt", + "users"."oauthId", + "users"."updatedAt", + "users"."storageLabel", + "users"."name", + "users"."quotaSizeInBytes", + "users"."quotaUsageInBytes", + "users"."status", + "users"."profileChangedAt" + from + "users" + where + "users"."id" = "libraries"."ownerId" + ) as obj + ) as "owner" +from + "libraries" +where + "libraries"."deletedAt" is not null +order by + "createdAt" asc -- LibraryRepository.getStatistics -SELECT - "libraries"."id" AS "libraries_id", - "libraries"."name" AS "libraries_name", - "libraries"."ownerId" AS "libraries_ownerId", - "libraries"."importPaths" AS "libraries_importPaths", - "libraries"."exclusionPatterns" AS "libraries_exclusionPatterns", - "libraries"."createdAt" AS "libraries_createdAt", - "libraries"."updatedAt" AS "libraries_updatedAt", - "libraries"."deletedAt" AS "libraries_deletedAt", - "libraries"."refreshedAt" AS "libraries_refreshedAt", - COUNT("assets"."id") FILTER ( - WHERE - "assets"."type" = 'IMAGE' - AND "assets"."isVisible" - ) AS "photos", - COUNT("assets"."id") FILTER ( - WHERE - "assets"."type" = 'VIDEO' - AND "assets"."isVisible" - ) AS "videos", - COALESCE(SUM("exif"."fileSizeInByte"), 0) AS "usage" -FROM - "libraries" "libraries" - LEFT JOIN "assets" "assets" ON "assets"."libraryId" = "libraries"."id" - AND ("assets"."deletedAt" IS NULL) - LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id" -WHERE - ("libraries"."id" = $1) - AND ("libraries"."deletedAt" IS NULL) -GROUP BY +select + count("assets"."id") filter ( + where + ( + "assets"."type" = $1 + and "assets"."isVisible" = $2 + ) + ) as "photos", + count("assets"."id") filter ( + where + ( + "assets"."type" = $3 + and "assets"."isVisible" = $4 + ) + ) as "videos", + coalesce(sum("exif"."fileSizeInByte"), 0) as "usage" +from + "libraries" + inner join "assets" on "assets"."libraryId" = "libraries"."id" + inner join "exif" on "exif"."assetId" = "assets"."id" +where + "libraries"."id" = $5 +group by "libraries"."id" diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts index 1446395854aabf..d0d8477f7277bb 100644 --- a/server/src/repositories/library.repository.ts +++ b/server/src/repositories/library.repository.ts @@ -1,84 +1,123 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { ExpressionBuilder, Kysely, sql } from 'kysely'; +import { jsonObjectFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; import { LibraryEntity } from 'src/entities/library.entity'; +import { AssetType } from 'src/enum'; import { ILibraryRepository } from 'src/interfaces/library.interface'; -import { IsNull, Not } from 'typeorm'; -import { Repository } from 'typeorm/repository/Repository.js'; + +const withOwner = (eb: ExpressionBuilder) => { + return jsonObjectFrom( + eb + .selectFrom('users') + .whereRef('users.id', '=', 'libraries.ownerId') + .select([ + 'users.id', + 'users.email', + 'users.createdAt', + 'users.profileImagePath', + 'users.isAdmin', + 'users.shouldChangePassword', + 'users.deletedAt', + 'users.oauthId', + 'users.updatedAt', + 'users.storageLabel', + 'users.name', + 'users.quotaSizeInBytes', + 'users.quotaUsageInBytes', + 'users.status', + 'users.profileChangedAt', + ]), + ).as('owner'); +}; @Injectable() export class LibraryRepository implements ILibraryRepository { - constructor(@InjectRepository(LibraryEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID] }) - get(id: string, withDeleted = false): Promise { - return this.repository.findOneOrFail({ - where: { - id, - }, - relations: { owner: true }, - withDeleted, - }); + get(id: string, withDeleted = false): Promise { + return this.db + .selectFrom('libraries') + .selectAll('libraries') + .select(withOwner) + .where('libraries.id', '=', id) + .$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null)) + .executeTakeFirst() as Promise; } @GenerateSql({ params: [] }) getAll(withDeleted = false): Promise { - return this.repository.find({ - relations: { - owner: true, - }, - order: { - createdAt: 'ASC', - }, - withDeleted, - }); + return this.db + .selectFrom('libraries') + .selectAll('libraries') + .select(withOwner) + .orderBy('createdAt', 'asc') + .$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null)) + .execute() as unknown as Promise; } @GenerateSql() getAllDeleted(): Promise { - return this.repository.find({ - where: { - deletedAt: Not(IsNull()), - }, - relations: { - owner: true, - }, - order: { - createdAt: 'ASC', - }, - withDeleted: true, - }); + return this.db + .selectFrom('libraries') + .selectAll('libraries') + .select(withOwner) + .where('libraries.deletedAt', 'is not', null) + .orderBy('createdAt', 'asc') + .execute() as unknown as Promise; } create(library: Omit): Promise { - return this.repository.save(library); + return this.db + .insertInto('libraries') + .values(library as LibraryEntity) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } async delete(id: string): Promise { - await this.repository.delete({ id }); + await this.db.deleteFrom('libraries').where('libraries.id', '=', id).execute(); } async softDelete(id: string): Promise { - await this.repository.softDelete({ id }); + await this.db.updateTable('libraries').set({ deletedAt: new Date() }).where('libraries.id', '=', id).execute(); } - async update(library: Partial): Promise { - return this.save(library); + update(library: Partial & { id: string }): Promise { + return this.db + .updateTable('libraries') + .set(library) + .where('libraries.id', '=', library.id) + .returningAll() + .executeTakeFirstOrThrow() as Promise; } @GenerateSql({ params: [DummyValue.UUID] }) async getStatistics(id: string): Promise { - const stats = await this.repository - .createQueryBuilder('libraries') - .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos') - .addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos') - .addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage') - .leftJoin('libraries.assets', 'assets') - .leftJoin('assets.exifInfo', 'exif') + const stats = await this.db + .selectFrom('libraries') + .innerJoin('assets', 'assets.libraryId', 'libraries.id') + .innerJoin('exif', 'exif.assetId', 'assets.id') + .select((eb) => + eb.fn + .count('assets.id') + .filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)])) + .as('photos'), + ) + .select((eb) => + eb.fn + .count('assets.id') + .filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.isVisible', '=', true)])) + .as('videos'), + ) + .select((eb) => eb.fn.coalesce((eb) => eb.fn.sum('exif.fileSizeInByte'), sql`0`).as('usage')) .groupBy('libraries.id') - .where('libraries.id = :id', { id }) - .getRawOne(); + .where('libraries.id', '=', id) + .executeTakeFirst(); if (!stats) { return; @@ -91,9 +130,4 @@ export class LibraryRepository implements ILibraryRepository { total: Number(stats.photos) + Number(stats.videos), }; } - - private async save(library: Partial) { - const { id } = await this.repository.save(library); - return this.repository.findOneByOrFail({ id }); - } }