feat: add modular services and repositories for improved code organization

Introduce repository pattern across multiple services, including `favorites`, `tags`, `sessions`, `reports`, `auth`, and more. Decouple crypto functionalities into modular services like `HashingService`, `JwtService`, and `EncryptionService`. Improve testability and maintainability by simplifying dependencies and consolidating utility logic.
This commit is contained in:
Mathis HERRIOT
2026-01-14 12:11:39 +01:00
parent 9c45bf11e4
commit 514bd354bf
64 changed files with 1801 additions and 1295 deletions

View File

@@ -1,14 +1,16 @@
import { createHash, randomBytes } from "node:crypto";
import { randomBytes } from "node:crypto";
import { Injectable, Logger } from "@nestjs/common";
import { and, eq } from "drizzle-orm";
import { DatabaseService } from "../database/database.service";
import { apiKeys } from "../database/schemas";
import { HashingService } from "../crypto/services/hashing.service";
import { ApiKeysRepository } from "./repositories/api-keys.repository";
@Injectable()
export class ApiKeysService {
private readonly logger = new Logger(ApiKeysService.name);
constructor(private readonly databaseService: DatabaseService) {}
constructor(
private readonly apiKeysRepository: ApiKeysRepository,
private readonly hashingService: HashingService,
) {}
async create(userId: string, name: string, expiresAt?: Date) {
this.logger.log(`Creating API key for user ${userId}: ${name}`);
@@ -16,9 +18,9 @@ export class ApiKeysService {
const randomPart = randomBytes(24).toString("hex");
const key = `${prefix}${randomPart}`;
const keyHash = createHash("sha256").update(key).digest("hex");
const keyHash = await this.hashingService.hashSha256(key);
await this.databaseService.db.insert(apiKeys).values({
await this.apiKeysRepository.create({
userId,
name,
prefix: prefix.substring(0, 8),
@@ -34,37 +36,18 @@ export class ApiKeysService {
}
async findAll(userId: string) {
return await this.databaseService.db
.select({
id: apiKeys.id,
name: apiKeys.name,
prefix: apiKeys.prefix,
isActive: apiKeys.isActive,
lastUsedAt: apiKeys.lastUsedAt,
expiresAt: apiKeys.expiresAt,
createdAt: apiKeys.createdAt,
})
.from(apiKeys)
.where(eq(apiKeys.userId, userId));
return await this.apiKeysRepository.findAll(userId);
}
async revoke(userId: string, keyId: string) {
this.logger.log(`Revoking API key ${keyId} for user ${userId}`);
return await this.databaseService.db
.update(apiKeys)
.set({ isActive: false, updatedAt: new Date() })
.where(and(eq(apiKeys.id, keyId), eq(apiKeys.userId, userId)))
.returning();
return await this.apiKeysRepository.revoke(userId, keyId);
}
async validateKey(key: string) {
const keyHash = createHash("sha256").update(key).digest("hex");
const keyHash = await this.hashingService.hashSha256(key);
const [apiKey] = await this.databaseService.db
.select()
.from(apiKeys)
.where(and(eq(apiKeys.keyHash, keyHash), eq(apiKeys.isActive, true)))
.limit(1);
const apiKey = await this.apiKeysRepository.findActiveByKeyHash(keyHash);
if (!apiKey) return null;
@@ -73,10 +56,7 @@ export class ApiKeysService {
}
// Update last used at
await this.databaseService.db
.update(apiKeys)
.set({ lastUsedAt: new Date() })
.where(eq(apiKeys.id, apiKey.id));
await this.apiKeysRepository.updateLastUsed(apiKey.id);
return apiKey;
}