openapi: "3.0.3"
info:
  title: Canteen Asset Tracker API
  version: "2.0.0"
  description: |
    REST API for Canteen Asset Geolocation Tool — asset CRUD, check-ins,
    customers, locations, rooms, users, geofences, visits, OCR, and CSV exports.

    ## Base URL
    `https://canteen.ourpad.casa:8901`

    ## Settings entities
    Dynamic CRUD at `/api/settings/{entity}` for: `categories`, `makes`,
    `models`, `key_names`, `key_types`, `badge_types`.

    ## Auth
    POST `/api/auth/login` returns a Bearer token. Most routes are currently
    unauthenticated but the client should attach `Authorization: Bearer <token>`.

servers:
  - url: https://canteen.ourpad.casa:8901
    description: Production

paths:
  # ── Health ────────────────────────────────────────────────────────────────
  /health:
    get:
      summary: Server health check
      operationId: health
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok

  # ── Auth ──────────────────────────────────────────────────────────────────
  /api/auth/login:
    post:
      summary: Login
      operationId: login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
      responses:
        "200":
          description: JWT token
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                  user:
                    type: object

  /api/auth/me:
    get:
      summary: Current user info
      operationId: auth_me
      security:
        - bearerAuth: []
      responses:
        "200":
          description: User profile
          content:
            application/json:
              schema:
                type: object

  # ── Assets ────────────────────────────────────────────────────────────────
  /api/assets:
    get:
      summary: List assets
      operationId: list_assets
      parameters:
        - name: category
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Array of assets
    post:
      summary: Create asset
      operationId: create_asset
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AssetCreate"
      responses:
        "201":
          description: Created asset

  /api/assets/{asset_id}:
    get:
      summary: Get asset
      operationId: get_asset
      parameters:
        - $ref: "#/components/parameters/AssetId"
      responses:
        "200":
          description: Asset object
    put:
      summary: Update asset
      operationId: update_asset
      parameters:
        - $ref: "#/components/parameters/AssetId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AssetUpdate"
      responses:
        "200":
          description: Updated asset
    delete:
      summary: Delete asset
      operationId: delete_asset
      parameters:
        - $ref: "#/components/parameters/AssetId"
      responses:
        "200":
          description: Deleted

  /api/assets/search:
    get:
      summary: Search assets by machine ID
      operationId: search_by_machine_id
      parameters:
        - name: machine_id
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Matching assets

  # ── Check-ins ─────────────────────────────────────────────────────────────
  /api/checkins:
    get:
      summary: List check-ins
      operationId: list_checkins
      parameters:
        - name: asset_id
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Array of check-ins
    post:
      summary: Create check-in
      operationId: create_checkin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CheckinCreate"
      responses:
        "201":
          description: Created check-in

  # ── Customers ─────────────────────────────────────────────────────────────
  /api/customers:
    get:
      summary: List customers
      operationId: list_customers
      responses:
        "200":
          description: Array of customers
    post:
      summary: Create customer
      operationId: create_customer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CustomerCreate"
      responses:
        "201":
          description: Created customer

  /api/customers/{cust_id}:
    get:
      summary: Get customer
      operationId: get_customer
      parameters:
        - $ref: "#/components/parameters/CustId"
      responses:
        "200":
          description: Customer object
    put:
      summary: Update customer
      operationId: update_customer
      parameters:
        - $ref: "#/components/parameters/CustId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CustomerUpdate"
      responses:
        "200":
          description: Updated customer
    delete:
      summary: Delete customer
      operationId: delete_customer
      parameters:
        - $ref: "#/components/parameters/CustId"
      responses:
        "200":
          description: Deleted

  # ── Locations ─────────────────────────────────────────────────────────────
  /api/locations:
    get:
      summary: List locations
      operationId: list_locations
      parameters:
        - name: customer_id
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Array of locations (each includes rooms array)
    post:
      summary: Create location
      operationId: create_location
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LocationCreate"
      responses:
        "201":
          description: Created location

  /api/locations/{loc_id}:
    get:
      summary: Get location
      operationId: get_location
      parameters:
        - $ref: "#/components/parameters/LocId"
      responses:
        "200":
          description: Location object
    put:
      summary: Update location
      operationId: update_location
      parameters:
        - $ref: "#/components/parameters/LocId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LocationUpdate"
      responses:
        "200":
          description: Updated location
    delete:
      summary: Delete location
      operationId: delete_location
      parameters:
        - $ref: "#/components/parameters/LocId"
      responses:
        "200":
          description: Deleted

  # ── Rooms ─────────────────────────────────────────────────────────────────
  /api/rooms:
    get:
      summary: List rooms
      operationId: list_rooms
      parameters:
        - name: location_id
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Array of rooms
    post:
      summary: Create room
      operationId: create_room
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RoomCreate"
      responses:
        "201":
          description: Created room

  /api/rooms/{room_id}:
    get:
      summary: Get room
      operationId: get_room
      parameters:
        - $ref: "#/components/parameters/RoomId"
      responses:
        "200":
          description: Room object
    put:
      summary: Update room
      operationId: update_room
      parameters:
        - $ref: "#/components/parameters/RoomId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RoomUpdate"
      responses:
        "200":
          description: Updated room
    delete:
      summary: Delete room
      operationId: delete_room
      parameters:
        - $ref: "#/components/parameters/RoomId"
      responses:
        "200":
          description: Deleted

  # ── Users ─────────────────────────────────────────────────────────────────
  /api/users:
    get:
      summary: List users
      operationId: list_users
      responses:
        "200":
          description: Array of users
    post:
      summary: Create user
      operationId: create_user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserCreate"
      responses:
        "201":
          description: Created user

  /api/users/{user_id}:
    get:
      summary: Get user
      operationId: get_user
      parameters:
        - $ref: "#/components/parameters/UserId"
      responses:
        "200":
          description: User object
    put:
      summary: Update user
      operationId: update_user
      parameters:
        - $ref: "#/components/parameters/UserId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserUpdate"
      responses:
        "200":
          description: Updated user
    delete:
      summary: Delete user
      operationId: delete_user
      parameters:
        - $ref: "#/components/parameters/UserId"
      responses:
        "200":
          description: Deleted

  /api/users/{user_id}/geofences:
    get:
      summary: List geofences assigned to a user (service areas)
      operationId: list_user_geofences
      parameters:
        - $ref: "#/components/parameters/UserId"
      responses:
        "200":
          description: Array of geofences assigned to this user

  # ── Geofences ─────────────────────────────────────────────────────────────
  /api/geofences:
    get:
      summary: List geofences
      operationId: list_geofences
      responses:
        "200":
          description: Array of geofences
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/GeofenceResponse"
    post:
      summary: Create geofence
      operationId: create_geofence
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GeofenceCreate"
      responses:
        "201":
          description: Created geofence
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GeofenceResponse"

  /api/geofences/{geofence_id}:
    put:
      summary: Update geofence
      operationId: update_geofence
      parameters:
        - $ref: "#/components/parameters/GeofenceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GeofenceUpdate"
      responses:
        "200":
          description: Updated geofence
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GeofenceResponse"
    delete:
      summary: Delete geofence
      operationId: delete_geofence
      parameters:
        - $ref: "#/components/parameters/GeofenceId"
      responses:
        "200":
          description: Deleted

  /api/geofences/check:
    post:
      summary: Check if point is inside a geofence
      operationId: check_geofence_point
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GeofencePointCheck"
      responses:
        "200":
          description: Geofence check result

  /api/proximity:
    get:
      summary: Proximity check — find assets near a GPS point
      operationId: proximity_check
      parameters:
        - name: lat
          in: query
          required: true
          schema:
            type: number
        - name: lng
          in: query
          required: true
          schema:
            type: number
        - name: radius_km
          in: query
          schema:
            type: number
            default: 1.0
      responses:
        "200":
          description: Nearby assets

  # ── Visits ────────────────────────────────────────────────────────────────
  /api/visits:
    get:
      summary: List visits
      operationId: list_visits
      parameters:
        - name: asset_id
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Array of visits
    post:
      summary: Create visit
      operationId: create_visit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VisitCreate"
      responses:
        "201":
          description: Created visit

  /api/visits/stats:
    get:
      summary: Visit statistics
      operationId: get_visit_stats
      responses:
        "200":
          description: Visit stats

  # ── Activity ──────────────────────────────────────────────────────────────
  /api/activity:
    get:
      summary: Activity feed
      operationId: list_activity
      parameters:
        - name: user_id
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
      responses:
        "200":
          description: Activity entries

  # ── Stats & Exports ───────────────────────────────────────────────────────
  /api/stats:
    get:
      summary: Dashboard statistics
      operationId: get_stats
      responses:
        "200":
          description: Aggregate stats

  /api/export/assets:
    get:
      summary: Export assets as CSV
      operationId: export_assets_csv
      responses:
        "200":
          description: CSV file
          content:
            text/csv:
              schema:
                type: string

  /api/export/checkins:
    get:
      summary: Export check-ins as CSV
      operationId: export_checkins_csv
      parameters:
        - name: asset_id
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: CSV file
          content:
            text/csv:
              schema:
                type: string

  /api/export/service-summary:
    get:
      summary: Export service summary as CSV
      operationId: export_service_summary_csv
      responses:
        "200":
          description: CSV file
          content:
            text/csv:
              schema:
                type: string

  # ── Uploads & OCR ─────────────────────────────────────────────────────────
  /api/upload/icon:
    post:
      summary: Upload icon image
      operationId: upload_icon
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Uploaded icon path

  /api/upload/photo:
    post:
      summary: Upload photo
      operationId: upload_photo
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Uploaded photo path

  /api/ocr:
    post:
      summary: OCR a sticker/image — extract machine ID
      operationId: ocr_sticker
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "200":
          description: Extracted text

  # ── Settings (dynamic) ────────────────────────────────────────────────────
  /api/settings/{entity}:
    get:
      summary: List settings entities
      operationId: list_settings
      parameters:
        - name: entity
          in: path
          required: true
          schema:
            type: string
            enum: [categories, makes, models, key_names, key_types, badge_types]
      responses:
        "200":
          description: Array of settings values
    post:
      summary: Create settings entity
      operationId: create_setting
      parameters:
        - name: entity
          in: path
          required: true
          schema:
            type: string
            enum: [categories, makes, models, key_names, key_types, badge_types]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        "201":
          description: Created entity

  /api/settings/{entity}/{eid}:
    get:
      summary: Get settings entity
      operationId: get_setting
      parameters:
        - name: entity
          in: path
          required: true
          schema:
            type: string
            enum: [categories, makes, models, key_names, key_types, badge_types]
        - $ref: "#/components/parameters/SettingId"
      responses:
        "200":
          description: Settings entity object
    put:
      summary: Update settings entity
      operationId: update_setting
      parameters:
        - name: entity
          in: path
          required: true
          schema:
            type: string
            enum: [categories, makes, models, key_names, key_types, badge_types]
        - $ref: "#/components/parameters/SettingId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        "200":
          description: Updated entity
    delete:
      summary: Delete settings entity
      operationId: delete_setting
      parameters:
        - name: entity
          in: path
          required: true
          schema:
            type: string
            enum: [categories, makes, models, key_names, key_types, badge_types]
        - $ref: "#/components/parameters/SettingId"
      responses:
        "204":
          description: Deleted

# ── Components ──────────────────────────────────────────────────────────────
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

  parameters:
    AssetId:
      name: asset_id
      in: path
      required: true
      schema:
        type: integer
    CustId:
      name: cust_id
      in: path
      required: true
      schema:
        type: integer
    LocId:
      name: loc_id
      in: path
      required: true
      schema:
        type: integer
    RoomId:
      name: room_id
      in: path
      required: true
      schema:
        type: integer
    UserId:
      name: user_id
      in: path
      required: true
      schema:
        type: integer
    GeofenceId:
      name: geofence_id
      in: path
      required: true
      schema:
        type: integer
    SettingId:
      name: eid
      in: path
      required: true
      schema:
        type: integer

  schemas:
    AssetKey:
      type: object
      properties:
        key_name:
          type: string
        key_type:
          type: string

    AssetBadge:
      type: object
      properties:
        badge_name:
          type: string

    AssetCreate:
      type: object
      required:
        - machine_id
        - name
      properties:
        machine_id:
          type: string
        name:
          type: string
        serial_number:
          type: string
        description:
          type: string
        category:
          type: string
          enum: [Furniture, Appliances, "Utensils & Serveware", Equipment, Other]
          default: Other
        status:
          type: string
          enum: [active, maintenance, retired]
          default: active
        make:
          type: string
        model:
          type: string
        address:
          type: string
        building_name:
          type: string
        building_number:
          type: string
        floor:
          type: string
        room:
          type: string
        trailer_number:
          type: string
        walking_directions:
          type: string
        map_link:
          type: string
        parking_location:
          type: string
        photo_path:
          type: string
        customer_id:
          type: integer
        location_id:
          type: integer
        assigned_to:
          type: integer
        latitude:
          type: number
        longitude:
          type: number
        geofence_radius_meters:
          type: integer
          default: 50
        keys:
          type: array
          items:
            $ref: "#/components/schemas/AssetKey"
        badges:
          type: array
          items:
            type: string

    AssetUpdate:
      type: object
      properties:
        machine_id:
          type: string
        name:
          type: string
        serial_number:
          type: string
        description:
          type: string
        category:
          type: string
        status:
          type: string
        make:
          type: string
        model:
          type: string
        address:
          type: string
        building_name:
          type: string
        building_number:
          type: string
        floor:
          type: string
        room:
          type: string
        trailer_number:
          type: string
        walking_directions:
          type: string
        map_link:
          type: string
        parking_location:
          type: string
        photo_path:
          type: string
        customer_id:
          type: integer
        location_id:
          type: integer
        assigned_to:
          type: integer
        latitude:
          type: number
        longitude:
          type: number
        geofence_radius_meters:
          type: integer
        keys:
          type: array
          items:
            $ref: "#/components/schemas/AssetKey"
        badges:
          type: array
          items:
            type: string

    CheckinCreate:
      type: object
      required:
        - asset_id
      properties:
        asset_id:
          type: integer
        latitude:
          type: number
        longitude:
          type: number
        accuracy:
          type: number
        photo_path:
          type: string
        notes:
          type: string
        user_id:
          type: integer

    CustomerContact:
      type: object
      properties:
        name:
          type: string
        phone:
          type: string
        email:
          type: string

    CustomerCreate:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        contacts:
          type: array
          items:
            $ref: "#/components/schemas/CustomerContact"

    CustomerUpdate:
      type: object
      properties:
        name:
          type: string
        contacts:
          type: array
          items:
            $ref: "#/components/schemas/CustomerContact"

    LoginRequest:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
        password:
          type: string

    LocationCreate:
      type: object
      required:
        - name
      properties:
        customer_id:
          type: integer
        name:
          type: string
        address:
          type: string
        building_name:
          type: string
        building_number:
          type: string
        floor:
          type: string
        trailer_number:
          type: string
        site_hours:
          type: string
        access_notes:
          type: string
        walking_directions:
          type: string
        map_link:
          type: string
        latitude:
          type: number
        longitude:
          type: number

    LocationUpdate:
      type: object
      properties:
        customer_id:
          type: integer
        name:
          type: string
        address:
          type: string
        building_name:
          type: string
        building_number:
          type: string
        floor:
          type: string
        trailer_number:
          type: string
        site_hours:
          type: string
        access_notes:
          type: string
        walking_directions:
          type: string
        map_link:
          type: string
        latitude:
          type: number
        longitude:
          type: number

    RoomCreate:
      type: object
      required:
        - location_id
        - name
      properties:
        location_id:
          type: integer
        name:
          type: string
        floor:
          type: string

    RoomUpdate:
      type: object
      properties:
        name:
          type: string
        floor:
          type: string
        location_id:
          type: integer

    UserCreate:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
        password:
          type: string
        role:
          type: string
          enum: [technician, admin]

    UserUpdate:
      type: object
      properties:
        role:
          type: string
        password:
          type: string

    GeofenceCreate:
      type: object
      required:
        - name
        - points
      properties:
        name:
          type: string
        points:
          type: array
          items:
            type: object
            properties:
              lat:
                type: number
              lng:
                type: number
        color:
          type: string
          default: "#3388ff"
        user_ids:
          type: array
          items:
            type: integer
          description: IDs of users assigned to this service area

    GeofenceResponse:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        points:
          type: array
        color:
          type: string
        created_at:
          type: string
        updated_at:
          type: string
        assigned_users:
          type: array
          items:
            $ref: "#/components/schemas/UserBrief"

    UserBrief:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        role:
          type: string

    GeofenceUpdate:
      type: object
      properties:
        name:
          type: string
        points:
          type: array
        color:
          type: string
        user_ids:
          type: array
          items:
            type: integer
          description: Replace assigned users for this service area

    GeofencePointCheck:
      type: object
      required:
        - lat
        - lng
      properties:
        lat:
          type: number
        lng:
          type: number

    VisitCreate:
      type: object
      required:
        - asset_id
      properties:
        user_id:
          type: integer
        asset_id:
          type: integer
        latitude:
          type: number
        longitude:
          type: number
        duration_minutes:
          type: integer
