openapi: 3.1.0
info:
  title: Api
  version: 0.1.0
  description: SPACEX Key Management API
servers:
  - url: /api
    description: Base API path
tags:
  - name: health
  - name: auth
  - name: keys
  - name: activity
  - name: users

paths:
  /healthz:
    get:
      operationId: healthCheck
      tags: [health]
      summary: Health check
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"

  /auth/login:
    post:
      operationId: login
      tags: [auth]
      summary: Admin login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginBody"
      responses:
        "200":
          description: Login successful
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthUser"
        "401":
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /auth/logout:
    post:
      operationId: logout
      tags: [auth]
      summary: Logout
      responses:
        "200":
          description: Logged out
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

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

  /auth/change-password:
    post:
      operationId: changePassword
      tags: [auth]
      summary: Change own password (leader only)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChangePasswordBody"
      responses:
        "200":
          description: Password changed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"
        "400":
          description: Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /profile:
    get:
      operationId: getProfile
      tags: [auth]
      summary: Get current user full profile
      responses:
        "200":
          description: Profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserProfile"
    put:
      operationId: updateProfile
      tags: [auth]
      summary: Update profile (display name, avatar URL)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateProfileBody"
      responses:
        "200":
          description: Updated profile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserProfile"

  /keys:
    get:
      operationId: listKeys
      tags: [keys]
      summary: List all generated keys
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [all, active, blocked, revoked, expired]
          required: false
        - name: search
          in: query
          schema:
            type: string
          required: false
      responses:
        "200":
          description: Keys list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Key"
    post:
      operationId: generateKeys
      tags: [keys]
      summary: Generate new keys
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateKeysBody"
      responses:
        "200":
          description: Generated keys
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Key"

  /keys/clear:
    delete:
      operationId: clearKeys
      tags: [keys]
      summary: Clear all keys
      responses:
        "200":
          description: Keys cleared
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

  /keys/validate:
    post:
      operationId: validateKey
      tags: [keys]
      summary: Validate a key and bind device UID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidateKeyBody"
      responses:
        "200":
          description: Key validation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidateKeyResponse"

  /keys/reset:
    post:
      operationId: resetKey
      tags: [keys]
      summary: Reset key device binding (3-hour cooldown)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResetKeyBody"
      responses:
        "200":
          description: Key reset
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"
        "400":
          description: Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /keys/{id}/revoke:
    post:
      operationId: revokeKey
      tags: [keys]
      summary: Revoke a key
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Key revoked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

  /keys/{id}/block:
    post:
      operationId: blockKey
      tags: [keys]
      summary: Block a key
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Key blocked
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

  /activity:
    get:
      operationId: listActivity
      tags: [activity]
      summary: List activity events
      parameters:
        - name: filter
          in: query
          schema:
            type: string
            enum: [all, created, activated, reset, blocked, revoked]
          required: false
      responses:
        "200":
          description: Activity list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ActivityEvent"

  /activity/stats:
    get:
      operationId: getActivityStats
      tags: [activity]
      summary: Get activity stats summary
      responses:
        "200":
          description: Stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ActivityStats"

  /users:
    get:
      operationId: listUsers
      tags: [users]
      summary: List all users
      responses:
        "200":
          description: Users list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/User"
    post:
      operationId: createUser
      tags: [users]
      summary: Create a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateUserBody"
      responses:
        "200":
          description: Created user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

  /users/{id}/ban:
    post:
      operationId: banUser
      tags: [users]
      summary: Ban a user (cannot ban another leader)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: User banned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

  /users/{id}/unban:
    post:
      operationId: unbanUser
      tags: [users]
      summary: Unban a user
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: User unbanned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

  /users/{id}/role:
    put:
      operationId: updateUserRole
      tags: [users]
      summary: Change user role (kicks their session)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateRoleBody"
      responses:
        "200":
          description: Role updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

components:
  schemas:
    HealthStatus:
      type: object
      properties:
        status:
          type: string
      required: [status]

    ErrorResponse:
      type: object
      properties:
        error:
          type: string
      required: [error]

    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
      required: [success]

    LoginBody:
      type: object
      properties:
        username:
          type: string
        password:
          type: string
      required: [username, password]

    AuthUser:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        role:
          type: string
          enum: [leader, user]
        displayName:
          type: string
          nullable: true
        avatarUrl:
          type: string
          nullable: true
      required: [id, username, role]

    ChangePasswordBody:
      type: object
      properties:
        currentPassword:
          type: string
        newPassword:
          type: string
      required: [currentPassword, newPassword]

    UserProfile:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        role:
          type: string
          enum: [leader, user]
        displayName:
          type: string
          nullable: true
        avatarUrl:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
      required: [id, username, role, createdAt]

    UpdateProfileBody:
      type: object
      properties:
        displayName:
          type: string
          nullable: true
        avatarUrl:
          type: string
          nullable: true

    Key:
      type: object
      properties:
        id:
          type: integer
        key:
          type: string
        status:
          type: string
          enum: [active, blocked, revoked, expired]
        deviceUid:
          type: string
          nullable: true
        expiresAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        cooldownUntil:
          type: string
          format: date-time
          nullable: true
      required: [id, key, status, expiresAt, createdAt]

    GenerateKeysBody:
      type: object
      properties:
        quantity:
          type: integer
          minimum: 1
          maximum: 100
        durationHours:
          type: integer
          minimum: 1
          maximum: 744
      required: [quantity, durationHours]

    ValidateKeyBody:
      type: object
      properties:
        key:
          type: string
        deviceUid:
          type: string
      required: [key, deviceUid]

    ValidateKeyResponse:
      type: object
      properties:
        valid:
          type: boolean
        message:
          type: string
        key:
          $ref: "#/components/schemas/Key"
          nullable: true
      required: [valid, message]

    ResetKeyBody:
      type: object
      properties:
        key:
          type: string
      required: [key]

    ActivityEvent:
      type: object
      properties:
        id:
          type: integer
        type:
          type: string
          enum: [created, activated, reset, blocked, revoked]
        keyValue:
          type: string
        deviceUid:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
      required: [id, type, keyValue, createdAt]

    ActivityStats:
      type: object
      properties:
        activated:
          type: integer
        reset:
          type: integer
        blocked:
          type: integer
        revoked:
          type: integer
      required: [activated, reset, blocked, revoked]

    User:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        role:
          type: string
          enum: [leader, user]
        banned:
          type: boolean
        displayName:
          type: string
          nullable: true
        avatarUrl:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
      required: [id, username, role, banned, createdAt]

    CreateUserBody:
      type: object
      properties:
        username:
          type: string
        password:
          type: string
        role:
          type: string
          enum: [leader, user]
      required: [username, password, role]

    UpdateRoleBody:
      type: object
      properties:
        role:
          type: string
          enum: [leader, user]
      required: [role]
