feat: add ClinicInvitationWebController and related templates for handling clinic invitations
- Implemented ClinicInvitationWebController to manage the invitation process via web. - Added view and respond methods to handle invitation display and responses. - Created result.html.twig and view.html.twig templates for rendering invitation results and views. - Integrated CSRF protection for form submissions. - Established routes for invitation viewing and responding.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Clinic Doctor Invitation API
|
||||
|
||||
> **Prefix:** `/api/v1/admin/clinic/...` (admin) and `/api/v1/clinic-invitation/...` (public)
|
||||
> **Prefix:** `/api/v1/admin/clinic/...` (admin) and `/api/v1/clinic-invitation/...` (public JSON) and `/i/...`, `/clinic-invitation/...` (public HTML pages)
|
||||
|
||||
Admins invite doctors to clinics via SMS. The doctor receives a secure 96-char token link valid for 72 hours.
|
||||
Admins invite doctors to clinics via SMS. The doctor receives a short (12-char hex) token link valid for 72 hours. Tapping the link opens a server-rendered HTML page (Twig) where the doctor accepts or rejects the invitation (see **Web pages** at the bottom).
|
||||
|
||||
---
|
||||
|
||||
@@ -51,7 +51,8 @@ Send an invitation to a doctor (by mobile number) to join a clinic.
|
||||
```
|
||||
|
||||
> SMS is dispatched **asynchronously** via Symfony Messenger → Redis queue.
|
||||
> SMS text: `"دکتر گرامی، کلینیک {name} شما را برای همکاری دعوت کرده است.\nبرای بررسی: {link}\nاین لینک تا ۷۲ ساعت معتبر است."`
|
||||
> SMS text: `"دکتر گرامی، کلینیک {name} شما را برای همکاری دعوت کرده است.\nبرای بررسی: {link}\nاین لینک تا ۷۲ ساعت معتبر است."`
|
||||
> `{link}` = `{APP_BASE_URL}/i/{token}` — short path + 12-char token to keep the SMS small (a long URL caused Kavenegar `431`).
|
||||
|
||||
### Errors
|
||||
| Code | HTTP | Description |
|
||||
@@ -385,3 +386,39 @@ Doctor accepts or rejects an invitation from their panel (no SMS token needed).
|
||||
| `ERR_NOT_FOUND_001` | 404 | Invitation not found or not owned by doctor |
|
||||
| `ERR_NOT_FOUND_001` | 410 | Invitation expired or already used |
|
||||
| `ERR_VALIDATION_001` | 422 | action is not accept or reject |
|
||||
|
||||
---
|
||||
|
||||
## Web pages (HTML, Twig) — SMS link target
|
||||
|
||||
Public, no JWT. These render **HTML** (not JSON) and are the target of the invitation SMS link. They are served outside the `^/(api|oauth|file/upload)/` firewall (like the payment result pages). Controller: `src/ClinicInvitation/Controller/ClinicInvitationWebController.php`; templates: `templates/invitation/{view,result}.html.twig`. The existing `/api/v1/clinic-invitation/...` JSON endpoints above are unchanged and remain for the React admin / app clients.
|
||||
|
||||
### GET `/i/{token}` (and alias `GET /clinic-invitation/{token}`)
|
||||
|
||||
Renders the invitation page. `/i/{token}` is the short form used in the SMS.
|
||||
|
||||
- **Usable invitation** → `200`, `view.html.twig`: clinic name, invited name, "valid 72h", and a POST form with **accept** / **reject** buttons (carries a CSRF token).
|
||||
- **Already accepted / rejected / expired / used** → `200`, `result.html.twig` in the matching state.
|
||||
- **Token not found** → `404`, `result.html.twig` state `notfound`.
|
||||
|
||||
### POST `/clinic-invitation/{token}/respond`
|
||||
|
||||
Processes the doctor's choice. **POST only** — accept/reject never happens on GET, so browser/bot prefetch of the SMS link cannot mutate the invitation.
|
||||
|
||||
**Form body (`application/x-www-form-urlencoded`):**
|
||||
| Field | Type | Values |
|
||||
|-------|------|--------|
|
||||
| `_token` | string | CSRF token `invitation_{token}` (rendered in the GET page) |
|
||||
| `action` | string | `accept` \| `reject` |
|
||||
|
||||
**Responses (all HTML):**
|
||||
| Situation | HTTP | Rendered |
|
||||
|-----------|------|----------|
|
||||
| `accept` ok | `200` | «درخواست شما تایید شد» + "ورود به پنل ادمین" button (`{APP_BASE_URL}/admin`) |
|
||||
| `reject` ok | `200` | «دعوت رد شد» |
|
||||
| Invalid/missing CSRF | `403` | `expired` page |
|
||||
| Expired / already used (service throws) | `200` | `expired` page |
|
||||
| Unknown `action` | `422` | `expired` page |
|
||||
| Token not found | `404` | `notfound` page |
|
||||
|
||||
> `accept` links the doctor to the clinic **only if** a doctor account exists for the invitation mobile (`accept()` looks it up by mobile). If none exists, the invitation is marked accepted but the doctor must still have/create an account to actually log in.
|
||||
|
||||
Reference in New Issue
Block a user