Skip to content

Commit a9be787

Browse files
committed
docs: update API invitations documentation
1 parent 48a80ce commit a9be787

File tree

1 file changed

+350
-0
lines changed

1 file changed

+350
-0
lines changed

docs/api/API_INVITATIONS.md

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,355 @@
11
# Invitations API Documentation
22

3+
## POST `/api/invitations`
4+
5+
Create and send an invitation to a new user.
6+
7+
**Request Body:**
8+
9+
```json
10+
{
11+
"email": "string",
12+
"firstName": "string",
13+
"lastName": "string",
14+
"role": "student | mentor | superAdmin",
15+
"projectId": "string (optional)"
16+
}
17+
```
18+
19+
**Success Response (201):**
20+
21+
```json
22+
{
23+
"message": "Invitation sent successfully",
24+
"invitation": {
25+
"id": "string",
26+
"email": "string",
27+
"role": "string",
28+
"token": "string",
29+
"createdAt": "ISO 8601",
30+
"expiresAt": "ISO 8601"
31+
}
32+
}
33+
```
34+
35+
**Authentication Required:** ✓ (superAdmin only)
36+
37+
---
38+
39+
## GET `/api/invitations`
40+
41+
Two modes depending on query parameters.
42+
43+
### List all invitations (no query params)
44+
45+
Returns all invitations for the admin dashboard.
46+
47+
**Success Response (200):**
48+
49+
```json
50+
[
51+
{
52+
"id": "string",
53+
"email": "string",
54+
"role": "string",
55+
"project": "string",
56+
"status": "Pending | Accepted | Expired",
57+
"createdAt": "ISO 8601",
58+
"expiresAt": "ISO 8601"
59+
}
60+
]
61+
```
62+
63+
### Validate an invitation token
64+
65+
**Query Parameters:**
66+
67+
- `token` (required) — invitation token from the registration link
68+
69+
**Success Response (200):**
70+
71+
```json
72+
{
73+
"valid": true,
74+
"invitation": {
75+
"id": "string",
76+
"email": "string",
77+
"role": "string",
78+
"token": "string",
79+
"createdAt": "ISO 8601",
80+
"expiresAt": "ISO 8601",
81+
"accepted": false
82+
}
83+
}
84+
```
85+
86+
---
87+
88+
## PATCH `/api/invitations`
89+
90+
Update the status of an invitation. Mutates the underlying `accepted` and `expiresAt` fields on the `Invitation` record to reflect the requested status.
91+
92+
**Request Body:**
93+
94+
```json
95+
{
96+
"id": "string",
97+
"status": "Accepted | Pending | Expired"
98+
}
99+
```
100+
101+
**Status behaviour:**
102+
103+
| `status` | Effect |
104+
|------------|------------------------------------------------------------------------------------------|
105+
| `Accepted` | Sets `accepted: true` |
106+
| `Pending` | Sets `accepted: false`, resets `expiresAt` to 7 days from now |
107+
| `Expired` | Sets `accepted: false`, sets `expiresAt: new Date(0)` — permanently invalidates the link |
108+
109+
**Success Response (200):**
110+
111+
```json
112+
{
113+
"message": "Invitation status updated"
114+
}
115+
```
116+
117+
**Error Responses:**
118+
119+
| Status | Reason |
120+
|--------|-------------------------------|
121+
| 400 | Missing `id` in request body |
122+
| 401 | Unauthenticated |
123+
| 403 | Not a superAdmin |
124+
| 500 | Internal server error |
125+
126+
---
127+
128+
## DELETE `/api/invitations?id=<id>`
129+
130+
Cascade-delete an invitation and permanently remove the associated user account along with all their data.
131+
132+
**Query Parameters:**
133+
134+
- `id` (required) — the invitation ID
135+
136+
**Deletion order (transactional):**
137+
138+
1. `MentorFeedback` records for the user's activities
139+
2. `Activity` records for the user
140+
3. `MentorFeedback` records given by the user as a mentor
141+
4. `MentorActivity` records for the user
142+
5. `Report` records
143+
6. `UserBadge` records
144+
7. `ProjectAllocation` records
145+
8. `ProjectMentor` records
146+
9. Null-out `invitedBy` on any invitations this user sent
147+
10. The `Invitation` record itself
148+
11. The `User` record
149+
150+
If no associated user exists (invitation was never accepted), only the `Invitation` record is deleted.
151+
152+
**Success Response (200):**
153+
154+
```json
155+
{
156+
"message": "Invitation deleted"
157+
}
158+
```
159+
160+
**Error Responses:**
161+
162+
| Status | Reason |
163+
|--------|---------------------------|
164+
| 400 | Missing `id` query param |
165+
| 404 | Invitation not found |
166+
| 500 | Internal server error |
167+
168+
---
169+
170+
## Frontend Hook: `useInvitation`
171+
172+
**Location:** `hooks/admin/useInvitation.ts`
173+
174+
Sends a `POST /api/invitations` request to create a new invitation and trigger the onboarding email.
175+
176+
**Parameters:**
177+
178+
| Name | Type | Description |
179+
|-------------|----------|----------------------------------------------------|
180+
| `email` | `string` | Recipient email address |
181+
| `firstName` | `string` | Recipient first name |
182+
| `lastName` | `string` | Recipient last name |
183+
| `role` | `string` | `"student"`, `"mentor"`, or `"superAdmin"` |
184+
| `projectId` | `string` | *(optional)* Project to assign the user to |
185+
186+
**Usage:**
187+
188+
```typescript
189+
const { mutate: sendInvitation, isPending } = useInvitation();
190+
191+
sendInvitation({
192+
email: "user@example.com",
193+
firstName: "John",
194+
lastName: "Doe",
195+
role: "student",
196+
});
197+
```
198+
199+
On success, invalidates the `["invitations"]` query key and shows a success toast. On error, shows an error toast.
200+
201+
---
202+
203+
## Frontend Hook: `useRecentInvitations`
204+
205+
**Location:** `hooks/admin/useInvitation.ts`
206+
207+
Fetches the full list of invitations from `GET /api/invitations` (no token param). Each item includes `id`, `email`, `role`, `project`, `status`, `createdAt`, and `expiresAt`.
208+
209+
**Returns:** `UseQueryResult` — array of invitation objects.
210+
211+
**Usage:**
212+
213+
```typescript
214+
const { data, isLoading, isError } = useRecentInvitations();
215+
216+
if (isLoading) return <Spinner />;
217+
if (isError) return <p>Failed to load invitations.</p>;
218+
219+
return data.map((inv) => <InvitationRow key={inv.id} {...inv} />);
220+
```
221+
222+
**Notes:**
223+
224+
- Auth is handled server-side via session cookies; no auth params are passed from the hook.
225+
- Throws `"Failed to fetch invitations"` on a non-OK response, surfaced via `error`.
226+
- Uses query key `["invitations"]`, which is invalidated by `useInvitation`, `useChangeInvitationStatus`, and `useDeleteInvitation` on success.
227+
228+
---
229+
230+
## Frontend Hook: `useExpireInvitation`
231+
232+
**Location:** `hooks/admin/useInvitation.ts`
233+
234+
Convenience wrapper around `useChangeInvitationStatus` that calls `PATCH /api/invitations` with `status: "Expired"`, permanently invalidating the invitation link without deleting the record.
235+
236+
**Parameters:**
237+
238+
| Name | Type | Description |
239+
|------|----------|------------------------------|
240+
| `id` | `string` | The invitation ID to expire |
241+
242+
**Usage:**
243+
244+
```typescript
245+
const { mutate: expireInvitation, isPending: isExpiring } = useExpireInvitation();
246+
247+
expireInvitation({ id: "abc123" });
248+
```
249+
250+
On success, invalidates `["invitations"]` and shows a success toast. On error, shows an error toast.
251+
252+
---
253+
254+
## Frontend Hook: `useChangeInvitationStatus`
255+
256+
**Location:** `hooks/admin/useInvitation.ts`
257+
258+
Calls `PATCH /api/invitations` to update the status of an invitation to `Accepted`, `Pending`, or `Expired`.
259+
260+
**Parameters:**
261+
262+
| Name | Type | Description |
263+
|----------|----------|------------------------------------------------------|
264+
| `id` | `string` | The invitation ID |
265+
| `status` | `string` | Target status: `"Accepted"`, `"Pending"`, or `"Expired"` |
266+
267+
**Usage:**
268+
269+
```typescript
270+
const { mutate: changeStatus, isPending: isChangingStatus } = useChangeInvitationStatus();
271+
272+
changeStatus({ id: "abc123", status: "Pending" });
273+
```
274+
275+
On success, invalidates `["invitations"]` and shows a success toast. On error, shows an error toast.
276+
277+
---
278+
279+
## Frontend Hook: `useDeleteInvitation`
280+
281+
**Location:** `hooks/admin/useInvitation.ts`
282+
283+
Calls `DELETE /api/invitations?id=<id>` to cascade-delete the invitation and all associated user data.
284+
285+
**Parameters:**
286+
287+
| Name | Type | Description |
288+
|------|----------|-----------------------------|
289+
| `id` | `string` | The invitation ID to delete |
290+
291+
**Usage:**
292+
293+
```typescript
294+
const { mutate: deleteInvitation, isPending: isDeleting } = useDeleteInvitation();
295+
296+
deleteInvitation({ id: "abc123" });
297+
```
298+
299+
On success, invalidates `["invitations"]` and shows a success toast. On error, shows an error toast.
300+
301+
---
302+
303+
## Frontend Hook: `useValidateInvitation`
304+
305+
**Location:** `hooks/admin/useInvitation.ts`
306+
307+
Validates a single invitation token by calling `GET /api/invitations?token=<token>`. The query only executes when `token` is a non-empty string (`enabled: !!token`), making it safe to call unconditionally before the token is available.
308+
309+
**Parameters:**
310+
311+
| Name | Type | Description |
312+
|---------|----------|-----------------------------------|
313+
| `token` | `string` | The invitation token to validate |
314+
315+
**Returns:** `UseQueryResult` — the response from `GET /api/invitations?token=<token>`.
316+
317+
**Success response shape:**
318+
319+
```json
320+
{
321+
"valid": true,
322+
"invitation": {
323+
"id": "string",
324+
"email": "string",
325+
"role": "string",
326+
"token": "string",
327+
"createdAt": "ISO 8601",
328+
"expiresAt": "ISO 8601",
329+
"accepted": false
330+
}
331+
}
332+
```
333+
334+
**Usage:**
335+
336+
```typescript
337+
// token typically comes from URL search params on the registration page
338+
const token = searchParams.get("token") ?? "";
339+
const { data, isLoading, isError } = useValidateInvitation(token);
340+
341+
if (isLoading) return <Spinner />;
342+
if (isError || !data?.valid) return <p>Invalid or expired invitation.</p>;
343+
344+
return <RegistrationForm invitation={data.invitation} />;
345+
```
346+
347+
**Notes:**
348+
349+
- Throws `"Invalid token"` on a non-OK response; surfaced via `error` from React Query.
350+
- Uses a scoped query key `["invitation", token]` so each token is cached independently.
351+
352+
3353
## POST `/api/invitations`
4354

5355
Create and send an invitation to a new user.

0 commit comments

Comments
 (0)