feat: add CommonModule with PurgeService and global exception filter
Introduced CommonModule to centralize shared functionality. Added PurgeService for automated database cleanup and a global exception filter for unified error handling.
This commit is contained in:
11
backend/src/common/common.module.ts
Normal file
11
backend/src/common/common.module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Global, Module } from "@nestjs/common";
|
||||||
|
import { DatabaseModule } from "../database/database.module";
|
||||||
|
import { PurgeService } from "./services/purge.service";
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [DatabaseModule],
|
||||||
|
providers: [PurgeService],
|
||||||
|
exports: [PurgeService],
|
||||||
|
})
|
||||||
|
export class CommonModule {}
|
||||||
56
backend/src/common/filters/http-exception.filter.ts
Normal file
56
backend/src/common/filters/http-exception.filter.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import {
|
||||||
|
ArgumentsHost,
|
||||||
|
Catch,
|
||||||
|
ExceptionFilter,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Logger,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import * as Sentry from "@sentry/nestjs";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
@Catch()
|
||||||
|
export class AllExceptionsFilter implements ExceptionFilter {
|
||||||
|
private readonly logger = new Logger("ExceptionFilter");
|
||||||
|
|
||||||
|
catch(exception: unknown, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const response = ctx.getResponse<Response>();
|
||||||
|
const request = ctx.getRequest<Request>();
|
||||||
|
|
||||||
|
const status =
|
||||||
|
exception instanceof HttpException
|
||||||
|
? exception.getStatus()
|
||||||
|
: HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
|
||||||
|
const message =
|
||||||
|
exception instanceof HttpException
|
||||||
|
? exception.getResponse()
|
||||||
|
: "Internal server error";
|
||||||
|
|
||||||
|
const errorResponse = {
|
||||||
|
statusCode: status,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
path: request.url,
|
||||||
|
method: request.method,
|
||||||
|
message:
|
||||||
|
typeof message === "object" && message !== null
|
||||||
|
? (message as Record<string, unknown>).message || message
|
||||||
|
: message,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
|
||||||
|
Sentry.captureException(exception);
|
||||||
|
this.logger.error(
|
||||||
|
`${request.method} ${request.url} - Error: ${exception instanceof Error ? exception.message : "Unknown error"}`,
|
||||||
|
exception instanceof Error ? exception.stack : "",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.warn(
|
||||||
|
`${request.method} ${request.url} - Status: ${status} - Message: ${JSON.stringify(message)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.status(status).json(errorResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
backend/src/common/interfaces/request.interface.ts
Normal file
8
backend/src/common/interfaces/request.interface.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Request } from "express";
|
||||||
|
|
||||||
|
export interface AuthenticatedRequest extends Request {
|
||||||
|
user: {
|
||||||
|
sub: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
63
backend/src/common/services/purge.service.ts
Normal file
63
backend/src/common/services/purge.service.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { Cron, CronExpression } from "@nestjs/schedule";
|
||||||
|
import { and, eq, isNotNull, lte } from "drizzle-orm";
|
||||||
|
import { DatabaseService } from "../../database/database.service";
|
||||||
|
import { contents, reports, sessions, users } from "../../database/schemas";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PurgeService {
|
||||||
|
private readonly logger = new Logger(PurgeService.name);
|
||||||
|
|
||||||
|
constructor(private readonly databaseService: DatabaseService) {}
|
||||||
|
|
||||||
|
// Toutes les nuits à minuit
|
||||||
|
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
||||||
|
async purgeExpiredData() {
|
||||||
|
this.logger.log("Starting automatic data purge...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// 1. Purge des sessions expirées
|
||||||
|
const deletedSessions = await this.databaseService.db
|
||||||
|
.delete(sessions)
|
||||||
|
.where(lte(sessions.expiresAt, now))
|
||||||
|
.returning();
|
||||||
|
this.logger.log(`Purged ${deletedSessions.length} expired sessions.`);
|
||||||
|
|
||||||
|
// 2. Purge des signalements obsolètes
|
||||||
|
const deletedReports = await this.databaseService.db
|
||||||
|
.delete(reports)
|
||||||
|
.where(lte(reports.expiresAt, now))
|
||||||
|
.returning();
|
||||||
|
this.logger.log(`Purged ${deletedReports.length} obsolete reports.`);
|
||||||
|
|
||||||
|
// 3. Purge des utilisateurs supprimés (Soft Delete > 30 jours)
|
||||||
|
const thirtyDaysAgo = new Date();
|
||||||
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||||
|
|
||||||
|
const deletedUsers = await this.databaseService.db
|
||||||
|
.delete(users)
|
||||||
|
.where(
|
||||||
|
and(eq(users.status, "deleted"), lte(users.deletedAt, thirtyDaysAgo)),
|
||||||
|
)
|
||||||
|
.returning();
|
||||||
|
this.logger.log(
|
||||||
|
`Purged ${deletedUsers.length} users marked for deletion more than 30 days ago.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Purge des contenus supprimés (Soft Delete > 30 jours)
|
||||||
|
const deletedContents = await this.databaseService.db
|
||||||
|
.delete(contents)
|
||||||
|
.where(
|
||||||
|
and(isNotNull(contents.deletedAt), lte(contents.deletedAt, thirtyDaysAgo)),
|
||||||
|
)
|
||||||
|
.returning();
|
||||||
|
this.logger.log(
|
||||||
|
`Purged ${deletedContents.length} contents marked for deletion more than 30 days ago.`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("Error during data purge", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user