mirror of
https://github.com/immich-app/immich.git
synced 2025-06-03 05:34:32 -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[];
|
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 = {
|
export type ApiKey = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -173,6 +186,7 @@ export const columns = {
|
|||||||
'shared_links.password',
|
'shared_links.password',
|
||||||
],
|
],
|
||||||
user: userColumns,
|
user: userColumns,
|
||||||
|
userWithPrefix: ['users.id', 'users.name', 'users.email', 'users.profileImagePath', 'users.profileChangedAt'],
|
||||||
userAdmin: [
|
userAdmin: [
|
||||||
...userColumns,
|
...userColumns,
|
||||||
'createdAt',
|
'createdAt',
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator';
|
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator';
|
||||||
|
import { Activity } from 'src/database';
|
||||||
import { mapUser, UserResponseDto } from 'src/dtos/user.dto';
|
import { mapUser, UserResponseDto } from 'src/dtos/user.dto';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { ActivityItem } from 'src/types';
|
|
||||||
import { Optional, ValidateUUID } from 'src/validation';
|
import { Optional, ValidateUUID } from 'src/validation';
|
||||||
|
|
||||||
export enum ReactionType {
|
export enum ReactionType {
|
||||||
@ -68,7 +68,7 @@ export class ActivityCreateDto extends ActivityDto {
|
|||||||
comment?: string;
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapActivity = (activity: ActivityItem): ActivityResponseDto => {
|
export const mapActivity = (activity: Activity): ActivityResponseDto => {
|
||||||
return {
|
return {
|
||||||
id: activity.id,
|
id: activity.id,
|
||||||
assetId: activity.assetId,
|
assetId: activity.assetId,
|
||||||
|
@ -3,6 +3,38 @@
|
|||||||
-- ActivityRepository.search
|
-- ActivityRepository.search
|
||||||
select
|
select
|
||||||
"activity".*,
|
"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
|
select
|
||||||
to_json(obj)
|
to_json(obj)
|
||||||
@ -18,17 +50,13 @@ select
|
|||||||
"users"
|
"users"
|
||||||
where
|
where
|
||||||
"users"."id" = "activity"."userId"
|
"users"."id" = "activity"."userId"
|
||||||
and "users"."deletedAt" is null
|
|
||||||
) as obj
|
) as obj
|
||||||
) as "user"
|
) as "user"
|
||||||
from
|
|
||||||
"activity"
|
-- ActivityRepository.delete
|
||||||
left join "assets" on "assets"."id" = "activity"."assetId"
|
delete from "activity"
|
||||||
and "assets"."deletedAt" is null
|
|
||||||
where
|
where
|
||||||
"activity"."albumId" = $1
|
"id" = $1::uuid
|
||||||
order by
|
|
||||||
"activity"."createdAt" asc
|
|
||||||
|
|
||||||
-- ActivityRepository.getStatistics
|
-- ActivityRepository.getStatistics
|
||||||
select
|
select
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { columns } from 'src/database';
|
import { columns } from 'src/database';
|
||||||
@ -14,16 +14,6 @@ export interface ActivitySearch {
|
|||||||
isLiked?: boolean;
|
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()
|
@Injectable()
|
||||||
export class ActivityRepository {
|
export class ActivityRepository {
|
||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
@ -35,7 +25,16 @@ export class ActivityRepository {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('activity')
|
.selectFrom('activity')
|
||||||
.selectAll('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))
|
.leftJoin('assets', (join) => join.onRef('assets.id', '=', 'activity.assetId').on('assets.deletedAt', 'is', null))
|
||||||
.$if(!!userId, (qb) => qb.where('activity.userId', '=', userId!))
|
.$if(!!userId, (qb) => qb.where('activity.userId', '=', userId!))
|
||||||
.$if(assetId === null, (qb) => qb.where('assetId', 'is', null))
|
.$if(assetId === null, (qb) => qb.where('assetId', 'is', null))
|
||||||
@ -46,10 +45,22 @@ export class ActivityRepository {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [{ albumId: DummyValue.UUID, userId: DummyValue.UUID }] })
|
||||||
async create(activity: Insertable<Activity>) {
|
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) {
|
async delete(id: string) {
|
||||||
await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute();
|
await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute();
|
||||||
}
|
}
|
||||||
@ -72,15 +83,4 @@ export class ActivityRepository {
|
|||||||
|
|
||||||
return count as number;
|
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 { Injectable } from '@nestjs/common';
|
||||||
|
import { Activity } from 'src/database';
|
||||||
import {
|
import {
|
||||||
ActivityCreateDto,
|
ActivityCreateDto,
|
||||||
ActivityDto,
|
ActivityDto,
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { Permission } from 'src/enum';
|
import { Permission } from 'src/enum';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { ActivityItem } from 'src/types';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ActivityService extends BaseService {
|
export class ActivityService extends BaseService {
|
||||||
@ -43,7 +43,7 @@ export class ActivityService extends BaseService {
|
|||||||
albumId: dto.albumId,
|
albumId: dto.albumId,
|
||||||
};
|
};
|
||||||
|
|
||||||
let activity: ActivityItem | undefined;
|
let activity: Activity | undefined;
|
||||||
let duplicate = false;
|
let duplicate = false;
|
||||||
|
|
||||||
if (dto.type === ReactionType.LIKE) {
|
if (dto.type === ReactionType.LIKE) {
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
TranscodeTarget,
|
TranscodeTarget,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from 'src/enum';
|
} from 'src/enum';
|
||||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
|
||||||
import { SearchRepository } from 'src/repositories/search.repository';
|
import { SearchRepository } from 'src/repositories/search.repository';
|
||||||
import { SessionRepository } from 'src/repositories/session.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>;
|
export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
|
||||||
|
|
||||||
type IActivityRepository = RepositoryInterface<ActivityRepository>;
|
|
||||||
type ISearchRepository = RepositoryInterface<SearchRepository>;
|
type ISearchRepository = RepositoryInterface<SearchRepository>;
|
||||||
type ISessionRepository = RepositoryInterface<SessionRepository>;
|
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 SearchPlacesItem = Awaited<ReturnType<ISearchRepository['searchPlaces']>>[0];
|
||||||
|
|
||||||
export type SessionItem = Awaited<ReturnType<ISessionRepository['getByUserId']>>[0];
|
export type SessionItem = Awaited<ReturnType<ISessionRepository['getByUserId']>>[0];
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import {
|
import {
|
||||||
|
Activity,
|
||||||
ApiKey,
|
ApiKey,
|
||||||
Asset,
|
Asset,
|
||||||
AuthApiKey,
|
AuthApiKey,
|
||||||
@ -13,7 +14,7 @@ import {
|
|||||||
} from 'src/database';
|
} from 'src/database';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { AssetStatus, AssetType, MemoryType, Permission, UserStatus } from 'src/enum';
|
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 newUuid = () => randomUUID() as string;
|
||||||
export const newUuids = () =>
|
export const newUuids = () =>
|
||||||
@ -154,7 +155,7 @@ const assetFactory = (asset: Partial<Asset> = {}) => ({
|
|||||||
...asset,
|
...asset,
|
||||||
});
|
});
|
||||||
|
|
||||||
const activityFactory = (activity: Partial<ActivityItem> = {}) => {
|
const activityFactory = (activity: Partial<Activity> = {}) => {
|
||||||
const userId = activity.userId || newUuid();
|
const userId = activity.userId || newUuid();
|
||||||
return {
|
return {
|
||||||
id: newUuid(),
|
id: newUuid(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user