diff --git a/backend/.migrations/meta/_journal.json b/backend/.migrations/meta/_journal.json index c5d94fe..309ca08 100644 --- a/backend/.migrations/meta/_journal.json +++ b/backend/.migrations/meta/_journal.json @@ -1,20 +1,27 @@ { - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1767618753676, - "tag": "0000_right_sally_floyd", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1768392191169, - "tag": "0001_purple_goliath", - "breakpoints": true - } - ] -} \ No newline at end of file + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1767618753676, + "tag": "0000_right_sally_floyd", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1768392191169, + "tag": "0001_purple_goliath", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1768393637823, + "tag": "0002_redundant_skin", + "breakpoints": true + } + ] +} diff --git a/backend/Dockerfile b/backend/Dockerfile index b0fd3a0..bf25c92 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,7 +1,6 @@ -FROM node:22-slim AS base +FROM pnpm/pnpm:22 AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable FROM base AS build WORKDIR /usr/src/app diff --git a/backend/src/auth/auth.module.ts b/backend/src/auth/auth.module.ts index 32b235c..5d44d45 100644 --- a/backend/src/auth/auth.module.ts +++ b/backend/src/auth/auth.module.ts @@ -1,4 +1,4 @@ -import { Module } from "@nestjs/common"; +import { forwardRef, Module } from "@nestjs/common"; import { CryptoModule } from "../crypto/crypto.module"; import { DatabaseModule } from "../database/database.module"; import { SessionsModule } from "../sessions/sessions.module"; @@ -9,7 +9,12 @@ import { RbacService } from "./rbac.service"; import { RbacRepository } from "./repositories/rbac.repository"; @Module({ - imports: [UsersModule, CryptoModule, SessionsModule, DatabaseModule], + imports: [ + forwardRef(() => UsersModule), + CryptoModule, + SessionsModule, + DatabaseModule, + ], controllers: [AuthController], providers: [AuthService, RbacService, RbacRepository], exports: [AuthService, RbacService], diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index 8e293c4..2133afb 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -1,5 +1,7 @@ import { BadRequestException, + forwardRef, + Inject, Injectable, Logger, UnauthorizedException, @@ -19,6 +21,7 @@ export class AuthService { private readonly logger = new Logger(AuthService.name); constructor( + @Inject(forwardRef(() => UsersService)) private readonly usersService: UsersService, private readonly hashingService: HashingService, private readonly jwtService: JwtService, diff --git a/backend/src/auth/rbac.service.spec.ts b/backend/src/auth/rbac.service.spec.ts index e638acc..a9d767b 100644 --- a/backend/src/auth/rbac.service.spec.ts +++ b/backend/src/auth/rbac.service.spec.ts @@ -48,7 +48,9 @@ describe("RbacService", () => { it("should return user permissions", async () => { const userId = "user-id"; const mockPermissions = ["read", "write"]; - mockRbacRepository.findPermissionsByUserId.mockResolvedValue(mockPermissions); + mockRbacRepository.findPermissionsByUserId.mockResolvedValue( + mockPermissions, + ); const result = await service.getUserPermissions(userId); diff --git a/backend/src/categories/categories.controller.ts b/backend/src/categories/categories.controller.ts index 5cad8c3..dfc2e74 100644 --- a/backend/src/categories/categories.controller.ts +++ b/backend/src/categories/categories.controller.ts @@ -1,3 +1,4 @@ +import { CacheInterceptor, CacheKey, CacheTTL } from "@nestjs/cache-manager"; import { Body, Controller, @@ -9,7 +10,6 @@ import { UseGuards, UseInterceptors, } 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"; diff --git a/backend/src/categories/categories.service.spec.ts b/backend/src/categories/categories.service.spec.ts index f0f43ab..a8cb747 100644 --- a/backend/src/categories/categories.service.spec.ts +++ b/backend/src/categories/categories.service.spec.ts @@ -1,9 +1,9 @@ -import { Test, TestingModule } from "@nestjs/testing"; import { CACHE_MANAGER } from "@nestjs/cache-manager"; +import { Test, TestingModule } from "@nestjs/testing"; import { CategoriesService } from "./categories.service"; -import { CategoriesRepository } from "./repositories/categories.repository"; import { CreateCategoryDto } from "./dto/create-category.dto"; import { UpdateCategoryDto } from "./dto/update-category.dto"; +import { CategoriesRepository } from "./repositories/categories.repository"; describe("CategoriesService", () => { let service: CategoriesService; @@ -75,7 +75,9 @@ describe("CategoriesService", () => { describe("create", () => { it("should create a category and generate slug", async () => { const dto: CreateCategoryDto = { name: "Test Category" }; - mockCategoriesRepository.create.mockResolvedValue([{ ...dto, slug: "test-category" }]); + mockCategoriesRepository.create.mockResolvedValue([ + { ...dto, slug: "test-category" }, + ]); const result = await service.create(dto); @@ -91,7 +93,9 @@ describe("CategoriesService", () => { it("should update a category and regenerate slug", async () => { const id = "1"; const dto: UpdateCategoryDto = { name: "New Name" }; - mockCategoriesRepository.update.mockResolvedValue([{ id, ...dto, slug: "new-name" }]); + mockCategoriesRepository.update.mockResolvedValue([ + { id, ...dto, slug: "new-name" }, + ]); const result = await service.update(id, dto); diff --git a/backend/src/categories/categories.service.ts b/backend/src/categories/categories.service.ts index 5a41bea..ddfbba0 100644 --- a/backend/src/categories/categories.service.ts +++ b/backend/src/categories/categories.service.ts @@ -1,9 +1,9 @@ -import { Injectable, Logger, Inject } from "@nestjs/common"; import { CACHE_MANAGER } from "@nestjs/cache-manager"; -import { Cache } from "cache-manager"; -import { CategoriesRepository } from "./repositories/categories.repository"; +import { Inject, Injectable, Logger } from "@nestjs/common"; +import type { Cache } from "cache-manager"; import { CreateCategoryDto } from "./dto/create-category.dto"; import { UpdateCategoryDto } from "./dto/update-category.dto"; +import { CategoriesRepository } from "./repositories/categories.repository"; @Injectable() export class CategoriesService { diff --git a/backend/src/categories/repositories/categories.repository.ts b/backend/src/categories/repositories/categories.repository.ts index 3ef00c6..a15b7c8 100644 --- a/backend/src/categories/repositories/categories.repository.ts +++ b/backend/src/categories/repositories/categories.repository.ts @@ -33,7 +33,10 @@ export class CategoriesRepository { .returning(); } - async update(id: string, data: UpdateCategoryDto & { slug?: string; updatedAt: Date }) { + async update( + id: string, + data: UpdateCategoryDto & { slug?: string; updatedAt: Date }, + ) { return await this.databaseService.db .update(categories) .set(data) diff --git a/backend/src/common/interfaces/storage.interface.ts b/backend/src/common/interfaces/storage.interface.ts index c65062a..f0d7f0a 100644 --- a/backend/src/common/interfaces/storage.interface.ts +++ b/backend/src/common/interfaces/storage.interface.ts @@ -9,10 +9,7 @@ export interface IStorageService { bucketName?: string, ): Promise; - getFile( - fileName: string, - bucketName?: string, - ): Promise; + getFile(fileName: string, bucketName?: string): Promise; getFileUrl( fileName: string, @@ -28,7 +25,7 @@ export interface IStorageService { deleteFile(fileName: string, bucketName?: string): Promise; - getFileInfo(fileName: string, bucketName?: string): Promise; + getFileInfo(fileName: string, bucketName?: string): Promise; moveFile( sourceFileName: string, diff --git a/backend/src/common/services/purge.service.spec.ts b/backend/src/common/services/purge.service.spec.ts index 4768400..5a5d320 100644 --- a/backend/src/common/services/purge.service.spec.ts +++ b/backend/src/common/services/purge.service.spec.ts @@ -9,10 +9,16 @@ import { PurgeService } from "./purge.service"; describe("PurgeService", () => { let service: PurgeService; - const mockSessionsRepository = { purgeExpired: jest.fn().mockResolvedValue([]) }; - const mockReportsRepository = { purgeObsolete: jest.fn().mockResolvedValue([]) }; + const mockSessionsRepository = { + purgeExpired: jest.fn().mockResolvedValue([]), + }; + const mockReportsRepository = { + purgeObsolete: jest.fn().mockResolvedValue([]), + }; const mockUsersRepository = { purgeDeleted: jest.fn().mockResolvedValue([]) }; - const mockContentsRepository = { purgeSoftDeleted: jest.fn().mockResolvedValue([]) }; + const mockContentsRepository = { + purgeSoftDeleted: jest.fn().mockResolvedValue([]), + }; beforeEach(async () => { jest.clearAllMocks(); diff --git a/backend/src/common/services/purge.service.ts b/backend/src/common/services/purge.service.ts index 106fd3d..0ddfad6 100644 --- a/backend/src/common/services/purge.service.ts +++ b/backend/src/common/services/purge.service.ts @@ -42,9 +42,8 @@ export class PurgeService { ); // 4. Purge des contenus supprimés (Soft Delete > 30 jours) - const deletedContents = await this.contentsRepository.purgeSoftDeleted( - thirtyDaysAgo, - ); + const deletedContents = + await this.contentsRepository.purgeSoftDeleted(thirtyDaysAgo); this.logger.log( `Purged ${deletedContents.length} contents marked for deletion more than 30 days ago.`, ); diff --git a/backend/src/contents/contents.service.spec.ts b/backend/src/contents/contents.service.spec.ts index 2f4fff8..cc6059f 100644 --- a/backend/src/contents/contents.service.spec.ts +++ b/backend/src/contents/contents.service.spec.ts @@ -2,11 +2,10 @@ jest.mock("uuid", () => ({ v4: jest.fn(() => "mocked-uuid"), })); +import { CACHE_MANAGER } from "@nestjs/cache-manager"; import { BadRequestException } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; -import { CACHE_MANAGER } from "@nestjs/cache-manager"; import { Test, TestingModule } from "@nestjs/testing"; -import { DatabaseService } from "../database/database.service"; import { MediaService } from "../media/media.service"; import { S3Service } from "../s3/s3.service"; import { ContentsService } from "./contents.service"; @@ -44,9 +43,7 @@ describe("ContentsService", () => { }; const mockCacheManager = { - store: { - keys: jest.fn().mockResolvedValue([]), - }, + clear: jest.fn(), del: jest.fn(), }; @@ -141,7 +138,9 @@ describe("ContentsService", () => { describe("incrementViews", () => { it("should increment views", async () => { - mockContentsRepository.incrementViews.mockResolvedValue([{ id: "1", views: 1 }]); + mockContentsRepository.incrementViews.mockResolvedValue([ + { id: "1", views: 1 }, + ]); const result = await service.incrementViews("1"); expect(mockContentsRepository.incrementViews).toHaveBeenCalledWith("1"); expect(result[0].views).toBe(1); diff --git a/backend/src/contents/contents.service.ts b/backend/src/contents/contents.service.ts index 695384a..c0f58ec 100644 --- a/backend/src/contents/contents.service.ts +++ b/backend/src/contents/contents.service.ts @@ -1,13 +1,22 @@ -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 { + BadRequestException, + Inject, + Injectable, + Logger, +} from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; +import type { Cache } from "cache-manager"; import { v4 as uuidv4 } from "uuid"; -import type { IMediaService } from "../common/interfaces/media.interface"; +import type { + IMediaService, + MediaProcessingResult, +} from "../common/interfaces/media.interface"; import type { IStorageService } from "../common/interfaces/storage.interface"; import { MediaService } from "../media/media.service"; import { S3Service } from "../s3/s3.service"; +import { CreateContentDto } from "./dto/create-content.dto"; +import { UploadContentDto } from "./dto/upload-content.dto"; import { ContentsRepository } from "./repositories/contents.repository"; @Injectable() @@ -24,11 +33,7 @@ export class ContentsService { 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); - } + await this.cacheManager.clear(); } async getUploadUrl(userId: string, fileName: string) { @@ -40,12 +45,7 @@ export class ContentsService { async uploadAndProcess( userId: string, file: Express.Multer.File, - data: { - title: string; - type: "meme" | "gif"; - categoryId?: string; - tags?: string[]; - }, + data: UploadContentDto, ) { this.logger.log(`Uploading and processing file for user ${userId}`); // 0. Validation du format et de la taille @@ -86,7 +86,7 @@ export class ContentsService { } // 2. Transcodage - let processed; + let processed: MediaProcessingResult; if (file.mimetype.startsWith("image/")) { // Image ou GIF -> WebP (format moderne, bien supporté) processed = await this.mediaService.processImage(file.buffer, "webp"); @@ -129,7 +129,7 @@ export class ContentsService { return { data, totalCount }; } - async create(userId: string, data: any) { + async create(userId: string, data: CreateContentDto) { this.logger.log(`Creating content for user ${userId}: ${data.title}`); const { tags: tagNames, ...contentData } = data; @@ -166,7 +166,7 @@ export class ContentsService { return this.contentsRepository.findOne(idOrSlug); } - generateBotHtml(content: any): string { + generateBotHtml(content: { title: string; storageKey: string }): string { const imageUrl = this.getFileUrl(content.storageKey); return ` diff --git a/backend/src/contents/repositories/contents.repository.ts b/backend/src/contents/repositories/contents.repository.ts index d044c51..8fbc2d0 100644 --- a/backend/src/contents/repositories/contents.repository.ts +++ b/backend/src/contents/repositories/contents.repository.ts @@ -6,9 +6,9 @@ import { exists, ilike, isNull, - sql, lte, type SQL, + sql, } from "drizzle-orm"; import { DatabaseService } from "../../database/database.service"; import { @@ -113,10 +113,7 @@ export class ContentsRepository { .select() .from(favorites) .where( - and( - eq(favorites.contentId, contents.id), - eq(favorites.userId, userId), - ), + and(eq(favorites.contentId, contents.id), eq(favorites.userId, userId)), ), ), ); @@ -143,7 +140,6 @@ export class ContentsRepository { author: { id: users.uuid, username: users.username, - avatarUrl: users.avatarUrl, }, category: { id: categories.id, @@ -173,18 +169,13 @@ export class ContentsRepository { return results.map((r) => ({ ...r, - tags: tagsForContents - .filter((t) => t.contentId === r.id) - .map((t) => t.name), + tags: tagsForContents.filter((t) => t.contentId === r.id).map((t) => t.name), })); } async create(data: NewContentInDb & { userId: string }, tagNames?: string[]) { return await this.databaseService.db.transaction(async (tx) => { - const [newContent] = await tx - .insert(contents) - .values(data) - .returning(); + const [newContent] = await tx.insert(contents).values(data).returning(); if (tagNames && tagNames.length > 0) { for (const tagName of tagNames) { @@ -325,10 +316,7 @@ export class ContentsRepository { .select() .from(favorites) .where( - and( - eq(favorites.contentId, contents.id), - eq(favorites.userId, userId), - ), + and(eq(favorites.contentId, contents.id), eq(favorites.userId, userId)), ), ), ); @@ -377,7 +365,12 @@ export class ContentsRepository { async purgeSoftDeleted(before: Date) { return await this.databaseService.db .delete(contents) - .where(and(sql`${contents.deletedAt} IS NOT NULL`, lte(contents.deletedAt, before))) + .where( + and( + sql`${contents.deletedAt} IS NOT NULL`, + lte(contents.deletedAt, before), + ), + ) .returning(); } } diff --git a/backend/src/crypto/crypto.module.ts b/backend/src/crypto/crypto.module.ts index 75836b1..d85f7cf 100644 --- a/backend/src/crypto/crypto.module.ts +++ b/backend/src/crypto/crypto.module.ts @@ -1,8 +1,8 @@ import { Module } from "@nestjs/common"; import { CryptoService } from "./crypto.service"; +import { EncryptionService } from "./services/encryption.service"; import { HashingService } from "./services/hashing.service"; import { JwtService } from "./services/jwt.service"; -import { EncryptionService } from "./services/encryption.service"; import { PostQuantumService } from "./services/post-quantum.service"; @Module({ diff --git a/backend/src/crypto/crypto.service.spec.ts b/backend/src/crypto/crypto.service.spec.ts index e46fe9b..82a4081 100644 --- a/backend/src/crypto/crypto.service.spec.ts +++ b/backend/src/crypto/crypto.service.spec.ts @@ -64,9 +64,9 @@ jest.mock("jose", () => ({ })); import { CryptoService } from "./crypto.service"; +import { EncryptionService } from "./services/encryption.service"; import { HashingService } from "./services/hashing.service"; import { JwtService } from "./services/jwt.service"; -import { EncryptionService } from "./services/encryption.service"; import { PostQuantumService } from "./services/post-quantum.service"; describe("CryptoService", () => { diff --git a/backend/src/database/schemas/index.ts b/backend/src/database/schemas/index.ts index e54a30a..e271c71 100644 --- a/backend/src/database/schemas/index.ts +++ b/backend/src/database/schemas/index.ts @@ -3,9 +3,9 @@ export * from "./audit_logs"; export * from "./categories"; export * from "./content"; export * from "./favorites"; +export * from "./pgp"; export * from "./rbac"; export * from "./reports"; export * from "./sessions"; export * from "./tags"; export * from "./users"; -export * from "./pgp"; diff --git a/backend/src/database/schemas/pgp.ts b/backend/src/database/schemas/pgp.ts index cc514e8..f872b26 100644 --- a/backend/src/database/schemas/pgp.ts +++ b/backend/src/database/schemas/pgp.ts @@ -55,6 +55,9 @@ export function pgpSymEncrypt(value: string | SQL, key: string | SQL) { /** * @deprecated Utiliser directement les colonnes de type pgpEncrypted qui gèrent maintenant le déchiffrement automatiquement. */ -export function pgpSymDecrypt(column: AnyPgColumn, key: string | SQL): SQL { +export function pgpSymDecrypt( + column: AnyPgColumn, + key: string | SQL, +): SQL { return sql`pgp_sym_decrypt(${column}, ${key})`.mapWith(column) as SQL; } diff --git a/backend/src/database/schemas/users.ts b/backend/src/database/schemas/users.ts index 9190acd..bdc1b7f 100644 --- a/backend/src/database/schemas/users.ts +++ b/backend/src/database/schemas/users.ts @@ -1,4 +1,3 @@ -import { SQL, sql } from "drizzle-orm"; import { boolean, index, diff --git a/backend/src/favorites/favorites.service.spec.ts b/backend/src/favorites/favorites.service.spec.ts index a2b636b..20c9497 100644 --- a/backend/src/favorites/favorites.service.spec.ts +++ b/backend/src/favorites/favorites.service.spec.ts @@ -34,7 +34,9 @@ describe("FavoritesService", () => { describe("addFavorite", () => { it("should add a favorite", async () => { - mockFavoritesRepository.findContentById.mockResolvedValue({ id: "content1" }); + mockFavoritesRepository.findContentById.mockResolvedValue({ + id: "content1", + }); mockFavoritesRepository.add.mockResolvedValue([ { userId: "u1", contentId: "content1" }, ]); @@ -53,7 +55,9 @@ describe("FavoritesService", () => { }); it("should throw ConflictException on duplicate favorite", async () => { - mockFavoritesRepository.findContentById.mockResolvedValue({ id: "content1" }); + mockFavoritesRepository.findContentById.mockResolvedValue({ + id: "content1", + }); mockFavoritesRepository.add.mockRejectedValue(new Error("Duplicate")); await expect(service.addFavorite("u1", "content1")).rejects.toThrow( ConflictException, @@ -63,7 +67,9 @@ describe("FavoritesService", () => { describe("removeFavorite", () => { it("should remove a favorite", async () => { - mockFavoritesRepository.remove.mockResolvedValue([{ userId: "u1", contentId: "c1" }]); + mockFavoritesRepository.remove.mockResolvedValue([ + { userId: "u1", contentId: "c1" }, + ]); const result = await service.removeFavorite("u1", "c1"); expect(result).toEqual({ userId: "u1", contentId: "c1" }); expect(repository.remove).toHaveBeenCalledWith("u1", "c1"); diff --git a/backend/src/favorites/favorites.service.ts b/backend/src/favorites/favorites.service.ts index b001b16..4b2f638 100644 --- a/backend/src/favorites/favorites.service.ts +++ b/backend/src/favorites/favorites.service.ts @@ -14,7 +14,7 @@ export class FavoritesService { async addFavorite(userId: string, contentId: string) { this.logger.log(`Adding favorite: user ${userId}, content ${contentId}`); - + const content = await this.favoritesRepository.findContentById(contentId); if (!content) { throw new NotFoundException("Content not found"); diff --git a/backend/src/media/strategies/media-processor.strategy.ts b/backend/src/media/strategies/media-processor.strategy.ts index 330ca77..94a4893 100644 --- a/backend/src/media/strategies/media-processor.strategy.ts +++ b/backend/src/media/strategies/media-processor.strategy.ts @@ -2,5 +2,8 @@ import type { MediaProcessingResult } from "../../common/interfaces/media.interf export interface IMediaProcessorStrategy { canHandle(mimeType: string): boolean; - process(buffer: Buffer, options?: any): Promise; + process( + buffer: Buffer, + options?: Record, + ): Promise; } diff --git a/backend/src/reports/reports.service.spec.ts b/backend/src/reports/reports.service.spec.ts index 9564996..a741493 100644 --- a/backend/src/reports/reports.service.spec.ts +++ b/backend/src/reports/reports.service.spec.ts @@ -34,7 +34,11 @@ describe("ReportsService", () => { it("should create a report", async () => { const reporterId = "u1"; const data = { contentId: "c1", reason: "spam" }; - mockReportsRepository.create.mockResolvedValue({ id: "r1", ...data, reporterId }); + mockReportsRepository.create.mockResolvedValue({ + id: "r1", + ...data, + reporterId, + }); const result = await service.create(reporterId, data); @@ -54,7 +58,9 @@ describe("ReportsService", () => { describe("updateStatus", () => { it("should update report status", async () => { - mockReportsRepository.updateStatus.mockResolvedValue([{ id: "r1", status: "resolved" }]); + mockReportsRepository.updateStatus.mockResolvedValue([ + { id: "r1", status: "resolved" }, + ]); const result = await service.updateStatus("r1", "resolved"); expect(result[0].status).toBe("resolved"); expect(repository.updateStatus).toHaveBeenCalledWith("r1", "resolved"); diff --git a/backend/src/reports/reports.service.ts b/backend/src/reports/reports.service.ts index b014831..aa58adb 100644 --- a/backend/src/reports/reports.service.ts +++ b/backend/src/reports/reports.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from "@nestjs/common"; -import { ReportsRepository } from "./repositories/reports.repository"; import { CreateReportDto } from "./dto/create-report.dto"; +import { ReportsRepository } from "./repositories/reports.repository"; @Injectable() export class ReportsService { diff --git a/backend/src/reports/repositories/reports.repository.ts b/backend/src/reports/repositories/reports.repository.ts index 7404d46..9f113fe 100644 --- a/backend/src/reports/repositories/reports.repository.ts +++ b/backend/src/reports/repositories/reports.repository.ts @@ -11,7 +11,7 @@ export class ReportsRepository { reporterId: string; contentId?: string; tagId?: string; - reason: string; + reason: "inappropriate" | "spam" | "copyright" | "other"; description?: string; }) { const [newReport] = await this.databaseService.db diff --git a/backend/src/sessions/repositories/sessions.repository.ts b/backend/src/sessions/repositories/sessions.repository.ts index 050f368..59619e4 100644 --- a/backend/src/sessions/repositories/sessions.repository.ts +++ b/backend/src/sessions/repositories/sessions.repository.ts @@ -32,7 +32,7 @@ export class SessionsRepository { return result[0] || null; } - async update(sessionId: string, data: any) { + async update(sessionId: string, data: Record) { const [updatedSession] = await this.databaseService.db .update(sessions) .set({ ...data, updatedAt: new Date() }) diff --git a/backend/src/sessions/sessions.module.ts b/backend/src/sessions/sessions.module.ts index c69d49b..a5c4367 100644 --- a/backend/src/sessions/sessions.module.ts +++ b/backend/src/sessions/sessions.module.ts @@ -1,8 +1,8 @@ import { Module } from "@nestjs/common"; import { CryptoModule } from "../crypto/crypto.module"; import { DatabaseModule } from "../database/database.module"; -import { SessionsService } from "./sessions.service"; import { SessionsRepository } from "./repositories/sessions.repository"; +import { SessionsService } from "./sessions.service"; @Module({ imports: [DatabaseModule, CryptoModule], diff --git a/backend/src/sessions/sessions.service.spec.ts b/backend/src/sessions/sessions.service.spec.ts index 37343cc..0f2afe3 100644 --- a/backend/src/sessions/sessions.service.spec.ts +++ b/backend/src/sessions/sessions.service.spec.ts @@ -15,8 +15,8 @@ import { UnauthorizedException } from "@nestjs/common"; import { Test, TestingModule } from "@nestjs/testing"; import { HashingService } from "../crypto/services/hashing.service"; import { JwtService } from "../crypto/services/jwt.service"; -import { SessionsService } from "./sessions.service"; import { SessionsRepository } from "./repositories/sessions.repository"; +import { SessionsService } from "./sessions.service"; describe("SessionsService", () => { let service: SessionsService; @@ -76,7 +76,10 @@ describe("SessionsService", () => { userId: "u1", expiresAt, }); - mockSessionsRepository.update.mockResolvedValue({ id: "s1", refreshToken: "new-token" }); + mockSessionsRepository.update.mockResolvedValue({ + id: "s1", + refreshToken: "new-token", + }); const result = await service.refreshSession("old-token"); diff --git a/backend/src/sessions/sessions.service.ts b/backend/src/sessions/sessions.service.ts index 8659765..fa11205 100644 --- a/backend/src/sessions/sessions.service.ts +++ b/backend/src/sessions/sessions.service.ts @@ -30,7 +30,8 @@ export class SessionsService { } async refreshSession(oldRefreshToken: string) { - const session = await this.sessionsRepository.findValidByRefreshToken(oldRefreshToken); + const session = + await this.sessionsRepository.findValidByRefreshToken(oldRefreshToken); if (!session || session.expiresAt < new Date()) { if (session) { diff --git a/backend/src/tags/tags.controller.ts b/backend/src/tags/tags.controller.ts index ae08024..ae11e2c 100644 --- a/backend/src/tags/tags.controller.ts +++ b/backend/src/tags/tags.controller.ts @@ -1,3 +1,4 @@ +import { CacheInterceptor, CacheTTL } from "@nestjs/cache-manager"; import { Controller, DefaultValuePipe, @@ -6,7 +7,6 @@ import { Query, UseInterceptors, } from "@nestjs/common"; -import { CacheInterceptor, CacheTTL } from "@nestjs/cache-manager"; import { TagsService } from "./tags.service"; @Controller("tags") diff --git a/backend/src/tags/tags.module.ts b/backend/src/tags/tags.module.ts index f3fba4a..456c36a 100644 --- a/backend/src/tags/tags.module.ts +++ b/backend/src/tags/tags.module.ts @@ -1,8 +1,8 @@ import { Module } from "@nestjs/common"; import { DatabaseModule } from "../database/database.module"; +import { TagsRepository } from "./repositories/tags.repository"; import { TagsController } from "./tags.controller"; import { TagsService } from "./tags.service"; -import { TagsRepository } from "./repositories/tags.repository"; @Module({ imports: [DatabaseModule], diff --git a/backend/src/tags/tags.service.spec.ts b/backend/src/tags/tags.service.spec.ts index 8eddf61..4b02a4e 100644 --- a/backend/src/tags/tags.service.spec.ts +++ b/backend/src/tags/tags.service.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from "@nestjs/testing"; -import { TagsService } from "./tags.service"; import { TagsRepository } from "./repositories/tags.repository"; +import { TagsService } from "./tags.service"; describe("TagsService", () => { let service: TagsService; diff --git a/backend/src/users/repositories/users.repository.ts b/backend/src/users/repositories/users.repository.ts index c56ccf2..7a3f538 100644 --- a/backend/src/users/repositories/users.repository.ts +++ b/backend/src/users/repositories/users.repository.ts @@ -1,8 +1,7 @@ import { Injectable } from "@nestjs/common"; import { and, eq, lte, sql } from "drizzle-orm"; import { DatabaseService } from "../../database/database.service"; -import { users, contents, favorites } from "../../database/schemas"; -import type { UpdateUserDto } from "../dto/update-user.dto"; +import { contents, favorites, users } from "../../database/schemas"; @Injectable() export class UsersRepository { @@ -99,7 +98,7 @@ export class UsersRepository { return result[0] || null; } - async update(uuid: string, data: any) { + async update(uuid: string, data: Partial) { return await this.databaseService.db .update(users) .set({ ...data, updatedAt: new Date() }) diff --git a/backend/src/users/users.controller.ts b/backend/src/users/users.controller.ts index 673f9a5..c036337 100644 --- a/backend/src/users/users.controller.ts +++ b/backend/src/users/users.controller.ts @@ -1,9 +1,12 @@ +import { CacheInterceptor, CacheTTL } from "@nestjs/cache-manager"; import { Body, Controller, DefaultValuePipe, Delete, + forwardRef, Get, + Inject, Param, ParseIntPipe, Patch, @@ -13,7 +16,6 @@ import { UseGuards, UseInterceptors, } from "@nestjs/common"; -import { CacheInterceptor, CacheKey, CacheTTL } from "@nestjs/cache-manager"; import { AuthService } from "../auth/auth.service"; import { Roles } from "../auth/decorators/roles.decorator"; import { AuthGuard } from "../auth/guards/auth.guard"; @@ -27,6 +29,7 @@ import { UsersService } from "./users.service"; export class UsersController { constructor( private readonly usersService: UsersService, + @Inject(forwardRef(() => AuthService)) private readonly authService: AuthService, ) {} diff --git a/backend/src/users/users.module.ts b/backend/src/users/users.module.ts index 317a7a7..a15fae5 100644 --- a/backend/src/users/users.module.ts +++ b/backend/src/users/users.module.ts @@ -1,13 +1,13 @@ -import { Module } from "@nestjs/common"; +import { forwardRef, Module } from "@nestjs/common"; import { AuthModule } from "../auth/auth.module"; import { CryptoModule } from "../crypto/crypto.module"; import { DatabaseModule } from "../database/database.module"; +import { UsersRepository } from "./repositories/users.repository"; import { UsersController } from "./users.controller"; import { UsersService } from "./users.service"; -import { UsersRepository } from "./repositories/users.repository"; @Module({ - imports: [DatabaseModule, CryptoModule, AuthModule], + imports: [DatabaseModule, CryptoModule, forwardRef(() => AuthModule)], controllers: [UsersController], providers: [UsersService, UsersRepository], exports: [UsersService], diff --git a/backend/src/users/users.service.spec.ts b/backend/src/users/users.service.spec.ts index 637017f..2bc21f5 100644 --- a/backend/src/users/users.service.spec.ts +++ b/backend/src/users/users.service.spec.ts @@ -11,10 +11,10 @@ jest.mock("jose", () => ({ jwtVerify: jest.fn(), })); -import { Test, TestingModule } from "@nestjs/testing"; import { CACHE_MANAGER } from "@nestjs/cache-manager"; -import { UsersService } from "./users.service"; +import { Test, TestingModule } from "@nestjs/testing"; import { UsersRepository } from "./repositories/users.repository"; +import { UsersService } from "./users.service"; describe("UsersService", () => { let service: UsersService; @@ -91,7 +91,9 @@ describe("UsersService", () => { describe("update", () => { it("should update a user", async () => { - mockUsersRepository.update.mockResolvedValue([{ uuid: "uuid1", displayName: "New" }]); + mockUsersRepository.update.mockResolvedValue([ + { uuid: "uuid1", displayName: "New" }, + ]); const result = await service.update("uuid1", { displayName: "New" }); expect(result[0].displayName).toBe("New"); }); diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index 59e68bd..8c7a97e 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -1,8 +1,8 @@ -import { Injectable, Logger, Inject } from "@nestjs/common"; import { CACHE_MANAGER } from "@nestjs/cache-manager"; -import { Cache } from "cache-manager"; -import { UsersRepository } from "./repositories/users.repository"; +import { Inject, Injectable, Logger } from "@nestjs/common"; +import type { Cache } from "cache-manager"; import { UpdateUserDto } from "./dto/update-user.dto"; +import { UsersRepository } from "./repositories/users.repository"; @Injectable() export class UsersService { diff --git a/backend/test/__mocks__/cuid2.js b/backend/test/__mocks__/cuid2.js deleted file mode 100644 index e95da3d..0000000 --- a/backend/test/__mocks__/cuid2.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - createCuid: () => () => 'mocked-cuid', -}; \ No newline at end of file diff --git a/backend/test/__mocks__/jose.js b/backend/test/__mocks__/jose.js deleted file mode 100644 index 006ccab..0000000 --- a/backend/test/__mocks__/jose.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - SignJWT: class { - constructor() { return this; } - setProtectedHeader() { return this; } - setIssuedAt() { return this; } - setExpirationTime() { return this; } - sign() { return Promise.resolve('mocked-token'); } - }, - jwtVerify: () => Promise.resolve({ payload: { sub: 'mocked-user' } }), - importJWK: () => Promise.resolve({}), - exportJWK: () => Promise.resolve({}), - generateKeyPair: () => Promise.resolve({ publicKey: {}, privateKey: {} }), -}; \ No newline at end of file diff --git a/backend/test/__mocks__/ml-kem.js b/backend/test/__mocks__/ml-kem.js deleted file mode 100644 index 2ef4c06..0000000 --- a/backend/test/__mocks__/ml-kem.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - ml_kem768: { - keygen: () => ({ publicKey: Buffer.alloc(1184), secretKey: Buffer.alloc(2400) }), - encapsulate: () => ({ cipherText: Buffer.alloc(1088), sharedSecret: Buffer.alloc(32) }), - decapsulate: () => Buffer.alloc(32), - } -}; \ No newline at end of file diff --git a/backend/test/__mocks__/sha3.js b/backend/test/__mocks__/sha3.js deleted file mode 100644 index b1a25f2..0000000 --- a/backend/test/__mocks__/sha3.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - sha3_256: () => ({ update: () => ({ digest: () => Buffer.alloc(32) }) }), - sha3_512: () => ({ update: () => ({ digest: () => Buffer.alloc(64) }) }), - shake256: () => ({ update: () => ({ digest: () => Buffer.alloc(32) }) }), -}; \ No newline at end of file diff --git a/backend/test/app.e2e-spec.ts b/backend/test/app.e2e-spec.ts deleted file mode 100644 index 8e18dd4..0000000 --- a/backend/test/app.e2e-spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { INestApplication } from "@nestjs/common"; -import { Test, TestingModule } from "@nestjs/testing"; -import request from "supertest"; -import { App } from "supertest/types"; -import { AppModule } from "../src/app.module"; - -describe("AppController (e2e)", () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it("/ (GET)", () => { - return request(app.getHttpServer()) - .get("/") - .expect(200) - .expect("Hello World!"); - }); -}); diff --git a/backend/test/jest-e2e.json b/backend/test/jest-e2e.json deleted file mode 100644 index f43e8a6..0000000 --- a/backend/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -}