mirror of
https://github.com/immich-app/immich.git
synced 2025-01-28 00:59:18 -05:00
fix: activity types (#15368)
This commit is contained in:
parent
0ce62d8efd
commit
6ce1533117
15 changed files with 75 additions and 57 deletions
|
@ -20,14 +20,14 @@ import { ErrorInterceptor } from 'src/middleware/error.interceptor';
|
|||
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
|
||||
import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
|
||||
import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
|
||||
import { repositories } from 'src/repositories';
|
||||
import { providers, repositories } from 'src/repositories';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { teardownTelemetry } from 'src/repositories/telemetry.repository';
|
||||
import { services } from 'src/services';
|
||||
import { CliService } from 'src/services/cli.service';
|
||||
import { DatabaseService } from 'src/services/database.service';
|
||||
|
||||
const common = [...services, ...repositories];
|
||||
const common = [...services, ...providers, ...repositories];
|
||||
|
||||
const middleware = [
|
||||
FileUploadInterceptor,
|
||||
|
@ -73,7 +73,7 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
|
|||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.telemetryRepository.setup({ repositories: repositories.map(({ useClass }) => useClass) });
|
||||
this.telemetryRepository.setup({ repositories: [...providers.map(({ useClass }) => useClass), ...repositories] });
|
||||
|
||||
this.jobRepository.setup({ services });
|
||||
if (this.worker === ImmichWorker.MICROSERVICES) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { format } from 'sql-formatter';
|
|||
import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators';
|
||||
import { entities } from 'src/entities';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { repositories } from 'src/repositories';
|
||||
import { providers, repositories } from 'src/repositories';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { AuthService } from 'src/services/auth.service';
|
||||
|
@ -43,7 +43,7 @@ export class SqlLogger implements Logger {
|
|||
|
||||
const reflector = new Reflector();
|
||||
|
||||
type Repository = (typeof repositories)[0]['useClass'];
|
||||
type Repository = (typeof providers)[0]['useClass'];
|
||||
type Provider = { provide: any; useClass: Repository };
|
||||
type SqlGeneratorOptions = { targetDir: string };
|
||||
|
||||
|
@ -57,7 +57,11 @@ class SqlGenerator {
|
|||
async run() {
|
||||
try {
|
||||
await this.setup();
|
||||
for (const repository of repositories) {
|
||||
const targets = [
|
||||
...providers,
|
||||
...repositories.map((repository) => ({ provide: repository, useClass: repository as any })),
|
||||
];
|
||||
for (const repository of targets) {
|
||||
if (repository.provide === ILoggerRepository) {
|
||||
continue;
|
||||
}
|
||||
|
@ -99,7 +103,7 @@ class SqlGenerator {
|
|||
TypeOrmModule.forFeature(entities),
|
||||
OpenTelemetryModule.forRoot(otel),
|
||||
],
|
||||
providers: [...repositories, AuthService, SchedulerRegistry],
|
||||
providers: [...providers, ...repositories, AuthService, SchedulerRegistry],
|
||||
}).compile();
|
||||
|
||||
this.app = await moduleFixture.createNestApplication().init();
|
||||
|
|
3
server/src/database.ts
Normal file
3
server/src/database.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const columns = {
|
||||
userDto: ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'],
|
||||
} as const;
|
|
@ -1,7 +1,8 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator';
|
||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
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 {
|
||||
|
@ -67,13 +68,13 @@ export class ActivityCreateDto extends ActivityDto {
|
|||
comment?: string;
|
||||
}
|
||||
|
||||
export function mapActivity(activity: ActivityEntity): ActivityResponseDto {
|
||||
export const mapActivity = (activity: ActivityItem): ActivityResponseDto => {
|
||||
return {
|
||||
id: activity.id,
|
||||
assetId: activity.assetId,
|
||||
createdAt: activity.createdAt,
|
||||
comment: activity.comment,
|
||||
type: activity.isLiked ? ReactionType.LIKE : ReactionType.COMMENT,
|
||||
user: mapUser(activity.user),
|
||||
user: mapUser(activity.user as unknown as UserEntity),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { Insertable } from 'kysely';
|
||||
import { Activity } from 'src/db';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { ActivitySearch } from 'src/repositories/activity.repository';
|
||||
|
||||
export const IActivityRepository = 'IActivityRepository';
|
||||
|
||||
export interface IActivityRepository {
|
||||
search(options: ActivitySearch): Promise<ActivityEntity[]>;
|
||||
create(activity: Insertable<Activity>): Promise<ActivityEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
getStatistics(options: { albumId: string; assetId?: string }): Promise<number>;
|
||||
}
|
|
@ -9,7 +9,11 @@ select
|
|||
from
|
||||
(
|
||||
select
|
||||
*
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users"
|
||||
where
|
||||
|
|
|
@ -2,10 +2,9 @@ import { Injectable } from '@nestjs/common';
|
|||
import { ExpressionBuilder, Insertable, Kysely } from 'kysely';
|
||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { columns } from 'src/database';
|
||||
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 { asUuid } from 'src/utils/database';
|
||||
|
||||
export interface ActivitySearch {
|
||||
|
@ -19,18 +18,18 @@ const withUser = (eb: ExpressionBuilder<DB, 'activity'>) => {
|
|||
return jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom('users')
|
||||
.selectAll()
|
||||
.select(columns.userDto)
|
||||
.whereRef('users.id', '=', 'activity.userId')
|
||||
.where('users.deletedAt', 'is', null),
|
||||
).as('user');
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ActivityRepository implements IActivityRepository {
|
||||
export class ActivityRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [{ albumId: DummyValue.UUID }] })
|
||||
search(options: ActivitySearch): Promise<ActivityEntity[]> {
|
||||
search(options: ActivitySearch) {
|
||||
const { userId, assetId, albumId, isLiked } = options;
|
||||
|
||||
return this.db
|
||||
|
@ -44,14 +43,14 @@ export class ActivityRepository implements IActivityRepository {
|
|||
.$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<ActivityEntity[]>;
|
||||
.execute();
|
||||
}
|
||||
|
||||
async create(activity: Insertable<Activity>) {
|
||||
return this.save(activity);
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
async delete(id: string) {
|
||||
await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute();
|
||||
}
|
||||
|
||||
|
@ -79,6 +78,6 @@ export class ActivityRepository implements IActivityRepository {
|
|||
.selectAll('activity')
|
||||
.select(withUser)
|
||||
.where('activity.id', '=', asUuid(id))
|
||||
.executeTakeFirstOrThrow() as unknown as Promise<ActivityEntity>;
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||
|
@ -78,8 +77,12 @@ import { VersionHistoryRepository } from 'src/repositories/version-history.repos
|
|||
import { ViewRepository } from 'src/repositories/view-repository';
|
||||
|
||||
export const repositories = [
|
||||
//
|
||||
ActivityRepository,
|
||||
];
|
||||
|
||||
export const providers = [
|
||||
{ provide: IAccessRepository, useClass: AccessRepository },
|
||||
{ provide: IActivityRepository, useClass: ActivityRepository },
|
||||
{ provide: IAlbumRepository, useClass: AlbumRepository },
|
||||
{ provide: IAlbumUserRepository, useClass: AlbumUserRepository },
|
||||
{ provide: IAssetRepository, useClass: AssetRepository },
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { BadRequestException } from '@nestjs/common';
|
||||
import { ReactionType } from 'src/dtos/activity.dto';
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { ActivityService } from 'src/services/activity.service';
|
||||
import { IActivityRepository } from 'src/types';
|
||||
import { activityStub } from 'test/fixtures/activity.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||
|
|
|
@ -5,15 +5,15 @@ import {
|
|||
ActivityResponseDto,
|
||||
ActivitySearchDto,
|
||||
ActivityStatisticsResponseDto,
|
||||
mapActivity,
|
||||
MaybeDuplicate,
|
||||
ReactionLevel,
|
||||
ReactionType,
|
||||
mapActivity,
|
||||
} from 'src/dtos/activity.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
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: ActivityEntity | null = null;
|
||||
let activity: ActivityItem | undefined;
|
||||
let duplicate = false;
|
||||
|
||||
if (dto.type === ReactionType.LIKE) {
|
||||
|
|
|
@ -7,7 +7,6 @@ import { StorageCore } from 'src/cores/storage.core';
|
|||
import { Users } from 'src/db';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||
|
@ -45,6 +44,7 @@ import { ITrashRepository } from 'src/interfaces/trash.interface';
|
|||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
||||
import { IViewRepository } from 'src/interfaces/view.interface';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
|
||||
import { getConfig, updateConfig } from 'src/utils/config';
|
||||
|
||||
|
@ -54,7 +54,7 @@ export class BaseService {
|
|||
constructor(
|
||||
@Inject(ILoggerRepository) protected logger: ILoggerRepository,
|
||||
@Inject(IAccessRepository) protected accessRepository: IAccessRepository,
|
||||
@Inject(IActivityRepository) protected activityRepository: IActivityRepository,
|
||||
protected activityRepository: ActivityRepository,
|
||||
@Inject(IAuditRepository) protected auditRepository: IAuditRepository,
|
||||
@Inject(IAlbumRepository) protected albumRepository: IAlbumRepository,
|
||||
@Inject(IAlbumUserRepository) protected albumUserRepository: IAlbumUserRepository,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { Permission } from 'src/enum';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
|
||||
export type AuthApiKey = {
|
||||
id: string;
|
||||
|
@ -7,3 +8,11 @@ export type AuthApiKey = {
|
|||
user: UserEntity;
|
||||
permissions: Permission[];
|
||||
};
|
||||
|
||||
export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
|
||||
|
||||
export type IActivityRepository = RepositoryInterface<ActivityRepository>;
|
||||
|
||||
export type ActivityItem =
|
||||
| Awaited<ReturnType<IActivityRepository['create']>>
|
||||
| Awaited<ReturnType<IActivityRepository['search']>>[0];
|
||||
|
|
32
server/test/fixtures/activity.stub.ts
vendored
32
server/test/fixtures/activity.stub.ts
vendored
|
@ -1,33 +1,39 @@
|
|||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { ActivityItem } from 'src/types';
|
||||
import { albumStub } from 'test/fixtures/album.stub';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
|
||||
export const activityStub = {
|
||||
oneComment: Object.freeze<ActivityEntity>({
|
||||
oneComment: Object.freeze<ActivityItem>({
|
||||
id: 'activity-1',
|
||||
comment: 'comment',
|
||||
isLiked: false,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
userId: 'admin_id',
|
||||
user: {
|
||||
id: 'admin_id',
|
||||
name: 'admin',
|
||||
email: 'admin@test.com',
|
||||
profileImagePath: '',
|
||||
profileChangedAt: new Date('2021-01-01'),
|
||||
},
|
||||
assetId: assetStub.image.id,
|
||||
asset: assetStub.image,
|
||||
albumId: albumStub.oneAsset.id,
|
||||
album: albumStub.oneAsset,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
liked: Object.freeze<ActivityEntity>({
|
||||
liked: Object.freeze<ActivityItem>({
|
||||
id: 'activity-2',
|
||||
comment: null,
|
||||
isLiked: true,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
userId: 'admin_id',
|
||||
user: {
|
||||
id: 'admin_id',
|
||||
name: 'admin',
|
||||
email: 'admin@test.com',
|
||||
profileImagePath: '',
|
||||
profileChangedAt: new Date('2021-01-01'),
|
||||
},
|
||||
assetId: assetStub.image.id,
|
||||
asset: assetStub.image,
|
||||
albumId: albumStub.oneAsset.id,
|
||||
album: albumStub.oneAsset,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { IActivityRepository } from 'src/types';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
export const newActivityRepositoryMock = (): Mocked<IActivityRepository> => {
|
||||
|
|
|
@ -3,7 +3,9 @@ import { Writable } from 'node:stream';
|
|||
import { PNG } from 'pngjs';
|
||||
import { ImmichWorker } from 'src/enum';
|
||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { IActivityRepository } from 'src/types';
|
||||
import { newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||
import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock';
|
||||
import { newAlbumUserRepositoryMock } from 'test/repositories/album-user.repository.mock';
|
||||
|
@ -104,7 +106,7 @@ export const newTestService = <T extends BaseService>(
|
|||
const sut = new Service(
|
||||
loggerMock,
|
||||
accessMock,
|
||||
activityMock,
|
||||
activityMock as IActivityRepository as ActivityRepository,
|
||||
auditMock,
|
||||
albumMock,
|
||||
albumUserMock,
|
||||
|
|
Loading…
Add table
Reference in a new issue