openapi: 3.0.3
info:
  title: Items API
  description: |
    Example domain API for the **Domain API Template**.

    This is a deliberately simple "Items" domain included as a working reference
    implementation. It demonstrates the full template pattern:

    - JWT-based authentication with two roles (`contributor`, `viewer`)
    - Role-based access control (RBAC)
    - Ownership-scoped write operations
    - Pagination on list endpoints
    - Standard error response shapes

    **Replace this spec** with your own domain's OpenAPI contract when using this template.
    Run `task domain:init` to copy blank spec templates into `docs/specifications/`.
  version: 1.0.0
  contact:
    name: Domain API Template
    email: template@example.com

servers:
  - url: http://localhost:3000
    description: Local development server

tags:
  - name: Auth
    description: Authentication — register, login, refresh tokens, and logout
  - name: Items
    description: Item management — add, list, view, edit, and remove items

paths:
  /v1/auth/register:
    post:
      tags: [Auth]
      summary: Register a new user
      description: Create a new user account with either `contributor` or `viewer` role. Returns access and refresh tokens on success.
      operationId: registerUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
      responses:
        '201':
          description: User registered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '409':
          $ref: '#/components/responses/ConflictError'

  /v1/auth/login:
    post:
      tags: [Auth]
      summary: Log in
      description: Authenticate with email and password. Returns access and refresh tokens.
      operationId: loginUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'

  /v1/auth/refresh:
    post:
      tags: [Auth]
      summary: Refresh access token
      description: Exchange a refresh token for a new access token and refresh token pair.
      operationId: refreshToken
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshRequest'
      responses:
        '200':
          description: Token refreshed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          $ref: '#/components/responses/AuthenticationError'

  /v1/auth/logout:
    post:
      tags: [Auth]
      summary: Log out
      description: Invalidate the current session. Requires a valid bearer token.
      operationId: logoutUser
      security:
        - bearerAuth: []
      responses:
        '204':
          description: Logged out successfully
        '401':
          $ref: '#/components/responses/AuthenticationError'

  /v1/items:
    get:
      tags: [Items]
      summary: List items
      description: Returns a paginated list of all items. Accessible by both `contributor` and `viewer` roles.
      operationId: listItems
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Paginated list of items
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ItemList'
        '401':
          $ref: '#/components/responses/AuthenticationError'

    post:
      tags: [Items]
      summary: Add an item
      description: Add a new item to the catalogue. Only `contributor` role may add items. The item is associated with the authenticated contributor.
      operationId: createItem
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateItemRequest'
      responses:
        '201':
          description: Item added successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Item'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '403':
          $ref: '#/components/responses/ForbiddenError'

  /v1/items/{itemId}:
    get:
      tags: [Items]
      summary: Get an item
      description: Retrieve a single item by its ID. Accessible by both `contributor` and `viewer` roles.
      operationId: getItem
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ItemId'
      responses:
        '200':
          description: Item found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Item'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '404':
          $ref: '#/components/responses/NotFoundError'

    patch:
      tags: [Items]
      summary: Edit an item
      description: Partially edit an item. Only the `contributor` who added the item may edit it.
      operationId: updateItem
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ItemId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateItemRequest'
      responses:
        '200':
          description: Item edited successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Item'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'

    delete:
      tags: [Items]
      summary: Remove an item
      description: Remove an item from the catalogue. Only the `contributor` who added the item may remove it.
      operationId: deleteItem
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ItemId'
      responses:
        '204':
          description: Item removed successfully
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  parameters:
    ItemId:
      name: itemId
      in: path
      required: true
      description: Unique identifier of the item
      schema:
        type: string
        format: uuid

    Page:
      name: page
      in: query
      required: false
      description: Page number (1-based)
      schema:
        type: integer
        minimum: 1
        default: 1

    PageSize:
      name: pageSize
      in: query
      required: false
      description: Number of results per page
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20

  schemas:
    RegisterRequest:
      type: object
      required: [email, password, firstName, lastName, role]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
        firstName:
          type: string
          minLength: 1
        lastName:
          type: string
          minLength: 1
        role:
          type: string
          enum: [contributor, viewer]

    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string

    RefreshRequest:
      type: object
      required: [refreshToken]
      properties:
        refreshToken:
          type: string

    AuthResponse:
      type: object
      required: [accessToken, refreshToken, expiresIn, user]
      properties:
        accessToken:
          type: string
        refreshToken:
          type: string
        expiresIn:
          type: integer
          description: Access token lifetime in seconds
        user:
          $ref: '#/components/schemas/UserSummary'

    UserSummary:
      type: object
      required: [id, email, firstName, lastName, role]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        firstName:
          type: string
        lastName:
          type: string
        role:
          type: string
          enum: [contributor, viewer]

    Item:
      type: object
      required: [id, name, description, status, contributorId, createdAt, updatedAt]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          minLength: 1
        description:
          type: string
          nullable: true
        status:
          type: string
          enum: [active, archived]
        contributorId:
          type: string
          format: uuid
          description: ID of the contributor who added this item
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    ItemList:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Item'
        pagination:
          $ref: '#/components/schemas/Pagination'

    Pagination:
      type: object
      required: [page, pageSize, total]
      properties:
        page:
          type: integer
        pageSize:
          type: integer
        total:
          type: integer

    CreateItemRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
          minLength: 1
        description:
          type: string
          nullable: true

    UpdateItemRequest:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
          minLength: 1
        description:
          type: string
          nullable: true
        status:
          type: string
          enum: [active, archived]

    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string

    ValidationError:
      type: object
      required: [code, message, details]
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: array
          items:
            type: object
            required: [field, issue]
            properties:
              field:
                type: string
              issue:
                type: string

  responses:
    ValidationError:
      description: Request body or parameters failed validation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
    AuthenticationError:
      description: Missing or invalid authentication token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ForbiddenError:
      description: Authenticated user lacks permission for this operation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFoundError:
      description: The requested resource was not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ConflictError:
      description: A resource with the same unique identifier already exists
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
