Nieuws (Backend)
Technisch Ontwerp
De kern van de nieuwsmodule is een mediasysteem dat foto's, video's, PDF-documenten en tekstberichten met een eigen icoon aankan. Afbeeldingen worden bij upload omgezet naar WebP. Video's worden via ffmpeg getranscodeerd naar H.264/AAC MP4 (met faststart) en krijgen een WebP-thumbnail op een derde van de looptijd. Van PDF's worden pagina-previews gerasterd en wordt een opgeschoonde download-versie bewaard. De zware verwerking gebeurt asynchroon; een bericht krijgt daarom een media_status die de levenscyclus volgt.
Media-verwerkingsproces
flowchart TD
Start([Upload]) --> Type{media_type}
Type -- foto --> Img["POST /media/ multipart"]
Img --> WebP[uploaded_image_to_webp_bytes] --> S3img[(S3)]
Type -- video/pdf --> Pre["POST /media/upload-url/ presigned"]
Pre --> Direct[Browser/app upload direct naar S3]
Direct --> Complete["POST /media/complete/"]
Complete --> Queue["media_status = queued"]
Queue --> Celery{Celery / ECS}
Celery -- video --> Trans["transcode_news_video: H.264/AAC MP4 + thumbnail"]
Celery -- pdf --> Rast["rasterize_news_pdf: pagina-previews + download"]
Trans --> Ready["media_status = ready"]
Rast --> Ready
Celery -- fout --> Failed["media_status = failed + media_error"]
Failed --> Retry["POST /items/{id}/retry-transcode/"]
Retry --> Queue
Een nieuw bericht krijgt standaard een vervaldatum van 180 dagen na aanmaak. Een nachtelijke taak verwijdert verlopen berichten. Bij aanmaken of bijwerken met notifyColleagues=true gaat er een melding naar alle actieve teamleden behalve de actor.
Datamodel (ERD)
erDiagram
NewsCategory ||--o{ NewsItem : category
NewsItem ||--o{ NewsItemLike : item
User ||--o| NewsItem : created_by
User ||--o{ NewsItemLike : user
NewsCategory {
uuid id PK
string name
string color
datetime created_at
datetime updated_at
}
NewsItem {
uuid id PK
uuid category_id FK
string title
string subtitle
text content
datetime expires_at
string media_type
string media_status
string media_error
string media_s3_key
string media_raw_s3_key
string media_thumbnail_s3_key
string media_download_s3_key
string media_file_name
json media_pages
string media_icon_name
string media_icon_color
bigint created_by_id FK
datetime created_at
datetime updated_at
}
NewsItemLike {
int id PK
uuid item_id FK
bigint user_id FK
datetime created_at
}
NewsPendingUpload {
uuid id PK
string s3_key
string status
datetime created_at
}
media_type is photo, video, pdf of none; bij none toont de app een gekozen icoon (media_icon_name plus media_icon_color) in plaats van een bestand. media_status is ready, queued, processing of failed. media_raw_s3_key bevat de ruwe upload voor transcodering; media_s3_key de verwerkte versie. CHECK-constraints bewaken de media-invarianten (bij none geen S3-keys maar wel icoon-velden; bij bestandstypen een media_s3_key en media_file_name; bij pdf een download-key zodra de verwerking klaar is). NewsItemLike heeft een unieke constraint op (item, user). NewsPendingUpload (subklasse van PendingMediaUpload) volgt de levenscyclus van directe S3-uploads. De User-FK's gebruiken db_constraint=False omdat de gebruiker in een ander schema leeft; created_by is daarom een integer-PK.
API & Communicatie
Categorieen
GET /api/nieuws/categories/- alle categorieen (open voor elk teamlid).POST /api/nieuws/categories/- categorie aanmaken (news.edit).PATCH /api/nieuws/categories/{id}/- categorie bijwerken (news.edit).DELETE /api/nieuws/categories/{id}/- categorie verwijderen (news.edit);400als er nog berichten aan hangen.
Berichten
GET /api/nieuws/items/- lijst. Queryparameters:categoryId,search,includeExpired(vereistnews.edit),page,page_size,paginate. Zonder?paginate=1is de respons een platte array (achterwaarts compatibel); met?paginate=1een envelope (results,page,page_size,total,total_pages,next).POST /api/nieuws/items/- bericht aanmaken (news.edit).GET /api/nieuws/items/{id}/- detail. Verlopen berichten zijn alleen zichtbaar metnews.edit.PATCH /api/nieuws/items/{id}/- bewerken (news.edit).DELETE /api/nieuws/items/{id}/- verwijderen (news.edit).POST /api/nieuws/items/{id}/like/enDELETE /api/nieuws/items/{id}/like/- liken en unliken (elk teamlid, geen rolrecht).POST /api/nieuws/items/{id}/retry-transcode/- mislukte video-transcodering of PDF-rasterisatie opnieuw starten (news.edit).
Media-upload
POST /api/nieuws/media/- multipart-upload voor afbeeldingen; zet om naar WebP en geeft de S3-key terug (news.edit).POST /api/nieuws/media/upload-url/- presigned POST voor directe S3-upload van video of PDF (news.edit).POST /api/nieuws/media/complete/- directe upload verifieren en de transcodering/rasterisatie inplannen (news.edit).
Overig
GET /api/nieuws/ping/- heartbeat ({"ok": true, "feature": "nieuws"}).
Achtergrondtaken
cleanup_expired_news_items- verwijdert nachtelijk verlopen berichten over alle tenants.transcode_news_video- transcodeert video naar H.264/AAC MP4 plus thumbnail.rasterize_news_pdf- rastert PDF naar pagina-previews en een download-versie.reconcile_stuck_news_videos- markeert berichten die te lang inqueued/processingblijven alsfailed.cleanup_news_pending_uploads- ruimt verlatenNewsPendingUpload-rijen en hun S3-objecten op.
Foutafhandeling & Statuscodes
400- ongeldige data, bestand te groot of buiten de limieten (afbeelding 10 MB, video 150 MB / 120 s, PDF 25 MB / 50 pagina's), of een categorie verwijderen die nog berichten heeft.401- geen geldig token.403- mutatie zondernews.edit.404- bericht of categorie bestaat niet (meer) of valt buiten de tenant.202- geaccepteerd maar nog in verwerking (directe upload afronden, retry-transcode).
Autorisatie & Beveiliging
- Data is strikt gescheiden per apotheek (tenant isolation); elke actie loopt via JWT.
- Lezen en liken staan open voor elk geauthenticeerd teamlid. Alle inhoudswijzigingen (berichten, categorieen, media, retry) vereisen
news.edit. - Tekstinhoud wordt server-side gesaneerd met bleach volgens een vaste allowlist (TipTap StarterKit:
p,h2,h3,ul,ol,li,blockquote,hr,a,strong,em,u,br; alleentext-alignals stijl). De inhoud is begrensd opNEWS_CONTENT_MAX_CHARS(50000). - Media-keys worden bij upload gecontroleerd op het tenant-prefix zodat cross-tenant S3-paden worden geweigerd. Signed URLs worden alleen meegegeven zodra de media
readyis.
Bestandsstructuur & Verantwoordelijkheden
models.py-NewsCategory,NewsItem,NewsItemLike,NewsPendingUploadmet de media-invarianten.views.py- categorie-, bericht-, like-, media- en ping-endpoints met de in-view rolrechtcontrole.services.py- zichtbaarheid en zoeken, CRUD, like/unlike, beeldverwerking, video-transcodering, PDF-rasterisatie en het inplannen van de taken.serializers.py- in- en output-serializers inclusief de media-payload en de bleach-sanering.tasks.py- de Celery-taken voor opschoning, transcodering, rasterisatie en reconciliatie.urls.py- routedefinities.
Belangrijke bestanden
backend/features/nieuws/models.pybackend/features/nieuws/services.pybackend/features/nieuws/views.pybackend/features/nieuws/tasks.py