mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
refactor: activity item (#17470)
* refactor: activity item * fix query * qualified columns --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
This commit is contained in:
parent
ae8af84101
commit
cf2c0260a6
@ -30,6 +30,19 @@ export type AuthApiKey = {
|
||||
permissions: Permission[];
|
||||
};
|
||||
|
||||
export type Activity = {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
albumId: string;
|
||||
userId: string;
|
||||
user: User;
|
||||
assetId: string | null;
|
||||
comment: string | null;
|
||||
isLiked: boolean;
|
||||
updateId: string;
|
||||
};
|
||||
|
||||
export type ApiKey = {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -173,6 +186,7 @@ export const columns = {
|
||||
'shared_links.password',
|
||||
],
|
||||
user: userColumns,
|
||||
userWithPrefix: ['users.id', 'users.name', 'users.email', 'users.profileImagePath', 'users.profileChangedAt'],
|
||||
userAdmin: [
|
||||
...userColumns,
|
||||
'createdAt',
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator';
|
||||
import { Activity } from 'src/database';
|
||||
import { mapUser, UserResponseDto } from 'src/dtos/user.dto';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { ActivityItem } from 'src/types';
|
||||
import { Optional, ValidateUUID } from 'src/validation';
|
||||
|
||||
export enum ReactionType {
|
||||
@ -68,7 +68,7 @@ export class ActivityCreateDto extends ActivityDto {
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export const mapActivity = (activity: ActivityItem): ActivityResponseDto => {
|
||||
export const mapActivity = (activity: Activity): ActivityResponseDto => {
|
||||
return {
|
||||
id: activity.id,
|
||||
assetId: activity.assetId,
|
||||
|
@ -3,6 +3,38 @@
|
||||
-- ActivityRepository.search
|
||||
select
|
||||
"activity".*,
|
||||
to_json("user") as "user"
|
||||
from
|
||||
"activity"
|
||||
inner join "users" on "users"."id" = "activity"."userId"
|
||||
and "users"."deletedAt" is null
|
||||
inner join lateral (
|
||||
select
|
||||
"users"."id",
|
||||
"users"."name",
|
||||
"users"."email",
|
||||
"users"."profileImagePath",
|
||||
"users"."profileChangedAt"
|
||||
from
|
||||
(
|
||||
select
|
||||
1
|
||||
) as "dummy"
|
||||
) as "user" on true
|
||||
left join "assets" on "assets"."id" = "activity"."assetId"
|
||||
and "assets"."deletedAt" is null
|
||||
where
|
||||
"activity"."albumId" = $1
|
||||
order by
|
||||
"activity"."createdAt" asc
|
||||
|
||||
-- ActivityRepository.create
|
||||
insert into
|
||||
"activity" ("albumId", "userId")
|
||||
values
|
||||
($1, $2)
|
||||
returning
|
||||
*,
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
@ -18,17 +50,13 @@ select
|
||||
"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
|
||||
|
||||
-- ActivityRepository.delete
|
||||
delete from "activity"
|
||||
where
|
||||
"activity"."albumId" = $1
|
||||
order by
|
||||
"activity"."createdAt" asc
|
||||
"id" = $1::uuid
|
||||
|
||||
-- ActivityRepository.getStatistics
|
||||
select
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ExpressionBuilder, Insertable, Kysely } from 'kysely';
|
||||
import { Insertable, Kysely, NotNull, sql } from 'kysely';
|
||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { columns } from 'src/database';
|
||||
@ -14,16 +14,6 @@ export interface ActivitySearch {
|
||||
isLiked?: boolean;
|
||||
}
|
||||
|
||||
const withUser = (eb: ExpressionBuilder<DB, 'activity'>) => {
|
||||
return jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom('users')
|
||||
.select(columns.user)
|
||||
.whereRef('users.id', '=', 'activity.userId')
|
||||
.where('users.deletedAt', 'is', null),
|
||||
).as('user');
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ActivityRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
@ -35,7 +25,16 @@ export class ActivityRepository {
|
||||
return this.db
|
||||
.selectFrom('activity')
|
||||
.selectAll('activity')
|
||||
.select(withUser)
|
||||
.innerJoin('users', (join) => join.onRef('users.id', '=', 'activity.userId').on('users.deletedAt', 'is', null))
|
||||
.innerJoinLateral(
|
||||
(eb) =>
|
||||
eb
|
||||
.selectFrom(sql`(select 1)`.as('dummy'))
|
||||
.select(columns.userWithPrefix)
|
||||
.as('user'),
|
||||
(join) => join.onTrue(),
|
||||
)
|
||||
.select((eb) => eb.fn.toJson('user').as('user'))
|
||||
.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))
|
||||
@ -46,10 +45,22 @@ export class ActivityRepository {
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ albumId: DummyValue.UUID, userId: DummyValue.UUID }] })
|
||||
async create(activity: Insertable<Activity>) {
|
||||
return this.save(activity);
|
||||
return this.db
|
||||
.insertInto('activity')
|
||||
.values(activity)
|
||||
.returningAll()
|
||||
.returning((eb) =>
|
||||
jsonObjectFrom(eb.selectFrom('users').whereRef('users.id', '=', 'activity.userId').select(columns.user)).as(
|
||||
'user',
|
||||
),
|
||||
)
|
||||
.$narrowType<{ user: NotNull }>()
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async delete(id: string) {
|
||||
await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute();
|
||||
}
|
||||
@ -72,15 +83,4 @@ export class ActivityRepository {
|
||||
|
||||
return count as number;
|
||||
}
|
||||
|
||||
private async save(entity: Insertable<Activity>) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Activity } from 'src/database';
|
||||
import {
|
||||
ActivityCreateDto,
|
||||
ActivityDto,
|
||||
@ -13,7 +14,6 @@ import {
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { ActivityItem } from 'src/types';
|
||||
|
||||
@Injectable()
|
||||
export class ActivityService extends BaseService {
|
||||
@ -43,7 +43,7 @@ export class ActivityService extends BaseService {
|
||||
albumId: dto.albumId,
|
||||
};
|
||||
|
||||
let activity: ActivityItem | undefined;
|
||||
let activity: Activity | undefined;
|
||||
let duplicate = false;
|
||||
|
||||
if (dto.type === ReactionType.LIKE) {
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
TranscodeTarget,
|
||||
VideoCodec,
|
||||
} from 'src/enum';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { SearchRepository } from 'src/repositories/search.repository';
|
||||
import { SessionRepository } from 'src/repositories/session.repository';
|
||||
|
||||
@ -21,14 +20,9 @@ export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T
|
||||
|
||||
export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
|
||||
|
||||
type IActivityRepository = RepositoryInterface<ActivityRepository>;
|
||||
type ISearchRepository = RepositoryInterface<SearchRepository>;
|
||||
type ISessionRepository = RepositoryInterface<SessionRepository>;
|
||||
|
||||
export type ActivityItem =
|
||||
| Awaited<ReturnType<IActivityRepository['create']>>
|
||||
| Awaited<ReturnType<IActivityRepository['search']>>[0];
|
||||
|
||||
export type SearchPlacesItem = Awaited<ReturnType<ISearchRepository['searchPlaces']>>[0];
|
||||
|
||||
export type SessionItem = Awaited<ReturnType<ISessionRepository['getByUserId']>>[0];
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import {
|
||||
Activity,
|
||||
ApiKey,
|
||||
Asset,
|
||||
AuthApiKey,
|
||||
@ -13,7 +14,7 @@ import {
|
||||
} from 'src/database';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetStatus, AssetType, MemoryType, Permission, UserStatus } from 'src/enum';
|
||||
import { ActivityItem, OnThisDayData } from 'src/types';
|
||||
import { OnThisDayData } from 'src/types';
|
||||
|
||||
export const newUuid = () => randomUUID() as string;
|
||||
export const newUuids = () =>
|
||||
@ -154,7 +155,7 @@ const assetFactory = (asset: Partial<Asset> = {}) => ({
|
||||
...asset,
|
||||
});
|
||||
|
||||
const activityFactory = (activity: Partial<ActivityItem> = {}) => {
|
||||
const activityFactory = (activity: Partial<Activity> = {}) => {
|
||||
const userId = activity.userId || newUuid();
|
||||
return {
|
||||
id: newUuid(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user