feat: add logging and caching enhancements across core services
Integrate `Logger` for consistent logging in services like `reports`, `categories`, `users`, `contents`, and more. Introduce caching capabilities with `CacheInterceptor` and manual cache clearing logic for categories, users, and contents. Add request throttling to critical auth endpoints for enhanced rate limiting.
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
import { createHash, randomBytes } from "node:crypto";
|
import { createHash, randomBytes } from "node:crypto";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { DatabaseService } from "../database/database.service";
|
import { DatabaseService } from "../database/database.service";
|
||||||
import { apiKeys } from "../database/schemas";
|
import { apiKeys } from "../database/schemas";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiKeysService {
|
export class ApiKeysService {
|
||||||
|
private readonly logger = new Logger(ApiKeysService.name);
|
||||||
|
|
||||||
constructor(private readonly databaseService: DatabaseService) {}
|
constructor(private readonly databaseService: DatabaseService) {}
|
||||||
|
|
||||||
async create(userId: string, name: string, expiresAt?: Date) {
|
async create(userId: string, name: string, expiresAt?: Date) {
|
||||||
|
this.logger.log(`Creating API key for user ${userId}: ${name}`);
|
||||||
const prefix = "mg_live_";
|
const prefix = "mg_live_";
|
||||||
const randomPart = randomBytes(24).toString("hex");
|
const randomPart = randomBytes(24).toString("hex");
|
||||||
const key = `${prefix}${randomPart}`;
|
const key = `${prefix}${randomPart}`;
|
||||||
@@ -46,6 +49,7 @@ export class ApiKeysService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async revoke(userId: string, keyId: string) {
|
async revoke(userId: string, keyId: string) {
|
||||||
|
this.logger.log(`Revoking API key ${keyId} for user ${userId}`);
|
||||||
return await this.databaseService.db
|
return await this.databaseService.db
|
||||||
.update(apiKeys)
|
.update(apiKeys)
|
||||||
.set({ isActive: false, updatedAt: new Date() })
|
.set({ isActive: false, updatedAt: new Date() })
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Body, Controller, Headers, Post, Req, Res } from "@nestjs/common";
|
import { Body, Controller, Headers, Post, Req, Res } from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { Throttle } from "@nestjs/throttler";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { getIronSession } from "iron-session";
|
import { getIronSession } from "iron-session";
|
||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
@@ -16,11 +17,13 @@ export class AuthController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post("register")
|
@Post("register")
|
||||||
|
@Throttle({ default: { limit: 5, ttl: 60000 } })
|
||||||
register(@Body() registerDto: RegisterDto) {
|
register(@Body() registerDto: RegisterDto) {
|
||||||
return this.authService.register(registerDto);
|
return this.authService.register(registerDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("login")
|
@Post("login")
|
||||||
|
@Throttle({ default: { limit: 5, ttl: 60000 } })
|
||||||
async login(
|
async login(
|
||||||
@Body() loginDto: LoginDto,
|
@Body() loginDto: LoginDto,
|
||||||
@Headers("user-agent") userAgent: string,
|
@Headers("user-agent") userAgent: string,
|
||||||
@@ -52,6 +55,7 @@ export class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post("verify-2fa")
|
@Post("verify-2fa")
|
||||||
|
@Throttle({ default: { limit: 5, ttl: 60000 } })
|
||||||
async verifyTwoFactor(
|
async verifyTwoFactor(
|
||||||
@Body() verify2faDto: Verify2faDto,
|
@Body() verify2faDto: Verify2faDto,
|
||||||
@Headers("user-agent") userAgent: string,
|
@Headers("user-agent") userAgent: string,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Injectable,
|
Injectable,
|
||||||
|
Logger,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
@@ -14,6 +15,8 @@ import { RegisterDto } from "./dto/register.dto";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
|
private readonly logger = new Logger(AuthService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
private readonly cryptoService: CryptoService,
|
private readonly cryptoService: CryptoService,
|
||||||
@@ -22,6 +25,7 @@ export class AuthService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async generateTwoFactorSecret(userId: string) {
|
async generateTwoFactorSecret(userId: string) {
|
||||||
|
this.logger.log(`Generating 2FA secret for user ${userId}`);
|
||||||
const user = await this.usersService.findOne(userId);
|
const user = await this.usersService.findOne(userId);
|
||||||
if (!user) throw new UnauthorizedException();
|
if (!user) throw new UnauthorizedException();
|
||||||
|
|
||||||
@@ -42,6 +46,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async enableTwoFactor(userId: string, token: string) {
|
async enableTwoFactor(userId: string, token: string) {
|
||||||
|
this.logger.log(`Enabling 2FA for user ${userId}`);
|
||||||
const secret = await this.usersService.getTwoFactorSecret(userId);
|
const secret = await this.usersService.getTwoFactorSecret(userId);
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
throw new BadRequestException("2FA not initiated");
|
throw new BadRequestException("2FA not initiated");
|
||||||
@@ -57,6 +62,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async disableTwoFactor(userId: string, token: string) {
|
async disableTwoFactor(userId: string, token: string) {
|
||||||
|
this.logger.log(`Disabling 2FA for user ${userId}`);
|
||||||
const secret = await this.usersService.getTwoFactorSecret(userId);
|
const secret = await this.usersService.getTwoFactorSecret(userId);
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
throw new BadRequestException("2FA not enabled");
|
throw new BadRequestException("2FA not enabled");
|
||||||
@@ -72,6 +78,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async register(dto: RegisterDto) {
|
async register(dto: RegisterDto) {
|
||||||
|
this.logger.log(`Registering new user: ${dto.username}`);
|
||||||
const { username, email, password } = dto;
|
const { username, email, password } = dto;
|
||||||
|
|
||||||
const passwordHash = await this.cryptoService.hashPassword(password);
|
const passwordHash = await this.cryptoService.hashPassword(password);
|
||||||
@@ -91,6 +98,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async login(dto: LoginDto, userAgent?: string, ip?: string) {
|
async login(dto: LoginDto, userAgent?: string, ip?: string) {
|
||||||
|
this.logger.log(`Login attempt for email: ${dto.email}`);
|
||||||
const { email, password } = dto;
|
const { email, password } = dto;
|
||||||
|
|
||||||
const emailHash = await this.cryptoService.hashEmail(email);
|
const emailHash = await this.cryptoService.hashEmail(email);
|
||||||
@@ -141,6 +149,7 @@ export class AuthService {
|
|||||||
userAgent?: string,
|
userAgent?: string,
|
||||||
ip?: string,
|
ip?: string,
|
||||||
) {
|
) {
|
||||||
|
this.logger.log(`2FA verification attempt for user ${userId}`);
|
||||||
const user = await this.usersService.findOneWithPrivateData(userId);
|
const user = await this.usersService.findOneWithPrivateData(userId);
|
||||||
if (!user || !user.isTwoFactorEnabled) {
|
if (!user || !user.isTwoFactorEnabled) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Patch,
|
Patch,
|
||||||
Post,
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
UseInterceptors,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
import { CacheInterceptor, CacheKey, CacheTTL } from "@nestjs/cache-manager";
|
||||||
|
import { Roles } from "../auth/decorators/roles.decorator";
|
||||||
|
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||||
|
import { RolesGuard } from "../auth/guards/roles.guard";
|
||||||
import { CategoriesService } from "./categories.service";
|
import { CategoriesService } from "./categories.service";
|
||||||
import { CreateCategoryDto } from "./dto/create-category.dto";
|
import { CreateCategoryDto } from "./dto/create-category.dto";
|
||||||
import { UpdateCategoryDto } from "./dto/update-category.dto";
|
import { UpdateCategoryDto } from "./dto/update-category.dto";
|
||||||
@@ -16,6 +22,9 @@ export class CategoriesController {
|
|||||||
constructor(private readonly categoriesService: CategoriesService) {}
|
constructor(private readonly categoriesService: CategoriesService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@UseInterceptors(CacheInterceptor)
|
||||||
|
@CacheKey("categories/all")
|
||||||
|
@CacheTTL(3600000) // 1 heure
|
||||||
findAll() {
|
findAll() {
|
||||||
return this.categoriesService.findAll();
|
return this.categoriesService.findAll();
|
||||||
}
|
}
|
||||||
@@ -25,18 +34,23 @@ export class CategoriesController {
|
|||||||
return this.categoriesService.findOne(id);
|
return this.categoriesService.findOne(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ces routes devraient être protégées par un AdminGuard
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@UseGuards(AuthGuard, RolesGuard)
|
||||||
|
@Roles("admin")
|
||||||
create(@Body() createCategoryDto: CreateCategoryDto) {
|
create(@Body() createCategoryDto: CreateCategoryDto) {
|
||||||
return this.categoriesService.create(createCategoryDto);
|
return this.categoriesService.create(createCategoryDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(":id")
|
@Patch(":id")
|
||||||
|
@UseGuards(AuthGuard, RolesGuard)
|
||||||
|
@Roles("admin")
|
||||||
update(@Param("id") id: string, @Body() updateCategoryDto: UpdateCategoryDto) {
|
update(@Param("id") id: string, @Body() updateCategoryDto: UpdateCategoryDto) {
|
||||||
return this.categoriesService.update(id, updateCategoryDto);
|
return this.categoriesService.update(id, updateCategoryDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(":id")
|
@Delete(":id")
|
||||||
|
@UseGuards(AuthGuard, RolesGuard)
|
||||||
|
@Roles("admin")
|
||||||
remove(@Param("id") id: string) {
|
remove(@Param("id") id: string) {
|
||||||
return this.categoriesService.remove(id);
|
return this.categoriesService.remove(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable, Logger, Inject } from "@nestjs/common";
|
||||||
|
import { CACHE_MANAGER } from "@nestjs/cache-manager";
|
||||||
|
import { Cache } from "cache-manager";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { DatabaseService } from "../database/database.service";
|
import { DatabaseService } from "../database/database.service";
|
||||||
import { categories } from "../database/schemas";
|
import { categories } from "../database/schemas";
|
||||||
@@ -7,7 +9,17 @@ import { UpdateCategoryDto } from "./dto/update-category.dto";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CategoriesService {
|
export class CategoriesService {
|
||||||
constructor(private readonly databaseService: DatabaseService) {}
|
private readonly logger = new Logger(CategoriesService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly databaseService: DatabaseService,
|
||||||
|
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private async clearCategoriesCache() {
|
||||||
|
this.logger.log("Clearing categories cache");
|
||||||
|
await this.cacheManager.del("categories/all");
|
||||||
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
return await this.databaseService.db
|
return await this.databaseService.db
|
||||||
@@ -27,17 +39,22 @@ export class CategoriesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateCategoryDto) {
|
async create(data: CreateCategoryDto) {
|
||||||
|
this.logger.log(`Creating category: ${data.name}`);
|
||||||
const slug = data.name
|
const slug = data.name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/ /g, "-")
|
.replace(/ /g, "-")
|
||||||
.replace(/[^\w-]/g, "");
|
.replace(/[^\w-]/g, "");
|
||||||
return await this.databaseService.db
|
const result = await this.databaseService.db
|
||||||
.insert(categories)
|
.insert(categories)
|
||||||
.values({ ...data, slug })
|
.values({ ...data, slug })
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
await this.clearCategoriesCache();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, data: UpdateCategoryDto) {
|
async update(id: string, data: UpdateCategoryDto) {
|
||||||
|
this.logger.log(`Updating category: ${id}`);
|
||||||
const updateData = {
|
const updateData = {
|
||||||
...data,
|
...data,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
@@ -48,17 +65,24 @@ export class CategoriesService {
|
|||||||
.replace(/[^\w-]/g, "")
|
.replace(/[^\w-]/g, "")
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
return await this.databaseService.db
|
const result = await this.databaseService.db
|
||||||
.update(categories)
|
.update(categories)
|
||||||
.set(updateData)
|
.set(updateData)
|
||||||
.where(eq(categories.id, id))
|
.where(eq(categories.id, id))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
await this.clearCategoriesCache();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string) {
|
async remove(id: string) {
|
||||||
return await this.databaseService.db
|
this.logger.log(`Removing category: ${id}`);
|
||||||
|
const result = await this.databaseService.db
|
||||||
.delete(categories)
|
.delete(categories)
|
||||||
.where(eq(categories.id, id))
|
.where(eq(categories.id, id))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
await this.clearCategoriesCache();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { CACHE_MANAGER } from "@nestjs/cache-manager";
|
||||||
|
import { Cache } from "cache-manager";
|
||||||
|
import { Inject } from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import {
|
import {
|
||||||
and,
|
and,
|
||||||
@@ -27,13 +30,25 @@ import { CreateContentDto } from "./dto/create-content.dto";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ContentsService {
|
export class ContentsService {
|
||||||
|
private readonly logger = new Logger(ContentsService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly databaseService: DatabaseService,
|
private readonly databaseService: DatabaseService,
|
||||||
private readonly s3Service: S3Service,
|
private readonly s3Service: S3Service,
|
||||||
private readonly mediaService: MediaService,
|
private readonly mediaService: MediaService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
|
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
private async clearContentsCache() {
|
||||||
|
this.logger.log("Clearing contents cache");
|
||||||
|
const keys = await this.cacheManager.store.keys();
|
||||||
|
const contentsKeys = keys.filter((key) => key.startsWith("contents/"));
|
||||||
|
for (const key of contentsKeys) {
|
||||||
|
await this.cacheManager.del(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getUploadUrl(userId: string, fileName: string) {
|
async getUploadUrl(userId: string, fileName: string) {
|
||||||
const key = `uploads/${userId}/${Date.now()}-${fileName}`;
|
const key = `uploads/${userId}/${Date.now()}-${fileName}`;
|
||||||
const url = await this.s3Service.getUploadUrl(key);
|
const url = await this.s3Service.getUploadUrl(key);
|
||||||
@@ -50,6 +65,7 @@ export class ContentsService {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
this.logger.log(`Uploading and processing file for user ${userId}`);
|
||||||
// 0. Validation du format et de la taille
|
// 0. Validation du format et de la taille
|
||||||
const allowedMimeTypes = [
|
const allowedMimeTypes = [
|
||||||
"image/png",
|
"image/png",
|
||||||
@@ -225,6 +241,7 @@ export class ContentsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async create(userId: string, data: CreateContentDto) {
|
async create(userId: string, data: CreateContentDto) {
|
||||||
|
this.logger.log(`Creating content for user ${userId}: ${data.title}`);
|
||||||
const { tags: tagNames, ...contentData } = data;
|
const { tags: tagNames, ...contentData } = data;
|
||||||
|
|
||||||
const slug = await this.ensureUniqueSlug(contentData.title);
|
const slug = await this.ensureUniqueSlug(contentData.title);
|
||||||
@@ -264,6 +281,7 @@ export class ContentsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.clearContentsCache();
|
||||||
return newContent;
|
return newContent;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -285,11 +303,17 @@ export class ContentsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string, userId: string) {
|
async remove(id: string, userId: string) {
|
||||||
return await this.databaseService.db
|
this.logger.log(`Removing content ${id} for user ${userId}`);
|
||||||
|
const result = await this.databaseService.db
|
||||||
.update(contents)
|
.update(contents)
|
||||||
.set({ deletedAt: new Date() })
|
.set({ deletedAt: new Date() })
|
||||||
.where(and(eq(contents.id, id), eq(contents.userId, userId)))
|
.where(and(eq(contents.id, id), eq(contents.userId, userId)))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
await this.clearContentsCache();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(idOrSlug: string) {
|
async findOne(idOrSlug: string) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ConflictException,
|
ConflictException,
|
||||||
Injectable,
|
Injectable,
|
||||||
|
Logger,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
@@ -9,9 +10,12 @@ import { contents, favorites } from "../database/schemas";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FavoritesService {
|
export class FavoritesService {
|
||||||
|
private readonly logger = new Logger(FavoritesService.name);
|
||||||
|
|
||||||
constructor(private readonly databaseService: DatabaseService) {}
|
constructor(private readonly databaseService: DatabaseService) {}
|
||||||
|
|
||||||
async addFavorite(userId: string, contentId: string) {
|
async addFavorite(userId: string, contentId: string) {
|
||||||
|
this.logger.log(`Adding favorite: user ${userId}, content ${contentId}`);
|
||||||
// Vérifier si le contenu existe
|
// Vérifier si le contenu existe
|
||||||
const content = await this.databaseService.db
|
const content = await this.databaseService.db
|
||||||
.select()
|
.select()
|
||||||
@@ -35,6 +39,7 @@ export class FavoritesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeFavorite(userId: string, contentId: string) {
|
async removeFavorite(userId: string, contentId: string) {
|
||||||
|
this.logger.log(`Removing favorite: user ${userId}, content ${contentId}`);
|
||||||
const result = await this.databaseService.db
|
const result = await this.databaseService.db
|
||||||
.delete(favorites)
|
.delete(favorites)
|
||||||
.where(and(eq(favorites.userId, userId), eq(favorites.contentId, contentId)))
|
.where(and(eq(favorites.userId, userId), eq(favorites.contentId, contentId)))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { desc, eq } from "drizzle-orm";
|
import { desc, eq } from "drizzle-orm";
|
||||||
import { DatabaseService } from "../database/database.service";
|
import { DatabaseService } from "../database/database.service";
|
||||||
import { reports } from "../database/schemas";
|
import { reports } from "../database/schemas";
|
||||||
@@ -6,9 +6,12 @@ import { CreateReportDto } from "./dto/create-report.dto";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReportsService {
|
export class ReportsService {
|
||||||
|
private readonly logger = new Logger(ReportsService.name);
|
||||||
|
|
||||||
constructor(private readonly databaseService: DatabaseService) {}
|
constructor(private readonly databaseService: DatabaseService) {}
|
||||||
|
|
||||||
async create(reporterId: string, data: CreateReportDto) {
|
async create(reporterId: string, data: CreateReportDto) {
|
||||||
|
this.logger.log(`Creating report from user ${reporterId}`);
|
||||||
const [newReport] = await this.databaseService.db
|
const [newReport] = await this.databaseService.db
|
||||||
.insert(reports)
|
.insert(reports)
|
||||||
.values({
|
.values({
|
||||||
@@ -35,6 +38,7 @@ export class ReportsService {
|
|||||||
id: string,
|
id: string,
|
||||||
status: "pending" | "reviewed" | "resolved" | "dismissed",
|
status: "pending" | "reviewed" | "resolved" | "dismissed",
|
||||||
) {
|
) {
|
||||||
|
this.logger.log(`Updating report ${id} status to ${status}`);
|
||||||
return await this.databaseService.db
|
return await this.databaseService.db
|
||||||
.update(reports)
|
.update(reports)
|
||||||
.set({ status, updatedAt: new Date() })
|
.set({ status, updatedAt: new Date() })
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
Query,
|
Query,
|
||||||
|
UseInterceptors,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
import { CacheInterceptor, CacheTTL } from "@nestjs/cache-manager";
|
||||||
import { TagsService } from "./tags.service";
|
import { TagsService } from "./tags.service";
|
||||||
|
|
||||||
@Controller("tags")
|
@Controller("tags")
|
||||||
@@ -12,6 +14,8 @@ export class TagsController {
|
|||||||
constructor(private readonly tagsService: TagsService) {}
|
constructor(private readonly tagsService: TagsService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@UseInterceptors(CacheInterceptor)
|
||||||
|
@CacheTTL(300000) // 5 minutes
|
||||||
findAll(
|
findAll(
|
||||||
@Query("limit", new DefaultValuePipe(10), ParseIntPipe) limit: number,
|
@Query("limit", new DefaultValuePipe(10), ParseIntPipe) limit: number,
|
||||||
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { desc, eq, ilike, sql } from "drizzle-orm";
|
import { desc, eq, ilike, sql } from "drizzle-orm";
|
||||||
import { DatabaseService } from "../database/database.service";
|
import { DatabaseService } from "../database/database.service";
|
||||||
import { contentsToTags, tags } from "../database/schemas";
|
import { contentsToTags, tags } from "../database/schemas";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TagsService {
|
export class TagsService {
|
||||||
|
private readonly logger = new Logger(TagsService.name);
|
||||||
|
|
||||||
constructor(private readonly databaseService: DatabaseService) {}
|
constructor(private readonly databaseService: DatabaseService) {}
|
||||||
|
|
||||||
async findAll(options: {
|
async findAll(options: {
|
||||||
@@ -13,6 +15,7 @@ export class TagsService {
|
|||||||
query?: string;
|
query?: string;
|
||||||
sortBy?: "popular" | "recent";
|
sortBy?: "popular" | "recent";
|
||||||
}) {
|
}) {
|
||||||
|
this.logger.log(`Fetching tags with options: ${JSON.stringify(options)}`);
|
||||||
const { limit, offset, query, sortBy } = options;
|
const { limit, offset, query, sortBy } = options;
|
||||||
|
|
||||||
let whereClause = sql`1=1`;
|
let whereClause = sql`1=1`;
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
Req,
|
Req,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
|
UseInterceptors,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
import { CacheInterceptor, CacheKey, CacheTTL } from "@nestjs/cache-manager";
|
||||||
import { AuthService } from "../auth/auth.service";
|
import { AuthService } from "../auth/auth.service";
|
||||||
import { Roles } from "../auth/decorators/roles.decorator";
|
import { Roles } from "../auth/decorators/roles.decorator";
|
||||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||||
@@ -41,6 +43,8 @@ export class UsersController {
|
|||||||
|
|
||||||
// Listing public d'un profil
|
// Listing public d'un profil
|
||||||
@Get("public/:username")
|
@Get("public/:username")
|
||||||
|
@UseInterceptors(CacheInterceptor)
|
||||||
|
@CacheTTL(60000) // 1 minute
|
||||||
findPublicProfile(@Param("username") username: string) {
|
findPublicProfile(@Param("username") username: string) {
|
||||||
return this.usersService.findPublicProfile(username);
|
return this.usersService.findPublicProfile(username);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable, Logger, Inject } from "@nestjs/common";
|
||||||
|
import { CACHE_MANAGER } from "@nestjs/cache-manager";
|
||||||
|
import { Cache } from "cache-manager";
|
||||||
import { eq, sql } from "drizzle-orm";
|
import { eq, sql } from "drizzle-orm";
|
||||||
import { CryptoService } from "../crypto/crypto.service";
|
import { CryptoService } from "../crypto/crypto.service";
|
||||||
import { DatabaseService } from "../database/database.service";
|
import { DatabaseService } from "../database/database.service";
|
||||||
@@ -11,11 +13,20 @@ import { UpdateUserDto } from "./dto/update-user.dto";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
|
private readonly logger = new Logger(UsersService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly databaseService: DatabaseService,
|
private readonly databaseService: DatabaseService,
|
||||||
private readonly cryptoService: CryptoService,
|
private readonly cryptoService: CryptoService,
|
||||||
|
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
private async clearUserCache(username?: string) {
|
||||||
|
if (username) {
|
||||||
|
await this.cacheManager.del(`users/profile/${username}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async create(data: {
|
async create(data: {
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
@@ -119,11 +130,17 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(uuid: string, data: UpdateUserDto) {
|
async update(uuid: string, data: UpdateUserDto) {
|
||||||
return await this.databaseService.db
|
this.logger.log(`Updating user profile for ${uuid}`);
|
||||||
|
const result = await this.databaseService.db
|
||||||
.update(users)
|
.update(users)
|
||||||
.set({ ...data, updatedAt: new Date() })
|
.set({ ...data, updatedAt: new Date() })
|
||||||
.where(eq(users.uuid, uuid))
|
.where(eq(users.uuid, uuid))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (result[0]) {
|
||||||
|
await this.clearUserCache(result[0].username);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateConsent(
|
async updateConsent(
|
||||||
|
|||||||
Reference in New Issue
Block a user