From f4cd20a0100ceacb161226ab3bab37f97ba93693 Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:00:46 +0100 Subject: [PATCH] docs: add PlantUML backend architecture diagram - Introduced a detailed PlantUML diagram illustrating backend modules, services, controllers, and key relationships. - Enhanced documentation with visual representation of system architecture for improved understanding. --- backend.plantuml | 756 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 756 insertions(+) create mode 100644 backend.plantuml diff --git a/backend.plantuml b/backend.plantuml new file mode 100644 index 0000000..f34c632 --- /dev/null +++ b/backend.plantuml @@ -0,0 +1,756 @@ +@startuml + +!theme plain +top to bottom direction +skinparam linetype ortho + +class AdminController { + constructor(adminService: AdminService): + getStats(): Promise<{users: number, contents: numbe… +} +class AdminModule +class AdminService { + constructor(usersRepository: UsersRepository, contentsRepository: ContentsRepository, categoriesRepository: CategoriesRepository): + getStats(): Promise<{users: number, contents: numbe… +} +class AllExceptionsFilter { + logger: Logger + catch(exception: unknown, host: ArgumentsHost): void +} +class ApiKeysController { + constructor(apiKeysService: ApiKeysService): + create(req: AuthenticatedRequest, createApiKeyDto: CreateApiKeyDto): Promise<{name: string, key: string, exp… + findAll(req: AuthenticatedRequest): Promise + revoke(req: AuthenticatedRequest, id: string): Promise +} +class ApiKeysModule +class ApiKeysRepository { + constructor(databaseService: DatabaseService): + create(data: {userId: string; name: string; prefix: string; keyHash: string; expiresAt?: Date}): Promise + findAll(userId: string): Promise + revoke(userId: string, keyId: string): Promise + findActiveByKeyHash(keyHash: string): Promise + updateLastUsed(id: string): Promise +} +class ApiKeysService { + constructor(apiKeysRepository: ApiKeysRepository, hashingService: HashingService): + logger: Logger + create(userId: string, name: string, expiresAt?: Date): Promise<{name: string, key: string, exp… + findAll(userId: string): Promise + revoke(userId: string, keyId: string): Promise + validateKey(key: string): Promise +} +class AppController { + constructor(appService: AppService): + getHello(): string +} +class AppModule { + configure(consumer: MiddlewareConsumer): void +} +class AppService { + getHello(): string +} +class AuditLogInDb +class AuthController { + constructor(authService: AuthService, bootstrapService: BootstrapService, configService: ConfigService): + register(registerDto: RegisterDto): Promise<{message: string, userId: any}> + login(loginDto: LoginDto, userAgent: string, req: Request, res: Response): Promise +} +class AuthGuard { + constructor(jwtService: JwtService, configService: ConfigService): + canActivate(context: ExecutionContext): Promise +} +class AuthModule +class AuthService { + constructor(usersService: UsersService, hashingService: HashingService, jwtService: JwtService, sessionsService: SessionsService, configService: ConfigService): + logger: Logger + generateTwoFactorSecret(userId: string): Promise<{secret: string, qrCodeDataUrl:… + enableTwoFactor(userId: string, token: string): Promise<{message: string}> + disableTwoFactor(userId: string, token: string): Promise<{message: string}> + register(dto: RegisterDto): Promise<{message: string, userId: any}> + login(dto: LoginDto, userAgent?: string, ip?: string): Promise<{message: string, requires2FA: … + verifyTwoFactorLogin(userId: string, token: string, userAgent?: string, ip?: string): Promise<{message: string, access_token:… + refresh(refreshToken: string): Promise<{access_token: string, refresh_… + logout(): Promise<{message: string}> +} +class AuthenticatedRequest { + user: {sub: string, username: string} +} +class BootstrapService { + constructor(rbacService: RbacService, usersService: UsersService, configService: ConfigService): + logger: Logger + bootstrapToken: string | null + onApplicationBootstrap(): Promise + generateBootstrapToken(): void + consumeToken(token: string, username: string): Promise<{message: string}> +} +class CategoriesController { + constructor(categoriesService: CategoriesService): + findAll(): Promise + findOne(id: string): Promise + create(createCategoryDto: CreateCategoryDto): Promise + update(id: string, updateCategoryDto: UpdateCategoryDto): Promise + remove(id: string): Promise +} +class CategoriesModule +class CategoriesRepository { + constructor(databaseService: DatabaseService): + findAll(): Promise + countAll(): Promise + findOne(id: string): Promise + create(data: CreateCategoryDto & {slug: string}): Promise + update(id: string, data: UpdateCategoryDto & {slug?: string; updatedAt: Date}): Promise + remove(id: string): Promise +} +class CategoriesService { + constructor(categoriesRepository: CategoriesRepository, cacheManager: Cache): + logger: Logger + clearCategoriesCache(): Promise + findAll(): Promise + findOne(id: string): Promise + create(data: CreateCategoryDto): Promise + update(id: string, data: UpdateCategoryDto): Promise + remove(id: string): Promise +} +class CategoryInDb +class ClamScanner { + scanStream(stream: Readable): Promise<{isInfected: boolean, viruses: … +} +class CommonModule +class ContentInDb +class ContentType { + MEME: + GIF: +} +class ContentsController { + constructor(contentsService: ContentsService): + create(req: AuthenticatedRequest, createContentDto: CreateContentDto): Promise + getUploadUrl(req: AuthenticatedRequest, fileName: string): Promise<{url: string, key: string}> + upload(req: AuthenticatedRequest, file: Express.Multer.File, uploadContentDto: UploadContentDto): Promise + explore(req: AuthenticatedRequest, limit: number, offset: number, sort?: "trend" | "recent", tag?: string, category?: string, author?: string): Promise<{data: any, totalCount: any}> + trends(req: AuthenticatedRequest, limit: number, offset: number): Promise<{data: any, totalCount: any}> + recent(req: AuthenticatedRequest, limit: number, offset: number): Promise<{data: any, totalCount: any}> + findOne(idOrSlug: string, req: AuthenticatedRequest, res: Response): Promise + incrementUsage(id: string): Promise + update(id: string, req: AuthenticatedRequest, updateContentDto: any): Promise + remove(id: string, req: AuthenticatedRequest): Promise + removeAdmin(id: string): Promise + updateAdmin(id: string, updateContentDto: any): Promise +} +class ContentsModule +class ContentsRepository { + constructor(databaseService: DatabaseService): + findAll(options: FindAllOptions): Promise + create(data: NewContentInDb & {userId: string}, tagNames?: string[]): Promise + findOne(idOrSlug: string, userId?: string): Promise + count(options: {tag?: string; category?: string; author?: string; query?: string; favoritesOnly?: boolean; userId?: string}): Promise + incrementViews(id: string): Promise + incrementUsage(id: string): Promise + softDelete(id: string, userId: string): Promise + softDeleteAdmin(id: string): Promise + update(id: string, data: Partial): Promise + findBySlug(slug: string): Promise + purgeSoftDeleted(before: Date): Promise +} +class ContentsService { + constructor(contentsRepository: ContentsRepository, s3Service: IStorageService, mediaService: IMediaService, configService: ConfigService, cacheManager: Cache): + logger: Logger + clearContentsCache(): Promise + getUploadUrl(userId: string, fileName: string): Promise<{url: string, key: string}> + uploadAndProcess(userId: string, file: Express.Multer.File, data: UploadContentDto): Promise + findAll(options: {limit: number; offset: number; sortBy?: "trend" | "recent"; tag?: string; category?: string; author?: string; query?: string; favoritesOnly?: boolean; userId?: string}): Promise<{data: any, totalCount: any}> + create(userId: string, data: CreateContentDto): Promise + incrementViews(id: string): Promise + incrementUsage(id: string): Promise + remove(id: string, userId: string): Promise + removeAdmin(id: string): Promise + updateAdmin(id: string, data: any): Promise + update(id: string, userId: string, data: any): Promise + findOne(idOrSlug: string, userId?: string): Promise + generateBotHtml(content: {title: string; storageKey: string}): string + generateSlug(text: string): string + ensureUniqueSlug(title: string): Promise +} +class CrawlerDetectionMiddleware { + logger: Logger + SUSPICIOUS_PATTERNS: RegExp[] + BOT_USER_AGENTS: RegExp[] + use(req: Request, res: Response, next: NextFunction): void +} +class CreateApiKeyDto { + name: string + expiresAt: string +} +class CreateCategoryDto { + name: string + description: string + iconUrl: string +} +class CreateContentDto { + type: "meme" | "gif" + title: string + storageKey: string + mimeType: string + fileSize: number + categoryId: string + tags: string[] +} +class CreateReportDto { + contentId: string + tagId: string + reason: "inappropriate" | "spam" | "copyright" … + description: string +} +class CryptoModule +class CryptoService { + constructor(hashingService: HashingService, jwtService: JwtService, encryptionService: EncryptionService, postQuantumService: PostQuantumService): + hashEmail(email: string): Promise + hashIp(ip: string): Promise + getPgpEncryptionKey(): string + hashPassword(password: string): Promise + verifyPassword(password: string, hash: string): Promise + generateJwt(payload: jose.JWTPayload, expiresIn?: string): Promise + verifyJwt(token: string): Promise + encryptContent(content: string): Promise + decryptContent(jwe: string): Promise + signContent(content: string): Promise + verifyContentSignature(jws: string): Promise + generatePostQuantumKeyPair(): {publicKey: Uint8Array… + encapsulate(publicKey: Uint8Array): {cipherText: Uint8Array, sharedSecret: … + decapsulate(cipherText: Uint8Array, secretKey: Uint8Array): Uint8Array +} +class DatabaseModule +class DatabaseService { + constructor(configService: ConfigService): + logger: Logger + pool: Pool + db: ReturnType + onModuleInit(): Promise + onModuleDestroy(): Promise + getDatabaseConnectionString(): string +} +class EncryptionService { + constructor(configService: ConfigService): + logger: Logger + jwtSecret: Uint8Array + encryptionKey: Uint8Array + encryptContent(content: string): Promise + decryptContent(jwe: string): Promise + signContent(content: string): Promise + verifyContentSignature(jws: string): Promise + getPgpEncryptionKey(): string +} +class Env +class FavoriteInDb +class FavoritesController { + constructor(favoritesService: FavoritesService): + add(req: AuthenticatedRequest, contentId: string): Promise + remove(req: AuthenticatedRequest, contentId: string): Promise + list(req: AuthenticatedRequest, limit: number, offset: number): Promise +} +class FavoritesModule +class FavoritesRepository { + constructor(databaseService: DatabaseService): + findContentById(contentId: string): Promise + add(userId: string, contentId: string): Promise + remove(userId: string, contentId: string): Promise + findByUserId(userId: string, limit: number, offset: number): Promise +} +class FavoritesService { + constructor(favoritesRepository: FavoritesRepository): + logger: Logger + addFavorite(userId: string, contentId: string): Promise + removeFavorite(userId: string, contentId: string): Promise + getUserFavorites(userId: string, limit: number, offset: number): Promise +} +class FindAllOptions { + limit: number + offset: number + sortBy: "trend" | "recent" + tag: string + category: string + author: string + query: string + favoritesOnly: boolean + userId: string +} +class HTTPLoggerMiddleware { + logger: Logger + use(request: Request, response: Response, next: NextFunction): void +} +class HashingService { + hashEmail(email: string): Promise + hashIp(ip: string): Promise + hashSha256(text: string): Promise + hashPassword(password: string): Promise + verifyPassword(password: string, hash: string): Promise +} +class HealthController { + constructor(databaseService: DatabaseService, cacheManager: Cache): + check(): Promise +} +class IMailService { + sendEmailValidation(email: string, token: string): Promise + sendPasswordReset(email: string, token: string): Promise +} +class IMediaProcessorStrategy { + canHandle(mimeType: string): boolean + process(buffer: Buffer, options?: Record): Promise +} +class IMediaService { + scanFile(buffer: Buffer, filename: string): Promise + processImage(buffer: Buffer, format?: "webp" | "avif", resize?: {width?: number; height?: number}): Promise + processVideo(buffer: Buffer, format?: "webm" | "av1"): Promise +} +class IStorageService { + uploadFile(fileName: string, file: Buffer, mimeType: string, metaData?: Record, bucketName?: string): Promise + getFile(fileName: string, bucketName?: string): Promise + getFileUrl(fileName: string, expiry?: number, bucketName?: string): Promise + getUploadUrl(fileName: string, expiry?: number, bucketName?: string): Promise + deleteFile(fileName: string, bucketName?: string): Promise + getFileInfo(fileName: string, bucketName?: string): Promise + moveFile(sourceFileName: string, destinationFileName: string, sourceBucketName?: string, destinationBucketName?: string): Promise + getPublicUrl(storageKey: string): string +} +class ImageProcessorStrategy { + logger: Logger + canHandle(mimeType: string): boolean + process(buffer: Buffer, options?: {format: "webp" | "avif"; resize?: {width?: number; height?: number}}): Promise +} +class JwtService { + constructor(configService: ConfigService): + logger: Logger + jwtSecret: Uint8Array + generateJwt(payload: jose.JWTPayload, expiresIn?: string): Promise + verifyJwt(token: string): Promise +} +class LoginDto { + email: string + password: string +} +class MailModule +class MailService { + constructor(mailerService: MailerService, configService: ConfigService): + logger: Logger + domain: string + sendEmailValidation(email: string, token: string): Promise + sendPasswordReset(email: string, token: string): Promise +} +class MediaController { + constructor(s3Service: S3Service): + logger: Logger + getFile(path: string, res: Response): Promise +} +class MediaModule +class MediaProcessingResult { + buffer: Buffer + mimeType: string + extension: string + width: number + height: number + size: number +} +class MediaProcessingResult { + buffer: Buffer + mimeType: string + extension: string + width: number + height: number + size: number +} +class MediaService { + constructor(configService: ConfigService, imageProcessor: ImageProcessorStrategy, videoProcessor: VideoProcessorStrategy): + logger: Logger + clamscan: ClamScanner | null + isClamAvInitialized: boolean + initClamScan(): Promise + scanFile(buffer: Buffer, filename: string): Promise + processImage(buffer: Buffer, format?: "webp" | "avif", resize?: {width?: number; height?: number}): Promise + processVideo(buffer: Buffer, format?: "webm" | "av1"): Promise +} +class NewAuditLogInDb +class NewCategoryInDb +class NewContentInDb +class NewFavoriteInDb +class NewReportInDb +class NewTagInDb +class NewUserInDb +class OptionalAuthGuard { + constructor(jwtService: JwtService, configService: ConfigService): + canActivate(context: ExecutionContext): Promise +} +class PostQuantumService { + generatePostQuantumKeyPair(): {publicKey: Uint8Array… + encapsulate(publicKey: Uint8Array): {cipherText: Uint8Array, sharedSecret: … + decapsulate(cipherText: Uint8Array, secretKey: Uint8Array): Uint8Array +} +class PurgeService { + constructor(sessionsRepository: SessionsRepository, reportsRepository: ReportsRepository, usersRepository: UsersRepository, contentsRepository: ContentsRepository): + logger: Logger + purgeExpiredData(): Promise +} +class RbacRepository { + constructor(databaseService: DatabaseService): + findRolesByUserId(userId: string): Promise + findPermissionsByUserId(userId: string): Promise + countRoles(): Promise + countAdmins(): Promise + createRole(name: string, slug: string, description?: string): Promise + assignRole(userId: string, roleSlug: string): Promise +} +class RbacService { + constructor(rbacRepository: RbacRepository): + logger: Logger + onApplicationBootstrap(): Promise + seedRoles(): Promise + getUserRoles(userId: string): Promise + getUserPermissions(userId: string): Promise + countAdmins(): Promise + assignRoleToUser(userId: string, roleSlug: string): Promise +} +class RefreshDto { + refresh_token: string +} +class RegisterDto { + username: string + displayName: string + email: string + password: string +} +class ReportInDb +class ReportReason { + INAPPROPRIATE: + SPAM: + COPYRIGHT: + OTHER: +} +class ReportStatus { + PENDING: + REVIEWED: + RESOLVED: + DISMISSED: +} +class ReportsController { + constructor(reportsService: ReportsService): + create(req: AuthenticatedRequest, createReportDto: CreateReportDto): Promise + findAll(limit: number, offset: number): Promise + updateStatus(id: string, updateReportStatusDto: UpdateReportStatusDto): Promise +} +class ReportsModule +class ReportsRepository { + constructor(databaseService: DatabaseService): + create(data: {reporterId: string; contentId?: string; tagId?: string; reason: "inappropriate" | "spam" | "copyright" | "other"; description?: string}): Promise + findAll(limit: number, offset: number): Promise + updateStatus(id: string, status: "pending" | "reviewed" | "resolved" | "dismissed"): Promise + purgeObsolete(now: Date): Promise +} +class ReportsService { + constructor(reportsRepository: ReportsRepository): + logger: Logger + create(reporterId: string, data: CreateReportDto): Promise + findAll(limit: number, offset: number): Promise + updateStatus(id: string, status: "pending" | "reviewed" | "resolved" | "dismissed"): Promise +} +class RequestWithUser { + user: {sub?: string, username?: string, id?: … +} +class RolesGuard { + constructor(reflector: Reflector, rbacService: RbacService): + canActivate(context: ExecutionContext): Promise +} +class S3Module +class S3Service { + constructor(configService: ConfigService): + logger: Logger + minioClient: Minio.Client + bucketName: string + onModuleInit(): Promise + ensureBucketExists(bucketName: string): Promise + uploadFile(fileName: string, file: Buffer, mimeType: string, metaData?: Minio.ItemBucketMetadata, bucketName?: string): Promise + getFile(fileName: string, bucketName?: string): Promise + getFileUrl(fileName: string, expiry?: number, bucketName?: string): Promise + getUploadUrl(fileName: string, expiry?: number, bucketName?: string): Promise + deleteFile(fileName: string, bucketName?: string): Promise + getFileInfo(fileName: string, bucketName?: string): Promise + moveFile(sourceFileName: string, destinationFileName: string, sourceBucketName?: string, destinationBucketName?: string): Promise + getPublicUrl(storageKey: string): string +} +class ScanResult { + isInfected: boolean + virusName: string +} +class ScanResult { + isInfected: boolean + virusName: string +} +class SessionData { + accessToken: string + refreshToken: string + userId: string +} +class SessionsModule +class SessionsRepository { + constructor(databaseService: DatabaseService): + create(data: {userId: string; refreshToken: string; userAgent?: string; ipHash?: string | null; expiresAt: Date}): Promise + findValidByRefreshToken(refreshToken: string): Promise + update(sessionId: string, data: Record): Promise + revoke(sessionId: string): Promise + revokeAllByUserId(userId: string): Promise + purgeExpired(now: Date): Promise +} +class SessionsService { + constructor(sessionsRepository: SessionsRepository, hashingService: HashingService, jwtService: JwtService): + createSession(userId: string, userAgent?: string, ip?: string): Promise + refreshSession(oldRefreshToken: string): Promise + revokeSession(sessionId: string): Promise + revokeAllUserSessions(userId: string): Promise +} +class TagInDb +class TagsController { + constructor(tagsService: TagsService): + findAll(limit: number, offset: number, query?: string, sort?: "popular" | "recent"): Promise +} +class TagsModule +class TagsRepository { + constructor(databaseService: DatabaseService): + findAll(options: {limit: number; offset: number; query?: string; sortBy?: "popular" | "recent"}): Promise +} +class TagsService { + constructor(tagsRepository: TagsRepository): + logger: Logger + findAll(options: {limit: number; offset: number; query?: string; sortBy?: "popular" | "recent"}): Promise +} +class UpdateCategoryDto +class UpdateConsentDto { + termsVersion: string + privacyVersion: string +} +class UpdateReportStatusDto { + status: "pending" | "reviewed" | "resolved" | "… +} +class UpdateUserDto { + displayName: string + bio: string + avatarUrl: string + status: "active" | "verification" | "suspended"… + role: string +} +class UploadContentDto { + type: "meme" | "gif" + title: string + categoryId: string + tags: string[] +} +class UserInDb +class UsersController { + constructor(usersService: UsersService, authService: AuthService): + findAll(limit: number, offset: number): Promise<{data: any, totalCount: any}> + findPublicProfile(username: string): Promise + findMe(req: AuthenticatedRequest): Promise + exportMe(req: AuthenticatedRequest): Promise + updateAvatar(req: AuthenticatedRequest, file: Express.Multer.File): Promise + updateConsent(req: AuthenticatedRequest, consentDto: UpdateConsentDto): Promise + removeMe(req: AuthenticatedRequest): Promise + removeAdmin(uuid: string): Promise + updateAdmin(uuid: string, updateUserDto: UpdateUserDto): Promise + setup2fa(req: AuthenticatedRequest): Promise<{secret: string, qrCodeDataUrl:… + enable2fa(req: AuthenticatedRequest, token: string): Promise<{message: string}> + disable2fa(req: AuthenticatedRequest, token: string): Promise<{message: string}> +} +class UsersModule +class UsersRepository { + constructor(databaseService: DatabaseService): + create(data: {username: string; email: string; passwordHash: string; emailHash: string}): Promise + findByEmailHash(emailHash: string): Promise + findOneWithPrivateData(uuid: string): Promise + countAll(): Promise + findAll(limit: number, offset: number): Promise + findByUsername(username: string): Promise + findOne(uuid: string): Promise + update(uuid: string, data: Partial): Promise + getTwoFactorSecret(uuid: string): Promise + getUserContents(uuid: string): Promise + getUserFavorites(uuid: string): Promise + softDeleteUserAndContents(uuid: string): Promise + purgeDeleted(before: Date): Promise +} +class UsersService { + constructor(usersRepository: UsersRepository, cacheManager: Cache, rbacService: RbacService, mediaService: IMediaService, s3Service: IStorageService): + logger: Logger + clearUserCache(username?: string): Promise + create(data: {username: string; email: string; passwordHash: string; emailHash: string}): Promise + findByEmailHash(emailHash: string): Promise + findOneWithPrivateData(uuid: string): Promise + findAll(limit: number, offset: number): Promise<{data: any, totalCount: any}> + findPublicProfile(username: string): Promise + findOne(uuid: string): Promise + update(uuid: string, data: UpdateUserDto): Promise + updateAvatar(uuid: string, file: Express.Multer.File): Promise + updateConsent(uuid: string, termsVersion: string, privacyVersion: string): Promise + setTwoFactorSecret(uuid: string, secret: string): Promise + toggleTwoFactor(uuid: string, enabled: boolean): Promise + getTwoFactorSecret(uuid: string): Promise + exportUserData(uuid: string): Promise +} +class Verify2faDto { + userId: string + token: string +} +class VideoProcessorStrategy { + logger: Logger + canHandle(mimeType: string): boolean + process(buffer: Buffer, options?: {format: "webm" | "av1"}): Promise +} + +AdminController -[#595959,dashed]-> AdminService +AdminService -[#595959,dashed]-> CategoriesRepository +AdminService -[#595959,dashed]-> ContentsRepository +AdminService -[#595959,dashed]-> UsersRepository +AllExceptionsFilter -[#595959,dashed]-> RequestWithUser +ApiKeysController -[#595959,dashed]-> ApiKeysService +ApiKeysController -[#595959,dashed]-> AuthenticatedRequest +ApiKeysController -[#595959,dashed]-> CreateApiKeyDto +ApiKeysRepository -[#595959,dashed]-> DatabaseService +ApiKeysService -[#595959,dashed]-> ApiKeysRepository +ApiKeysService -[#595959,dashed]-> ApiKeysService +ApiKeysService -[#595959,dashed]-> HashingService +AppController -[#595959,dashed]-> AppService +AppModule -[#595959,dashed]-> CrawlerDetectionMiddleware +AppModule -[#595959,dashed]-> HTTPLoggerMiddleware +AuthController -[#595959,dashed]-> AuthService +AuthController -[#595959,dashed]-> BootstrapService +AuthController -[#595959,dashed]-> LoginDto +AuthController -[#595959,dashed]-> RegisterDto +AuthController -[#595959,dashed]-> SessionData +AuthController -[#595959,dashed]-> Verify2faDto +AuthGuard -[#595959,dashed]-> JwtService +AuthGuard -[#595959,dashed]-> SessionData +AuthService -[#595959,dashed]-> AuthService +AuthService -[#595959,dashed]-> HashingService +AuthService -[#595959,dashed]-> JwtService +AuthService -[#595959,dashed]-> LoginDto +AuthService -[#595959,dashed]-> RegisterDto +AuthService -[#595959,dashed]-> SessionsService +AuthService -[#595959,dashed]-> UsersService +BootstrapService -[#595959,dashed]-> BootstrapService +BootstrapService -[#595959,dashed]-> RbacService +BootstrapService -[#595959,dashed]-> UsersService +CategoriesController -[#595959,dashed]-> AuthGuard +CategoriesController -[#595959,dashed]-> CategoriesService +CategoriesController -[#595959,dashed]-> CreateCategoryDto +CategoriesController -[#595959,dashed]-> RolesGuard +CategoriesController -[#595959,dashed]-> UpdateCategoryDto +CategoriesRepository -[#595959,dashed]-> CreateCategoryDto +CategoriesRepository -[#595959,dashed]-> DatabaseService +CategoriesRepository -[#595959,dashed]-> UpdateCategoryDto +CategoriesService -[#595959,dashed]-> CategoriesRepository +CategoriesService -[#595959,dashed]-> CategoriesService +CategoriesService -[#595959,dashed]-> CreateCategoryDto +CategoriesService -[#595959,dashed]-> UpdateCategoryDto +ContentsController -[#595959,dashed]-> AuthGuard +ContentsController -[#595959,dashed]-> AuthenticatedRequest +ContentsController -[#595959,dashed]-> ContentsService +ContentsController -[#595959,dashed]-> CreateContentDto +ContentsController -[#595959,dashed]-> OptionalAuthGuard +ContentsController -[#595959,dashed]-> RolesGuard +ContentsController -[#595959,dashed]-> UploadContentDto +ContentsRepository -[#595959,dashed]-> DatabaseService +ContentsRepository -[#595959,dashed]-> FindAllOptions +ContentsRepository -[#595959,dashed]-> NewContentInDb +ContentsService -[#595959,dashed]-> ContentsRepository +ContentsService -[#595959,dashed]-> ContentsService +ContentsService -[#595959,dashed]-> CreateContentDto +ContentsService -[#595959,dashed]-> IMediaService +ContentsService -[#595959,dashed]-> IStorageService +ContentsService -[#595959,dashed]-> MediaProcessingResult +ContentsService -[#595959,dashed]-> MediaService +ContentsService -[#595959,dashed]-> S3Service +ContentsService -[#595959,dashed]-> UploadContentDto +CryptoService -[#595959,dashed]-> EncryptionService +CryptoService -[#595959,dashed]-> HashingService +CryptoService -[#595959,dashed]-> JwtService +CryptoService -[#595959,dashed]-> PostQuantumService +DatabaseService -[#595959,dashed]-> DatabaseService +EncryptionService -[#595959,dashed]-> EncryptionService +FavoritesController -[#595959,dashed]-> AuthenticatedRequest +FavoritesController -[#595959,dashed]-> FavoritesService +FavoritesRepository -[#595959,dashed]-> DatabaseService +FavoritesService -[#595959,dashed]-> FavoritesRepository +FavoritesService -[#595959,dashed]-> FavoritesService +HealthController -[#595959,dashed]-> DatabaseService +IMediaProcessorStrategy -[#595959,dashed]-> MediaProcessingResult +IMediaService -[#595959,dashed]-> MediaProcessingResult +IMediaService -[#595959,dashed]-> ScanResult +ImageProcessorStrategy -[#008200,dashed]-^ IMediaProcessorStrategy +ImageProcessorStrategy -[#595959,dashed]-> ImageProcessorStrategy +ImageProcessorStrategy -[#595959,dashed]-> MediaProcessingResult +JwtService -[#595959,dashed]-> JwtService +MailService -[#008200,dashed]-^ IMailService +MailService -[#595959,dashed]-> MailService +MediaController -[#595959,dashed]-> MediaController +MediaController -[#595959,dashed]-> S3Service +MediaService -[#595959,dashed]-> ClamScanner +MediaService -[#008200,dashed]-^ IMediaService +MediaService -[#595959,dashed]-> ImageProcessorStrategy +MediaService -[#595959,dashed]-> MediaProcessingResult +MediaService -[#595959,dashed]-> MediaService +MediaService -[#595959,dashed]-> ScanResult +MediaService -[#595959,dashed]-> VideoProcessorStrategy +OptionalAuthGuard -[#595959,dashed]-> JwtService +OptionalAuthGuard -[#595959,dashed]-> SessionData +PurgeService -[#595959,dashed]-> ContentsRepository +PurgeService -[#595959,dashed]-> PurgeService +PurgeService -[#595959,dashed]-> ReportsRepository +PurgeService -[#595959,dashed]-> SessionsRepository +PurgeService -[#595959,dashed]-> UsersRepository +RbacRepository -[#595959,dashed]-> DatabaseService +RbacService -[#595959,dashed]-> RbacRepository +RbacService -[#595959,dashed]-> RbacService +ReportsController -[#595959,dashed]-> AuthGuard +ReportsController -[#595959,dashed]-> AuthenticatedRequest +ReportsController -[#595959,dashed]-> CreateReportDto +ReportsController -[#595959,dashed]-> ReportsService +ReportsController -[#595959,dashed]-> RolesGuard +ReportsController -[#595959,dashed]-> UpdateReportStatusDto +ReportsRepository -[#595959,dashed]-> DatabaseService +ReportsService -[#595959,dashed]-> CreateReportDto +ReportsService -[#595959,dashed]-> ReportsRepository +ReportsService -[#595959,dashed]-> ReportsService +RolesGuard -[#595959,dashed]-> RbacService +S3Service -[#008200,dashed]-^ IStorageService +S3Service -[#595959,dashed]-> S3Service +SessionsRepository -[#595959,dashed]-> DatabaseService +SessionsService -[#595959,dashed]-> HashingService +SessionsService -[#595959,dashed]-> JwtService +SessionsService -[#595959,dashed]-> SessionsRepository +TagsController -[#595959,dashed]-> TagsService +TagsRepository -[#595959,dashed]-> DatabaseService +TagsService -[#595959,dashed]-> TagsRepository +TagsService -[#595959,dashed]-> TagsService +UsersController -[#595959,dashed]-> AuthGuard +UsersController -[#595959,dashed]-> AuthService +UsersController -[#595959,dashed]-> AuthenticatedRequest +UsersController -[#595959,dashed]-> RolesGuard +UsersController -[#595959,dashed]-> UpdateConsentDto +UsersController -[#595959,dashed]-> UpdateUserDto +UsersController -[#595959,dashed]-> UsersService +UsersRepository -[#595959,dashed]-> DatabaseService +UsersService -[#595959,dashed]-> IMediaService +UsersService -[#595959,dashed]-> IStorageService +UsersService -[#595959,dashed]-> MediaService +UsersService -[#595959,dashed]-> RbacService +UsersService -[#595959,dashed]-> S3Service +UsersService -[#595959,dashed]-> UpdateUserDto +UsersService -[#595959,dashed]-> UsersRepository +UsersService -[#595959,dashed]-> UsersService +VideoProcessorStrategy -[#008200,dashed]-^ IMediaProcessorStrategy +VideoProcessorStrategy -[#595959,dashed]-> MediaProcessingResult +VideoProcessorStrategy -[#595959,dashed]-> VideoProcessorStrategy +@enduml