import { Injectable } from '@nestjs/common'; import { Insertable, Kysely, NotNull, sql, Updateable } from 'kysely'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; import _ from 'lodash'; import { InjectKysely } from 'nestjs-kysely'; import { Album, columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; import { MapAsset } from 'src/dtos/asset-response.dto'; import { SharedLinkType } from 'src/enum'; import { DB } from 'src/schema'; import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; export type SharedLinkSearchOptions = { userId: string; albumId?: string; }; @Injectable() export class SharedLinkRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) get(userId: string, id: string) { return this.db .selectFrom('shared_link') .selectAll('shared_link') .leftJoinLateral( (eb) => eb .selectFrom('shared_link_asset') .whereRef('shared_link.id', '=', 'shared_link_asset.sharedLinksId') .innerJoin('asset', 'asset.id', 'shared_link_asset.assetsId') .where('asset.deletedAt', 'is', null) .selectAll('asset') .innerJoinLateral( (eb) => eb .selectFrom('asset_exif') .selectAll('asset_exif') .whereRef('asset_exif.assetId', '=', 'asset.id') .as('exifInfo'), (join) => join.onTrue(), ) .select((eb) => eb.fn.toJson('exifInfo').as('exifInfo')) .orderBy('asset.fileCreatedAt', 'asc') .as('a'), (join) => join.onTrue(), ) .leftJoinLateral( (eb) => eb .selectFrom('album') .selectAll('album') .whereRef('album.id', '=', 'shared_link.albumId') .where('album.deletedAt', 'is', null) .leftJoin('album_asset', 'album_asset.albumsId', 'album.id') .leftJoinLateral( (eb) => eb .selectFrom('asset') .selectAll('asset') .whereRef('album_asset.assetsId', '=', 'asset.id') .where('asset.deletedAt', 'is', null) .innerJoinLateral( (eb) => eb .selectFrom('asset_exif') .selectAll('asset_exif') .whereRef('asset_exif.assetId', '=', 'asset.id') .as('exifInfo'), (join) => join.onTrue(), ) .select((eb) => eb.fn.toJson(eb.table('exifInfo')).as('exifInfo')) .orderBy('asset.fileCreatedAt', 'asc') .as('assets'), (join) => join.onTrue(), ) .innerJoinLateral( (eb) => eb .selectFrom('user') .selectAll('user') .whereRef('user.id', '=', 'album.ownerId') .where('user.deletedAt', 'is', null) .as('owner'), (join) => join.onTrue(), ) .select((eb) => eb.fn .coalesce( eb.fn .jsonAgg('assets') .orderBy('assets.fileCreatedAt', 'asc') .filterWhere('assets.id', 'is not', null), sql`'[]'`, ) .as('assets'), ) .select((eb) => eb.fn.toJson('owner').as('owner')) .groupBy(['album.id', sql`"owner".*`]) .as('album'), (join) => join.onTrue(), ) .select((eb) => eb.fn .coalesce(eb.fn.jsonAgg('a').filterWhere('a.id', 'is not', null), sql`'[]'`) .$castTo() .as('assets'), ) .groupBy(['shared_link.id', sql`"album".*`]) .select((eb) => eb.fn.toJson('album').$castTo().as('album')) .where('shared_link.id', '=', id) .where('shared_link.userId', '=', userId) .where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)])) .orderBy('shared_link.createdAt', 'desc') .executeTakeFirst(); } @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] }) getAll({ userId, albumId }: SharedLinkSearchOptions) { return this.db .selectFrom('shared_link') .selectAll('shared_link') .where('shared_link.userId', '=', userId) .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinksId', 'shared_link.id') .leftJoinLateral( (eb) => eb .selectFrom('asset') .select((eb) => eb.fn.jsonAgg('asset').as('assets')) .whereRef('asset.id', '=', 'shared_link_asset.assetsId') .where('asset.deletedAt', 'is', null) .as('assets'), (join) => join.onTrue(), ) .select('assets.assets') .$narrowType<{ assets: NotNull }>() .leftJoinLateral( (eb) => eb .selectFrom('album') .selectAll('album') .whereRef('album.id', '=', 'shared_link.albumId') .innerJoinLateral( (eb) => eb .selectFrom('user') .select([ 'user.id', 'user.email', 'user.createdAt', 'user.profileImagePath', 'user.isAdmin', 'user.shouldChangePassword', 'user.deletedAt', 'user.oauthId', 'user.updatedAt', 'user.storageLabel', 'user.name', 'user.quotaSizeInBytes', 'user.quotaUsageInBytes', 'user.status', 'user.profileChangedAt', ]) .whereRef('user.id', '=', 'album.ownerId') .where('user.deletedAt', 'is', null) .as('owner'), (join) => join.onTrue(), ) .select((eb) => eb.fn.toJson('owner').as('owner')) .where('album.deletedAt', 'is', null) .as('album'), (join) => join.onTrue(), ) .select((eb) => eb.fn.toJson('album').$castTo().as('album')) .where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)])) .$if(!!albumId, (eb) => eb.where('shared_link.albumId', '=', albumId!)) .orderBy('shared_link.createdAt', 'desc') .distinctOn(['shared_link.createdAt']) .execute(); } @GenerateSql({ params: [DummyValue.BUFFER] }) getByKey(key: Buffer) { return this.authBuilder().where('shared_link.key', '=', key).executeTakeFirst(); } @GenerateSql({ params: [DummyValue.BUFFER] }) getBySlug(slug: string) { return this.authBuilder().where('shared_link.slug', '=', slug).executeTakeFirst(); } private authBuilder() { return this.db .selectFrom('shared_link') .leftJoin('album', 'album.id', 'shared_link.albumId') .where('album.deletedAt', 'is', null) .select((eb) => [ ...columns.authSharedLink, jsonObjectFrom( eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'), ).as('user'), ]) .where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)])); } async create(entity: Insertable & { assetIds?: string[] }) { const { id } = await this.db .insertInto('shared_link') .values(_.omit(entity, 'assetIds')) .returningAll() .executeTakeFirstOrThrow(); if (entity.assetIds && entity.assetIds.length > 0) { await this.db .insertInto('shared_link_asset') .values(entity.assetIds!.map((assetsId) => ({ assetsId, sharedLinksId: id }))) .execute(); } return this.getSharedLinks(id); } async update(entity: Updateable & { id: string; assetIds?: string[] }) { const { id } = await this.db .updateTable('shared_link') .set(_.omit(entity, 'assets', 'album', 'assetIds')) .where('shared_link.id', '=', entity.id) .returningAll() .executeTakeFirstOrThrow(); if (entity.assetIds && entity.assetIds.length > 0) { await this.db .insertInto('shared_link_asset') .values(entity.assetIds!.map((assetsId) => ({ assetsId, sharedLinksId: id }))) .execute(); } return this.getSharedLinks(id); } async remove(id: string): Promise { await this.db.deleteFrom('shared_link').where('shared_link.id', '=', id).execute(); } private getSharedLinks(id: string) { return this.db .selectFrom('shared_link') .selectAll('shared_link') .where('shared_link.id', '=', id) .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinksId', 'shared_link.id') .leftJoinLateral( (eb) => eb .selectFrom('asset') .whereRef('asset.id', '=', 'shared_link_asset.assetsId') .selectAll('asset') .innerJoinLateral( (eb) => eb.selectFrom('asset_exif').whereRef('asset_exif.assetId', '=', 'asset.id').selectAll().as('exif'), (join) => join.onTrue(), ) .as('assets'), (join) => join.onTrue(), ) .select((eb) => eb.fn .coalesce(eb.fn.jsonAgg('assets').filterWhere('assets.id', 'is not', null), sql`'[]'`) .$castTo() .as('assets'), ) .groupBy('shared_link.id') .executeTakeFirstOrThrow(); } }