feat(media): storage for images and audio in notes (M1)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m4s

- Table media (migration 5), files in $DATA_DIR/media/<docId>/.
- POST /documents/{id}/media (write access), POST .../media/fetch copies an
  external https image (SSRF-guarded, not in the demo), GET /media/{id}
  with range requests for readers of a note that shows the file.
- Format detected from the content (no SVG); own sandbox CSP on answers.
- Blocks point to files through their content (media:<id>), so the note
  sync needs no change; compaction removes files no block mentions any
  more after a day. Demo reset and demo start clear the folder.
- Limits MEDIA_MAX_IMAGE_MB, MEDIA_MAX_AUDIO_MB, MEDIA_QUOTA_MB,
  MEDIA_DEMO_MAX_MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-29 22:57:59 +01:00
1 parent e385a44fb9
commit 177fa49363
16 files changed
+637

No files matched your search

+18
View File
@@ -46,6 +46,18 @@ type Config struct {
// Speech recognition, configured only by environment (STT_*)
Speech SpeechConfig
// Images and audio in notes (MEDIA_*)
Media MediaConfig
}
// MediaConfig limits uploads of images and audio recordings in notes. Sizes in MB; QuotaMB 0 = no limit
// per user. The demo uses DemoMaxMB for every file.
type MediaConfig struct {
MaxImageMB int
MaxAudioMB int
QuotaMB int
DemoMaxMB int
}
// SpeechConfig: with STT_URL set, voice input is transcribed by a self-hosted, OpenAI-compatible
@@ -222,6 +234,12 @@ func Load() *Config {
PerUserPerHour: envInt("STT_PER_USER_PER_HOUR", 60, 1, 100000),
TimeoutSeconds: envInt("STT_TIMEOUT_SECONDS", 120, 5, 3600),
},
Media: MediaConfig{
MaxImageMB: envInt("MEDIA_MAX_IMAGE_MB", 15, 1, 1000),
MaxAudioMB: envInt("MEDIA_MAX_AUDIO_MB", 50, 1, 2000),
QuotaMB: envInt("MEDIA_QUOTA_MB", 0, 0, 10000000),
DemoMaxMB: envInt("MEDIA_DEMO_MAX_MB", 3, 1, 100),
},
Legal: LegalTexts{
ImprintURL: legalURL("LEGAL_IMPRINT_URL"),
ImprintContent: legalContent("LEGAL_IMPRINT_CONTENT"),