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:
- Fetches the Pro and the current Admin record in parallel.
- If no CNI file path but
cniVerifiedAtis already set → 409CNI_ALREADY_VERIFIED(includesverifiedByname resolved from audit log). - 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()andcniFilePath = nullon the Pro row. - Inserts an
AuditLogentry withaction: "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(includesverifiedBy,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
Related Processes
- cni-upload-analysis — file must be uploaded before verification
- account-deactivation — admin can also deactivate instead of verifying