Compare commits
9 commits
main
...
dev/audit-
Author | SHA1 | Date | |
---|---|---|---|
|
023b64ad02 | ||
|
93686edaa3 | ||
|
17902861a9 | ||
|
cee2c2ec45 | ||
|
69800f78dc | ||
|
f2bb21ea5d | ||
|
707b9c68a0 | ||
|
3aea58a015 | ||
|
4e760c50a7 |
17 changed files with 196 additions and 0 deletions
|
@ -2021,6 +2021,38 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/audit": {
|
||||
"get": {
|
||||
"operationId": "getAuditRecords",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Audit"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/admin-sign-up": {
|
||||
"post": {
|
||||
"operationId": "adminSignUp",
|
||||
|
|
7
server/src/domain/audit/audit.repository.ts
Normal file
7
server/src/domain/audit/audit.repository.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { AuditEntity } from '@app/infra/entities';
|
||||
|
||||
export const IAuditRepository = 'IAuditRepository';
|
||||
|
||||
export interface IAuditRepository {
|
||||
get(): Promise<AuditEntity[]>;
|
||||
}
|
13
server/src/domain/audit/audit.service.ts
Normal file
13
server/src/domain/audit/audit.service.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { IAuditRepository } from '.';
|
||||
|
||||
@Injectable()
|
||||
export class AuditService {
|
||||
private logger = new Logger(AuditService.name);
|
||||
|
||||
constructor(@Inject(IAuditRepository) private repository: IAuditRepository) {}
|
||||
|
||||
async getAuditRecords(): Promise<any> {
|
||||
return this.repository.get();
|
||||
}
|
||||
}
|
2
server/src/domain/audit/index.ts
Normal file
2
server/src/domain/audit/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './audit.repository';
|
||||
export * from './audit.service';
|
|
@ -2,6 +2,7 @@ import { DynamicModule, Global, Module, ModuleMetadata, OnApplicationShutdown, P
|
|||
import { AlbumService } from './album';
|
||||
import { APIKeyService } from './api-key';
|
||||
import { AssetService } from './asset';
|
||||
import { AuditService } from './audit';
|
||||
import { AuthService } from './auth';
|
||||
import { FacialRecognitionService } from './facial-recognition';
|
||||
import { JobService } from './job';
|
||||
|
@ -46,6 +47,7 @@ const providers: Provider[] = [
|
|||
return configService.getConfig();
|
||||
},
|
||||
},
|
||||
AuditService,
|
||||
];
|
||||
|
||||
@Global()
|
||||
|
|
|
@ -2,6 +2,7 @@ export * from './access';
|
|||
export * from './album';
|
||||
export * from './api-key';
|
||||
export * from './asset';
|
||||
export * from './audit';
|
||||
export * from './auth';
|
||||
export * from './communication';
|
||||
export * from './crypto';
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
APIKeyController,
|
||||
AppController,
|
||||
AssetController,
|
||||
AuditController,
|
||||
AuthController,
|
||||
JobController,
|
||||
OAuthController,
|
||||
|
@ -42,6 +43,7 @@ import {
|
|||
AppController,
|
||||
AlbumController,
|
||||
APIKeyController,
|
||||
AuditController,
|
||||
AuthController,
|
||||
JobController,
|
||||
OAuthController,
|
||||
|
|
18
server/src/immich/controllers/audit.controller.ts
Normal file
18
server/src/immich/controllers/audit.controller.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { AuditService } from '@app/domain/audit';
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
|
||||
@ApiTags('Audit')
|
||||
@Controller('audit')
|
||||
@Authenticated({ admin: true })
|
||||
@UseValidation()
|
||||
export class AuditController {
|
||||
constructor(private service: AuditService) {}
|
||||
|
||||
@Get()
|
||||
getAuditRecords(): Promise<any> {
|
||||
return this.service.getAuditRecords();
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ export * from './album.controller';
|
|||
export * from './api-key.controller';
|
||||
export * from './app.controller';
|
||||
export * from './asset.controller';
|
||||
export * from './audit.controller';
|
||||
export * from './auth.controller';
|
||||
export * from './job.controller';
|
||||
export * from './oauth.controller';
|
||||
|
|
|
@ -17,6 +17,7 @@ export const databaseConfig: PostgresConnectionOptions = {
|
|||
entities: [__dirname + '/entities/*.entity.{js,ts}'],
|
||||
synchronize: false,
|
||||
migrations: [__dirname + '/migrations/*.{js,ts}'],
|
||||
subscribers: [__dirname + '/subscribers/*.{js,ts}'],
|
||||
migrationsRun: true,
|
||||
connectTimeoutMS: 10000, // 10 seconds
|
||||
...urlOrParts,
|
||||
|
|
35
server/src/infra/entities/audit.entity.ts
Normal file
35
server/src/infra/entities/audit.entity.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
export enum DatabaseAction {
|
||||
CREATE = 'CREATE',
|
||||
UPDATE = 'UPDATE',
|
||||
DELETE = 'DELETE',
|
||||
}
|
||||
|
||||
export enum EntityType {
|
||||
ASSET = 'ASSET',
|
||||
}
|
||||
|
||||
@Entity('audit')
|
||||
export class AuditEntity {
|
||||
@PrimaryGeneratedColumn('increment')
|
||||
id!: number;
|
||||
|
||||
@Column()
|
||||
entityType!: EntityType;
|
||||
|
||||
@Column()
|
||||
entityId!: string;
|
||||
|
||||
@Column()
|
||||
action!: DatabaseAction;
|
||||
|
||||
@Column()
|
||||
ownerId!: string;
|
||||
|
||||
@Column()
|
||||
userId!: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
}
|
|
@ -2,6 +2,7 @@ import { AlbumEntity } from './album.entity';
|
|||
import { APIKeyEntity } from './api-key.entity';
|
||||
import { AssetFaceEntity } from './asset-face.entity';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
import { AuditEntity } from './audit.entity';
|
||||
import { PartnerEntity } from './partner.entity';
|
||||
import { PersonEntity } from './person.entity';
|
||||
import { SharedLinkEntity } from './shared-link.entity';
|
||||
|
@ -15,6 +16,7 @@ export * from './album.entity';
|
|||
export * from './api-key.entity';
|
||||
export * from './asset-face.entity';
|
||||
export * from './asset.entity';
|
||||
export * from './audit.entity';
|
||||
export * from './exif.entity';
|
||||
export * from './partner.entity';
|
||||
export * from './person.entity';
|
||||
|
@ -30,6 +32,7 @@ export const databaseEntities = [
|
|||
APIKeyEntity,
|
||||
AssetEntity,
|
||||
AssetFaceEntity,
|
||||
AuditEntity,
|
||||
PartnerEntity,
|
||||
PersonEntity,
|
||||
SharedLinkEntity,
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
IAccessRepository,
|
||||
IAlbumRepository,
|
||||
IAssetRepository,
|
||||
IAuditRepository,
|
||||
ICommunicationRepository,
|
||||
ICryptoRepository,
|
||||
IFaceRepository,
|
||||
|
@ -35,6 +36,7 @@ import {
|
|||
AlbumRepository,
|
||||
APIKeyRepository,
|
||||
AssetRepository,
|
||||
AuditRepository,
|
||||
CommunicationRepository,
|
||||
CryptoRepository,
|
||||
FaceRepository,
|
||||
|
@ -58,6 +60,7 @@ const providers: Provider[] = [
|
|||
{ provide: IAccessRepository, useClass: AccessRepository },
|
||||
{ provide: IAlbumRepository, useClass: AlbumRepository },
|
||||
{ provide: IAssetRepository, useClass: AssetRepository },
|
||||
{ provide: IAuditRepository, useClass: AuditRepository },
|
||||
{ provide: ICommunicationRepository, useClass: CommunicationRepository },
|
||||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||
{ provide: IFaceRepository, useClass: FaceRepository },
|
||||
|
|
15
server/src/infra/migrations/1691462205943-AddAuditTable.ts
Normal file
15
server/src/infra/migrations/1691462205943-AddAuditTable.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddAuditTable1691462205943 implements MigrationInterface {
|
||||
name = 'AddAuditTable1691462205943';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "audit" ("id" SERIAL NOT NULL, "entityType" character varying NOT NULL, "entityId" character varying NOT NULL, "action" character varying NOT NULL, "ownerId" character varying NOT NULL, "userId" character varying NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_1d3d120ddaf7bc9b1ed68ed463a" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "audit"`);
|
||||
}
|
||||
}
|
12
server/src/infra/repositories/audit.repository.ts
Normal file
12
server/src/infra/repositories/audit.repository.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { IAuditRepository } from '@app/domain/audit/audit.repository';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AuditEntity } from '../entities';
|
||||
|
||||
export class AuditRepository implements IAuditRepository {
|
||||
constructor(@InjectRepository(AuditEntity) private repository: Repository<AuditEntity>) {}
|
||||
|
||||
get(): Promise<AuditEntity[]> {
|
||||
return this.repository.find();
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ export * from './access.repository';
|
|||
export * from './album.repository';
|
||||
export * from './api-key.repository';
|
||||
export * from './asset.repository';
|
||||
export * from './audit.repository';
|
||||
export * from './communication.repository';
|
||||
export * from './crypto.repository';
|
||||
export * from './face.repository';
|
||||
|
|
48
server/src/infra/subscribers/asset.subscriber.ts
Normal file
48
server/src/infra/subscribers/asset.subscriber.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { EntitySubscriberInterface, EventSubscriber, InsertEvent, LoadEvent, RemoveEvent, UpdateEvent } from 'typeorm';
|
||||
import { AssetEntity, AuditEntity, DatabaseAction, EntityType } from '../entities';
|
||||
|
||||
@EventSubscriber()
|
||||
export class AssetAudit implements EntitySubscriberInterface<AssetEntity> {
|
||||
private assetEntity: AssetEntity = new AssetEntity();
|
||||
|
||||
listenTo() {
|
||||
return AssetEntity;
|
||||
}
|
||||
|
||||
afterLoad(entity: AssetEntity): void | Promise<any> {
|
||||
this.assetEntity = entity;
|
||||
}
|
||||
|
||||
async afterInsert(event: InsertEvent<AssetEntity>): Promise<any> {
|
||||
const auditRepository = event.manager.getRepository(AuditEntity);
|
||||
const auditEntity = this.getAssetAudit(DatabaseAction.CREATE);
|
||||
|
||||
await auditRepository.save(auditEntity);
|
||||
}
|
||||
|
||||
async afterRemove(event: RemoveEvent<AssetEntity>): Promise<any> {
|
||||
const auditRepository = event.manager.getRepository(AuditEntity);
|
||||
const auditEntity = this.getAssetAudit(DatabaseAction.DELETE);
|
||||
|
||||
await auditRepository.save(auditEntity);
|
||||
}
|
||||
|
||||
async afterUpdate(event: UpdateEvent<AssetEntity>): Promise<any> {
|
||||
const auditRepository = event.manager.getRepository(AuditEntity);
|
||||
const auditEntity = this.getAssetAudit(DatabaseAction.UPDATE);
|
||||
|
||||
await auditRepository.save(auditEntity);
|
||||
}
|
||||
|
||||
private getAssetAudit(action: DatabaseAction): AuditEntity {
|
||||
const auditEntity = new AuditEntity();
|
||||
|
||||
auditEntity.action = action;
|
||||
auditEntity.entityType = EntityType.ASSET;
|
||||
auditEntity.entityId = this.assetEntity.id;
|
||||
auditEntity.ownerId = this.assetEntity.ownerId;
|
||||
auditEntity.userId = this.assetEntity.ownerId;
|
||||
|
||||
return auditEntity;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue