openapi: 3.0.3
info:
  title: BDBOX API
  version: "1.0.0"
  description: |
    Standardizovan opis postojećeg BDBOX API v1 interfejsa.

    Ovaj dokument je samo opis postojećeg API-ja. Ne generiše runtime kod, ne mijenja postojeće PHP rute
    i ne predstavlja poseban API sloj.

    Glavni javni API docs su dostupni na `/api/v1/docs/`.
servers:
  - url: https://www.bijeljina.org/bdbox/api/v1
    description: BDBOX API v1

security:
  - bearerAuth: []

tags:
  - name: System
  - name: Auth
  - name: Categories
  - name: Locations
  - name: Public Listings
  - name: Own Listings
  - name: Listing Write
  - name: Listing Images

paths:
  /health:
    get:
      tags: [System]
      summary: API status
      security: []
      responses:
        "200":
          description: API je dostupan.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          status:
                            type: string
                            example: ok
                          time:
                            type: string
                            format: date-time

  /auth-test:
    get:
      tags: [Auth]
      summary: Provjera Bearer tokena
      description: Zahtijeva `public:read` scope.
      responses:
        "200":
          description: Token je validan.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          authenticated:
                            type: boolean
                            example: true
                          client:
                            $ref: "#/components/schemas/AuthClient"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /categories:
    get:
      tags: [Categories]
      summary: Lista kategorija
      description: Zahtijeva `public:read` ili `listings:read` scope.
      parameters:
        - in: query
          name: parent_id
          schema:
            type: integer
            minimum: 1
          required: false
      responses:
        "200":
          description: Lista kategorija.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/Category"
                      meta:
                        allOf:
                          - $ref: "#/components/schemas/Meta"
                          - type: object
                            properties:
                              total:
                                type: integer

  /categories/{id}:
    get:
      tags: [Categories]
      summary: Detalj kategorije
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Kategorija.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/Category"
        "404":
          $ref: "#/components/responses/NotFound"

  /categories/{id}/custom-fields:
    get:
      tags: [Categories]
      summary: Custom fields za kategoriju
      description: |
        Vraća polja potrebna za create/update forme oglasa.

        `allowed_values` su tekstualne kanonske vrijednosti koje se šalju u `custom_fields`.
        Ne šalju se ID-jevi opcija. Ključevi u `custom_fields` objektu su ID-jevi polja.
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Kategorija i polja.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          category:
                            $ref: "#/components/schemas/WritableCategory"
                          custom_fields:
                            type: array
                            items:
                              $ref: "#/components/schemas/CustomFieldDefinition"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"

  /locations/cities:
    get:
      tags: [Locations]
      summary: Lista gradova
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPageLocations"
        - in: query
          name: state_id
          schema:
            type: integer
            minimum: 1
        - in: query
          name: q
          schema:
            type: string
            minLength: 2
            maxLength: 100
        - in: query
          name: sort
          schema:
            type: string
            enum: [name_asc, id_asc]
            default: name_asc
      responses:
        "200":
          description: Lista gradova.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/City"
                      meta:
                        $ref: "#/components/schemas/PaginationMeta"

  /locations/cities/{id}:
    get:
      tags: [Locations]
      summary: Detalj grada
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Grad.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/City"
        "404":
          $ref: "#/components/responses/NotFound"

  /listings:
    get:
      tags: [Public Listings]
      summary: Lista javnih oglasa
      description: Vraća samo javne oglase sa `status=approved` i `paid=1`.
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPageListings"
        - in: query
          name: category_id
          schema:
            type: integer
            minimum: 1
        - in: query
          name: city_id
          schema:
            type: integer
            minimum: 1
        - in: query
          name: q
          schema:
            type: string
            minLength: 2
            maxLength: 100
        - in: query
          name: price_min
          schema:
            type: number
            minimum: 0
        - in: query
          name: price_max
          schema:
            type: number
            minimum: 0
        - in: query
          name: sort
          schema:
            type: string
            enum: [date_desc, date_asc, price_asc, price_desc]
            default: date_desc
      responses:
        "200":
          description: Lista javnih oglasa.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/PublicListingSummary"
                      meta:
                        $ref: "#/components/schemas/PaginationMeta"

    post:
      tags: [Listing Write]
      summary: Kreiranje oglasa
      description: |
        Zahtijeva autentifikovanog korisnika i `listings:write` scope.

        Standardno kreira `pending` oglas. Trusted server klijent sa ručno dodijeljenim
        `listings:auto_approve` scope-om kreira `approved` oglas.

        Server API klijent mora poslati `external_id`.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ListingCreateRequest"
      responses:
        "201":
          description: Oglas je kreiran.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/ListingCreateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationError"

  /listings/{id}:
    get:
      tags: [Public Listings]
      summary: Detalj javnog oglasa
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Javni oglas.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/PublicListingDetail"
        "404":
          $ref: "#/components/responses/NotFound"

    patch:
      tags: [Listing Write]
      summary: Izmjena vlastitog oglasa
      description: |
        Zahtijeva `listings:write`.

        Ne dozvoljava promjenu kategorije, grada, statusa, slug-a, ownera, external ID-a ni slika.
      parameters:
        - $ref: "#/components/parameters/IdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ListingPatchRequest"
      responses:
        "200":
          description: Oglas je izmijenjen.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/OwnerListingDetail"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"

    delete:
      tags: [Listing Write]
      summary: Brisanje oglasa
      description: Soft delete. Zahtijeva `listings:delete`.
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Oglas je obrisan iz korisničkog i javnog prikaza.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /listings/by-external-id/{external_id}:
    patch:
      tags: [Listing Write]
      summary: Izmjena vlastitog oglasa po external ID-u
      parameters:
        - $ref: "#/components/parameters/ExternalIdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ListingPatchRequest"
      responses:
        "200":
          description: Oglas je izmijenjen.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"
        "404":
          $ref: "#/components/responses/NotFound"

  /listings/{id}/deactivate:
    post:
      tags: [Listing Write]
      summary: Deaktivacija oglasa
      description: Postavlja vlastiti oglas u `pending`. Zahtijeva `listings:write`.
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Status promijenjen ili već u istom stanju.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /listings/{id}/complete:
    post:
      tags: [Listing Write]
      summary: Završavanje oglasa
      description: Postavlja vlastiti oglas u `completed`/`zavrsen`. Zahtijeva `listings:delete`.
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Oglas završen.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /listings/{id}/renew:
    post:
      tags: [Listing Write]
      summary: Obnova oglasa
      description: Obnavlja samo approved vlastiti oglas. Zahtijeva `listings:write` i Idempotency-Key.
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Oglas obnovljen ili replay istog zahtjeva.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /listings/{id}/main-image:
    post:
      tags: [Listing Images]
      summary: Upload ili zamjena glavne slike
      description: Multipart form-key je `image`. Jedna slika po zahtjevu.
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/ImageUploadRequest"
      responses:
        "201":
          description: Glavna slika uploadovana.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

    delete:
      tags: [Listing Images]
      summary: Brisanje glavne slike
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Glavna slika obrisana ili već ne postoji.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /listings/{id}/images:
    post:
      tags: [Listing Images]
      summary: Upload galerijske slike
      description: Multipart form-key je `image`. Endpoint prima jednu sliku po zahtjevu.
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/ImageUploadRequest"
      responses:
        "201":
          description: Galerijska slika uploadovana.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /listings/{id}/main-image/rotate:
    post:
      tags: [Listing Images]
      summary: Rotacija glavne slike
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RotateRequest"
      responses:
        "200":
          description: Glavna slika rotirana.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /listings/{id}/images/{photo_id}/rotate:
    post:
      tags: [Listing Images]
      summary: Rotacija galerijske slike
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - in: path
          name: photo_id
          required: true
          schema:
            type: integer
            minimum: 1
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RotateRequest"
      responses:
        "200":
          description: Galerijska slika rotirana.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /listings/{id}/images/{photo_id}:
    delete:
      tags: [Listing Images]
      summary: Brisanje galerijske slike
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - in: path
          name: photo_id
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Galerijska slika obrisana.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /me/listings:
    get:
      tags: [Own Listings]
      summary: Lista vlastitih oglasa
      description: Zahtijeva `listings:read_own` i autentifikovanog korisnika.
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPageListings"
        - in: query
          name: status
          schema:
            type: string
            enum: [approved, pending, completed, trashed]
        - in: query
          name: category_id
          schema:
            type: integer
            minimum: 1
        - in: query
          name: q
          schema:
            type: string
            minLength: 2
            maxLength: 100
        - in: query
          name: sort
          schema:
            type: string
            enum: [date_desc, date_asc, price_asc, price_desc]
      responses:
        "200":
          description: Lista vlastitih oglasa.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/OwnerListingSummary"
                      meta:
                        $ref: "#/components/schemas/PaginationMeta"

  /me/listings/{id}:
    get:
      tags: [Own Listings]
      summary: Detalj vlastitog oglasa
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Vlastiti oglas.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/OwnerListingDetail"
        "404":
          $ref: "#/components/responses/NotFound"

  /me/listings/by-external-id/{external_id}:
    get:
      tags: [Own Listings]
      summary: Detalj vlastitog oglasa po external ID-u
      parameters:
        - $ref: "#/components/parameters/ExternalIdPath"
      responses:
        "200":
          description: Vlastiti oglas.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/SuccessEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/OwnerListingDetail"
        "404":
          $ref: "#/components/responses/NotFound"

  /me/listings/{id}/images:
    get:
      tags: [Listing Images]
      summary: Slike vlastitog oglasa
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Inventory slika.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: opaque
      description: BDBOX API Bearer token ili user access token.

  parameters:
    IdPath:
      in: path
      name: id
      required: true
      schema:
        type: integer
        minimum: 1
    ExternalIdPath:
      in: path
      name: external_id
      required: true
      schema:
        type: string
        pattern: "^[A-Za-z0-9._:-]{1,128}$"
    Page:
      in: query
      name: page
      required: false
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPageListings:
      in: query
      name: per_page
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 50
        default: 20
    PerPageLocations:
      in: query
      name: per_page
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 100
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: true
      schema:
        type: string
        minLength: 16
        maxLength: 128
      description: Jedinstven key po write pokušaju. Isti key koristiti samo za retry istog zahtjeva.

  responses:
    BadRequest:
      description: Neispravan zahtjev.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    Unauthorized:
      description: Token nedostaje ili nije validan.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    Forbidden:
      description: Token nema potreban scope ili korisnički kontekst.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    NotFound:
      description: Resurs nije pronađen.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    Conflict:
      description: Konflikt stanja ili idempotency/external ID konflikt.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    ValidationError:
      description: Validacija nije prošla.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"

  schemas:
    SuccessEnvelope:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          enum: [true]
        data:
          type: object
        meta:
          $ref: "#/components/schemas/Meta"

    ErrorEnvelope:
      type: object
      required: [success, error, meta]
      properties:
        success:
          type: boolean
          enum: [false]
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              example: validation_error
            message:
              type: string
            details:
              type: object
              additionalProperties: true
        meta:
          $ref: "#/components/schemas/Meta"

    Meta:
      type: object
      required: [request_id, api_version]
      properties:
        request_id:
          type: string
          example: req_20260625_120000_ab12cd34
        api_version:
          type: string
          example: v1

    PaginationMeta:
      allOf:
        - $ref: "#/components/schemas/Meta"
        - type: object
          properties:
            total:
              type: integer
            page:
              type: integer
            per_page:
              type: integer
            total_pages:
              type: integer
            sort:
              type: string
            filters:
              type: object
              additionalProperties: true

    AuthClient:
      type: object
      properties:
        id:
          type: integer
        naziv:
          type: string
        environment:
          type: string
          enum: [test, live]
        client_type:
          type: string
        scopes:
          type: array
          items:
            type: string

    Category:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        plural_name:
          type: string
          nullable: true
        slug:
          type: string
        parent_id:
          type: integer
          nullable: true
        icon:
          type: string
          nullable: true
        background:
          type: string
          nullable: true
        sort_order:
          type: integer
          nullable: true
        online_sale:
          type: integer
          nullable: true
        premium:
          type: integer
          nullable: true

    WritableCategory:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        slug:
          type: string
        parent_id:
          type: integer
        writable:
          type: boolean

    CustomFieldDefinition:
      type: object
      properties:
        id:
          type: integer
          description: ID polja. Koristi se kao key u custom_fields objektu.
        name:
          type: string
        type:
          type: string
          enum: [radio, checkbox, select, text, multiline, url, number, decimal, time]
        required:
          type: boolean
        allowed_values:
          type: array
          description: Tekstualne kanonske vrijednosti, ne ID-jevi opcija.
          items:
            type: string
        tooltip:
          type: string
          nullable: true
        order:
          type: integer

    City:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        slug:
          type: string
        lat:
          type: number
          nullable: true
        lng:
          type: number
          nullable: true
        state:
          type: object
          nullable: true
          properties:
            id:
              type: integer
            name:
              type: string
            abbr:
              type: string
            slug:
              type: string
        country:
          type: object
          properties:
            id:
              type: integer
            abbr:
              type: string

    PriceInfo:
      type: object
      required: [type]
      properties:
        type:
          type: string
          enum: [fixed, negotiable, no_price]
        amount:
          type: number
          nullable: true
        currency:
          type: string
          readOnly: true
          example: BAM

    ListingCreateRequest:
      type: object
      required: [title, short_description, category_id, city_id, price_info]
      additionalProperties: false
      properties:
        external_id:
          type: string
          pattern: "^[A-Za-z0-9._:-]{1,128}$"
          description: Obavezan za server API klijente.
        title:
          type: string
          minLength: 3
          maxLength: 80
        short_description:
          type: string
          minLength: 1
          maxLength: 100
        description:
          type: string
          nullable: true
          maxLength: 20000
        category_id:
          type: integer
          minimum: 1
        city_id:
          type: integer
          minimum: 1
        price_info:
          $ref: "#/components/schemas/PriceInfo"
        location:
          $ref: "#/components/schemas/ListingLocationInput"
        contact:
          $ref: "#/components/schemas/ListingContactInput"
        custom_fields:
          type: object
          description: |
            Key je ID custom field polja kao string. Vrijednost je jedna vrijednost ili niz za checkbox.
            Select/radio vrijednosti su tekst iz allowed_values.
          additionalProperties:
            oneOf:
              - type: string
              - type: number
              - type: array
                items:
                  type: string

    ListingPatchRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        title:
          type: string
          minLength: 3
          maxLength: 80
        short_description:
          type: string
          minLength: 1
          maxLength: 100
        description:
          type: string
          nullable: true
          maxLength: 20000
        price_info:
          $ref: "#/components/schemas/PriceInfo"
        location:
          $ref: "#/components/schemas/ListingLocationInput"
        contact:
          $ref: "#/components/schemas/ListingContactInput"
        custom_fields:
          type: object
          additionalProperties:
            oneOf:
              - type: string
              - type: number
              - type: array
                items:
                  type: string

    ListingLocationInput:
      type: object
      additionalProperties: false
      properties:
        address:
          type: string
          maxLength: 255
          nullable: true
        postal_code:
          type: string
          maxLength: 20
          nullable: true
        lat:
          type: number
          minimum: -90
          maximum: 90
          nullable: true
        lng:
          type: number
          minimum: -180
          maximum: 180
          nullable: true

    ListingContactInput:
      type: object
      additionalProperties: false
      properties:
        phone:
          type: string
          maxLength: 30
          nullable: true
        whatsapp_country_code:
          type: string
          maxLength: 30
          nullable: true
        whatsapp_area_code:
          type: string
          maxLength: 30
          nullable: true
        whatsapp_phone:
          type: string
          maxLength: 30
          nullable: true

    ListingCreateResponse:
      type: object
      properties:
        id:
          type: integer
        external_id:
          type: string
          nullable: true
        status:
          type: string
          enum: [approved, pending]
        is_public:
          type: boolean
        created_at:
          type: string

    PublicListingSummary:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        slug:
          type: string
        url:
          type: string
          format: uri
        price:
          type: number
          nullable: true
        currency:
          type: string
          example: BAM
        city:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
        category_ids:
          type: array
          items:
            type: integer
        main_image_url:
          type: string
          nullable: true
        created_at:
          type: string

    PublicListingDetail:
      allOf:
        - $ref: "#/components/schemas/PublicListingSummary"
        - type: object
          properties:
            description:
              type: string
              nullable: true
            short_description:
              type: string
              nullable: true
            price_info:
              $ref: "#/components/schemas/PriceInfo"
            categories:
              type: array
              items:
                type: object
            images:
              type: array
              items:
                $ref: "#/components/schemas/ImageObject"
            custom_fields:
              type: array
              items:
                type: object
            seller:
              type: object
              nullable: true

    OwnerListingSummary:
      allOf:
        - $ref: "#/components/schemas/PublicListingSummary"
        - type: object
          properties:
            public_url:
              type: string
              nullable: true
            status:
              type: string
              enum: [approved, pending, completed, trashed]
            is_public:
              type: boolean
            renewed_at:
              type: string
              nullable: true
            expires_at:
              type: string
              nullable: true
            view_count:
              type: integer
              nullable: true

    OwnerListingDetail:
      allOf:
        - $ref: "#/components/schemas/OwnerListingSummary"
        - type: object
          properties:
            description:
              type: string
              nullable: true
            short_description:
              type: string
              nullable: true
            categories:
              type: array
              items:
                type: object
            images:
              type: array
              items:
                $ref: "#/components/schemas/ImageObject"
            custom_fields:
              type: array
              items:
                type: object

    ImageUploadRequest:
      type: object
      required: [image]
      properties:
        image:
          type: string
          format: binary
          description: JPEG ili PNG. Maksimalno 10 MiB i 40 MP.

    RotateRequest:
      type: object
      required: [degrees]
      properties:
        degrees:
          type: integer
          enum: [90, 180, 270]

    ImageObject:
      type: object
      properties:
        id:
          type: integer
          nullable: true
        url:
          type: string
          format: uri
        thumbnail_url:
          type: string
          format: uri
          nullable: true
        original_url:
          type: string
          format: uri
          nullable: true
        image_key:
          type: string
          nullable: true
        is_main:
          type: boolean
          nullable: true
