CNI Verification by Admin

Summary

After a Pro uploads their identity document, an admin reviews it in the admin panel and marks it as verified. The system atomically clears the file path, purges the file from disk, records the action in the audit log, sends a confirmation email to the Pro, and posts a reply to the Slack thread. Concurrent admin attempts are safely rejected.

State Diagram

stateDiagram-v2
    state "En attente de revue" as PendingReview
    state "Image consultée" as ViewingImage
    state "Vérification en cours" as Verifying
    state "Déjà vérifié (conflit)" as AlreadyVerified
    state "Pas de CNI trouvée" as NoCni
    state "CNI vérifiée" as Verified

    [*] --> PendingReview: CNI uploaded, cniVerifiedAt = null
    PendingReview --> ViewingImage: admin opens CNI image
    ViewingImage --> Verifying: admin clicks "Vérifier"
    Verifying --> AlreadyVerified: race condition (another admin verified first) → 409
    Verifying --> NoCni: cniFilePath missing but not yet verified → 400
    Verifying --> Verified: DB updated, file purged, email sent, Slack notified
    Verified --> [*]
    AlreadyVerified --> [*]
    NoCni --> [*]

Steps

1. View CNI Image (Actor: admin)

GET /admin/pros/:id/cni. The server reads the stored WebP from UPLOAD_PRIVATE_DIR via readCniImage(proId) and streams it with Content-Type: image/webp, Cache-Control: no-store, X-Content-Type-Options: nosniff. The file is served only to authenticated admins — it is never publicly accessible.

If the file is missing on disk (orphan), returns 500 CNI_FILE_MISSING.

Triggers: Admin opens the Pro’s fiche in the admin panel Outcome: Admin sees the identity document image

2. Verify CNI (Actor: admin)

POST /admin/pros/:id/verify-cni. The system:

  1. Fetches the Pro and the current Admin record in parallel.
  2. If no CNI file path but cniVerifiedAt is already set → 409 CNI_ALREADY_VERIFIED (includes verifiedBy name resolved from audit log).
  3. If no CNI file path and not verified → 400 NO_CNI_TO_VERIFY.

Triggers: Admin clicks the verify button Outcome: Validation checks pass

3. Atomic DB Update + Audit Log (Actor: system)

In a single prisma.$transaction:

  • Sets cniVerifiedAt = now() and cniFilePath = null on the Pro row.
  • Inserts an AuditLog entry with action: "pro.verify_cni", targetType: "pro", targetId, actorId.

Triggers: Validation checks pass Outcome: Pro marked as verified; file path cleared; action logged

4. File Purge (Actor: system)

deleteCniImage(proId) is called after the transaction commits. Errors are logged but do not affect the response — an orphan file on disk is acceptable (it will never be served again since cniFilePath is null).

Triggers: Transaction committed Outcome: Identity document file deleted from UPLOAD_PRIVATE_DIR

5. Confirmation Email (Actor: system)

sendEmail({ template: "pro-cni-verified", to: pro.email, data: { firstName } }) is called. Fire-and-forget — errors are logged but do not block the admin response.

Triggers: Transaction committed Outcome: Pro receives email confirming identity verification

6. Slack Thread Reply (Actor: system)

notifyProCniVerified posts to #pro-registration in the Pro’s thread (slackThreadTs): “CNI validée” with the admin’s name. Only fires if slackThreadTs is set. Fire-and-forget.

Triggers: Transaction committed Outcome: Admin team notified; thread closed visually

Error States

  • Pro not found → 404 NOT_FOUND
  • CNI already verified by another admin → 409 CNI_ALREADY_VERIFIED (includes verifiedBy, verifiedAt)
  • No CNI file to verify → 400 NO_CNI_TO_VERIFY
  • File missing on disk (serve step) → 500 CNI_FILE_MISSING
  • Email send failure → logged, not surfaced
  • Slack notification failure → logged, not surfaced
  • File deletion failure → logged, orphan tolerated