Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { JwtAuthGuard } from './auth/guards/jwt.guard';
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { AuditsModule } from './audits/audits.module';
import { CostCentersModule } from './cost-centers/cost-centers.module';
import { AuditLogsModule } from './audit-logs/audit-logs.module';

@Module({
imports: [
Expand Down Expand Up @@ -64,7 +65,8 @@ import { CostCentersModule } from './cost-centers/cost-centers.module';
EmailModule,
NewsletterModule,
AuditsModule,
CostCentersModule,
CostCentersModule,
AuditLogsModule,
],
controllers: [AppController],
providers: [
Expand Down
13 changes: 13 additions & 0 deletions backend/src/audit-logs/audit-logs.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable prettier/prettier */
import { Controller, Get, Param } from '@nestjs/common';
import { AuditLogsService } from './audit-logs.service';

@Controller('audit-logs')
export class AuditLogsController {
constructor(private readonly auditLogsService: AuditLogsService) {}

@Get(':userId')
getLogs(@Param('userId') userId: string) {
return this.auditLogsService.findByUser(userId);
}
}
32 changes: 32 additions & 0 deletions backend/src/audit-logs/audit-logs.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable prettier/prettier */
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
import { AuditLogsService } from './audit-logs.service';

@Injectable()
export class AuditLogsInterceptor implements NestInterceptor {
constructor(private readonly auditLogsService: AuditLogsService) {}

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest();
const user = req.user; // requires auth
const { method, originalUrl, body, params, query } = req;

return next.handle().pipe(
tap(async (data) => {
await this.auditLogsService.createLog({
action: `${method} ${originalUrl}`,
entity: params?.id ? originalUrl.split('/')[1] : null,
entityId: params?.id,
userId: user?.id || 'anonymous',
details: { body, query, response: data },
});
}),
);
}
}
9 changes: 9 additions & 0 deletions backend/src/audit-logs/audit-logs.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { AuditLogsService } from './audit-logs.service';
import { AuditLogsController } from './audit-logs.controller';

@Module({
controllers: [AuditLogsController],
providers: [AuditLogsService],
})
export class AuditLogsModule {}
22 changes: 22 additions & 0 deletions backend/src/audit-logs/audit-logs.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable prettier/prettier */
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AuditLog } from './entities/audit-log.entity';

@Injectable()
export class AuditLogsService {
constructor(
@InjectRepository(AuditLog)
private readonly auditLogsRepo: Repository<AuditLog>,
) {}

async createLog(log: Partial<AuditLog>) {
const auditLog = this.auditLogsRepo.create(log);
return this.auditLogsRepo.save(auditLog);
}

async findByUser(userId: string) {
return this.auditLogsRepo.find({ where: { userId }, order: { timestamp: 'DESC' } });
}
}
1 change: 1 addition & 0 deletions backend/src/audit-logs/dto/create-audit-log.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class CreateAuditLogDto {}
4 changes: 4 additions & 0 deletions backend/src/audit-logs/dto/update-audit-log.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateAuditLogDto } from './create-audit-log.dto';

export class UpdateAuditLogDto extends PartialType(CreateAuditLogDto) {}
26 changes: 26 additions & 0 deletions backend/src/audit-logs/entities/audit-log.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable prettier/prettier */
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';

@Entity('audit_logs')
export class AuditLog {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
action: string; // e.g. LOGIN, CREATE_USER, UPDATE_ASSET

@Column({ nullable: true })
entity: string; // e.g. User, Asset

@Column({ nullable: true })
entityId: string; // e.g. the affected entity's ID

@Column()
userId: string; // who performed the action

@Column({ type: 'json', nullable: true })
details: any; // extra payload

@CreateDateColumn()
timestamp: Date;
}
15 changes: 11 additions & 4 deletions backend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable prettier/prettier */
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

Expand All @@ -6,11 +7,13 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ValidationPipe, ClassSerializerInterceptor } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

import { AuditLogsInterceptor } from './audit-logs/audit-logs.interceptor';
import { AuditLogsService } from './audit-logs/audit-logs.service';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

//GLOBAL VALIDATION
// GLOBAL VALIDATION
app.useGlobalPipes(
new ValidationPipe({
transform: true,
Expand All @@ -19,10 +22,14 @@ async function bootstrap() {
}),
);

//GLOBAL SERIALIZATION
// GLOBAL SERIALIZATION
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));

//ENABLE CORS
// GLOBAL AUDIT LOGGING INTERCEPTOR
const auditLogsService = app.get(AuditLogsService);
app.useGlobalInterceptors(new AuditLogsInterceptor(auditLogsService));

// ENABLE CORS
app.enableCors({
origin:
process.env.NODE_ENV === 'production'
Expand Down Expand Up @@ -51,4 +58,4 @@ async function bootstrap() {
await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
console.log(`Server is listening at: ${await app.getUrl()}`);
}
bootstrap();
bootstrap();