Ga naar inhoud

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); 400 als er nog berichten aan hangen.

Berichten

  • GET /api/nieuws/items/ - lijst. Queryparameters: categoryId, search, includeExpired (vereist news.edit), page, page_size, paginate. Zonder ?paginate=1 is de respons een platte array (achterwaarts compatibel); met ?paginate=1 een 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 met news.edit.
  • PATCH /api/nieuws/items/{id}/ - bewerken (news.edit).
  • DELETE /api/nieuws/items/{id}/ - verwijderen (news.edit).
  • POST /api/nieuws/items/{id}/like/ en DELETE /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 in queued/processing blijven als failed.
  • cleanup_news_pending_uploads - ruimt verlaten NewsPendingUpload-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 zonder news.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; alleen text-align als stijl). De inhoud is begrensd op NEWS_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 ready is.

Bestandsstructuur & Verantwoordelijkheden

  • models.py - NewsCategory, NewsItem, NewsItemLike, NewsPendingUpload met 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.py
  • backend/features/nieuws/services.py
  • backend/features/nieuws/views.py
  • backend/features/nieuws/tasks.py

API & Communicatie (Swagger)