Add file module for uploads and storage

- File model + migration: file metadata, content lives in object storage
- IFileStorage port with local disk and S3-compatible (RustFS/MinIO) adapters
- POST /files, GET /files/:id/download, DELETE /files/:id
- MIME sniffed from content; only safe images served inline, nosniff header
- FileOwnerRegistry: owner modules register to grant visibility and keep
  their files from GC; FileGcService removes orphaned files hourly
- TransformResponseInterceptor passes StreamableFile through

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
vadimandClaude Opus 5.5 committed 2026-10-02 09:17:55 +03:00
1 parent 5cfc8986d7
commit f32c6e348d
49 files changed
+1992 -2

No files matched your search

+21
View File
@@ -25,6 +25,7 @@ model User {
notificationPreferences NotificationPreference[]
notificationMessages NotificationMessage[]
comments Comment[]
uploadedFiles File[]
@@unique([email])
@@map("user")
@@ -209,3 +210,23 @@ model Comment {
@@index([authorId])
@@map("comment")
}
/// Загруженный файл. Ничего не знает о владельце: к задачам, комментариям и т.п.
/// файлы привязываются через таблицы связей в модулях-владельцах.
/// Файл, на который никто не ссылается, считается сиротой и удаляется GC.
/// storageKey — ключ объекта в хранилище, имя от пользователя в него не попадает.
model File {
id String @id @default(uuid(7))
storageKey String
fileName String
mimeType String
size Int
uploaderId String?
uploader User? @relation(fields: [uploaderId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now()) @db.Timestamptz(3)
@@unique([storageKey])
@@index([uploaderId])
@@index([createdAt])
@@map("file")
}