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 { CryptoService } from "../crypto/crypto.service"; import { DatabaseService } from "../database/database.service"; import { contents, favorites, users, } from "../database/schemas"; import { UpdateUserDto } from "./dto/update-user.dto"; @Injectable() export class UsersService { private readonly logger = new Logger(UsersService.name); constructor( private readonly databaseService: DatabaseService, 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: { username: string; email: string; passwordHash: string; emailHash: string; }) { const [newUser] = await this.databaseService.db .insert(users) .values({ username: data.username, email: data.email, emailHash: data.emailHash, passwordHash: data.passwordHash, }) .returning(); return newUser; } async findByEmailHash(emailHash: string) { const result = await this.databaseService.db .select({ uuid: users.uuid, username: users.username, email: users.email, passwordHash: users.passwordHash, status: users.status, isTwoFactorEnabled: users.isTwoFactorEnabled, }) .from(users) .where(eq(users.emailHash, emailHash)) .limit(1); return result[0] || null; } async findOneWithPrivateData(uuid: string) { const result = await this.databaseService.db .select({ uuid: users.uuid, username: users.username, email: users.email, displayName: users.displayName, status: users.status, isTwoFactorEnabled: users.isTwoFactorEnabled, createdAt: users.createdAt, updatedAt: users.updatedAt, }) .from(users) .where(eq(users.uuid, uuid)) .limit(1); return result[0] || null; } async findAll(limit: number, offset: number) { const totalCountResult = await this.databaseService.db .select({ count: sql`count(*)` }) .from(users); const totalCount = Number(totalCountResult[0].count); const data = await this.databaseService.db .select({ uuid: users.uuid, username: users.username, displayName: users.displayName, status: users.status, createdAt: users.createdAt, }) .from(users) .limit(limit) .offset(offset); return { data, totalCount }; } async findPublicProfile(username: string) { const result = await this.databaseService.db .select({ uuid: users.uuid, username: users.username, displayName: users.displayName, createdAt: users.createdAt, }) .from(users) .where(eq(users.username, username)) .limit(1); return result[0] || null; } async findOne(uuid: string) { const result = await this.databaseService.db .select() .from(users) .where(eq(users.uuid, uuid)) .limit(1); return result[0] || null; } async update(uuid: string, data: UpdateUserDto) { this.logger.log(`Updating user profile for ${uuid}`); const result = await this.databaseService.db .update(users) .set({ ...data, updatedAt: new Date() }) .where(eq(users.uuid, uuid)) .returning(); if (result[0]) { await this.clearUserCache(result[0].username); } return result; } async updateConsent( uuid: string, termsVersion: string, privacyVersion: string, ) { return await this.databaseService.db .update(users) .set({ termsVersion, privacyVersion, gdprAcceptedAt: new Date(), updatedAt: new Date(), }) .where(eq(users.uuid, uuid)) .returning(); } async setTwoFactorSecret(uuid: string, secret: string) { return await this.databaseService.db .update(users) .set({ twoFactorSecret: secret, updatedAt: new Date(), }) .where(eq(users.uuid, uuid)) .returning(); } async toggleTwoFactor(uuid: string, enabled: boolean) { return await this.databaseService.db .update(users) .set({ isTwoFactorEnabled: enabled, updatedAt: new Date(), }) .where(eq(users.uuid, uuid)) .returning(); } async getTwoFactorSecret(uuid: string): Promise { const result = await this.databaseService.db .select({ secret: users.twoFactorSecret, }) .from(users) .where(eq(users.uuid, uuid)) .limit(1); return result[0]?.secret || null; } async exportUserData(uuid: string) { const user = await this.findOneWithPrivateData(uuid); if (!user) return null; const userContents = await this.databaseService.db .select() .from(contents) .where(eq(contents.userId, uuid)); const userFavorites = await this.databaseService.db .select() .from(favorites) .where(eq(favorites.userId, uuid)); return { profile: user, contents: userContents, favorites: userFavorites, exportedAt: new Date(), }; } async remove(uuid: string) { return await this.databaseService.db.transaction(async (tx) => { // Soft delete de l'utilisateur const userResult = await tx .update(users) .set({ status: "deleted", deletedAt: new Date() }) .where(eq(users.uuid, uuid)) .returning(); // Soft delete de tous ses contenus await tx .update(contents) .set({ deletedAt: new Date() }) .where(eq(contents.userId, uuid)); return userResult; }); } }