Ga naar inhoud

Profiel (Backend)

Technisch Ontwerp

De backend van de profielmodule beheert per gebruiker een ProfileSettings-rij in het tenant-schema. De module is bewust dun: er is geen aparte servicelaag, alle logica zit in de views (features/profile/views.py).

  • Privacy-master: zet een gebruiker visible_to_team op false, dan worden ook share_phone, share_email, share_birthday_with_team en share_work_days_with_team automatisch op false gezet binnen dezelfde PATCH. Andere modules (Team, Verjaardagen, Agenda) lezen deze velden bij het serializen van collega's.
  • Avatar-upload: synchroon. De frontend resized de afbeelding al naar 512 bij 512. De backend valideert magic bytes plus pixel- en bytecap, decodeert met Pillow, encodeert opnieuw als WebP (quality=80, method=4) en plaatst het object in S3 onder de sleutel <tenant_schema>/user/<user_id>/<uuid>.webp. De vorige sleutel wordt na succesvolle upload best-effort verwijderd. De response bevat direct een signed avatar_url. Er is geen Celery-task en geen 202/poll-flow.
  • Pushvoorkeuren: opgeslagen als JSON-blob (push_preferences) plus master-flag (push_disabled_all). De keys komen uit notifications.constants.PUSH_NOTIFICATION_KEYS (15 categorieen). Defaults staan op true, behalve atlas_response_ready, review_invitation_accepted, review_invitation_declined en review_letter_failed (die staan default uit).
  • E-mailvoorkeuren: idem (email_preferences, email_disabled_all). De keys komen uit EMAIL_NOTIFICATION_KEYS (13 categorieen). Defaults staan op false, behalve birthday_self en birthday_colleague. De master email_disabled_all staat standaard op true (opt-in).
  • Atlas push prompt: het veld atlas_push_prompt_seen markeert dat de gebruiker eenmalig de Atlas-banner heeft gezien, zodat die niet opnieuw verschijnt.
  • Thema: theme-voorkeur wordt expliciet niet opgeslagen, omdat licht of donker een apparaat-instelling is.
  • Gegevensexport: het profielscherm biedt een zelf-service export (GET /api/profile/data-export/) voor inzage en dataportabiliteit (AVG art 15 en 20). Deze leest gegevens samen uit meerdere bronnen maar schrijft niets in ProfileSettings.
  • Wachtwoord, 2FA en sessies: deze acties leven in de auth-module (/api/auth/security/..., /api/auth/devices/...) en worden vanuit het profielscherm aangeroepen, maar horen niet bij dit datamodel.
  • Gekoppelde accounts: de profielroute toont en beheert de eigen OIDC-koppelingen (ZORG-ID, Google, Apple) via de auth-OIDC-endpoints. De koppelingen leven in het auth-datamodel, niet in ProfileSettings. Zie de authenticatie-documentatie voor details.
flowchart TD
    A[POST /api/profile/avatar/] --> B[AvatarUploadIn validate]
    B --> C[_validate_uploaded_image: size + magic bytes]
    C --> D[Pillow decode + pixel cap check]
    D --> E[Encode WebP quality=80]
    E --> F[put_object S3 key tenant/user/uuid.webp]
    F --> G[ProfileSettings.avatar_s3_key = key]
    G --> H[delete_object oude key best-effort]
    H --> I[audit_logger.log AVATAR_UPLOADED]
    I --> J[Response avatar_url signed]

Datamodel (ERD)

erDiagram
    User ||--|| ProfileSettings : "bezit"
    ProfileSettings {
        bigint id PK
        bigint user_id FK
        boolean share_phone
        boolean share_email
        boolean visible_to_team
        boolean share_birthday_with_team
        boolean share_work_days_with_team
        boolean receive_team_birthday_emails
        boolean haptics_enabled
        boolean push_disabled_all
        json push_preferences
        boolean atlas_push_prompt_seen
        boolean email_disabled_all
        json email_preferences
        string avatar_s3_key
        datetime updated_at
    }

ProfileSettings heeft een OneToOneField naar settings.AUTH_USER_MODEL. De rij wordt lazily aangemaakt via get_or_create bij de eerste GET of PATCH. Het veld avatar_s3_key bevat alleen de S3-sleutel; de signed URL wordt per response opnieuw gegenereerd door ProfileSettingsOut.get_avatar_url.

API & Communicatie

Alle endpoints staan onder /api/profile/ en vereisen IsAuthenticated. JSON in en JSON uit, behalve avatar-upload (multipart).

  • GET /api/profile/ping/: healthcheck, geeft {ok: true, feature: "profile"}.
  • GET /api/profile/settings/: huidige privacy- en voorkeurinstellingen plus signed avatar_url.
  • PATCH /api/profile/settings/: optionele velden share_phone, share_email, visible_to_team, share_birthday_with_team, share_work_days_with_team, receive_team_birthday_emails, haptics_enabled. Bij visible_to_team=false worden afhankelijke deel-toggles geforceerd op false.
  • POST /api/profile/avatar/: multipart image. Synchroon: valideert, converteert naar WebP, plaatst in S3, slaat sleutel op en geeft {avatar_url} terug. Geen polling.
  • DELETE /api/profile/avatar/: verwijdert het object in S3 en leegt avatar_s3_key. Response {avatar_url: ""}.
  • GET /api/profile/push-preferences/: master-flag, JSON-map per categorie en atlas_push_prompt_seen.
  • PATCH /api/profile/push-preferences/: optionele velden push_disabled_all, push_preferences (object met booleans), atlas_push_prompt_seen. De backend doet een merge in plaats van replace, zodat onbekende keys niet verloren gaan.
  • GET /api/profile/email-preferences/: master-flag plus JSON-map.
  • PATCH /api/profile/email-preferences/: optionele velden email_disabled_all, email_preferences (merge-update).
  • GET /api/profile/data-export/: zelf-service gegevensexport (AVG art 15 en 20). Antwoord bevat schema_version, user, profile_settings, reviews_created_by_user en audit_entries_about_user. De export is afgeschermd met een aparte rate-throttle (ScopedRateThrottle, scope data_export). Patientgegevens vallen er bewust buiten.

Wachtwoord, 2FA, sessies en gekoppelde accounts zijn auth-endpoints en horen niet bij dit module-datamodel.

Foutafhandeling & Statuscodes

  • 200 OK: succesvolle GET, PATCH, avatar-upload of avatar-delete.
  • 400 Bad Request: validatiefout op een veld, te grote afbeelding, niet-ondersteund bestandstype, decompression-bomb, of ongeldige boolean-map (Voorkeur '<key>' moet boolean zijn, Te veel voorkeuren (max 250), Voorkeur key is te lang (max 80)).
  • 401 Unauthorized: ontbrekend of verlopen JWT.
  • 502 Bad Gateway: S3 weigerde de upload (Upload naar S3 mislukt.).

Avatarvalidatie controleert magic bytes voor PNG, JPEG, GIF, WebP en HEIC, met een harde cap van 5 MB en 10 megapixels.

Autorisatie & Beveiliging

  • Tenant-isolatie: ProfileSettings leeft per tenant-schema. JWT bevat tenant_schema en de queryset gaat altijd via request.user.
  • Eigenaarschap: een gebruiker kan alleen zijn eigen ProfileSettings lezen of muteren; views gebruiken request.user direct als sleutel.
  • Geen RBAC: profiel is een basisscherm voor elke ingelogde gebruiker en heeft geen required_permission.
  • Gekoppelde accounts koppelen of ontkoppelen verloopt via de auth-OIDC-endpoints. De profielmodule toont alleen de actie en bewaart de koppeling niet zelf.
  • S3-sleutels bevatten een UUID-suffix en zijn niet raadbaar; toegang loopt altijd via een signed URL.
  • Avatarverwerking is hard begrensd: 5 MB bytes-cap, 10 MP pixel-cap, en Pillow's MAX_IMAGE_PIXELS is op dezelfde waarde gezet om decompression-bombs te blokkeren.
  • Audit log: muterende acties worden vastgelegd via audit_logger. De gebeurtenissen zijn PROFILE_PRIVACY_CHANGED (privacy-instellingen), PROFILE_NOTIFICATIONS_CHANGED (push- of e-mailvoorkeuren, met kanaal en gewijzigde velden), AVATAR_UPLOADED (grootte en of een eerdere foto is vervangen) en DATA_EXPORTED (aantallen reviews en audit-regels).

Bestandsstructuur & Verantwoordelijkheden

  • backend/features/profile/models.py: definitie van ProfileSettings met privacy-, push- en e-mailvoorkeuren plus avatar-sleutel.
  • backend/features/profile/serializers.py: in- en uit-serializers voor settings, push, e-mail en avatar; bevat de generieke _validate_bool_dict voor JSON-maps.
  • backend/features/profile/views.py: de endpoints ping, settings, avatar, push en e-mail, avatar-validatie en WebP-conversie, S3-upload en cleanup, plus de privacy-master-cascade en de notificatie-defaults.
  • backend/features/profile/data_export.py: de zelf-service gegevensexport (UserDataExportView) met de export-serializers.
  • backend/features/profile/urls.py: route-tabel onder /api/profile/.
  • backend/features/profile/apps.py: Django app-config.
  • backend/features/profile/migrations/: schema-migraties voor ProfileSettings.
  • backend/features/profile/tests/test_profile_endpoints.py: integratietests voor settings, avatar, push en e-mail.

Belangrijke bestanden

  • backend/features/profile/models.py
  • backend/features/profile/views.py
  • backend/features/profile/serializers.py
  • backend/features/profile/urls.py
  • backend/notifications/constants.py

API & Communicatie (Swagger)