From f9f095529f9ed5f9162227b3e97bdc9219299a9a Mon Sep 17 00:00:00 2001 From: Daniel Dietzler Date: Thu, 9 Jan 2025 19:39:14 +0100 Subject: [PATCH] refactor: migrate activity repo to kysely Co-authored-by: Jason Rasmussen --- server/src/interfaces/activity.interface.ts | 6 +- server/src/queries/activity.repository.sql | 112 ++++++------------ .../src/repositories/activity.repository.ts | 111 ++++++++--------- server/src/services/activity.service.ts | 2 +- 4 files changed, 95 insertions(+), 136 deletions(-) diff --git a/server/src/interfaces/activity.interface.ts b/server/src/interfaces/activity.interface.ts index 5e7b4d2c72557..c42d3cc8aaba3 100644 --- a/server/src/interfaces/activity.interface.ts +++ b/server/src/interfaces/activity.interface.ts @@ -1,3 +1,5 @@ +import { Insertable } from 'kysely'; +import { Activity } from 'src/db'; import { ActivityEntity } from 'src/entities/activity.entity'; import { ActivitySearch } from 'src/repositories/activity.repository'; @@ -5,7 +7,7 @@ export const IActivityRepository = 'IActivityRepository'; export interface IActivityRepository { search(options: ActivitySearch): Promise; - create(activity: Partial): Promise; + create(activity: Insertable): Promise; delete(id: string): Promise; - getStatistics(assetId: string | undefined, albumId: string): Promise; + getStatistics(options: { albumId: string; assetId?: string }): Promise; } diff --git a/server/src/queries/activity.repository.sql b/server/src/queries/activity.repository.sql index 44042c0e6d39f..9ce1ec46ee749 100644 --- a/server/src/queries/activity.repository.sql +++ b/server/src/queries/activity.repository.sql @@ -1,85 +1,41 @@ -- 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"."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"."quotaSizeInBytes" AS "ActivityEntity__ActivityEntity_user_quotaSizeInBytes", - "ActivityEntity__ActivityEntity_user"."quotaUsageInBytes" AS "ActivityEntity__ActivityEntity_user_quotaUsageInBytes", - "ActivityEntity__ActivityEntity_user"."profileChangedAt" AS "ActivityEntity__ActivityEntity_user_profileChangedAt" -FROM - "activity" "ActivityEntity" - LEFT JOIN "users" "ActivityEntity__ActivityEntity_user" ON "ActivityEntity__ActivityEntity_user"."id" = "ActivityEntity"."userId" - AND ( - "ActivityEntity__ActivityEntity_user"."deletedAt" IS NULL - ) - LEFT JOIN "assets" "ActivityEntity__ActivityEntity_asset" ON "ActivityEntity__ActivityEntity_asset"."id" = "ActivityEntity"."assetId" - AND ( - "ActivityEntity__ActivityEntity_asset"."deletedAt" IS NULL - ) -WHERE +select + "activity".*, ( - ("ActivityEntity"."albumId" = $1) - AND ( + select + to_json(obj) + from ( - ( - "ActivityEntity__ActivityEntity_asset"."deletedAt" IS NULL - ) - ) - ) - AND ( - ( - ( - "ActivityEntity__ActivityEntity_user"."deletedAt" IS NULL - ) - ) - ) - ) -ORDER BY - "ActivityEntity"."createdAt" ASC + select + * + from + "users" + where + "users"."id" = "activity"."userId" + and "users"."deletedAt" is null + ) as obj + ) as "user" +from + "activity" + left join "assets" on "assets"."id" = "activity"."assetId" + and "assets"."deletedAt" is null +where + "activity"."albumId" = $1 +order by + "activity"."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" - LEFT JOIN "assets" "ActivityEntity__ActivityEntity_asset" ON "ActivityEntity__ActivityEntity_asset"."id" = "ActivityEntity"."assetId" -WHERE - ( - ("ActivityEntity"."assetId" = $1) - AND ("ActivityEntity"."albumId" = $2) - AND ("ActivityEntity"."isLiked" = $3) - AND ( - ( - ( - "ActivityEntity__ActivityEntity_asset"."deletedAt" IS NULL - ) - ) - ) - AND ( - ( - ( - "ActivityEntity__ActivityEntity_user"."deletedAt" IS NULL - ) - ) - ) - ) +select + count(*) as "count" +from + "activity" + left join "users" on "users"."id" = "activity"."userId" + left join "assets" on "assets"."id" = "activity"."assetId" +where + "activity"."assetId" = $1 + and "activity"."albumId" = $2 + and "activity"."isLiked" = $3 + and "users"."deletedAt" is null + and "assets"."deletedAt" is null diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts index 0f0a0cb60eb6b..c7bff91978981 100644 --- a/server/src/repositories/activity.repository.ts +++ b/server/src/repositories/activity.repository.ts @@ -1,9 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { ExpressionBuilder, Insertable, Kysely } from 'kysely'; +import { jsonObjectFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { Activity, DB } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { ActivityEntity } from 'src/entities/activity.entity'; import { IActivityRepository } from 'src/interfaces/activity.interface'; -import { IsNull, Repository } from 'typeorm'; +import { asUuid } from 'src/utils/database'; export interface ActivitySearch { albumId?: string; @@ -12,73 +15,71 @@ export interface ActivitySearch { isLiked?: boolean; } +const withUser = (eb: ExpressionBuilder) => { + return jsonObjectFrom( + eb + .selectFrom('users') + .selectAll() + .whereRef('users.id', '=', 'activity.userId') + .where('users.deletedAt', 'is', null), + ).as('user'); +}; + @Injectable() export class ActivityRepository implements IActivityRepository { - constructor(@InjectRepository(ActivityEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [{ albumId: DummyValue.UUID }] }) search(options: ActivitySearch): Promise { const { userId, assetId, albumId, isLiked } = options; - return this.repository.find({ - where: { - userId, - assetId: assetId === null ? IsNull() : assetId, - albumId, - isLiked, - asset: { - deletedAt: IsNull(), - }, - user: { - deletedAt: IsNull(), - }, - }, - relations: { - user: true, - }, - order: { - createdAt: 'ASC', - }, - }); + + return this.db + .selectFrom('activity') + .selectAll('activity') + .select(withUser) + .leftJoin('assets', (join) => join.onRef('assets.id', '=', 'activity.assetId').on('assets.deletedAt', 'is', null)) + .$if(!!userId, (qb) => qb.where('activity.userId', '=', userId!)) + .$if(assetId === null, (qb) => qb.where('assetId', 'is', null)) + .$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!)) + .$if(!!albumId, (qb) => qb.where('activity.albumId', '=', albumId!)) + .$if(isLiked !== undefined, (qb) => qb.where('activity.isLiked', '=', isLiked!)) + .orderBy('activity.createdAt', 'asc') + .execute() as unknown as Promise; } - create(entity: Partial): Promise { - return this.save(entity); + async create(activity: Insertable) { + return this.save(activity); } async delete(id: string): Promise { - await this.repository.delete(id); + await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute(); } - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - getStatistics(assetId: string, albumId: string): Promise { - return this.repository.count({ - where: { - assetId, - albumId, - isLiked: false, - asset: { - deletedAt: IsNull(), - }, - user: { - deletedAt: IsNull(), - }, - }, - relations: { - user: true, - }, - withDeleted: true, - }); + @GenerateSql({ params: [{ albumId: DummyValue.UUID, assetId: DummyValue.UUID }] }) + async getStatistics({ albumId, assetId }: { albumId: string; assetId?: string }): Promise { + const { count } = await this.db + .selectFrom('activity') + .select((eb) => eb.fn.countAll().as('count')) + .leftJoin('users', 'users.id', 'activity.userId') + .leftJoin('assets', 'assets.id', 'activity.assetId') + .$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!)) + .where('activity.albumId', '=', albumId) + .where('activity.isLiked', '=', false) + .where('users.deletedAt', 'is', null) + .where('assets.deletedAt', 'is', null) + .executeTakeFirstOrThrow(); + + return count as number; } - private async save(entity: Partial) { - const { id } = await this.repository.save(entity); - return this.repository.findOneOrFail({ - where: { - id, - }, - relations: { - user: true, - }, - }); + private async save(entity: Insertable) { + const { id } = await this.db.insertInto('activity').values(entity).returning('id').executeTakeFirstOrThrow(); + + return this.db + .selectFrom('activity') + .selectAll('activity') + .select(withUser) + .where('activity.id', '=', asUuid(id)) + .executeTakeFirstOrThrow() as unknown as Promise; } } diff --git a/server/src/services/activity.service.ts b/server/src/services/activity.service.ts index fce104ecbdfbf..ea7f8b5c0a7ae 100644 --- a/server/src/services/activity.service.ts +++ b/server/src/services/activity.service.ts @@ -31,7 +31,7 @@ export class ActivityService extends BaseService { async getStatistics(auth: AuthDto, dto: ActivityDto): Promise { await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] }); - return { comments: await this.activityRepository.getStatistics(dto.assetId, dto.albumId) }; + return { comments: await this.activityRepository.getStatistics({ albumId: dto.albumId, assetId: dto.assetId }) }; } async create(auth: AuthDto, dto: ActivityCreateDto): Promise> {