openapi: 3.1.0
info:
  # Do not change the title, if the title changes, the import paths will be broken
  title: Api
  version: 0.1.0
  description: Nick-Mash Trade API specification
servers:
  - url: /api
    description: Base API path
tags:
  - name: health
    description: Health operations
  - name: auth
    description: Admin authentication
  - name: products
    description: Product catalog
  - name: categories
    description: Product categories
  - name: filters
    description: Category-specific dynamic filters
  - name: blog
    description: Blog articles
  - name: partners
    description: Partner logos slider
  - name: gallery
    description: Gallery images
  - name: storage
    description: Object storage
  - name: contact-messages
    description: Contact form messages
  - name: product-inquiries
    description: Product-specific inquiry messages
  - name: settings
    description: Site-wide settings
paths:
  /healthz:
    get:
      operationId: healthCheck
      tags: [health]
      summary: Health check
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"

  # --- AUTH ---
  /auth/login:
    post:
      operationId: adminLogin
      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: adminLogout
      tags: [auth]
      summary: Admin logout
      responses:
        "200":
          description: Logged out
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /auth/me:
    get:
      operationId: getAuthMe
      tags: [auth]
      summary: Get current admin session
      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"

  # --- CATEGORIES ---
  /categories:
    get:
      operationId: listCategories
      tags: [categories]
      summary: List all categories
      responses:
        "200":
          description: List of categories
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Category"
    post:
      operationId: createCategory
      tags: [categories]
      summary: Create a category (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCategoryBody"
      responses:
        "201":
          description: Created category
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Category"

  /categories/{id}:
    patch:
      operationId: updateCategory
      tags: [categories]
      summary: Update a category (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCategoryBody"
      responses:
        "200":
          description: Updated category
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Category"
    delete:
      operationId: deleteCategory
      tags: [categories]
      summary: Delete a category (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted
        "409":
          description: Category still has products assigned to it
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    enum: [category_in_use]
                  productCount:
                    type: integer
                required:
                  - error
                  - productCount

  /categories/{id}/image:
    post:
      operationId: uploadCategoryImage
      tags: [categories]
      summary: Upload and convert a category background image to WebP (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
              required:
                - image
      responses:
        "200":
          description: Upload successful, returns the updated category
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Category"
        "400":
          description: Invalid or missing image

  # --- FILTERS ---
  /filters:
    get:
      operationId: listFilters
      tags: [filters]
      summary: List filters (optionally by category)
      parameters:
        - in: query
          name: categoryId
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: List of filters
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Filter"
    post:
      operationId: createFilter
      tags: [filters]
      summary: Create a filter (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateFilterBody"
      responses:
        "201":
          description: Created filter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Filter"

  /filters/{id}:
    patch:
      operationId: updateFilter
      tags: [filters]
      summary: Update a filter (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateFilterBody"
      responses:
        "200":
          description: Updated filter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Filter"
    delete:
      operationId: deleteFilter
      tags: [filters]
      summary: Delete a filter (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  # --- PRODUCTS ---
  /products:
    get:
      operationId: listProducts
      tags: [products]
      summary: List products with optional filters
      parameters:
        - in: query
          name: search
          required: false
          schema:
            type: string
        - in: query
          name: categoryId
          required: false
          schema:
            type: integer
        - in: query
          name: condition
          required: false
          schema:
            type: string
            enum: [new, used, buyout]
        - in: query
          name: minPrice
          required: false
          schema:
            type: number
        - in: query
          name: maxPrice
          required: false
          schema:
            type: number
        - in: query
          name: attrs
          required: false
          style: deepObject
          explode: true
          schema:
            type: object
            additionalProperties:
              type: string
            description: "Attribute key-value pairs to filter by (e.g. attrs[power_kw]=5.5)"
        - in: query
          name: page
          required: false
          schema:
            type: integer
        - in: query
          name: limit
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: Paginated product list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductListResponse"
    post:
      operationId: createProduct
      tags: [products]
      summary: Create a product (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateProductBody"
      responses:
        "201":
          description: Created product
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"

  /products/{id}:
    get:
      operationId: getProduct
      tags: [products]
      summary: Get a single product
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Product detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      operationId: updateProduct
      tags: [products]
      summary: Update a product (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateProductBody"
      responses:
        "200":
          description: Updated product
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
    delete:
      operationId: deleteProduct
      tags: [products]
      summary: Delete a product (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  /products/stats:
    get:
      operationId: getProductStats
      tags: [products]
      summary: Get product statistics (counts by category/condition)
      responses:
        "200":
          description: Product statistics
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductStats"

  # --- BLOG ---
  /blog:
    get:
      operationId: listBlogPosts
      tags: [blog]
      summary: List blog posts
      parameters:
        - in: query
          name: page
          required: false
          schema:
            type: integer
        - in: query
          name: limit
          required: false
          schema:
            type: integer
        - in: query
          name: published
          required: false
          schema:
            type: boolean
      responses:
        "200":
          description: Blog post list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogListResponse"
    post:
      operationId: createBlogPost
      tags: [blog]
      summary: Create a blog post (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateBlogPostBody"
      responses:
        "201":
          description: Created blog post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"

  /blog/{slug}:
    get:
      operationId: getBlogPost
      tags: [blog]
      summary: Get a blog post by slug
      parameters:
        - in: path
          name: slug
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Blog post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      operationId: updateBlogPost
      tags: [blog]
      summary: Update a blog post (admin)
      parameters:
        - in: path
          name: slug
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateBlogPostBody"
      responses:
        "200":
          description: Updated blog post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"
    delete:
      operationId: deleteBlogPost
      tags: [blog]
      summary: Delete a blog post (admin)
      parameters:
        - in: path
          name: slug
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted

  # --- PARTNERS ---
  /partners:
    get:
      operationId: listPartners
      tags: [partners]
      summary: List partner logos
      responses:
        "200":
          description: Partner list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Partner"
    post:
      operationId: createPartner
      tags: [partners]
      summary: Create a partner (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePartnerBody"
      responses:
        "201":
          description: Created partner
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Partner"

  /partners/{id}:
    patch:
      operationId: updatePartner
      tags: [partners]
      summary: Update a partner (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePartnerBody"
      responses:
        "200":
          description: Updated partner
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Partner"
    delete:
      operationId: deletePartner
      tags: [partners]
      summary: Delete a partner (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  # --- CONTACT MESSAGES ---
  /contact-messages:
    post:
      operationId: createContactMessage
      tags: [contact-messages]
      summary: Submit a contact message (public)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateContactMessageBody"
      responses:
        "201":
          description: Message created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactMessage"
    get:
      operationId: listContactMessages
      tags: [contact-messages]
      summary: List contact messages (admin)
      parameters:
        - in: query
          name: isRead
          schema:
            type: boolean
        - in: query
          name: page
          schema:
            type: integer
        - in: query
          name: limit
          schema:
            type: integer
      responses:
        "200":
          description: Paginated contact messages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactMessagesPage"

  /contact-messages/{id}:
    patch:
      operationId: updateContactMessage
      tags: [contact-messages]
      summary: Mark message read/unread (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateContactMessageBody"
      responses:
        "200":
          description: Updated message
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactMessage"
    delete:
      operationId: deleteContactMessage
      tags: [contact-messages]
      summary: Delete a contact message (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  # --- PRODUCT INQUIRIES ---
  /product-inquiries:
    post:
      operationId: createProductInquiry
      tags: [product-inquiries]
      summary: Submit a product inquiry (public)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateProductInquiryBody"
      responses:
        "201":
          description: Inquiry created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductInquiry"
    get:
      operationId: listProductInquiries
      tags: [product-inquiries]
      summary: List product inquiries (admin)
      parameters:
        - in: query
          name: page
          schema:
            type: integer
        - in: query
          name: limit
          schema:
            type: integer
      responses:
        "200":
          description: Paginated product inquiries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductInquiriesPage"

  /product-inquiries/{id}:
    patch:
      operationId: updateProductInquiry
      tags: [product-inquiries]
      summary: Mark product inquiry read/unread (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateProductInquiryBody"
      responses:
        "200":
          description: Updated inquiry
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductInquiry"
    delete:
      operationId: deleteProductInquiry
      tags: [product-inquiries]
      summary: Delete a product inquiry (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  # --- SETTINGS ---
  /settings:
    get:
      operationId: getSettings
      tags: [settings]
      summary: Get site settings (public)
      responses:
        "200":
          description: Current settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SiteSettings"
    patch:
      operationId: updateSettings
      tags: [settings]
      summary: Update site settings (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSiteSettingsBody"
      responses:
        "200":
          description: Updated settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SiteSettings"
        "401":
          description: Unauthorized

  # --- GALLERY ---
  /gallery:
    get:
      operationId: listGalleryImages
      tags: [gallery]
      summary: List published gallery images (public)
      responses:
        "200":
          description: List of gallery images
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/GalleryImage"

  /gallery/tags:
    get:
      operationId: listGalleryTags
      tags: [gallery]
      summary: List distinct tags from published gallery images with counts (public)
      responses:
        "200":
          description: List of tags with counts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/GalleryTag"

  /gallery/upload:
    post:
      operationId: uploadGalleryImage
      tags: [gallery]
      summary: Upload a gallery image (admin) — converts to WebP
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                image:
                  type: string
                  format: binary
                captionBg:
                  type: string
                captionEn:
                  type: string
                sortOrder:
                  type: integer
                tags:
                  type: string
                  description: JSON array of strings or comma-separated tags
              required:
                - image
      responses:
        "201":
          description: Created gallery image
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GalleryImage"
        "400":
          description: Invalid or missing image

  /gallery/by-url:
    post:
      operationId: createGalleryImageByUrl
      tags: [gallery]
      summary: Create a gallery image from a pasted URL (admin) — stores URL as-is
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                imageUrl:
                  type: string
                captionBg:
                  type: string
                captionEn:
                  type: string
                sortOrder:
                  type: integer
                tags:
                  type: array
                  items:
                    type: string
              required:
                - imageUrl
      responses:
        "201":
          description: Created gallery image
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GalleryImage"
        "400":
          description: Invalid or missing imageUrl

  /gallery/admin:
    get:
      operationId: listAllGalleryImages
      tags: [gallery]
      summary: List all gallery images including hidden (admin)
      responses:
        "200":
          description: List of all gallery images
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/GalleryImage"

  /gallery/{id}:
    patch:
      operationId: updateGalleryImage
      tags: [gallery]
      summary: Update a gallery image caption/order/visibility (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateGalleryImageBody"
      responses:
        "200":
          description: Updated gallery image
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GalleryImage"
        "404":
          description: Not found
    delete:
      operationId: deleteGalleryImage
      tags: [gallery]
      summary: Delete a gallery image (admin)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  # --- STORAGE ---
  /storage/uploads/request-url:
    post:
      operationId: requestUploadUrl
      tags: [storage]
      summary: Request a presigned upload URL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RequestUploadUrlBody"
      responses:
        "200":
          description: Presigned upload URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RequestUploadUrlResponse"

components:
  schemas:
    MirrorStatus:
      type: object
      properties:
        configured:
          type: boolean
        errorCount:
          type: integer
        lastError:
          type: string
          nullable: true
        lastErrorAt:
          type: string
          nullable: true
      required:
        - configured
        - errorCount
        - lastError
        - lastErrorAt

    HealthStatus:
      type: object
      properties:
        status:
          type: string
        mirror:
          $ref: "#/components/schemas/MirrorStatus"
      required:
        - status
        - mirror

    ErrorResponse:
      type: object
      properties:
        error:
          type: string
      required:
        - error

    MessageResponse:
      type: object
      properties:
        message:
          type: string
      required:
        - message

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

    AuthUser:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        token:
          type: string
          description: >
            Bearer token returned only by POST /auth/login. The web client
            stores it in sessionStorage and sends it as
            `Authorization: Bearer <token>` on subsequent requests so admin
            auth keeps working in cross-site iframe contexts where Chrome
            silently drops the session cookie.
      required:
        - id
        - username

    LocalizedString:
      type: object
      properties:
        bg:
          type: string
        en:
          type: string
      required:
        - bg
        - en

    Category:
      type: object
      properties:
        id:
          type: integer
        nameBg:
          type: string
        nameEn:
          type: string
        slug:
          type: string
        icon:
          type: ["string", "null"]
        imageUrl:
          type: ["string", "null"]
          description: "Storage path or URL of the category background image"
        descriptionBg:
          type: ["string", "null"]
          description: "Short category description in Bulgarian"
        descriptionEn:
          type: ["string", "null"]
          description: "Short category description in English"
        productCount:
          type: integer
      required:
        - id
        - nameBg
        - nameEn
        - slug
        - productCount

    CreateCategoryBody:
      type: object
      properties:
        nameBg:
          type: string
        nameEn:
          type: string
        slug:
          type: string
        icon:
          type: ["string", "null"]
        imageUrl:
          type: ["string", "null"]
        descriptionBg:
          type: ["string", "null"]
        descriptionEn:
          type: ["string", "null"]
      required:
        - nameBg
        - nameEn
        - slug

    UpdateCategoryBody:
      type: object
      properties:
        nameBg:
          type: string
        nameEn:
          type: string
        slug:
          type: string
        icon:
          type: ["string", "null"]
        imageUrl:
          type: ["string", "null"]
        descriptionBg:
          type: ["string", "null"]
        descriptionEn:
          type: ["string", "null"]

    Filter:
      type: object
      properties:
        id:
          type: integer
        categoryIds:
          type: ["array", "null"]
          items:
            type: integer
          description: "Null means global filter (applies to all categories). Array of category IDs this filter applies to."
        nameBg:
          type: string
        nameEn:
          type: string
        fieldKey:
          type: string
        filterType:
          type: string
          enum: [range, select, multiselect, boolean, tag]
        options:
          type: ["string", "null"]
          description: Comma-separated options for select/multiselect types
        unit:
          type: ["string", "null"]
      required:
        - id
        - nameBg
        - nameEn
        - fieldKey
        - filterType

    CreateFilterBody:
      type: object
      properties:
        categoryIds:
          type: ["array", "null"]
          items:
            type: integer
          description: "Omit or set null for a global filter. Pass array of category IDs to restrict to specific categories."
        nameBg:
          type: string
        nameEn:
          type: string
        fieldKey:
          type: string
          description: "Auto-derived from nameBg if omitted"
        filterType:
          type: string
          enum: [range, select, multiselect, boolean, tag]
        options:
          type: ["string", "null"]
        unit:
          type: ["string", "null"]
      required:
        - nameBg
        - nameEn
        - filterType

    UpdateFilterBody:
      type: object
      properties:
        categoryIds:
          type: ["array", "null"]
          items:
            type: integer
        nameBg:
          type: string
        nameEn:
          type: string
        fieldKey:
          type: string
        filterType:
          type: string
          enum: [range, select, multiselect, boolean, tag]
        options:
          type: ["string", "null"]
        unit:
          type: ["string", "null"]

    Product:
      type: object
      properties:
        id:
          type: integer
        titleBg:
          type: string
        titleEn:
          type: string
        descriptionBg:
          type: string
        descriptionEn:
          type: string
        categoryId:
          type: integer
        categoryNameBg:
          type: ["string", "null"]
        categoryNameEn:
          type: ["string", "null"]
        price:
          type: ["number", "null"]
        condition:
          type: string
          enum: [new, used, buyout]
        images:
          type: array
          items:
            type: string
        attributes:
          type: ["string", "null"]
          description: JSON object of filter-key to value mappings
        isPublished:
          type: boolean
        createdAt:
          type: string
      required:
        - id
        - titleBg
        - titleEn
        - descriptionBg
        - descriptionEn
        - categoryId
        - condition
        - images
        - isPublished
        - createdAt

    CreateProductBody:
      type: object
      properties:
        titleBg:
          type: string
        titleEn:
          type: string
        descriptionBg:
          type: string
        descriptionEn:
          type: string
        categoryId:
          type: integer
        price:
          type: ["number", "null"]
        condition:
          type: string
          enum: [new, used, buyout]
        images:
          type: array
          items:
            type: string
        attributes:
          type: ["string", "null"]
        isPublished:
          type: boolean
      required:
        - titleBg
        - titleEn
        - descriptionBg
        - descriptionEn
        - categoryId
        - condition
        - images
        - isPublished

    UpdateProductBody:
      type: object
      properties:
        titleBg:
          type: string
        titleEn:
          type: string
        descriptionBg:
          type: string
        descriptionEn:
          type: string
        categoryId:
          type: integer
        price:
          type: ["number", "null"]
        condition:
          type: string
          enum: [new, used, buyout]
        images:
          type: array
          items:
            type: string
        attributes:
          type: ["string", "null"]
        isPublished:
          type: boolean

    ProductListResponse:
      type: object
      properties:
        products:
          type: array
          items:
            $ref: "#/components/schemas/Product"
        total:
          type: integer
        page:
          type: integer
        limit:
          type: integer
      required:
        - products
        - total
        - page
        - limit

    ProductStats:
      type: object
      properties:
        total:
          type: integer
        byCategory:
          type: array
          items:
            $ref: "#/components/schemas/CategoryCount"
        byCondition:
          type: array
          items:
            $ref: "#/components/schemas/ConditionCount"
      required:
        - total
        - byCategory
        - byCondition

    CategoryCount:
      type: object
      properties:
        categoryId:
          type: integer
        nameBg:
          type: string
        nameEn:
          type: string
        count:
          type: integer
      required:
        - categoryId
        - nameBg
        - nameEn
        - count

    ConditionCount:
      type: object
      properties:
        condition:
          type: string
        count:
          type: integer
      required:
        - condition
        - count

    BlogPost:
      type: object
      properties:
        id:
          type: integer
        slug:
          type: string
        titleBg:
          type: string
        titleEn:
          type: string
        contentBg:
          type: string
        contentEn:
          type: string
        excerptBg:
          type: string
        excerptEn:
          type: string
        coverImage:
          type: ["string", "null"]
        metaTitleBg:
          type: ["string", "null"]
        metaTitleEn:
          type: ["string", "null"]
        metaDescriptionBg:
          type: ["string", "null"]
        metaDescriptionEn:
          type: ["string", "null"]
        images:
          type: array
          items:
            type: string
          maxItems: 30
        isPublished:
          type: boolean
        publishedAt:
          type: ["string", "null"]
        createdAt:
          type: string
      required:
        - id
        - slug
        - titleBg
        - titleEn
        - contentBg
        - contentEn
        - excerptBg
        - excerptEn
        - images
        - isPublished
        - createdAt

    CreateBlogPostBody:
      type: object
      properties:
        slug:
          type: string
        titleBg:
          type: string
        titleEn:
          type: string
        contentBg:
          type: string
        contentEn:
          type: string
        excerptBg:
          type: string
        excerptEn:
          type: string
        coverImage:
          type: ["string", "null"]
        metaTitleBg:
          type: ["string", "null"]
        metaTitleEn:
          type: ["string", "null"]
        metaDescriptionBg:
          type: ["string", "null"]
        metaDescriptionEn:
          type: ["string", "null"]
        images:
          type: array
          items:
            type: string
          maxItems: 30
        isPublished:
          type: boolean
      required:
        - slug
        - titleBg
        - titleEn
        - contentBg
        - contentEn
        - excerptBg
        - excerptEn
        - isPublished

    UpdateBlogPostBody:
      type: object
      properties:
        titleBg:
          type: string
        titleEn:
          type: string
        contentBg:
          type: string
        contentEn:
          type: string
        excerptBg:
          type: string
        excerptEn:
          type: string
        coverImage:
          type: ["string", "null"]
        metaTitleBg:
          type: ["string", "null"]
        metaTitleEn:
          type: ["string", "null"]
        metaDescriptionBg:
          type: ["string", "null"]
        metaDescriptionEn:
          type: ["string", "null"]
        images:
          type: array
          items:
            type: string
          maxItems: 30
        isPublished:
          type: boolean

    BlogListResponse:
      type: object
      properties:
        posts:
          type: array
          items:
            $ref: "#/components/schemas/BlogPost"
        total:
          type: integer
        page:
          type: integer
        limit:
          type: integer
      required:
        - posts
        - total
        - page
        - limit

    Partner:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        logoUrl:
          type: string
        websiteUrl:
          type: ["string", "null"]
        sortOrder:
          type: integer
      required:
        - id
        - name
        - logoUrl
        - sortOrder

    CreatePartnerBody:
      type: object
      properties:
        name:
          type: string
        logoUrl:
          type: string
        websiteUrl:
          type: ["string", "null"]
        sortOrder:
          type: integer
      required:
        - name
        - logoUrl
        - sortOrder

    UpdatePartnerBody:
      type: object
      properties:
        name:
          type: string
        logoUrl:
          type: string
        websiteUrl:
          type: ["string", "null"]
        sortOrder:
          type: integer

    ContactMessage:
      type: object
      properties:
        id:
          type: integer
        companyName:
          type: ["string", "null"]
        name:
          type: ["string", "null"]
        phone:
          type: ["string", "null"]
        email:
          type: ["string", "null"]
        subject:
          type: ["string", "null"]
        message:
          type: string
        imageUrls:
          type: array
          items:
            type: string
        isRead:
          type: boolean
        createdAt:
          type: string
      required:
        - id
        - message
        - imageUrls
        - isRead
        - createdAt

    CreateContactMessageBody:
      type: object
      properties:
        companyName:
          type: string
        name:
          type: string
        phone:
          type: string
        email:
          type: string
        subject:
          type: string
        message:
          type: string
        imageUrls:
          type: array
          items:
            type: string
          maxItems: 5
      required:
        - companyName
        - phone
        - message

    UpdateContactMessageBody:
      type: object
      properties:
        isRead:
          type: boolean

    ContactMessagesPage:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ContactMessage"
        total:
          type: integer
        page:
          type: integer
        limit:
          type: integer
        totalPages:
          type: integer
      required:
        - items
        - total
        - page
        - limit
        - totalPages

    ProductInquiry:
      type: object
      properties:
        id:
          type: integer
        productId:
          type: integer
        productName:
          type: string
        companyName:
          type: ["string", "null"]
        name:
          type: ["string", "null"]
        phone:
          type: string
        email:
          type: ["string", "null"]
        subject:
          type: ["string", "null"]
        message:
          type: string
        isRead:
          type: boolean
        createdAt:
          type: string
      required:
        - id
        - productId
        - productName
        - phone
        - message
        - isRead
        - createdAt

    CreateProductInquiryBody:
      type: object
      properties:
        productId:
          type: integer
        productName:
          type: string
        companyName:
          type: string
        name:
          type: string
        phone:
          type: string
        email:
          type: string
        subject:
          type: string
        message:
          type: string
      required:
        - productId
        - productName
        - companyName
        - phone
        - message

    UpdateProductInquiryBody:
      type: object
      properties:
        isRead:
          type: boolean

    ProductInquiriesPage:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ProductInquiry"
        total:
          type: integer
        page:
          type: integer
        limit:
          type: integer
        totalPages:
          type: integer
      required:
        - items
        - total
        - page
        - limit
        - totalPages

    GalleryImage:
      type: object
      properties:
        id:
          type: integer
        imageUrl:
          type: string
        captionBg:
          type: ["string", "null"]
        captionEn:
          type: ["string", "null"]
        sortOrder:
          type: integer
        isPublished:
          type: boolean
        tags:
          type: array
          items:
            type: string
        createdAt:
          type: string
      required:
        - id
        - imageUrl
        - sortOrder
        - isPublished
        - tags
        - createdAt

    UpdateGalleryImageBody:
      type: object
      properties:
        captionBg:
          type: ["string", "null"]
        captionEn:
          type: ["string", "null"]
        sortOrder:
          type: integer
        isPublished:
          type: boolean
        tags:
          type: array
          items:
            type: string

    GalleryTag:
      type: object
      properties:
        tag:
          type: string
        count:
          type: integer
      required:
        - tag
        - count

    SiteSettings:
      type: object
      properties:
        aboutVideoUrl:
          type: ["string", "null"]
        activityImage1:
          type: ["string", "null"]
        activityImage2:
          type: ["string", "null"]
        activityImage3:
          type: ["string", "null"]
        advantagesMediaUrl:
          type: ["string", "null"]
        homeAboutImage:
          type: ["string", "null"]
      required: []

    UpdateSiteSettingsBody:
      type: object
      properties:
        aboutVideoUrl:
          type: ["string", "null"]
        activityImage1:
          type: ["string", "null"]
        activityImage2:
          type: ["string", "null"]
        activityImage3:
          type: ["string", "null"]
        advantagesMediaUrl:
          type: ["string", "null"]
        homeAboutImage:
          type: ["string", "null"]

    RequestUploadUrlBody:
      type: object
      properties:
        name:
          type: string
        size:
          type: integer
        contentType:
          type: string
      required:
        - name
        - size
        - contentType

    RequestUploadUrlResponse:
      type: object
      properties:
        uploadURL:
          type: string
        objectPath:
          type: string
        metadata:
          type: object
          properties:
            name:
              type: string
            size:
              type: integer
            contentType:
              type: string
          required:
            - name
            - size
            - contentType
      required:
        - uploadURL
        - objectPath
        - metadata
