Skip to main content

Media

Updated: 2026-09-14 — post/story land in bucket private and are returned as presigned URLs; MediaProcessingWorker polls PostgreSQL (not Kafka).

Bellamy Book uses a Media layer for user and content assets: avatars, wallpapers (cover images), posts, stories, chat, tickets, and blog images/files. Media files are stored in MinIO or Cloudflare R2 (switchable via Storage:Provider config — see Storage Configuration). The API exposes upload endpoints and URLs for reading; MediaProcessingWorker polls pending MediaFile rows in PostgreSQL and processes uploads asynchronously (thumbnails, transcoding).


Overview​

  • Uploads: User avatar, wallpaper, post/story media; default avatar/wallpaper (admin). Blog images/files via api/Blog. Chat/ticket via their controllers.
  • API: api/media — upload/read helpers; api/media/performance — stats (admin).
  • Storage buckets: public, private, temp, files, support, backups. Temp cleanup: TempFileCleanupJob. Full setup: Storage, R2 Setup (includes Dev vs Prod env).

Where objects end up​

MediaFinal bucketHow the client loads it
Post image/videoprivateFeed/story APIs return presigned S3 URLs
Story image/videoprivateSame (presigned)
Avatar / coverpublicDirect CDN URL (R2__PublicUrl / MinIO public URL)
Comment / chat thumbspublic (chat often via /api/chat/media stream)CDN or API stream
Blog imagespublicPrefer /api/blogs/images/{id} stream; DTO may also expose CDN
Ticket imagespublic (after process); upload stages in temp/api/tickets/images/…

API Endpoints​

Uploads (api/media)​

MethodEndpointDescription
POST/api/media/avatar/{userId}Upload user avatar
POST/api/media/wallpaper/{userId}Upload user cover/wallpaper
POST/api/media/video/{userId}Upload user video
POST/api/media/story/{userId}Upload story media
POST/api/media/default/avatarUpload default avatar (admin)
POST/api/media/default/wallpaperUpload default wallpaper (admin)

Read (api/media)​

MethodEndpointDescription
GET/api/media/avatar/{userId}Get user avatar URL
GET/api/media/wallpaper/{userId}Get user wallpaper URL
GET/api/media/{mediaFileId}Get media file by ID
GET/api/media/{mediaFileId}/statusGet processing status
GET/api/media/user/{userId}Get media files for user
GET/api/media/default/avatarGet default avatar URL
GET/api/media/default/wallpaperGet default wallpaper URL

Performance (api/media/performance) — Admin/Monitoring​

MethodEndpointDescription
GET/api/media/performance/statsAggregate media stats
GET/api/media/performance/errorsProcessing errors
GET/api/media/performance/stats/by-typeStats by media type

Processing​

  • MediaProcessingWorker polls pending MediaFile rows (not a Kafka consumer), generates sizes / transcodes video, writes to the final bucket, and marks status Completed.
  • Feed and story queries load media via GetMediaFilesByIdsAsync / GetMediaFileByIdAsync, which presign any …/private/… URLs before returning DTOs.
  • TempFileCleanupJob (Quartz) deletes files in the temp bucket older than 24 hours.
Gray images on production R2

If post/story stay gray: deploy an API build that includes R2 region auto + private URL presigning, and ensure the R2 private bucket exists. Cloudflare Worker update is optional for post/story (presign bypasses the Worker). See R2 Setup.