Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

13 - State Management (Webhooks Module)

Per-screen Cubit/Bloc design on top of 00-shared/06 conventions (stack: flutter_bloc + get_it; server state via dio repository; LoadState = Initial/Loading/Success/Error(ApiException)). Mermaid diagrams included.


1. Cubit map

CubitScreen(s) (05)Data
WebhookListCubit1List<Webhook>, per-row actions
WebhookFormCubit2/4form model (name, url, events, secret, enabled), field errors, submit
WebhookDetailCubit3Webhook, metrics {total, success, failed, pending}
DeliveryLogListCubit3 preview / 5List<DeliveryLog> (≤ 50, webhooks.service.ts:88-93)
WebhookActionCubit7/8/9test / retry / pause / resume async actions

Repositories (WebhookRepository, DeliveryLogRepository in features/webhooks/data/) are the only layer touching HTTP; they map envelopes to models and throw ApiException(status, message) (00-shared/06 §2-3). Models: Webhook (webhook.schema.ts:8-30), DeliveryLog (webhook-delivery-log.schema.ts:7-39).

2. WebhookListCubit

stateDiagram-v2
    [*] --> Initial
    Initial --> Loading: fetch()
    Loading --> Success: GET /webhooks (200)
    Loading --> Error: 401/500
    Success --> Loading: pullToRefresh
    Success --> Error: refetch fails (keep stale)
    Success --> Success: delete OK (optimistic)
    Success --> Success: delete 404/500 (rollback + snackbar)
  • Fetch: GET /api/v1/webhooks - no pagination, sort createdAt: -1 (webhooks.service.ts:35-37).
  • Delete flow: confirm dialog → DELETE /api/v1/webhooks/:id (webhooks.controller.ts:45-50) → optimistic row removal; rollback on error.

3. WebhookDetailCubit (config + metrics)

sequenceDiagram
    participant S as Screen
    participant D as WebhookDetailCubit
    participant R as Repo
    participant A as API
    S->>D: load(id)
    D->>R: getById(id) + metrics(id)
    R->>A: GET /webhooks/:id  |  GET /webhooks/:id/metrics
    A-->>R: doc  |  {total, success, failed, pending}
    R-->>D: Webhook + Metrics
    D-->>S: Success(doc, metrics) / Error(404)
  • Independent per-block load: webhook doc and metrics each have their own LoadState; metrics failure never blanks config (webhooks.service.ts:142-156).
  • Pause/resume: WebhookActionCubitPOST /webhooks/:id/pause|resume (webhooks.controller.ts:78-90) → optimistic enabled flip, rollback on error (webhooks.service.ts:158-161).

4. Delivery/Retry state machine (the core diagram)

stateDiagram-v2
    [*] --> Idle
    Idle --> Enqueueing: test() / retryLatest()
    Enqueueing --> Queued: 200 {message: "…queued"}
    Enqueueing --> NoFailure: 404 "No failed deliveries to retry" (retry only)
    Enqueueing --> Error: 401/500
    Queued --> PendingRow: watch logs → newest row status=pending
    PendingRow --> SuccessRow: worker 2xx → status=success
    PendingRow --> FailedRow: worker non-2xx/timeout → status=failed
    FailedRow --> Enqueueing: retryLatest() (manual retry)
    FailedRow --> Exhausted: 3 BullMQ attempts used (job rethrow)
    Exhausted --> Enqueueing: manual retry re-queues same payload
    Queued --> Stale: no row after 20s → "Still queued?" hint
    Stale --> PendingRow: row appears
  • Test: POST /webhooks/:id/test (webhooks.controller.ts:65-70) - job eventType: 'WebhookTested' (webhooks.service.ts:130-138).
  • Retry: POST /webhooks/:id/retry (:58-63); server requires a latest failed log (webhooks.service.ts:103-108); re-queues that attempt's original payload with correlationId: '' (:110-118).
  • Worker side (server truth): attempts 3 / exponential backoff 5 s (webhooks.service.ts:80-83); 10 s fetch timeout (webhook-delivery.worker.ts:81); status transitions pending → success|failed (webhooks.service.ts:163-193, webhook-delivery-log.schema.ts:18-23).
  • Client implication: a Queued state is terminal for the HTTP action; all delivery transitions are observed via logs polling, not HTTP responses.

5. DeliveryLogListCubit

stateDiagram-v2
    [*] --> Initial
    Initial --> Loading: fetch(webhookId)
    Loading --> Loaded(list): GET /webhooks/:id/logs (≤ 50)
    Loading --> Error: 401/500
    Loaded --> Loading: pullToRefresh / afterTest / afterRetry
    Loaded --> DetailSheet: tap row (no extra fetch)
  • Logs endpoint: sort createdAt: -1, limit 50 (webhooks.service.ts:88-93).
  • After test/retry the cubit refreshes on a short delay (2-3 s) then again on user pull; a pending newest row keeps a light "processing" indicator (webhook-delivery-log.schema.ts:22-23).

6. WebhookFormCubit

stateDiagram-v2
    [*] --> Idle
    Idle --> Validating: submit()
    Validating --> Submitting: client valid
    Validating --> FieldError: invalid (name/url/events/secret)
    Submitting --> Done: POST 201 / PATCH 200 (pop to detail)
    Submitting --> ServerError: 400 → field messages; 401/500 → error state
    Done --> [*]
  • Create: POST /api/v1/webhooks (webhooks.controller.ts:21-25) body from CreateWebhookDto (create-webhook.dto.ts:11-34).
  • Edit: PATCH /api/v1/webhooks/:id with dirty fields only (UpdateWebhookDto, update-webhook.dto.ts:4); 404 → error state.
  • Server 400 messages mapped to fields (mirrors class-validator rules, 08 §4).

7. Cross-cutting

  • Cache: webhook list cached in memory per tenant; detail/logs read cache-first then refresh (offline tolerance, 00-shared/10 §2).
  • Events as hints: the client does NOT consume the domain-event stream for webhook UI; server state is the only truth (logs polling).
  • Permission gating: cubits expose canCreate/canRead/canUpdate/canDelete from RBAC (permissions.constants.ts:89-92); UI hides FAB/actions/menus accordingly (e.g. no "New webhook" without webhook.create).
  • Secret handling: secret kept only in memory in DetailCubit; never cached to disk; forms never pre-fill it (blank = keep, 08 §6).
  • Planned cubits: inbound-receiver list (planned) per IMPLEMENTATION_PLAN.md:48 - no data contract yet.