Files
fm_be/docs/contact-create-flow.md
2026-07-16 22:16:45 +07:00

8.9 KiB

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:

{
  "data": {
    "type": "auth_pin_verify",
    "attributes": {
      "pin": "123456",
      "action": "2a59e8b4-5a72-4be3-b8fc-4f49941181c3"
    }
  }
}

Success response:

{
  "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.

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:

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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.