init push
This commit is contained in:
359
docs/contact-create-flow.md
Normal file
359
docs/contact-create-flow.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# Contact Create Flow (Frontend Guide)
|
||||
|
||||
This document explains the full flow to create a contact, send an automatic invite email, and let the invited user set their first password.
|
||||
|
||||
## 1) Prerequisites
|
||||
|
||||
1. Admin user is already logged in and has an `access token`.
|
||||
2. Admin user has already configured a Security PIN.
|
||||
3. Frontend knows the target `role_id` (fetch from `GET /api/v1/roles/get-all`).
|
||||
|
||||
## 2) Endpoints Used
|
||||
|
||||
1. `POST /api/v1/auth/pin/verify` to get a PIN `action_token`.
|
||||
2. `POST /api/v1/contacts/create` to create the contact (auto invite enabled).
|
||||
3. `POST /api/v1/auth/set-password` to set the first password from invite token.
|
||||
|
||||
## 3) Detailed Flow
|
||||
|
||||
### Step A - Verify PIN for contact create action
|
||||
|
||||
Action ID for contact create:
|
||||
- `2a59e8b4-5a72-4be3-b8fc-4f49941181c3`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "auth_pin_verify",
|
||||
"attributes": {
|
||||
"pin": "123456",
|
||||
"action": "2a59e8b4-5a72-4be3-b8fc-4f49941181c3"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Success response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "auth_pin_verify",
|
||||
"attributes": {
|
||||
"verified": true,
|
||||
"action_token": "<PIN_ACTION_TOKEN>",
|
||||
"action_token_ttl_ms": 300000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Store `action_token`, then pass it to contact create in header:
|
||||
- `X-PIN-Verification-Token: <PIN_ACTION_TOKEN>`
|
||||
|
||||
### Step B - Create Contact (auto invite)
|
||||
|
||||
Endpoint:
|
||||
- `POST /api/v1/contacts/create`
|
||||
|
||||
Required headers:
|
||||
- `Authorization: Bearer <ACCESS_TOKEN>`
|
||||
- `X-PIN-Verification-Token: <PIN_ACTION_TOKEN>`
|
||||
- `Content-Type: application/json`
|
||||
|
||||
Minimum required body:
|
||||
- `data.type = contact_<role>_create`
|
||||
- `attributes.role_id`
|
||||
- `attributes.email`
|
||||
- `attributes.first_name`
|
||||
- `attributes.last_name`
|
||||
|
||||
Allowed create types:
|
||||
- `contact_pilot_create`
|
||||
- `contact_doctor_create`
|
||||
- `contact_air_rescuer_create`
|
||||
- `contact_technician_create`
|
||||
- `contact_flight_assistant_create`
|
||||
- `contact_staff_create`
|
||||
|
||||
Rules:
|
||||
- `data.type` must match the role resolved from `attributes.role_id`.
|
||||
- Only one role-specific profile object is allowed, and it must match `data.type`.
|
||||
|
||||
> Note: backend now **automatically sends an invite email** after successful contact creation.
|
||||
|
||||
If PIN token is missing/invalid, API returns `202` with PIN verification required error.
|
||||
|
||||
### Step C - User clicks invite link from email
|
||||
|
||||
Invite email contains a link like:
|
||||
- `https://frontend-domain/set-password?token=<INVITE_TOKEN>`
|
||||
|
||||
This token already carries user identity (email + user id claims) and is single-use.
|
||||
|
||||
### Step D - Frontend submits set password
|
||||
|
||||
Endpoint:
|
||||
- `POST /api/v1/auth/set-password`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "auth_set_password",
|
||||
"attributes": {
|
||||
"token": "<INVITE_TOKEN>",
|
||||
"password": "StrongP@ssw0rd123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If successful, user can directly log in using `POST /api/v1/auth/login`.
|
||||
|
||||
## 4) Contact Create Payloads by Role
|
||||
|
||||
## Pilot
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "contact_pilot_create",
|
||||
"attributes": {
|
||||
"role_id": "<ROLE_ID_PILOT>",
|
||||
"email": "pilot1@example.com",
|
||||
"username": "pilot1",
|
||||
"first_name": "Marco",
|
||||
"last_name": "Melandri",
|
||||
"mobile_phone": "+628123456789",
|
||||
"timezone": "Asia/Jakarta",
|
||||
"is_active": true,
|
||||
"pilot": {
|
||||
"pilot_category": "regular",
|
||||
"short_name": "Marco",
|
||||
"license_no": "PIL-001",
|
||||
"license_issued_at": "2025-01-01T00:00:00Z",
|
||||
"license_expired_at": "2028-01-01T00:00:00Z",
|
||||
"technician_license_no": "TECH-001",
|
||||
"has_ifr_qualification": true,
|
||||
"is_chief_pilot": false,
|
||||
"is_dul": false,
|
||||
"total_flight_minutes": 12000,
|
||||
"responsible_pilot_minutes": 5000,
|
||||
"second_pilot_minutes": 3000,
|
||||
"double_command_minutes": 1000,
|
||||
"flight_instructor_minutes": 500,
|
||||
"night_flight_minutes": 1200,
|
||||
"ifr_flight_minutes": 800,
|
||||
"location": "Jakarta",
|
||||
"postcode": "12950",
|
||||
"street_line": "Jl. Sudirman No. 1",
|
||||
"weight_kg": 72.5,
|
||||
"photo_file_id": "/uploads/contacts/pilot1.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Doctor
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "contact_doctor_create",
|
||||
"attributes": {
|
||||
"role_id": "<ROLE_ID_DOCTOR>",
|
||||
"email": "doctor1@example.com",
|
||||
"username": "doctor1",
|
||||
"first_name": "Alicia",
|
||||
"last_name": "Pratama",
|
||||
"mobile_phone": "+628123450001",
|
||||
"timezone": "Asia/Jakarta",
|
||||
"is_active": true,
|
||||
"doctor": {
|
||||
"short_name": "Dr. Alicia",
|
||||
"medical_license_no": "MED-DOCTOR-001",
|
||||
"license_issued_at": "2024-01-01T00:00:00Z",
|
||||
"license_expired_at": "2029-01-01T00:00:00Z",
|
||||
"specialization": "Emergency Medicine",
|
||||
"sub_specialization": "Trauma",
|
||||
"is_chief_physician": false,
|
||||
"location": "Jakarta",
|
||||
"postcode": "12950",
|
||||
"street_line": "Jl. Jenderal Sudirman No. 10",
|
||||
"photo_file_id": "/uploads/contacts/doctor1.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Air Rescuer
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "contact_air_rescuer_create",
|
||||
"attributes": {
|
||||
"role_id": "<ROLE_ID_AIR_RESCUER>",
|
||||
"email": "airrescuer1@example.com",
|
||||
"username": "airrescuer1",
|
||||
"first_name": "Raka",
|
||||
"last_name": "Wijaya",
|
||||
"mobile_phone": "+628123450002",
|
||||
"timezone": "Asia/Jakarta",
|
||||
"is_active": true,
|
||||
"air_rescuer": {
|
||||
"short_name": "Raka",
|
||||
"is_chief_physician_in_charge": false,
|
||||
"responsible_flight_rescuer": false,
|
||||
"location": "Bandung",
|
||||
"postcode": "40115",
|
||||
"street_line": "Jl. Merdeka No. 20",
|
||||
"photo_file_id": "/uploads/contacts/airrescuer1.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Technician
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "contact_technician_create",
|
||||
"attributes": {
|
||||
"role_id": "<ROLE_ID_TECHNICIAN>",
|
||||
"email": "tech1@example.com",
|
||||
"username": "tech1",
|
||||
"first_name": "Bima",
|
||||
"last_name": "Nugraha",
|
||||
"mobile_phone": "+628123450003",
|
||||
"timezone": "Asia/Jakarta",
|
||||
"is_active": true,
|
||||
"technician": {
|
||||
"short_name": "Bima",
|
||||
"license_no": "TECH-7788",
|
||||
"is_chief_technician": false,
|
||||
"is_responsible_complaints": true,
|
||||
"location": "Surabaya",
|
||||
"postcode": "60231",
|
||||
"street_line": "Jl. Diponegoro No. 30",
|
||||
"photo_file_id": "/uploads/contacts/tech1.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Flight Assistant
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "contact_flight_assistant_create",
|
||||
"attributes": {
|
||||
"role_id": "<ROLE_ID_FLIGHT_ASSISTANT>",
|
||||
"email": "fa1@example.com",
|
||||
"username": "fa1",
|
||||
"first_name": "Nadia",
|
||||
"last_name": "Putri",
|
||||
"mobile_phone": "+628123450004",
|
||||
"timezone": "Asia/Jakarta",
|
||||
"is_active": true,
|
||||
"flight_assistant": {
|
||||
"short_name": "Nadia",
|
||||
"location": "Yogyakarta",
|
||||
"postcode": "55281",
|
||||
"street_line": "Jl. Kaliurang No. 15",
|
||||
"photo_file_id": "/uploads/contacts/fa1.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Staff
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "contact_staff_create",
|
||||
"attributes": {
|
||||
"role_id": "<ROLE_ID_STAFF>",
|
||||
"email": "staff1@example.com",
|
||||
"username": "staff1",
|
||||
"first_name": "Dina",
|
||||
"last_name": "Maharani",
|
||||
"mobile_phone": "+628123450005",
|
||||
"timezone": "Asia/Jakarta",
|
||||
"is_active": true,
|
||||
"staff": {
|
||||
"short_name": "Dina",
|
||||
"role_label": "Operations Staff",
|
||||
"location": "Semarang",
|
||||
"postcode": "50132",
|
||||
"street_line": "Jl. Pandanaran No. 8",
|
||||
"photo_file_id": "/uploads/contacts/staff1.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5) Example Create Contact Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "contact",
|
||||
"id": "019ce67e-9289-731d-ae4c-0156902aabbc",
|
||||
"attributes": {
|
||||
"role_code": "doctor",
|
||||
"role_name": "doctor",
|
||||
"email": "doctor1@example.com",
|
||||
"first_name": "Alicia",
|
||||
"last_name": "Pratama",
|
||||
"is_active": true,
|
||||
"doctor": {
|
||||
"short_name": "Dr. Alicia",
|
||||
"medical_license_no": "MED-DOCTOR-001"
|
||||
},
|
||||
"invite_sent": true,
|
||||
"invite_error": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If invite sending fails (for example SES/SQS config issue):
|
||||
- contact create is still successful,
|
||||
- `invite_sent = false`,
|
||||
- `invite_error` contains the error message.
|
||||
|
||||
## 6) Error Cases Frontend Should Handle
|
||||
|
||||
1. `202 PIN verification required` on contact create:
|
||||
- call `/auth/pin/verify` again (Step A), then retry create with `X-PIN-Verification-Token`.
|
||||
2. `422 Validation error`:
|
||||
- check payload fields (invalid role_id, invalid email, etc).
|
||||
3. `400` on create:
|
||||
- typically business/constraint conflict.
|
||||
4. `400` on set-password:
|
||||
- token invalid/expired/used or password does not meet rules.
|
||||
|
||||
## 7) Frontend Integration Summary
|
||||
|
||||
1. Admin logs in.
|
||||
2. Fetch `role_id` from roles list.
|
||||
3. Verify PIN (`/auth/pin/verify` with contact-create action ID).
|
||||
4. Create contact (`/contacts/create` + `X-PIN-Verification-Token`).
|
||||
5. Wait for invited user to open invite email.
|
||||
6. Set-password page reads `token` from query param.
|
||||
7. Submit `/auth/set-password` with `{ token, password }`.
|
||||
8. Redirect user to login page.
|
||||
Reference in New Issue
Block a user