openapi: 3.0.3
info:
  title: Eyetrosoft Signage System API
  version: "0.1.0"
servers:
  - url: http://localhost:8080/api
tags:
  - name: Auth
  - name: Admin
  - name: Player
  - name: Reports

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    Error:
      type: object
      properties:
        message: { type: string }
    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email: { type: string }
        password: { type: string }
    LoginResponse:
      type: object
      properties:
        token: { type: string }
        user:
          type: object
          properties:
            id: { type: integer }
            name: { type: string }
            email: { type: string }
            role: { type: string }
    DeviceLoginRequest:
      type: object
      required: [device_code]
      properties:
        device_code: { type: string }
        player_version: { type: string }
    DeviceLoginResponse:
      type: object
      properties:
        device_token: { type: string }
        device:
          type: object
          properties:
            id: { type: integer }
            name: { type: string }
            location_name: { type: string }
    Campaign:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        status: { type: string }
        notes: { type: string }
    Creative:
      type: object
      properties:
        id: { type: integer }
        campaign_id: { type: integer }
        type: { type: string, enum: [VIDEO, IMAGE] }
        storage_url: { type: string }
        checksum_sha256: { type: string }
        duration_seconds: { type: integer }
    CarouselCard:
      type: object
      properties:
        id: { type: integer }
        campaign_id: { type: integer }
        title: { type: string }
        cta_text: { type: string }
        image_url: { type: string }
        form_id: { type: integer }
        sort_order: { type: integer }
    Schedule:
      type: object
      properties:
        id: { type: integer }
        campaign_id: { type: integer }
        device_id: { type: integer }
        start_at: { type: string, format: date-time }
        end_at: { type: string, format: date-time }
        priority: { type: integer }
        is_active: { type: boolean }
    Manifest:
      type: object
      properties:
        device_id: { type: integer }
        now: { type: string, format: date-time }
        window_end: { type: string, format: date-time }
        active_campaign:
          type: object
          nullable: true
          properties:
            campaign: { $ref: '#/components/schemas/Campaign' }
            creatives:
              type: array
              items: { $ref: '#/components/schemas/Creative' }
            carousel_cards:
              type: array
              items: { $ref: '#/components/schemas/CarouselCard' }
            schedule: { $ref: '#/components/schemas/Schedule' }
    PlayEvent:
      type: object
      required: [event_type, started_at]
      properties:
        event_type: { type: string, enum: [PLAY_START, HEARTBEAT, PLAY_END] }
        device_id: { type: integer }
        campaign_id: { type: integer }
        creative_id: { type: integer }
        started_at: { type: string, format: date-time }
        ended_at: { type: string, format: date-time }
        duration_seconds: { type: integer }
        latitude: { type: number }
        longitude: { type: number }
        accuracy_m: { type: number }
        location_status: { type: string }

security:
  - bearerAuth: []

paths:
  /auth/login:
    post:
      tags: [Auth]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LoginRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LoginResponse' }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /auth/device-login:
    post:
      tags: [Auth, Player]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/DeviceLoginRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DeviceLoginResponse' }

  /admin/users:
    get:
      tags: [Admin]
      responses:
        "200":
          description: OK
    post:
      tags: [Admin]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email, password, role]
              properties:
                name: { type: string }
                email: { type: string }
                password: { type: string }
                role: { type: string, enum: [SUPER_ADMIN, CAMPAIGN_ADMIN, ANALYST] }
      responses:
        "201": { description: Created }

  /admin/devices:
    get:
      tags: [Admin]
      responses:
        "200": { description: OK }
    post:
      tags: [Admin]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [device_code, name]
              properties:
                device_code: { type: string }
                name: { type: string }
                location_name: { type: string }
                group_name: { type: string }
      responses:
        "201": { description: Created }

  /admin/campaigns:
    get:
      tags: [Admin]
      responses:
        "200": { description: OK }
    post:
      tags: [Admin]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                notes: { type: string }
      responses:
        "201": { description: Created }

  /admin/campaigns/{id}/creatives:
    post:
      tags: [Admin]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, storage_url]
              properties:
                type: { type: string, enum: [VIDEO, IMAGE] }
                storage_url: { type: string }
                checksum_sha256: { type: string }
                duration_seconds: { type: integer }
      responses:
        "201": { description: Created }

  /admin/campaigns/{id}/carousel:
    post:
      tags: [Admin]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [image_url]
              properties:
                title: { type: string }
                cta_text: { type: string }
                image_url: { type: string }
                form_id: { type: integer }
                sort_order: { type: integer }
      responses:
        "201": { description: Created }

  /admin/schedules:
    post:
      tags: [Admin]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [campaign_id, device_id, start_at, end_at]
              properties:
                campaign_id: { type: integer }
                device_id: { type: integer }
                start_at: { type: string, format: date-time }
                end_at: { type: string, format: date-time }
                priority: { type: integer }
      responses:
        "201": { description: Created }

  /device/manifest:
    get:
      tags: [Player]
      security: []
      parameters:
        - in: header
          name: X-Device-Token
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Manifest for next playback window
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Manifest' }

  /events/play:
    post:
      tags: [Player]
      security: []
      parameters:
        - in: header
          name: X-Device-Token
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PlayEvent' }
      responses:
        "200": { description: OK }

  /forms/submit:
    post:
      tags: [Player]
      security: []
      parameters:
        - in: header
          name: X-Device-Token
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [campaign_id, form_id, payload]
              properties:
                campaign_id: { type: integer }
                form_id: { type: integer }
                payload: { type: object }
      responses:
        "201": { description: Created }

  /reports/proof-of-play:
    get:
      tags: [Reports]
      parameters:
        - in: query
          name: from
          schema: { type: string, format: date-time }
        - in: query
          name: to
          schema: { type: string, format: date-time }
      responses:
        "200": { description: OK }

  /reports/submissions:
    get:
      tags: [Reports]
      parameters:
        - in: query
          name: from
          schema: { type: string, format: date-time }
        - in: query
          name: to
          schema: { type: string, format: date-time }
      responses:
        "200": { description: OK }
