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: BookWithNesto shortlet apartment platform API
servers:
  - url: /api
    description: Base API path
tags:
  - name: health
    description: Health operations
  - name: apartments
    description: Apartment listings
  - name: bookings
    description: Booking management
  - name: reviews
    description: Apartment reviews
  - name: blog
    description: Blog post management
  - name: contacts
    description: Contact/quote form submissions
  - name: chat
    description: In-app chat messages
  - name: gallery
    description: Gallery images
  - name: stats
    description: Dashboard statistics

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

  # ─── APARTMENTS ───────────────────────────────────────────────────────────
  /apartments:
    get:
      operationId: listApartments
      tags: [apartments]
      summary: List apartments with optional filters
      parameters:
        - name: city
          in: query
          schema: { type: string }
        - name: minPrice
          in: query
          schema: { type: number }
        - name: maxPrice
          in: query
          schema: { type: number }
        - name: bedrooms
          in: query
          schema: { type: integer }
        - name: amenities
          in: query
          schema: { type: string }
        - name: available
          in: query
          schema: { type: boolean }
        - name: page
          in: query
          schema: { type: integer }
        - name: limit
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: List of apartments
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Apartment"
    post:
      operationId: createApartment
      tags: [apartments]
      summary: Create a new apartment listing (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApartmentInput"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Apartment"

  /apartments/featured:
    get:
      operationId: listFeaturedApartments
      tags: [apartments]
      summary: Get featured/highlighted apartments for homepage
      responses:
        "200":
          description: Featured apartments
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Apartment"

  /apartments/cities:
    get:
      operationId: listCities
      tags: [apartments]
      summary: Get list of cities with apartment counts
      responses:
        "200":
          description: Cities with counts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/CityCount"

  /apartments/{id}:
    get:
      operationId: getApartment
      tags: [apartments]
      summary: Get apartment detail
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Apartment detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Apartment"
        "404":
          description: Not found
    patch:
      operationId: updateApartment
      tags: [apartments]
      summary: Update apartment (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApartmentUpdate"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Apartment"
    delete:
      operationId: deleteApartment
      tags: [apartments]
      summary: Delete apartment (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: Deleted

  /apartments/{id}/availability:
    get:
      operationId: getApartmentAvailability
      tags: [apartments]
      summary: Get booked date ranges for an apartment
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: List of booked date ranges
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/BookedDateRange"
    patch:
      operationId: updateApartmentAvailability
      tags: [apartments]
      summary: Toggle apartment availability (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AvailabilityUpdate"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Apartment"

  # ─── REVIEWS ──────────────────────────────────────────────────────────────
  /apartments/{id}/reviews:
    get:
      operationId: listApartmentReviews
      tags: [reviews]
      summary: Get reviews for an apartment
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Reviews
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Review"
    post:
      operationId: createReview
      tags: [reviews]
      summary: Submit a review
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReviewInput"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Review"

  # ─── BOOKINGS ─────────────────────────────────────────────────────────────
  /bookings:
    get:
      operationId: listBookings
      tags: [bookings]
      summary: List bookings (admin sees all, user sees own)
      responses:
        "200":
          description: Bookings list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Booking"
    post:
      operationId: createBooking
      tags: [bookings]
      summary: Create a booking inquiry
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BookingInput"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Booking"

  /bookings/{id}:
    get:
      operationId: getBooking
      tags: [bookings]
      summary: Get booking detail
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Booking
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Booking"
    patch:
      operationId: updateBookingStatus
      tags: [bookings]
      summary: Update booking status (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BookingStatusUpdate"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Booking"

  # ─── BLOG ─────────────────────────────────────────────────────────────────
  /blog:
    get:
      operationId: listBlogPosts
      tags: [blog]
      summary: List published blog posts
      parameters:
        - name: page
          in: query
          schema: { type: integer }
        - name: limit
          in: query
          schema: { type: integer }
        - name: category
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Blog posts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/BlogPost"
    post:
      operationId: createBlogPost
      tags: [blog]
      summary: Create blog post (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BlogPostInput"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"

  /blog/all:
    get:
      operationId: listAllBlogPosts
      tags: [blog]
      summary: List all blog posts including drafts (admin)
      responses:
        "200":
          description: All blog posts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/BlogPost"

  /blog/{id}:
    get:
      operationId: getBlogPost
      tags: [blog]
      summary: Get blog post detail
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Blog post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"
    patch:
      operationId: updateBlogPost
      tags: [blog]
      summary: Update blog post (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BlogPostUpdate"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"
    delete:
      operationId: deleteBlogPost
      tags: [blog]
      summary: Delete blog post (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: Deleted

  /blog/slug/{slug}:
    get:
      operationId: getBlogPostBySlug
      tags: [blog]
      summary: Get blog post by SEO slug
      parameters:
        - name: slug
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Blog post
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BlogPost"

  # ─── CONTACT / FORMS ──────────────────────────────────────────────────────
  /contacts:
    get:
      operationId: listContacts
      tags: [contacts]
      summary: List all form submissions (admin)
      responses:
        "200":
          description: Contacts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ContactSubmission"
    post:
      operationId: submitContact
      tags: [contacts]
      summary: Submit contact / quote form
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactInput"
      responses:
        "201":
          description: Submitted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactSubmission"

  /newsletter:
    post:
      operationId: subscribeNewsletter
      tags: [contacts]
      summary: Subscribe to newsletter
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewsletterInput"
      responses:
        "201":
          description: Subscribed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NewsletterSubscriber"

  /newsletter/subscribers:
    get:
      operationId: listNewsletterSubscribers
      tags: [contacts]
      summary: List newsletter subscribers (admin)
      responses:
        "200":
          description: Subscribers
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/NewsletterSubscriber"

  # ─── CHAT ─────────────────────────────────────────────────────────────────
  /chat/messages:
    get:
      operationId: listChatMessages
      tags: [chat]
      summary: List chat messages for current session/apartment
      parameters:
        - name: apartmentId
          in: query
          schema: { type: ["integer", "null"] }
        - name: sessionId
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Messages
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ChatMessage"
    post:
      operationId: sendChatMessage
      tags: [chat]
      summary: Send a chat message
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatMessageInput"
      responses:
        "201":
          description: Message sent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatMessage"

  # ─── GALLERY ──────────────────────────────────────────────────────────────
  /gallery:
    get:
      operationId: listGalleryImages
      tags: [gallery]
      summary: List gallery images
      parameters:
        - name: apartmentId
          in: query
          schema: { type: ["integer", "null"] }
        - name: category
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Gallery images
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/GalleryImage"
    post:
      operationId: createGalleryImage
      tags: [gallery]
      summary: Upload gallery image (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GalleryImageInput"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GalleryImage"

  /gallery/{id}:
    delete:
      operationId: deleteGalleryImage
      tags: [gallery]
      summary: Delete gallery image (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "204":
          description: Deleted

  /contacts/{id}:
    patch:
      operationId: updateContact
      tags: [contacts]
      summary: Update contact status (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactStatusUpdate"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactSubmission"

  # ─── SERVICE PACKAGES ─────────────────────────────────────────────────────
  /service-packages:
    get:
      operationId: listServicePackages
      tags: [services]
      summary: List service packages
      parameters:
        - name: serviceType
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Service packages
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ServicePackage"

  /service-packages/{id}:
    patch:
      operationId: updateServicePackage
      tags: [services]
      summary: Update service package price/description (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ServicePackageUpdate"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServicePackage"

  # ─── STATS (WOW endpoints) ────────────────────────────────────────────────
  /stats/dashboard:
    get:
      operationId: getDashboardStats
      tags: [stats]
      summary: Admin dashboard summary stats
      responses:
        "200":
          description: Stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardStats"

  /stats/cities:
    get:
      operationId: getCityStats
      tags: [stats]
      summary: Apartment count and avg price per city
      responses:
        "200":
          description: City stats
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/CityStats"

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

    BookedDateRange:
      type: object
      required: [checkIn, checkOut, status]
      properties:
        checkIn:  { type: string, format: date-time }
        checkOut: { type: string, format: date-time }
        status:   { type: string }

    # ─── APARTMENTS ──────────────────────────────────────────
    Apartment:
      type: object
      required: [id, name, city, pricePerNight, available, createdAt]
      properties:
        id:            { type: integer }
        name:          { type: string }
        slug:          { type: string }
        city:          { type: string }
        state:         { type: string }
        address:       { type: string }
        description:   { type: string }
        pricePerNight: { type: number }
        bedrooms:      { type: integer }
        bathrooms:     { type: integer }
        maxGuests:     { type: integer }
        amenities:     { type: string }
        images:        { type: string }
        featured:      { type: boolean }
        available:     { type: boolean }
        rating:        { type: ["number", "null"] }
        reviewCount:   { type: integer }
        createdAt:     { type: string }
        updatedAt:     { type: string }

    ApartmentInput:
      type: object
      required: [name, city, pricePerNight]
      properties:
        name:          { type: string }
        slug:          { type: string }
        city:          { type: string }
        state:         { type: string }
        address:       { type: string }
        description:   { type: string }
        pricePerNight: { type: number }
        bedrooms:      { type: integer }
        bathrooms:     { type: integer }
        maxGuests:     { type: integer }
        amenities:     { type: string }
        images:        { type: string }
        featured:      { type: boolean }
        available:     { type: boolean }

    ApartmentUpdate:
      type: object
      properties:
        name:          { type: string }
        slug:          { type: string }
        city:          { type: string }
        state:         { type: string }
        address:       { type: string }
        description:   { type: string }
        pricePerNight: { type: number }
        bedrooms:      { type: integer }
        bathrooms:     { type: integer }
        maxGuests:     { type: integer }
        amenities:     { type: string }
        images:        { type: string }
        featured:      { type: boolean }
        available:     { type: boolean }

    AvailabilityUpdate:
      type: object
      required: [available]
      properties:
        available: { type: boolean }

    CityCount:
      type: object
      required: [city, count]
      properties:
        city:  { type: string }
        count: { type: integer }

    # ─── REVIEWS ─────────────────────────────────────────────
    Review:
      type: object
      required: [id, apartmentId, rating, comment, createdAt]
      properties:
        id:          { type: integer }
        apartmentId: { type: integer }
        userId:      { type: ["string", "null"] }
        guestName:   { type: string }
        rating:      { type: integer }
        comment:     { type: string }
        createdAt:   { type: string }

    ReviewInput:
      type: object
      required: [rating, comment, guestName]
      properties:
        rating:    { type: integer }
        comment:   { type: string }
        guestName: { type: string }
        userId:    { type: string }

    # ─── BOOKINGS ────────────────────────────────────────────
    Booking:
      type: object
      required: [id, apartmentId, guestName, guestEmail, checkIn, checkOut, status, createdAt]
      properties:
        id:           { type: integer }
        apartmentId:  { type: integer }
        userId:       { type: ["string", "null"] }
        guestName:    { type: string }
        guestEmail:   { type: string }
        guestPhone:   { type: string }
        checkIn:      { type: string }
        checkOut:     { type: string }
        guests:       { type: integer }
        totalPrice:   { type: ["number", "null"] }
        status:       { type: string }
        notes:        { type: ["string", "null"] }
        createdAt:    { type: string }

    BookingInput:
      type: object
      required: [apartmentId, guestName, guestEmail, checkIn, checkOut]
      properties:
        apartmentId:  { type: integer }
        userId:       { type: string }
        guestName:    { type: string }
        guestEmail:   { type: string }
        guestPhone:   { type: string }
        checkIn:      { type: string }
        checkOut:     { type: string }
        guests:       { type: integer }
        notes:        { type: string }

    BookingStatusUpdate:
      type: object
      required: [status]
      properties:
        status: { type: string }

    # ─── BLOG ────────────────────────────────────────────────
    BlogPost:
      type: object
      required: [id, title, slug, status, createdAt]
      properties:
        id:               { type: integer }
        title:            { type: string }
        slug:             { type: string }
        excerpt:          { type: ["string", "null"] }
        content:          { type: string }
        featuredImage:    { type: ["string", "null"] }
        category:         { type: ["string", "null"] }
        tags:             { type: ["string", "null"] }
        status:           { type: string }
        metaTitle:        { type: ["string", "null"] }
        metaDescription:  { type: ["string", "null"] }
        authorName:       { type: string }
        createdAt:        { type: string }
        updatedAt:        { type: string }

    BlogPostInput:
      type: object
      required: [title, content]
      properties:
        title:           { type: string }
        slug:            { type: string }
        excerpt:         { type: string }
        content:         { type: string }
        featuredImage:   { type: string }
        category:        { type: string }
        tags:            { type: string }
        status:          { type: string }
        metaTitle:       { type: string }
        metaDescription: { type: string }
        authorName:      { type: string }

    BlogPostUpdate:
      type: object
      properties:
        title:           { type: string }
        slug:            { type: string }
        excerpt:         { type: string }
        content:         { type: string }
        featuredImage:   { type: string }
        category:        { type: string }
        tags:            { type: string }
        status:          { type: string }
        metaTitle:       { type: string }
        metaDescription: { type: string }
        authorName:      { type: string }

    # ─── CONTACTS ────────────────────────────────────────────
    ContactSubmission:
      type: object
      required: [id, type, name, email, message, createdAt]
      properties:
        id:          { type: integer }
        type:        { type: string }
        name:        { type: string }
        email:       { type: string }
        phone:       { type: ["string", "null"] }
        subject:     { type: ["string", "null"] }
        message:     { type: string }
        apartmentId: { type: ["integer", "null"] }
        status:      { type: string }
        createdAt:   { type: string }

    ContactInput:
      type: object
      required: [type, name, email, message]
      properties:
        type:        { type: string }
        name:        { type: string }
        email:       { type: string }
        phone:       { type: string }
        subject:     { type: string }
        message:     { type: string }
        apartmentId: { type: integer }

    ContactStatusUpdate:
      type: object
      properties:
        status: { type: string }

    NewsletterInput:
      type: object
      required: [email]
      properties:
        email: { type: string }
        name:  { type: string }

    NewsletterSubscriber:
      type: object
      required: [id, email, createdAt]
      properties:
        id:        { type: integer }
        email:     { type: string }
        name:      { type: ["string", "null"] }
        active:    { type: boolean }
        createdAt: { type: string }

    # ─── CHAT ────────────────────────────────────────────────
    ChatMessage:
      type: object
      required: [id, role, content, createdAt]
      properties:
        id:          { type: integer }
        apartmentId: { type: ["integer", "null"] }
        sessionId:   { type: string }
        userId:      { type: ["string", "null"] }
        role:        { type: string }
        content:     { type: string }
        createdAt:   { type: string }

    ChatMessageInput:
      type: object
      required: [role, content, sessionId]
      properties:
        apartmentId: { type: integer }
        sessionId:   { type: string }
        userId:      { type: string }
        role:        { type: string }
        content:     { type: string }

    # ─── GALLERY ─────────────────────────────────────────────
    GalleryImage:
      type: object
      required: [id, url, createdAt]
      properties:
        id:          { type: integer }
        apartmentId: { type: ["integer", "null"] }
        url:         { type: string }
        caption:     { type: ["string", "null"] }
        category:    { type: ["string", "null"] }
        sortOrder:   { type: integer }
        createdAt:   { type: string }

    GalleryImageInput:
      type: object
      required: [url]
      properties:
        apartmentId: { type: integer }
        url:         { type: string }
        caption:     { type: string }
        category:    { type: string }
        sortOrder:   { type: integer }

    # ─── SERVICE PACKAGES ────────────────────────────────────
    ServicePackage:
      type: object
      required: [id, serviceType, name, priceDisplay, sortOrder, active]
      properties:
        id:           { type: integer }
        serviceType:  { type: string }
        name:         { type: string }
        priceDisplay: { type: string }
        priceAmount:  { type: ["integer", "null"] }
        duration:     { type: ["string", "null"] }
        description:  { type: ["string", "null"] }
        features:     { type: string }
        isPopular:    { type: boolean }
        sortOrder:    { type: integer }
        active:       { type: boolean }
        updatedAt:    { type: string }

    ServicePackageUpdate:
      type: object
      properties:
        name:         { type: string }
        priceDisplay: { type: string }
        priceAmount:  { type: integer }
        duration:     { type: string }
        description:  { type: string }
        features:     { type: string }
        isPopular:    { type: boolean }
        active:       { type: boolean }

    # ─── STATS ───────────────────────────────────────────────
    DashboardStats:
      type: object
      required: [totalApartments, availableApartments, totalBookings, totalContacts, totalBlogPosts, totalReviews]
      properties:
        totalApartments:     { type: integer }
        availableApartments: { type: integer }
        totalBookings:       { type: integer }
        pendingBookings:     { type: integer }
        totalContacts:       { type: integer }
        totalBlogPosts:      { type: integer }
        totalReviews:        { type: integer }
        avgRating:           { type: ["number", "null"] }

    CityStats:
      type: object
      required: [city, count, avgPrice]
      properties:
        city:     { type: string }
        count:    { type: integer }
        avgPrice: { type: number }
