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.
238 lines
5.4 KiB
TypeScript
238 lines
5.4 KiB
TypeScript
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<number>`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<string | null> {
|
|
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;
|
|
});
|
|
}
|
|
}
|