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:
Mathis HERRIOT
2026-01-08 15:25:04 +01:00
parent 705f1ad6e0
commit 2218768adb
4 changed files with 138 additions and 0 deletions

View 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 {}

View 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);
}
}

View File

@@ -0,0 +1,8 @@
import { Request } from "express";
export interface AuthenticatedRequest extends Request {
user: {
sub: string;
username: string;
};
}

View 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);
}
}
}