Pro Profile and Branding Management

Summary

Authenticated pros can view and update their own profile. The GET endpoint returns a curated set of safe fields (password hash and other internals are never exposed). The PUT endpoint accepts a partial payload validated by proProfileUpdateSchema — schema-level additionalProperties: false prevents mass assignment of protected fields such as email, passwordHash, or isActive.

State Diagram

stateDiagram-v2
    state "Pro authentifié" as Authenticated
    state "Profil consulté" as ProfileFetched
    state "Profil mis à jour" as ProfileUpdated

    [*] --> Authenticated
    Authenticated --> ProfileFetched: pro reads profile
    Authenticated --> ProfileUpdated: pro updates profile
    ProfileFetched --> [*]
    ProfileUpdated --> [*]

Steps

1. Read Profile (Actor: pro)

GET /pro/profile. Returns a fixed select of safe fields: id, email, firstName, lastName, agencyName, jobTitle, phone, avatarUrl, logoUrl, siret, carteT, rcp, address, city, postalCode, latitude, longitude, isActive, createdAt, cniFilePath, cniVerifiedAt.

Never exposes passwordHash, deactivationReason, slackThreadTs, or raw token data.

Triggers: Pro loads their account page Outcome: Profile JSON returned

2. Update Profile (Actor: pro)

PUT /pro/profile with a partial body validated by proProfileUpdateSchema. Any subset of allowed fields can be sent — only those fields are updated (Prisma update with the validated body, which has additionalProperties: false).

Fields updatable by the pro include personal info (firstName, lastName, phone), agency details (agencyName, jobTitle), address (address, city, postalCode), geo coordinates (latitude, longitude), and branding URLs (avatarUrl, logoUrl).

Protected fields (email, passwordHash, isActive, siret, carteT, cniVerifiedAt, etc.) cannot be updated via this endpoint — the schema rejects them.

The response returns the same safe field set as GET (excluding branding fields that were not selected in the PUT response — see select in code).

Triggers: Pro edits their profile and saves Outcome: Updated Pro record returned

Error States

  • Unauthenticated request → 401 (handled by authenticatePro hook)
  • CNI gate active (>72h, no CNI) → 403 CNI_REQUIRED (this route is NOT whitelisted)
  • Schema validation failure → 400 (Fastify JSON schema error)

Notes

  • avatarUrl and logoUrl are plain string URLs — there is no upload endpoint for branding images through this route. Photo uploads for properties use a separate multipart route under /pro/properties/:id/photos.
  • The CNI upload endpoint lives at /pro/profile/cni-upload but is documented separately in cni-upload-analysis because its security profile and flow differ significantly.