openapi: 3.1.0
info:
  title: Api
  version: 0.1.0
  description: Model Directory & Booking Platform API
servers:
  - url: /api
    description: Base API path
tags:
  - name: health
    description: Health operations
  - name: auth
    description: Authentication
  - name: profiles
    description: Model profiles
  - name: locations
    description: Locations (hotels, venues, safehouses)
  - name: bookings
    description: Bookings
  - name: services
    description: Bookable services
  - name: memberships
    description: Membership tiers
  - name: dashboard
    description: Dashboard aggregates
  - name: admin
    description: Admin operations
  - name: Storage
    description: Object storage upload and serving endpoints.

paths:
  /storage/uploads/request-url:
    post:
      tags: [Storage]
      operationId: requestUploadUrl
      summary: Request a presigned URL for file upload
      description: |
        Returns a presigned GCS URL for direct upload. The client sends JSON
        metadata here, then uploads the file directly to the returned URL.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadUrlRequest'
      responses:
        '200':
          description: Presigned upload URL generated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadUrlResponse'
        '400':
          description: Missing or invalid required fields.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: Failed to generate upload URL.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
  /storage/public-objects/{filePath}:
    get:
      tags: [Storage]
      operationId: getPublicObject
      summary: Serve a public asset from PUBLIC_OBJECT_SEARCH_PATHS
      description: |
        Unconditionally public — no authentication or ACL checks.
        Searches PUBLIC_OBJECT_SEARCH_PATHS for the given file path.
      parameters:
        - name: filePath
          in: path
          required: true
          schema:
            type: string
          description: Relative file path within the public search paths.
      responses:
        '200':
          description: Object content streamed with correct Content-Type.
          content:
            '*/*':
              schema:
                type: string
                format: binary
        '404':
          description: File not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: Failed to serve public object.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
  /storage/objects/{objectPath}:
    get:
      tags: [Storage]
      operationId: getStorageObject
      summary: Serve an object entity from PRIVATE_OBJECT_DIR
      description: |
        Serves object entities uploaded via presigned URLs. These can optionally
        be protected with authentication or ACL checks based on the use case.
      parameters:
        - name: objectPath
          in: path
          required: true
          schema:
            type: string
          description: Object path within the private object dir (e.g. `uploads/some-uuid`).
      responses:
        '200':
          description: Object content streamed with correct Content-Type.
          content:
            '*/*':
              schema:
                type: string
                format: binary
        '404':
          description: Object not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: Failed to serve object.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
  /healthz:
    get:
      operationId: healthCheck
      tags: [health]
      summary: Health check
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"

  # Auth
  /auth/register:
    post:
      operationId: register
      tags: [auth]
      summary: Register a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterInput"
      responses:
        "201":
          description: User registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        "400":
          description: Validation error

  /auth/login:
    post:
      operationId: login
      tags: [auth]
      summary: Log in
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginInput"
      responses:
        "200":
          description: Logged in
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        "401":
          description: Invalid credentials

  /auth/logout:
    post:
      operationId: logout
      tags: [auth]
      summary: Log out
      responses:
        "204":
          description: Logged out

  /auth/me:
    get:
      operationId: getMe
      tags: [auth]
      summary: Get current user
      responses:
        "200":
          description: Current user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "401":
          description: Unauthorized

  /auth/me/update:
    patch:
      operationId: updateMe
      tags: [auth]
      summary: Update current user profile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserUpdate"
      responses:
        "200":
          description: Updated user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

  # Dashboard
  /dashboard/summary:
    get:
      operationId: getDashboardSummary
      tags: [dashboard]
      summary: Get dashboard summary for current user
      responses:
        "200":
          description: Summary stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardSummary"

  /dashboard/featured-profiles:
    get:
      operationId: getFeaturedProfiles
      tags: [dashboard]
      summary: Get featured model profiles
      responses:
        "200":
          description: Featured profiles
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Profile"

  /dashboard/featured-locations:
    get:
      operationId: getFeaturedLocations
      tags: [dashboard]
      summary: Get featured locations
      responses:
        "200":
          description: Featured locations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Location"

  /dashboard/recent-activity:
    get:
      operationId: getRecentActivity
      tags: [dashboard]
      summary: Get recent booking activity for current user
      responses:
        "200":
          description: Recent activity
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Booking"

  # Profiles
  /profiles:
    get:
      operationId: listProfiles
      tags: [profiles]
      summary: List all model profiles
      parameters:
        - name: search
          in: query
          schema:
            type: string
        - name: city
          in: query
          schema:
            type: string
      responses:
        "200":
          description: List of profiles
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Profile"
    post:
      operationId: createModelApplication
      tags: [profiles]
      summary: Submit a model registration application (pending admin approval)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterModelInput"
      responses:
        "201":
          description: Application submitted (pending approval)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "400":
          description: Invalid input
        "401":
          description: Unauthorized
        "409":
          description: Already has an application

  /profiles/{id}:
    get:
      operationId: getProfile
      tags: [profiles]
      summary: Get a profile by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "404":
          description: Not found

  # Locations
  /locations:
    get:
      operationId: listLocations
      tags: [locations]
      summary: List all locations
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum: [hotel, venue, safehouse]
        - name: city
          in: query
          schema:
            type: string
      responses:
        "200":
          description: List of locations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Location"

  /locations/{id}:
    get:
      operationId: getLocation
      tags: [locations]
      summary: Get a location by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Location
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Location"
        "404":
          description: Not found

  # Services
  /services:
    get:
      operationId: listServices
      tags: [services]
      summary: List bookable services
      responses:
        "200":
          description: List of services
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Service"

  # Bookings
  /bookings:
    get:
      operationId: listMyBookings
      tags: [bookings]
      summary: List current user's bookings
      responses:
        "200":
          description: List of bookings
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Booking"
    post:
      operationId: createBooking
      tags: [bookings]
      summary: Create a booking
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BookingInput"
      responses:
        "201":
          description: Created booking
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Booking"

  /bookings/{id}:
    get:
      operationId: getBooking
      tags: [bookings]
      summary: Get a booking by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Booking
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Booking"
    delete:
      operationId: cancelBooking
      tags: [bookings]
      summary: Cancel a booking
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Cancelled

  # Memberships
  /memberships/my:
    get:
      operationId: getMyMembership
      tags: [memberships]
      summary: Get current user's membership
      responses:
        "200":
          description: Membership
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Membership"

  /memberships/tiers:
    get:
      operationId: listMembershipTiers
      tags: [memberships]
      summary: List available membership plans
      responses:
        "200":
          description: Membership plans
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/MembershipPlan"

  /memberships/payment-methods:
    get:
      operationId: listPaymentMethods
      tags: [memberships]
      summary: List active payment methods
      responses:
        "200":
          description: Payment methods
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PaymentMethod"

  /memberships/subscribe:
    post:
      operationId: subscribeMembership
      tags: [memberships]
      summary: Submit a membership payment for verification
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubscribeInput"
      responses:
        "201":
          description: Created payment submission
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MembershipPayment"

  /memberships/my-payments:
    get:
      operationId: listMyMembershipPayments
      tags: [memberships]
      summary: List current user's membership payment submissions
      responses:
        "200":
          description: Payment submissions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/MembershipPayment"

  # Network / Assets
  /network/members:
    get:
      operationId: listNetworkMembers
      tags: [profiles]
      summary: List network members (other users in the platform)
      responses:
        "200":
          description: Network members
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/NetworkMember"

  /assets:
    get:
      operationId: listAssets
      tags: [profiles]
      summary: List platform assets (resources, links, tools)
      responses:
        "200":
          description: Assets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Asset"

  # Admin
  /admin/profiles:
    get:
      operationId: adminListProfiles
      tags: [admin]
      summary: Admin — list all profiles
      responses:
        "200":
          description: All profiles
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Profile"
    post:
      operationId: adminCreateProfile
      tags: [admin]
      summary: Admin — create a profile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileInput"
      responses:
        "201":
          description: Created profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"

  /admin/profiles/{id}:
    patch:
      operationId: adminUpdateProfile
      tags: [admin]
      summary: Admin — update a profile
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileUpdate"
      responses:
        "200":
          description: Updated profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
    delete:
      operationId: adminDeleteProfile
      tags: [admin]
      summary: Admin — delete a profile
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  /admin/locations:
    get:
      operationId: adminListLocations
      tags: [admin]
      summary: Admin — list all locations
      responses:
        "200":
          description: All locations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Location"
    post:
      operationId: adminCreateLocation
      tags: [admin]
      summary: Admin — create a location
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LocationInput"
      responses:
        "201":
          description: Created location
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Location"

  /admin/locations/{id}:
    patch:
      operationId: adminUpdateLocation
      tags: [admin]
      summary: Admin — update a location
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LocationUpdate"
      responses:
        "200":
          description: Updated location
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Location"
    delete:
      operationId: adminDeleteLocation
      tags: [admin]
      summary: Admin — delete a location
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  /admin/memberships:
    get:
      operationId: adminListMemberships
      tags: [admin]
      summary: Admin — list all user memberships
      responses:
        "200":
          description: All memberships
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Membership"

  /admin/memberships/{userId}:
    patch:
      operationId: adminUpdateMembership
      tags: [admin]
      summary: Admin — update a user's membership tier
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MembershipUpdate"
      responses:
        "200":
          description: Updated membership
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Membership"

  /admin/membership-plans:
    get:
      operationId: adminListMembershipPlans
      tags: [admin]
      summary: Admin — list all membership plans
      responses:
        "200":
          description: All membership plans
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/MembershipPlan"
    post:
      operationId: adminCreateMembershipPlan
      tags: [admin]
      summary: Admin — create a membership plan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MembershipPlanInput"
      responses:
        "201":
          description: Created plan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MembershipPlan"

  /admin/membership-plans/{id}:
    patch:
      operationId: adminUpdateMembershipPlan
      tags: [admin]
      summary: Admin — update a membership plan
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MembershipPlanUpdate"
      responses:
        "200":
          description: Updated plan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MembershipPlan"
    delete:
      operationId: adminDeleteMembershipPlan
      tags: [admin]
      summary: Admin — delete a membership plan
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  /admin/payment-methods:
    get:
      operationId: adminListPaymentMethods
      tags: [admin]
      summary: Admin — list all payment methods
      responses:
        "200":
          description: All payment methods
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PaymentMethod"

  /admin/payment-methods/{id}:
    patch:
      operationId: adminUpdatePaymentMethod
      tags: [admin]
      summary: Admin — update a payment method
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentMethodUpdate"
      responses:
        "200":
          description: Updated payment method
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentMethod"

  /admin/membership-payments:
    get:
      operationId: adminListMembershipPayments
      tags: [admin]
      summary: Admin — list all membership payment submissions
      responses:
        "200":
          description: All payment submissions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/MembershipPayment"

  /admin/membership-payments/{id}:
    patch:
      operationId: adminReviewMembershipPayment
      tags: [admin]
      summary: Admin — approve or reject a membership payment
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MembershipPaymentReview"
      responses:
        "200":
          description: Reviewed payment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MembershipPayment"

  /admin/bookings:
    get:
      operationId: adminListBookings
      tags: [admin]
      summary: Admin — list all bookings
      responses:
        "200":
          description: All bookings
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Booking"

  /admin/users:
    get:
      operationId: adminListUsers
      tags: [admin]
      summary: Admin — list all users
      responses:
        "200":
          description: All users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/User"

  # Booking status transitions
  /bookings/{id}/status:
    patch:
      operationId: updateBookingStatus
      tags: [bookings]
      summary: Update a booking's status (role-aware transition)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BookingStatusInput"
      responses:
        "200":
          description: Updated booking
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Booking"

  /bookings/{id}/proof:
    post:
      operationId: uploadBookingProof
      tags: [bookings]
      summary: Model uploads a proof-of-completion photo for a paid order
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProofUploadInput"
      responses:
        "200":
          description: Updated booking
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Booking"

  # Wallet
  /wallet:
    get:
      operationId: getWallet
      tags: [wallet]
      summary: Get current user's wallet balance and recent transactions
      responses:
        "200":
          description: Wallet
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Wallet"

  /wallet/transactions:
    get:
      operationId: listWalletTransactions
      tags: [wallet]
      summary: List all of the current user's wallet transactions
      responses:
        "200":
          description: Transactions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WalletTransaction"

  /wallet/deposits:
    get:
      operationId: listMyDeposits
      tags: [wallet]
      summary: List current user's deposit requests
      responses:
        "200":
          description: Deposits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WalletDeposit"
    post:
      operationId: createDeposit
      tags: [wallet]
      summary: Submit a wallet deposit request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DepositInput"
      responses:
        "201":
          description: Created deposit request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WalletDeposit"

  /wallet/withdrawals:
    get:
      operationId: listMyWithdrawals
      tags: [wallet]
      summary: List current user's withdrawal requests
      responses:
        "200":
          description: Withdrawals
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Withdrawal"
    post:
      operationId: createWithdrawal
      tags: [wallet]
      summary: Request a payout (model only)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WithdrawalInput"
      responses:
        "201":
          description: Created withdrawal request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Withdrawal"

  # Admin — wallets, deposits, withdrawals, commission
  /admin/wallets:
    get:
      operationId: adminListWallets
      tags: [admin]
      summary: Admin — list all wallets
      responses:
        "200":
          description: Wallets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AdminWallet"

  /admin/wallets/{userId}/credit:
    post:
      operationId: adminCreditWallet
      tags: [admin]
      summary: Admin — credit (or debit) a user's wallet
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WalletCreditInput"
      responses:
        "200":
          description: Updated wallet
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminWallet"

  /admin/deposits:
    get:
      operationId: adminListDeposits
      tags: [admin]
      summary: Admin — list all deposit requests
      responses:
        "200":
          description: Deposits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WalletDeposit"

  /admin/deposits/{id}:
    patch:
      operationId: adminReviewDeposit
      tags: [admin]
      summary: Admin — approve or reject a deposit request
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WalletReview"
      responses:
        "200":
          description: Reviewed deposit
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WalletDeposit"

  /admin/withdrawals:
    get:
      operationId: adminListWithdrawals
      tags: [admin]
      summary: Admin — list all withdrawal requests
      responses:
        "200":
          description: Withdrawals
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Withdrawal"

  /admin/withdrawals/{id}:
    patch:
      operationId: adminReviewWithdrawal
      tags: [admin]
      summary: Admin — approve or reject a withdrawal request
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WalletReview"
      responses:
        "200":
          description: Reviewed withdrawal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Withdrawal"

  /admin/commission:
    get:
      operationId: adminGetCommission
      tags: [admin]
      summary: Admin — commission earned (today, this month, total)
      responses:
        "200":
          description: Commission stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommissionStats"

  /admin/escrow:
    get:
      operationId: adminGetEscrow
      tags: [admin]
      summary: Admin — funds currently held in escrow and admin wallet balance
      responses:
        "200":
          description: Escrow stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EscrowStats"

  /admin/proofs:
    get:
      operationId: adminListProofs
      tags: [admin]
      summary: Admin — list order proof submissions for review
      responses:
        "200":
          description: Order proofs
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AdminOrderProof"

  /admin/proofs/{id}:
    patch:
      operationId: adminReviewProof
      tags: [admin]
      summary: Admin — approve or reject an order proof
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProofReviewInput"
      responses:
        "200":
          description: Reviewed proof
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminOrderProof"

  # Auth — model registration & password reset
  /auth/register-model:
    post:
      operationId: registerModel
      tags: [auth]
      summary: Register a model account (creates account + pending profile application)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterModelAccountInput"
      responses:
        "201":
          description: Model account created and logged in
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        "400":
          description: Validation error

  /auth/forgot-password:
    post:
      operationId: forgotPassword
      tags: [auth]
      summary: Request a password reset email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ForgotPasswordInput"
      responses:
        "204":
          description: If the account exists, a reset email was sent

  /auth/reset-password:
    post:
      operationId: resetPassword
      tags: [auth]
      summary: Reset password using a token from email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResetPasswordInput"
      responses:
        "204":
          description: Password reset
        "400":
          description: Invalid or expired token

  # Favorites (public users)
  /favorites:
    get:
      operationId: listFavorites
      tags: [favorites]
      summary: List the current user's favorited model profiles
      responses:
        "200":
          description: Favorited profiles
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Profile"
    post:
      operationId: addFavorite
      tags: [favorites]
      summary: Add a model profile to favorites
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FavoriteInput"
      responses:
        "201":
          description: Added
        "409":
          description: Already favorited

  /favorites/{profileId}:
    delete:
      operationId: removeFavorite
      tags: [favorites]
      summary: Remove a model profile from favorites
      parameters:
        - name: profileId
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Removed

  # Model self-service
  /me/profile:
    get:
      operationId: getMyProfile
      tags: [me]
      summary: Get the current model's own profile
      responses:
        "200":
          description: Model profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "404":
          description: No profile
    patch:
      operationId: updateMyProfile
      tags: [me]
      summary: Update the current model's profile and pricing
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ModelProfileUpdate"
      responses:
        "200":
          description: Updated profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"

  /me/earnings:
    get:
      operationId: getMyEarnings
      tags: [me]
      summary: Get earnings estimated from completed bookings
      responses:
        "200":
          description: Earnings summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EarningsSummary"

  /me/orders:
    get:
      operationId: listMyOrders
      tags: [me]
      summary: List incoming orders for the current model's profile
      responses:
        "200":
          description: Orders
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Booking"

  /me/photos:
    get:
      operationId: listMyPhotos
      tags: [me]
      summary: List the current model's photo gallery
      responses:
        "200":
          description: Photos
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ProfilePhoto"
    post:
      operationId: addMyPhoto
      tags: [me]
      summary: Add a photo to the gallery
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AddPhotoInput"
      responses:
        "201":
          description: Added photo
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProfilePhoto"

  /me/photos/{id}:
    delete:
      operationId: deleteMyPhoto
      tags: [me]
      summary: Delete a photo from the gallery
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

components:
  schemas:
    UploadUrlRequest:
      type: object
      required: [name, size, contentType]
      properties:
        name:
          type: string
          minLength: 1
          description: Original file name.
        size:
          type: integer
          minimum: 1
          description: File size in bytes.
        contentType:
          type: string
          minLength: 1
          description: MIME type of the file (e.g. `image/jpeg`).
    UploadUrlResponse:
      type: object
      required: [uploadURL, objectPath]
      properties:
        uploadURL:
          type: string
          format: uri
          description: Presigned GCS URL for PUT upload.
        objectPath:
          type: string
          description: Normalized object path (e.g. `/objects/uploads/uuid`). Store this in your database.
        metadata:
          $ref: '#/components/schemas/UploadUrlRequest'
    ErrorEnvelope:
      type: object
      required: [error]
      properties:
        error:
          type: string
    HealthStatus:
      type: object
      properties:
        status:
          type: string
      required:
        - status

    # Auth
    RegisterInput:
      type: object
      required: [email, password, name]
      properties:
        email:
          type: string
        password:
          type: string
        name:
          type: string
        phone:
          type: string

    LoginInput:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
        password:
          type: string

    AuthResponse:
      type: object
      required: [user, token]
      properties:
        user:
          $ref: "#/components/schemas/User"
        token:
          type: string

    User:
      type: object
      required: [id, email, name, role, isOnline, createdAt]
      properties:
        id:
          type: integer
        email:
          type: string
        name:
          type: string
        phone:
          type: ["string", "null"]
        role:
          type: string
          enum: [user, model, admin]
        isOnline:
          type: boolean
        avatarUrl:
          type: ["string", "null"]
        bio:
          type: ["string", "null"]
        createdAt:
          type: string

    UserUpdate:
      type: object
      properties:
        name:
          type: string
        bio:
          type: string
        avatarUrl:
          type: string

    # Dashboard
    DashboardSummary:
      type: object
      required: [totalBookings, activeBookings, membershipLevel, profilesCount, locationsCount]
      properties:
        totalBookings:
          type: integer
        activeBookings:
          type: integer
        membershipLevel:
          type: string
        profilesCount:
          type: integer
        locationsCount:
          type: integer

    # Profile
    Profile:
      type: object
      required: [id, profileId, name, city, isFeatured, isActive, approvalStatus, createdAt]
      properties:
        id:
          type: integer
        profileId:
          type: string
        userId:
          type: ["integer", "null"]
        name:
          type: string
        city:
          type: string
        photoUrl:
          type: ["string", "null"]
        bio:
          type: ["string", "null"]
        proStatus:
          type: ["string", "null"]
          enum: [professional, non_professional, null]
        price1h:
          type: ["integer", "null"]
        price2h:
          type: ["integer", "null"]
        priceFullDay:
          type: ["integer", "null"]
        priceNight:
          type: ["integer", "null"]
        services:
          type: ["string", "null"]
        contactPlatform:
          type: ["string", "null"]
        whatsappNumber:
          type: ["string", "null"]
        approvalStatus:
          type: string
          enum: [pending, approved, rejected]
        isFeatured:
          type: boolean
        isActive:
          type: boolean
        tags:
          type: array
          items:
            type: string
        createdAt:
          type: string

    RegisterModelInput:
      type: object
      required: [name, city, proStatus, price1h, price2h, priceFullDay, services, whatsappNumber]
      properties:
        name:
          type: string
        city:
          type: string
        proStatus:
          type: string
          enum: [professional, non_professional]
        price1h:
          type: integer
        price2h:
          type: integer
        priceFullDay:
          type: integer
        priceNight:
          type: integer
        services:
          type: string
        photoUrl:
          type: string
        bio:
          type: string
        contactPlatform:
          type: string
        whatsappNumber:
          type: string

    ProfileInput:
      type: object
      required: [name, city]
      properties:
        name:
          type: string
        city:
          type: string
        photoUrl:
          type: string
        bio:
          type: string
        isFeatured:
          type: boolean
        tags:
          type: array
          items:
            type: string

    ProfileUpdate:
      type: object
      properties:
        name:
          type: string
        city:
          type: string
        photoUrl:
          type: string
        bio:
          type: string
        proStatus:
          type: string
          enum: [professional, non_professional]
        price1h:
          type: integer
        price2h:
          type: integer
        priceFullDay:
          type: integer
        priceNight:
          type: integer
        services:
          type: string
        contactPlatform:
          type: string
        whatsappNumber:
          type: string
        approvalStatus:
          type: string
          enum: [pending, approved, rejected]
        isFeatured:
          type: boolean
        isActive:
          type: boolean
        tags:
          type: array
          items:
            type: string

    # Location
    Location:
      type: object
      required: [id, name, city, type, rating, isFeatured, isActive, createdAt]
      properties:
        id:
          type: integer
        name:
          type: string
        city:
          type: string
        type:
          type: string
          enum: [hotel, venue, safehouse]
        rating:
          type: number
        photoUrl:
          type: ["string", "null"]
        description:
          type: ["string", "null"]
        address:
          type: ["string", "null"]
        isFeatured:
          type: boolean
        isActive:
          type: boolean
        createdAt:
          type: string

    LocationInput:
      type: object
      required: [name, city, type]
      properties:
        name:
          type: string
        city:
          type: string
        type:
          type: string
          enum: [hotel, venue, safehouse]
        rating:
          type: number
        photoUrl:
          type: string
        description:
          type: string
        address:
          type: string
        isFeatured:
          type: boolean

    LocationUpdate:
      type: object
      properties:
        name:
          type: string
        city:
          type: string
        type:
          type: string
          enum: [hotel, venue, safehouse]
        rating:
          type: number
        photoUrl:
          type: string
        description:
          type: string
        address:
          type: string
        isFeatured:
          type: boolean
        isActive:
          type: boolean

    # Service
    Service:
      type: object
      required: [id, name, hourlyRate, isActive, createdAt]
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: ["string", "null"]
        hourlyRate:
          type: integer
        isActive:
          type: boolean
        createdAt:
          type: string

    # Booking
    Booking:
      type: object
      required: [id, userId, status, createdAt]
      properties:
        id:
          type: integer
        userId:
          type: integer
        profileId:
          type: ["integer", "null"]
        locationId:
          type: ["integer", "null"]
        serviceId:
          type: ["integer", "null"]
        profileName:
          type: ["string", "null"]
        locationName:
          type: ["string", "null"]
        serviceName:
          type: ["string", "null"]
        customerName:
          type: ["string", "null"]
        duration:
          type: ["string", "null"]
          enum: ["1h", "2h", "full_day", "night", null]
        locationType:
          type: ["string", "null"]
          enum: [in_studio, customer_location, null]
        totalPrice:
          type: ["integer", "null"]
        status:
          type: string
          enum: [pending, accepted, paid, proof_uploaded, completed, rejected, cancelled, refunded]
        notes:
          type: ["string", "null"]
        scheduledAt:
          type: ["string", "null"]
        createdAt:
          type: string

    BookingInput:
      type: object
      required: [profileId, serviceId, duration, locationType, scheduledAt]
      properties:
        profileId:
          type: integer
        locationId:
          type: integer
        serviceId:
          type: integer
        duration:
          type: string
          enum: ["1h", "2h", "full_day", "night"]
        locationType:
          type: string
          enum: [in_studio, customer_location]
        notes:
          type: string
        scheduledAt:
          type: string

    BookingStatusInput:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [pending, accepted, paid, proof_uploaded, completed, rejected, cancelled, refunded]

    ProofUploadInput:
      type: object
      required: [photoUrl]
      properties:
        photoUrl:
          type: string

    ProofReviewInput:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [approved, rejected]
        note:
          type: string

    AdminOrderProof:
      type: object
      required: [id, bookingId, photoUrl, status, createdAt]
      properties:
        id:
          type: integer
        bookingId:
          type: integer
        userId:
          type: integer
        photoUrl:
          type: string
        status:
          type: string
          enum: [pending, approved, rejected]
        note:
          type: ["string", "null"]
        reviewedAt:
          type: ["string", "null"]
        createdAt:
          type: string
        bookingStatus:
          type: ["string", "null"]
        totalPrice:
          type: ["integer", "null"]
        duration:
          type: ["string", "null"]
        profileName:
          type: ["string", "null"]
        customerName:
          type: ["string", "null"]

    EscrowStats:
      type: object
      required: [held, balance]
      properties:
        held:
          type: integer
        balance:
          type: integer

    # Wallet
    WalletTransaction:
      type: object
      required: [id, userId, type, amount, balanceAfter, createdAt]
      properties:
        id:
          type: integer
        userId:
          type: integer
        type:
          type: string
          enum: [deposit, order_payment, earning, commission, withdrawal, refund, escrow_in, escrow_out]
        amount:
          type: integer
        balanceAfter:
          type: integer
        bookingId:
          type: ["integer", "null"]
        description:
          type: ["string", "null"]
        createdAt:
          type: string

    Wallet:
      type: object
      required: [balance, transactions]
      properties:
        balance:
          type: integer
        transactions:
          type: array
          items:
            $ref: "#/components/schemas/WalletTransaction"

    WalletDeposit:
      type: object
      required: [id, userId, amount, method, transactionId, status, createdAt]
      properties:
        id:
          type: integer
        userId:
          type: integer
        amount:
          type: integer
        method:
          type: string
        transactionId:
          type: string
        status:
          type: string
          enum: [pending, approved, rejected]
        note:
          type: ["string", "null"]
        reviewedAt:
          type: ["string", "null"]
        createdAt:
          type: string
        userName:
          type: string
        userEmail:
          type: string

    Withdrawal:
      type: object
      required: [id, userId, amount, method, accountNumber, status, createdAt]
      properties:
        id:
          type: integer
        userId:
          type: integer
        amount:
          type: integer
        method:
          type: string
          enum: [bkash, nagad, rocket]
        accountNumber:
          type: string
        status:
          type: string
          enum: [pending, approved, rejected]
        note:
          type: ["string", "null"]
        reviewedAt:
          type: ["string", "null"]
        createdAt:
          type: string
        userName:
          type: string
        userEmail:
          type: string

    AdminWallet:
      type: object
      required: [id, userId, balance]
      properties:
        id:
          type: integer
        userId:
          type: integer
        balance:
          type: integer
        userName:
          type: string
        userEmail:
          type: string
        userRole:
          type: string
        createdAt:
          type: string

    CommissionStats:
      type: object
      required: [today, thisMonth, total]
      properties:
        today:
          type: integer
        thisMonth:
          type: integer
        total:
          type: integer

    DepositInput:
      type: object
      required: [amount, method, transactionId]
      properties:
        amount:
          type: integer
        method:
          type: string
        transactionId:
          type: string

    WithdrawalInput:
      type: object
      required: [amount, method, accountNumber]
      properties:
        amount:
          type: integer
        method:
          type: string
          enum: [bkash, nagad, rocket]
        accountNumber:
          type: string

    WalletCreditInput:
      type: object
      required: [amount]
      properties:
        amount:
          type: integer
        note:
          type: string

    WalletReview:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [approved, rejected]
        note:
          type: string

    # Membership
    Membership:
      type: object
      required: [id, userId, tier, userName, userEmail, createdAt]
      properties:
        id:
          type: integer
        userId:
          type: integer
        userName:
          type: string
        userEmail:
          type: string
        tier:
          type: string
        expiresAt:
          type: ["string", "null"]
        createdAt:
          type: string

    MembershipUpdate:
      type: object
      required: [tier]
      properties:
        tier:
          type: string
        expiresAt:
          type: string

    MembershipPlan:
      type: object
      required: [id, key, name, price, durationDays, features, sortOrder, isActive]
      properties:
        id:
          type: integer
        key:
          type: string
        name:
          type: string
        price:
          type: integer
        durationDays:
          type: integer
        description:
          type: ["string", "null"]
        features:
          type: array
          items:
            type: string
        sortOrder:
          type: integer
        isActive:
          type: boolean

    MembershipPlanInput:
      type: object
      required: [key, name, price]
      properties:
        key:
          type: string
        name:
          type: string
        price:
          type: integer
        durationDays:
          type: integer
        description:
          type: string
        features:
          type: array
          items:
            type: string
        sortOrder:
          type: integer
        isActive:
          type: boolean

    MembershipPlanUpdate:
      type: object
      properties:
        name:
          type: string
        price:
          type: integer
        durationDays:
          type: integer
        description:
          type: string
        features:
          type: array
          items:
            type: string
        sortOrder:
          type: integer
        isActive:
          type: boolean

    PaymentMethod:
      type: object
      required: [id, method, label, receivingNumber, isActive, sortOrder]
      properties:
        id:
          type: integer
        method:
          type: string
        label:
          type: string
        receivingNumber:
          type: string
        instructions:
          type: ["string", "null"]
        isActive:
          type: boolean
        sortOrder:
          type: integer

    PaymentMethodUpdate:
      type: object
      properties:
        label:
          type: string
        receivingNumber:
          type: string
        instructions:
          type: string
        isActive:
          type: boolean
        sortOrder:
          type: integer

    MembershipPayment:
      type: object
      required: [id, userId, planId, method, transactionId, amount, paymentNumber, status, createdAt]
      properties:
        id:
          type: integer
        userId:
          type: integer
        userName:
          type: string
        userEmail:
          type: string
        planId:
          type: integer
        planName:
          type: string
        method:
          type: string
        transactionId:
          type: string
        amount:
          type: integer
        paymentNumber:
          type: string
        status:
          type: string
          enum: [pending, approved, rejected]
        note:
          type: ["string", "null"]
        reviewedAt:
          type: ["string", "null"]
        createdAt:
          type: string

    SubscribeInput:
      type: object
      required: [planId, method, transactionId]
      properties:
        planId:
          type: integer
        method:
          type: string
        transactionId:
          type: string

    MembershipPaymentReview:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [approved, rejected]
        note:
          type: string

    # Network
    NetworkMember:
      type: object
      required: [id, name, isOnline, membershipTier, joinedAt]
      properties:
        id:
          type: integer
        name:
          type: string
        avatarUrl:
          type: ["string", "null"]
        isOnline:
          type: boolean
        membershipTier:
          type: string
        joinedAt:
          type: string

    # Assets
    Asset:
      type: object
      required: [id, title, category, url]
      properties:
        id:
          type: integer
        title:
          type: string
        description:
          type: ["string", "null"]
        category:
          type: string
        url:
          type: string
        iconName:
          type: ["string", "null"]

    # Account types & self-service
    RegisterModelAccountInput:
      type: object
      required: [name, email, password, city, proStatus, price1h, price2h, priceFullDay, services, whatsappNumber]
      properties:
        name:
          type: string
        email:
          type: string
        password:
          type: string
        city:
          type: string
        proStatus:
          type: string
          enum: [professional, non_professional]
        price1h:
          type: integer
        price2h:
          type: integer
        priceFullDay:
          type: integer
        priceNight:
          type: integer
        services:
          type: string
        whatsappNumber:
          type: string
        photoUrl:
          type: string
        bio:
          type: string

    ForgotPasswordInput:
      type: object
      required: [email]
      properties:
        email:
          type: string

    ResetPasswordInput:
      type: object
      required: [token, password]
      properties:
        token:
          type: string
        password:
          type: string

    FavoriteInput:
      type: object
      required: [profileId]
      properties:
        profileId:
          type: integer

    EarningsSummary:
      type: object
      required: [availableBalance, totalEarnings, completedBookings, recent]
      properties:
        availableBalance:
          type: integer
        totalEarnings:
          type: integer
        completedBookings:
          type: integer
        recent:
          type: array
          items:
            $ref: "#/components/schemas/WalletTransaction"

    ModelProfileUpdate:
      type: object
      properties:
        name:
          type: string
        city:
          type: string
        bio:
          type: string
        photoUrl:
          type: string
        proStatus:
          type: string
          enum: [professional, non_professional]
        price1h:
          type: integer
        price2h:
          type: integer
        priceFullDay:
          type: integer
        priceNight:
          type: integer
        services:
          type: string
        whatsappNumber:
          type: string

    AddPhotoInput:
      type: object
      required: [url]
      properties:
        url:
          type: string
        sortOrder:
          type: integer

    ProfilePhoto:
      type: object
      required: [id, profileId, url, sortOrder, createdAt]
      properties:
        id:
          type: integer
        profileId:
          type: integer
        url:
          type: string
        sortOrder:
          type: integer
        createdAt:
          type: string
