diff --git a/docs/adr/0001-synchronous-external-calls-mvp.md b/docs/adr/0001-synchronous-external-calls-mvp.md index 3e95cf1..d4b5946 100644 --- a/docs/adr/0001-synchronous-external-calls-mvp.md +++ b/docs/adr/0001-synchronous-external-calls-mvp.md @@ -16,12 +16,17 @@ For the MVP, OTP sends, booking notifications, and payment gateway calls run syn - Faster initial delivery with fewer moving parts. - Increased latency risk on endpoints that call external providers. -- Failures are immediately visible to clients and logged for support. +- Payment and OTP failures are surfaced to clients immediately (correct behaviour — clients need to know). +- Notification failures are absorbed: `notifications/services.py` catches provider errors, stores them as `FAILED` status, and never surfaces them to the client. A failed booking SMS does not cause the booking request to fail. This means notification failures require active monitoring rather than appearing in client-facing error rates. ## Alternatives Considered - Celery + Redis for all external calls: rejected for MVP due to infra overhead. -- Hybrid async for notifications only: rejected to keep the execution model consistent. +- Hybrid async for notifications only: not wrong in principle, deferred for operational simplicity. The three call types have genuinely different semantics: + - **Payment creation**: synchronous by design — the client needs the Moyasar redirect URL before the response returns. + - **OTP sends**: synchronous by design — users expect immediate confirmation that the code was sent. + - **Booking notifications**: fire-and-forget by nature — the booking is already committed and the client does not wait for delivery confirmation. + When notification latency becomes a problem (e.g. under load or with slow SMS providers), only notifications need to move off the request path. Payments and OTP sends should remain synchronous regardless. ## Related diff --git a/docs/architecture.md b/docs/architecture.md index 9fd0cb5..af001e2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -12,7 +12,7 @@ The Salon platform is a Django REST API backend with a React/Vite frontend, opti | **salons** | Salon catalog, services, staff, availability windows, reviews. Read-only public APIs. | | **bookings** | Booking model, validation (availability, overlap prevention), status transitions. Triggers notifications on create and status change. | | **payments** | Payment model, Moyasar integration (create, capture, refund), webhook reconciliation, idempotency. | -| **notifications** | Booking lifecycle notifications (SMS/WhatsApp). Reuses OTP providers; sends on booking created/confirmed/cancelled. | +| **notifications** | Booking lifecycle notifications (SMS/WhatsApp). Reuses OTP provider classes as an MVP shortcut; sends on booking created/confirmed/cancelled. See note below. | ## Data Model Overview @@ -35,6 +35,16 @@ User → React Frontend → Django API payments ──→ Moyasar gateway ``` +## Notification / OTP Provider Coupling (MVP Shortcut) + +`notifications/services.py` imports `PROVIDERS` from `apps.accounts.services.otp` and uses OTP provider instances (e.g. `AuthenticaOtpProvider`) to send booking SMSes. This works today because Authentica handles both authentication OTPs and general SMS delivery. + +Consequences of this coupling: +- Notifications and OTP delivery cannot independently use different providers (e.g. Twilio for OTP, Unifonic for notifications). +- The `notifications` app is conceptually coupled to the `accounts` app's auth infrastructure. + +This is an acceptable MVP shortcut. Before Phase 2, introduce a dedicated `NotificationProvider` abstraction in `notifications/` (mirroring `OtpProvider`) so the two systems can be configured and tested independently. + ## Async and Observability (MVP Decision) **Decision (MVP):** All OTP sends, booking notifications, and payment gateway calls run **synchronously** in the request/response path. No Celery, RQ, or other task queue for the initial launch. diff --git a/docs/risks.md b/docs/risks.md index 94493c2..69fe7eb 100644 --- a/docs/risks.md +++ b/docs/risks.md @@ -7,10 +7,11 @@ This file tracks known gaps and risks to address in future iterations. - OTP protections are basic; add device fingerprinting and IP throttling if needed. - Authentica OTP provider is implemented (SMS + WhatsApp via Authentica OTP); Unifonic remains a scaffold. - Social login is a placeholder. -- USERNAME_FIELD is still `email` while email can be null; verify admin/login flows. +- `USERNAME_FIELD = "email"` while `email` is nullable — concrete impact: Django admin user list shows blank for most customers (phone-only users); `create_superuser` requires email by default; DRF Simple JWT uses email as the lookup field. Fix: change `USERNAME_FIELD` to `"phone_number"` and update `UserManager.create_superuser` accordingly. ## Booking Integrity - Availability checks and overlap prevention are now enforced for staff bookings. +- **Race condition — booking overlap check is not atomic:** `validate_booking_request` runs the overlap query and returns; the view then calls `serializer.save()` in a separate step with no lock held. Two concurrent POST `/api/bookings/` requests for the same staff slot will both pass validation and both commit. Fix: wrap the overlap check and booking insert in a `select_for_update()` query (or use serializable transaction isolation) so only one request can hold the lock at a time. - No timezone handling or business hours enforcement. - No cancellation rules or refund logic.