mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	refactor: migrate activity repo to kysely (#15203)
Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
		
							parent
							
								
									2e12c46980
								
							
						
					
					
						commit
						1fb2b3f899
					
				@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import { Insertable } from 'kysely';
 | 
				
			||||||
 | 
					import { Activity } from 'src/db';
 | 
				
			||||||
import { ActivityEntity } from 'src/entities/activity.entity';
 | 
					import { ActivityEntity } from 'src/entities/activity.entity';
 | 
				
			||||||
import { ActivitySearch } from 'src/repositories/activity.repository';
 | 
					import { ActivitySearch } from 'src/repositories/activity.repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5,7 +7,7 @@ export const IActivityRepository = 'IActivityRepository';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface IActivityRepository {
 | 
					export interface IActivityRepository {
 | 
				
			||||||
  search(options: ActivitySearch): Promise<ActivityEntity[]>;
 | 
					  search(options: ActivitySearch): Promise<ActivityEntity[]>;
 | 
				
			||||||
  create(activity: Partial<ActivityEntity>): Promise<ActivityEntity>;
 | 
					  create(activity: Insertable<Activity>): Promise<ActivityEntity>;
 | 
				
			||||||
  delete(id: string): Promise<void>;
 | 
					  delete(id: string): Promise<void>;
 | 
				
			||||||
  getStatistics(assetId: string | undefined, albumId: string): Promise<number>;
 | 
					  getStatistics(options: { albumId: string; assetId?: string }): Promise<number>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,85 +1,41 @@
 | 
				
			|||||||
-- NOTE: This file is auto generated by ./sql-generator
 | 
					-- NOTE: This file is auto generated by ./sql-generator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- ActivityRepository.search
 | 
					-- ActivityRepository.search
 | 
				
			||||||
SELECT
 | 
					select
 | 
				
			||||||
  "ActivityEntity"."id" AS "ActivityEntity_id",
 | 
					  "activity".*,
 | 
				
			||||||
  "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
 | 
					 | 
				
			||||||
  (
 | 
					  (
 | 
				
			||||||
    ("ActivityEntity"."albumId" = $1)
 | 
					    select
 | 
				
			||||||
    AND (
 | 
					      to_json(obj)
 | 
				
			||||||
 | 
					    from
 | 
				
			||||||
      (
 | 
					      (
 | 
				
			||||||
        (
 | 
					        select
 | 
				
			||||||
          "ActivityEntity__ActivityEntity_asset"."deletedAt" IS NULL
 | 
					          *
 | 
				
			||||||
        )
 | 
					        from
 | 
				
			||||||
      )
 | 
					          "users"
 | 
				
			||||||
    )
 | 
					        where
 | 
				
			||||||
    AND (
 | 
					          "users"."id" = "activity"."userId"
 | 
				
			||||||
      (
 | 
					          and "users"."deletedAt" is null
 | 
				
			||||||
        (
 | 
					      ) as obj
 | 
				
			||||||
          "ActivityEntity__ActivityEntity_user"."deletedAt" IS NULL
 | 
					  ) as "user"
 | 
				
			||||||
        )
 | 
					from
 | 
				
			||||||
      )
 | 
					  "activity"
 | 
				
			||||||
    )
 | 
					  left join "assets" on "assets"."id" = "activity"."assetId"
 | 
				
			||||||
  )
 | 
					  and "assets"."deletedAt" is null
 | 
				
			||||||
ORDER BY
 | 
					where
 | 
				
			||||||
  "ActivityEntity"."createdAt" ASC
 | 
					  "activity"."albumId" = $1
 | 
				
			||||||
 | 
					order by
 | 
				
			||||||
 | 
					  "activity"."createdAt" asc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- ActivityRepository.getStatistics
 | 
					-- ActivityRepository.getStatistics
 | 
				
			||||||
SELECT
 | 
					select
 | 
				
			||||||
  COUNT(DISTINCT ("ActivityEntity"."id")) AS "cnt"
 | 
					  count(*) as "count"
 | 
				
			||||||
FROM
 | 
					from
 | 
				
			||||||
  "activity" "ActivityEntity"
 | 
					  "activity"
 | 
				
			||||||
  LEFT JOIN "users" "ActivityEntity__ActivityEntity_user" ON "ActivityEntity__ActivityEntity_user"."id" = "ActivityEntity"."userId"
 | 
					  left join "users" on "users"."id" = "activity"."userId"
 | 
				
			||||||
  LEFT JOIN "assets" "ActivityEntity__ActivityEntity_asset" ON "ActivityEntity__ActivityEntity_asset"."id" = "ActivityEntity"."assetId"
 | 
					  left join "assets" on "assets"."id" = "activity"."assetId"
 | 
				
			||||||
WHERE
 | 
					where
 | 
				
			||||||
  (
 | 
					  "activity"."assetId" = $1
 | 
				
			||||||
    ("ActivityEntity"."assetId" = $1)
 | 
					  and "activity"."albumId" = $2
 | 
				
			||||||
    AND ("ActivityEntity"."albumId" = $2)
 | 
					  and "activity"."isLiked" = $3
 | 
				
			||||||
    AND ("ActivityEntity"."isLiked" = $3)
 | 
					  and "users"."deletedAt" is null
 | 
				
			||||||
    AND (
 | 
					  and "assets"."deletedAt" is null
 | 
				
			||||||
      (
 | 
					 | 
				
			||||||
        (
 | 
					 | 
				
			||||||
          "ActivityEntity__ActivityEntity_asset"."deletedAt" IS NULL
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    AND (
 | 
					 | 
				
			||||||
      (
 | 
					 | 
				
			||||||
        (
 | 
					 | 
				
			||||||
          "ActivityEntity__ActivityEntity_user"."deletedAt" IS NULL
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,12 @@
 | 
				
			|||||||
import { Injectable } from '@nestjs/common';
 | 
					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 { DummyValue, GenerateSql } from 'src/decorators';
 | 
				
			||||||
import { ActivityEntity } from 'src/entities/activity.entity';
 | 
					import { ActivityEntity } from 'src/entities/activity.entity';
 | 
				
			||||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
 | 
					import { IActivityRepository } from 'src/interfaces/activity.interface';
 | 
				
			||||||
import { IsNull, Repository } from 'typeorm';
 | 
					import { asUuid } from 'src/utils/database';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ActivitySearch {
 | 
					export interface ActivitySearch {
 | 
				
			||||||
  albumId?: string;
 | 
					  albumId?: string;
 | 
				
			||||||
@ -12,73 +15,71 @@ export interface ActivitySearch {
 | 
				
			|||||||
  isLiked?: boolean;
 | 
					  isLiked?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const withUser = (eb: ExpressionBuilder<DB, 'activity'>) => {
 | 
				
			||||||
 | 
					  return jsonObjectFrom(
 | 
				
			||||||
 | 
					    eb
 | 
				
			||||||
 | 
					      .selectFrom('users')
 | 
				
			||||||
 | 
					      .selectAll()
 | 
				
			||||||
 | 
					      .whereRef('users.id', '=', 'activity.userId')
 | 
				
			||||||
 | 
					      .where('users.deletedAt', 'is', null),
 | 
				
			||||||
 | 
					  ).as('user');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class ActivityRepository implements IActivityRepository {
 | 
					export class ActivityRepository implements IActivityRepository {
 | 
				
			||||||
  constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
 | 
					  constructor(@InjectKysely() private db: Kysely<DB>) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @GenerateSql({ params: [{ albumId: DummyValue.UUID }] })
 | 
					  @GenerateSql({ params: [{ albumId: DummyValue.UUID }] })
 | 
				
			||||||
  search(options: ActivitySearch): Promise<ActivityEntity[]> {
 | 
					  search(options: ActivitySearch): Promise<ActivityEntity[]> {
 | 
				
			||||||
    const { userId, assetId, albumId, isLiked } = options;
 | 
					    const { userId, assetId, albumId, isLiked } = options;
 | 
				
			||||||
    return this.repository.find({
 | 
					
 | 
				
			||||||
      where: {
 | 
					    return this.db
 | 
				
			||||||
        userId,
 | 
					      .selectFrom('activity')
 | 
				
			||||||
        assetId: assetId === null ? IsNull() : assetId,
 | 
					      .selectAll('activity')
 | 
				
			||||||
        albumId,
 | 
					      .select(withUser)
 | 
				
			||||||
        isLiked,
 | 
					      .leftJoin('assets', (join) => join.onRef('assets.id', '=', 'activity.assetId').on('assets.deletedAt', 'is', null))
 | 
				
			||||||
        asset: {
 | 
					      .$if(!!userId, (qb) => qb.where('activity.userId', '=', userId!))
 | 
				
			||||||
          deletedAt: IsNull(),
 | 
					      .$if(assetId === null, (qb) => qb.where('assetId', 'is', null))
 | 
				
			||||||
        },
 | 
					      .$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!))
 | 
				
			||||||
        user: {
 | 
					      .$if(!!albumId, (qb) => qb.where('activity.albumId', '=', albumId!))
 | 
				
			||||||
          deletedAt: IsNull(),
 | 
					      .$if(isLiked !== undefined, (qb) => qb.where('activity.isLiked', '=', isLiked!))
 | 
				
			||||||
        },
 | 
					      .orderBy('activity.createdAt', 'asc')
 | 
				
			||||||
      },
 | 
					      .execute() as unknown as Promise<ActivityEntity[]>;
 | 
				
			||||||
      relations: {
 | 
					 | 
				
			||||||
        user: true,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      order: {
 | 
					 | 
				
			||||||
        createdAt: 'ASC',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  create(entity: Partial<ActivityEntity>): Promise<ActivityEntity> {
 | 
					  async create(activity: Insertable<Activity>) {
 | 
				
			||||||
    return this.save(entity);
 | 
					    return this.save(activity);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async delete(id: string): Promise<void> {
 | 
					  async delete(id: string): Promise<void> {
 | 
				
			||||||
    await this.repository.delete(id);
 | 
					    await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
 | 
					  @GenerateSql({ params: [{ albumId: DummyValue.UUID, assetId: DummyValue.UUID }] })
 | 
				
			||||||
  getStatistics(assetId: string, albumId: string): Promise<number> {
 | 
					  async getStatistics({ albumId, assetId }: { albumId: string; assetId?: string }): Promise<number> {
 | 
				
			||||||
    return this.repository.count({
 | 
					    const { count } = await this.db
 | 
				
			||||||
      where: {
 | 
					      .selectFrom('activity')
 | 
				
			||||||
        assetId,
 | 
					      .select((eb) => eb.fn.countAll().as('count'))
 | 
				
			||||||
        albumId,
 | 
					      .leftJoin('users', 'users.id', 'activity.userId')
 | 
				
			||||||
        isLiked: false,
 | 
					      .leftJoin('assets', 'assets.id', 'activity.assetId')
 | 
				
			||||||
        asset: {
 | 
					      .$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!))
 | 
				
			||||||
          deletedAt: IsNull(),
 | 
					      .where('activity.albumId', '=', albumId)
 | 
				
			||||||
        },
 | 
					      .where('activity.isLiked', '=', false)
 | 
				
			||||||
        user: {
 | 
					      .where('users.deletedAt', 'is', null)
 | 
				
			||||||
          deletedAt: IsNull(),
 | 
					      .where('assets.deletedAt', 'is', null)
 | 
				
			||||||
        },
 | 
					      .executeTakeFirstOrThrow();
 | 
				
			||||||
      },
 | 
					
 | 
				
			||||||
      relations: {
 | 
					    return count as number;
 | 
				
			||||||
        user: true,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      withDeleted: true,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async save(entity: Partial<ActivityEntity>) {
 | 
					  private async save(entity: Insertable<Activity>) {
 | 
				
			||||||
    const { id } = await this.repository.save(entity);
 | 
					    const { id } = await this.db.insertInto('activity').values(entity).returning('id').executeTakeFirstOrThrow();
 | 
				
			||||||
    return this.repository.findOneOrFail({
 | 
					
 | 
				
			||||||
      where: {
 | 
					    return this.db
 | 
				
			||||||
        id,
 | 
					      .selectFrom('activity')
 | 
				
			||||||
      },
 | 
					      .selectAll('activity')
 | 
				
			||||||
      relations: {
 | 
					      .select(withUser)
 | 
				
			||||||
        user: true,
 | 
					      .where('activity.id', '=', asUuid(id))
 | 
				
			||||||
      },
 | 
					      .executeTakeFirstOrThrow() as unknown as Promise<ActivityEntity>;
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -31,7 +31,7 @@ export class ActivityService extends BaseService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  async getStatistics(auth: AuthDto, dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
 | 
					  async getStatistics(auth: AuthDto, dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
 | 
				
			||||||
    await this.requireAccess({ auth, permission: Permission.ALBUM_READ, ids: [dto.albumId] });
 | 
					    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<MaybeDuplicate<ActivityResponseDto>> {
 | 
					  async create(auth: AuthDto, dto: ActivityCreateDto): Promise<MaybeDuplicate<ActivityResponseDto>> {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user