import { Injectable } from '@nestjs/common'; import { Insertable, Kysely, sql, Updateable } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { DummyValue, GenerateSql } from 'src/decorators'; import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; import { AssetType, AssetVisibility } from 'src/enum'; import { DB } from 'src/schema'; import { LibraryTable } from 'src/schema/tables/library.table'; export enum AssetSyncResult { DO_NOTHING, UPDATE, OFFLINE, CHECK_OFFLINE, } @Injectable() export class LibraryRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID] }) get(id: string, withDeleted = false) { return this.db .selectFrom('library') .selectAll('library') .where('library.id', '=', id) .$if(!withDeleted, (qb) => qb.where('library.deletedAt', 'is', null)) .executeTakeFirst(); } @GenerateSql({ params: [] }) getAll(withDeleted = false) { return this.db .selectFrom('library') .selectAll('library') .orderBy('createdAt', 'asc') .$if(!withDeleted, (qb) => qb.where('library.deletedAt', 'is', null)) .execute(); } @GenerateSql() getAllDeleted() { return this.db .selectFrom('library') .selectAll('library') .where('library.deletedAt', 'is not', null) .orderBy('createdAt', 'asc') .execute(); } create(library: Insertable) { return this.db.insertInto('library').values(library).returningAll().executeTakeFirstOrThrow(); } async delete(id: string) { await this.db.deleteFrom('library').where('library.id', '=', id).execute(); } async softDelete(id: string) { await this.db.updateTable('library').set({ deletedAt: new Date() }).where('library.id', '=', id).execute(); } update(id: string, library: Updateable) { return this.db .updateTable('library') .set(library) .where('library.id', '=', id) .returningAll() .executeTakeFirstOrThrow(); } @GenerateSql({ params: [DummyValue.UUID] }) async getStatistics(id: string): Promise { const stats = await this.db .selectFrom('library') .innerJoin('asset', 'asset.libraryId', 'library.id') .leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id') .select((eb) => eb.fn .countAll() .filterWhere((eb) => eb.and([eb('asset.type', '=', AssetType.Image), eb('asset.visibility', '!=', AssetVisibility.Hidden)]), ) .as('photos'), ) .select((eb) => eb.fn .countAll() .filterWhere((eb) => eb.and([eb('asset.type', '=', AssetType.Video), eb('asset.visibility', '!=', AssetVisibility.Hidden)]), ) .as('videos'), ) .select((eb) => eb.fn.coalesce((eb) => eb.fn.sum('asset_exif.fileSizeInByte'), eb.val(0)).as('usage')) .groupBy('library.id') .where('library.id', '=', id) .executeTakeFirst(); // possibly a new library with 0 assets if (!stats) { const zero = sql`0::int`; return this.db .selectFrom('library') .select(zero.as('photos')) .select(zero.as('videos')) .select(zero.as('usage')) .select(zero.as('total')) .where('library.id', '=', id) .executeTakeFirst(); } return { photos: stats.photos, videos: stats.videos, usage: stats.usage, total: stats.photos + stats.videos, }; } streamAssetIds(libraryId: string) { return this.db.selectFrom('asset').select(['id']).where('libraryId', '=', libraryId).stream(); } }