Backoffice portal updates: dashboard, seat management, pricing, audit logging, reporting

This commit is contained in:
Stephanos A
2026-06-14 10:26:22 +03:00
parent ceb7f17e88
commit 20d483ed00
20 changed files with 1995 additions and 405 deletions

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma.module';
import { AuditService } from './audit.service';
@Module({
imports: [PrismaModule],
providers: [AuditService],
exports: [AuditService],
})
export class AuditModule {}

View File

@@ -0,0 +1,92 @@
import { Injectable, Inject, Optional } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { PrismaService } from './prisma.service';
@Injectable()
export class AuditService {
constructor(
private prisma: PrismaService,
@Optional() @Inject(REQUEST) private request?: any,
) {}
async log(input: {
userId?: string;
action: 'CREATE' | 'UPDATE' | 'DELETE' | 'LOGIN' | 'LOGOUT' | 'VERIFY' | string;
entityType: string;
entityId?: string;
oldData?: any;
newData?: any;
}) {
try {
const ipAddress = this.getIpAddress();
const userAgent = this.getUserAgent();
await this.prisma.auditLog.create({
data: {
userId: input.userId,
action: input.action,
entityType: input.entityType,
entityId: input.entityId,
oldData: input.oldData,
newData: input.newData,
ipAddress,
userAgent,
},
});
} catch (error) {
console.error('Failed to log audit event:', error);
// Don't throw - audit logging should not break main operations
}
}
private getIpAddress(): string {
if (!this.request) return '';
return (
this.request.headers['x-forwarded-for']?.split(',')[0].trim() ||
this.request.headers['x-real-ip'] ||
this.request.connection?.remoteAddress ||
this.request.socket?.remoteAddress ||
this.request.ip ||
''
);
}
private getUserAgent(): string {
return this.request?.headers?.['user-agent'] || '';
}
async getLogs(filters: any = {}) {
const where: any = {};
if (filters.search) {
where.OR = [
{ entityId: { contains: filters.search, mode: 'insensitive' } },
{ user: { email: { contains: filters.search, mode: 'insensitive' } } },
{ user: { fullName: { contains: filters.search, mode: 'insensitive' } } },
];
}
if (filters.action) {
where.action = filters.action;
}
if (filters.entityType) {
where.entityType = filters.entityType;
}
return this.prisma.auditLog.findMany({
where,
include: { user: true },
orderBy: { createdAt: 'desc' },
take: 500, // Limit to last 500 logs
});
}
async getLog(id: string) {
return this.prisma.auditLog.findUnique({
where: { id },
include: { user: true },
});
}
}