Ga naar inhoud

Agenda (Backend)

De Agenda-backend beheert afspraken, deelnemers, herinneringen en iCalendar-synchronisatie binnen de Remedice-omgeving. De logica zit in de features.agenda Django-app.

Technisch Ontwerp

De module volgt een service-layer architectuur: de views blijven dun en delegeren naar services.py.

Aanmaken en bijwerken

Bij het aanmaken of bijwerken van een agendapunt bepaalt de service de deelnemers, plant de herinneringen in via Celery en stuurt optioneel een melding.

flowchart TD
    A[POST/PATCH AgendaItem] --> B[services.create/update_agenda_item]
    B --> C{participants_mode}
    C -- everyone --> D[Alle actieve tenant-gebruikers]
    C -- selection --> E[Unie van AgendaParticipant<br/>users + rollen]
    D --> F[resolve_participants]
    E --> F
    F --> G[upsert_reminders per deelnemer]
    G --> H[ClockedSchedule + PeriodicTask<br/>per herinnering]
    F --> I{notify_participants?}
    I -- Ja --> J[notifications.enqueue_notification]
    H --> K[Celery: send_agenda_reminder op tijdstip T]

Wanneer alleen de starttijd wijzigt, herberekent _reschedule_reminders de send_at van bestaande, nog niet verzonden herinneringen; herinneringen waarvan het nieuwe tijdstip in het verleden ligt, worden verwijderd.

Zichtbaarheidslogica

get_visible_items bepaalt welke items een gebruiker mag zien:

  • Gebruikers met agenda.edit zien alle items binnen de tenant.
  • Overige gebruikers zien alleen items die zij zelf aanmaakten, items met participants_mode = everyone, of items waar zij als deelnemer (via user) of via hun rol (via role) aan gekoppeld zijn.

Datamodel (ERD)

De structuur ondersteunt flexibele deelnemer-selectie en geautomatiseerde herinneringen. De primary keys van de agenda-entiteiten zijn UUID's; verwijzingen naar accounts.User gebruiken de integer primary key van dat model.

erDiagram
    User ||--o{ AgendaItem : "created_by"
    AgendaItem ||--o{ AgendaParticipant : "heeft"
    AgendaItem ||--o{ AgendaReminder : "heeft"
    User ||--o{ AgendaParticipant : "is deelnemer"
    User ||--o{ AgendaReminder : "ontvangt"
    User ||--o| WebcalToken : "heeft"

    AgendaItem {
        uuid id PK
        string title
        text description
        string location
        datetime start "db_index"
        datetime end "nullable"
        bool all_day
        string participants_mode "everyone | selection"
        bigint created_by_id FK "nullable, SET_NULL"
        datetime created_at
        datetime updated_at
    }

    AgendaParticipant {
        uuid id PK
        uuid agenda_item_id FK
        bigint user_id FK "nullable"
        string role "nullable"
    }

    AgendaReminder {
        uuid id PK
        uuid agenda_item_id FK
        bigint user_id FK
        string reminder_offset "at_time..2w"
        datetime send_at "db_index"
        datetime sent_at "nullable"
        string celery_task_name
    }

    WebcalToken {
        uuid id PK
        bigint user_id FK "OneToOne"
        uuid token "unique, db_index"
        datetime created_at
    }
  • AgendaParticipant: een CheckConstraint dwingt af dat precies een van user of role is gezet (user XOR role). Rollen zijn waarden als apotheker, assistent, arts.
  • AgendaReminder: UniqueConstraint op (agenda_item, user, reminder_offset). Een pre_delete signaal ruimt de gekoppelde ClockedSchedule en PeriodicTask op.

API & Communicatie

De API bestaat uit losse APIView-klassen onder /api/agenda/, met tenant-isolatie via TenantEnforcedJWTAuthentication.

  • GET /api/agenda/items/: gepagineerde lijst van zichtbare items. Query: date_from, date_to (ISO 8601), page, page_size (10/50/100). Zonder datums een bereik van 60 dagen rond nu, hard begrensd op 90 dagen. Antwoord: { results, page, page_size, total, total_pages }.
  • POST /api/agenda/items/: nieuw item. Body bevat onder meer title, start, end, all_day, participants_mode, roles, people (integer User-PK's), reminders (first/second offset), notify_participants.
  • GET /api/agenda/items/{item_id}/: details van een item.
  • PATCH /api/agenda/items/{item_id}/: item bijwerken.
  • DELETE /api/agenda/items/{item_id}/: item verwijderen (204).
  • GET /api/agenda/users/: actieve gebruikers binnen de tenant voor de deelnemer-kiezer.
  • GET /api/agenda/birthdays/: aankomende verjaardagen (komende 4 weken), gefilterd op share_birthday_with_team en visible_to_team uit het profiel. De leeftijd wordt nooit meegestuurd.
  • GET /api/agenda/webcal-token/ en DELETE /api/agenda/webcal-token/: het persoonlijke iCal-token ophalen respectievelijk roteren (de oude link vervalt).
  • GET /api/agenda/webcal/{schema}/{token}/: ongeauthenticeerde iCalendar-feed (text/calendar), beveiligd via het WebcalToken. Levert de zichtbare items van de tokenhouder (30 dagen terug, 365 dagen vooruit), met ETag-gebaseerde 304 Not Modified en X-Robots-Tag: noindex, nofollow.

Foutafhandeling & Statuscodes

  • 400 Bad Request: ongeldige datumnotatie, date_to voor date_from, een bereik groter dan 90 dagen, of validatiefouten op het serializer-niveau (zoals een ontbrekende title/start).
  • 403 Forbidden: POST zonder agenda.edit; PATCH/DELETE door een gebruiker die noch eigenaar is noch agenda.edit heeft.
  • 404 Not Found: het item bestaat niet in de tenant of is niet zichtbaar voor de gebruiker. De webcal-feed geeft ook 404 bij een ongeldig token, ongeldige schema-naam of een inactieve gebruiker (om accountstatus niet te lekken).
  • 304 Not Modified: webcal-feed wanneer de If-None-Match ETag overeenkomt.
  • 429 Too Many Requests: bij overschrijden van de throttles (agenda_write op schrijfacties, webcal_feed op de feed).

Autorisatie & Beveiliging

  • Tenant Isolatie: alle queries draaien binnen het schema van de actieve tenant. De webcal-feed schakelt expliciet naar het juiste schema via schema_context en valideert de schema-naam tegen de django-tenants conventie ([a-z][a-z0-9_]{0,62}) voordat er een query draait.
  • RBAC: lezen (GET /items/, /users/, /birthdays/, /webcal-token/) staat open voor elke ingelogde gebruiker; agenda.edit verbreedt de zichtbaarheid tot alle items. POST vereist agenda.edit. PATCH en DELETE vereisen agenda.edit of eigenaarschap van het item.
  • Privacy: in everyone-modus wordt het description-veld in de detailserializer weggelaten voor gebruikers die niet de aanmaker of een expliciet gekoppelde deelnemer zijn. Verjaardagen verschijnen alleen met expliciete profieltoestemming en zonder leeftijd.
  • Webcal-feed: ongeauthenticeerd maar token-gebaseerd; toegang vervalt zodra de gekoppelde gebruiker inactief of verwijderd is.

Bestandsstructuur & Verantwoordelijkheden

  • models.py: AgendaItem, AgendaParticipant (user XOR role), AgendaReminder, WebcalToken met hun constraints en indexen.
  • services.py: get_visible_items, resolve_participants, create_agenda_item, update_agenda_item, upsert_reminders/_reschedule_reminders, get_upcoming_birthdays.
  • views.py: de APIView-klassen met permissie-checks per methode en de ongeauthenticeerde AgendaWebcalView.
  • serializers.py: validatie en JSON-transformatie, inclusief de description-privacy in AgendaItemDetailSerializer.
  • tasks.py: send_agenda_reminder (verstuurt de melding), cleanup_old_agenda_items (verwijdert items ouder dan 16 weken) en cleanup_fired_oneoff_tasks.
  • urls.py: URL-routering voor de module.

Belangrijke bestanden

  • backend/features/agenda/models.py
  • backend/features/agenda/services.py
  • backend/features/agenda/views.py

API & Communicatie (Swagger)