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: TeaBoy OS API specification
servers:
  - url: /api
    description: Base API path
tags:
  - name: health
    description: Health operations
  - name: auth
    description: Authentication
  - name: apps
    description: App management
  - name: services
    description: Internal services (khidmati)
  - name: conversations
    description: Chat conversations
  - name: messages
    description: Chat messages
  - name: notifications
    description: Notifications
  - name: users
    description: User management
  - name: stats
    description: Dashboard stats
  - name: storage
    description: Object storage upload and serving endpoints

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

  # Auth
  /auth/register:
    post:
      operationId: register
      tags: [auth]
      summary: Register a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterBody"
      responses:
        "201":
          description: Registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthUser"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

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

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

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

  /auth/me/language:
    patch:
      operationId: updateLanguage
      tags: [auth]
      summary: Update preferred language
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateLanguageBody"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthUser"

  # Apps
  /apps:
    get:
      operationId: listApps
      tags: [apps]
      summary: List all active apps
      responses:
        "200":
          description: Apps list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/App"

    post:
      operationId: createApp
      tags: [apps]
      summary: Create a new app (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAppBody"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/App"

  /apps/{id}:
    get:
      operationId: getApp
      tags: [apps]
      summary: Get app by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: App
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/App"

    patch:
      operationId: updateApp
      tags: [apps]
      summary: Update app (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateAppBody"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/App"

    delete:
      operationId: deleteApp
      tags: [apps]
      summary: Delete app (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  /me/app-order:
    put:
      operationId: updateMyAppOrder
      tags: [apps]
      summary: Set the current user's preferred home apps order
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateMyAppOrderBody"
      responses:
        "200":
          description: Updated apps in new order
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/App"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # Settings
  /settings:
    get:
      operationId: getAppSettings
      tags: [settings]
      summary: Get application settings (public)
      responses:
        "200":
          description: Current settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppSettings"
    patch:
      operationId: updateAppSettings
      tags: [settings]
      summary: Update application settings (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateAppSettingsBody"
      responses:
        "200":
          description: Updated settings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppSettings"
        "400":
          description: Invalid input
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

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

  # Services
  /services:
    get:
      operationId: listServices
      tags: [services]
      summary: List all services
      responses:
        "200":
          description: Services list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Service"

    post:
      operationId: createService
      tags: [services]
      summary: Create a service (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateServiceBody"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Service"

  /services/{id}:
    get:
      operationId: getService
      tags: [services]
      summary: Get service by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Service
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Service"

    patch:
      operationId: updateService
      tags: [services]
      summary: Update service (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateServiceBody"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Service"

    delete:
      operationId: deleteService
      tags: [services]
      summary: Delete service (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  /service-categories:
    get:
      operationId: listServiceCategories
      tags: [services]
      summary: List service categories
      responses:
        "200":
          description: Categories
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ServiceCategory"

  # Conversations
  /conversations:
    get:
      operationId: listConversations
      tags: [conversations]
      summary: List conversations for current user
      responses:
        "200":
          description: Conversations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ConversationWithDetails"

    post:
      operationId: createConversation
      tags: [conversations]
      summary: Create a conversation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateConversationBody"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationWithDetails"

  /conversations/{id}:
    get:
      operationId: getConversation
      tags: [conversations]
      summary: Get conversation by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Conversation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationWithDetails"

  /conversations/{id}/messages:
    get:
      operationId: listMessages
      tags: [messages]
      summary: List messages in a conversation
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Messages
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/MessageWithSender"

    post:
      operationId: sendMessage
      tags: [messages]
      summary: Send a message
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMessageBody"
      responses:
        "201":
          description: Message sent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageWithSender"

  /conversations/{id}/read:
    post:
      operationId: markConversationRead
      tags: [messages]
      summary: Mark all messages in conversation as read
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Marked as read
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

  # Notifications
  /notifications:
    get:
      operationId: listNotifications
      tags: [notifications]
      summary: List notifications for current user
      responses:
        "200":
          description: Notifications
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Notification"

  /notifications/{id}/read:
    patch:
      operationId: markNotificationRead
      tags: [notifications]
      summary: Mark notification as read
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Marked as read
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Notification"

  /notifications/read-all:
    post:
      operationId: markAllNotificationsRead
      tags: [notifications]
      summary: Mark all notifications as read
      responses:
        "200":
          description: Done
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResponse"

  # Users (admin)
  /users:
    get:
      operationId: listUsers
      tags: [users]
      summary: List all users (admin)
      responses:
        "200":
          description: Users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/UserProfile"

    post:
      operationId: createUser
      tags: [users]
      summary: Create a user (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterBody"
      responses:
        "201":
          description: Created user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserProfile"
        "409":
          description: Username already taken

  /users/{id}:
    get:
      operationId: getUser
      tags: [users]
      summary: Get user by ID (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: User
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserProfile"

    patch:
      operationId: updateUser
      tags: [users]
      summary: Update user (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateUserBody"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserProfile"

    delete:
      operationId: deleteUser
      tags: [users]
      summary: Delete user (admin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "204":
          description: Deleted

  # Stats
  /stats/home:
    get:
      operationId: getHomeStats
      tags: [stats]
      summary: Get home screen stats
      responses:
        "200":
          description: Stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HomeStats"

  /stats/admin:
    get:
      operationId: getAdminStats
      tags: [stats]
      summary: Get admin dashboard trend stats
      responses:
        "200":
          description: Admin stats
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminStats"

components:
  schemas:
    HealthStatus:
      type: object
      properties:
        status:
          type: string
      required:
        - status

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

    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
      required:
        - success

    RegisterBody:
      type: object
      properties:
        username:
          type: string
        email:
          type: string
        password:
          type: string
        displayNameAr:
          type: ["string", "null"]
        displayNameEn:
          type: ["string", "null"]
        preferredLanguage:
          type: string
          default: "ar"
      required:
        - username
        - email
        - password

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

    UpdateLanguageBody:
      type: object
      properties:
        language:
          type: string
      required:
        - language

    AuthUser:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        email:
          type: string
        displayNameAr:
          type: ["string", "null"]
        displayNameEn:
          type: ["string", "null"]
        preferredLanguage:
          type: string
        avatarUrl:
          type: ["string", "null"]
        isActive:
          type: boolean
        roles:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - username
        - email
        - preferredLanguage
        - isActive
        - roles
        - createdAt

    UserProfile:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        email:
          type: string
        displayNameAr:
          type: ["string", "null"]
        displayNameEn:
          type: ["string", "null"]
        preferredLanguage:
          type: string
        avatarUrl:
          type: ["string", "null"]
        isActive:
          type: boolean
        roles:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - username
        - email
        - preferredLanguage
        - isActive
        - roles
        - createdAt

    UpdateUserBody:
      type: object
      properties:
        displayNameAr:
          type: ["string", "null"]
        displayNameEn:
          type: ["string", "null"]
        isActive:
          type: boolean
        preferredLanguage:
          type: string

    App:
      type: object
      properties:
        id:
          type: integer
        slug:
          type: string
        nameAr:
          type: string
        nameEn:
          type: string
        descriptionAr:
          type: ["string", "null"]
        descriptionEn:
          type: ["string", "null"]
        iconName:
          type: string
        route:
          type: string
        color:
          type: string
        isActive:
          type: boolean
        isSystem:
          type: boolean
        sortOrder:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
        - id
        - slug
        - nameAr
        - nameEn
        - iconName
        - route
        - color
        - isActive
        - isSystem
        - sortOrder
        - createdAt
        - updatedAt

    CreateAppBody:
      type: object
      properties:
        slug:
          type: string
        nameAr:
          type: string
        nameEn:
          type: string
        descriptionAr:
          type: ["string", "null"]
        descriptionEn:
          type: ["string", "null"]
        iconName:
          type: string
        route:
          type: string
        color:
          type: string
        isActive:
          type: boolean
        isSystem:
          type: boolean
        sortOrder:
          type: integer
      required:
        - slug
        - nameAr
        - nameEn
        - iconName
        - route
        - color

    UpdateAppBody:
      type: object
      properties:
        nameAr:
          type: string
        nameEn:
          type: string
        descriptionAr:
          type: ["string", "null"]
        descriptionEn:
          type: ["string", "null"]
        iconName:
          type: string
        route:
          type: string
        color:
          type: string
        isActive:
          type: boolean
        sortOrder:
          type: integer

    Service:
      type: object
      properties:
        id:
          type: integer
        categoryId:
          type: ["integer", "null"]
        nameAr:
          type: string
        nameEn:
          type: string
        descriptionAr:
          type: ["string", "null"]
        descriptionEn:
          type: ["string", "null"]
        imageUrl:
          type: ["string", "null"]
        price:
          type: ["string", "null"]
        isAvailable:
          type: boolean
        sortOrder:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
        - id
        - nameAr
        - nameEn
        - isAvailable
        - sortOrder
        - createdAt
        - updatedAt

    CreateServiceBody:
      type: object
      properties:
        categoryId:
          type: ["integer", "null"]
        nameAr:
          type: string
        nameEn:
          type: string
        descriptionAr:
          type: ["string", "null"]
        descriptionEn:
          type: ["string", "null"]
        imageUrl:
          type: ["string", "null"]
        price:
          type: ["string", "null"]
        isAvailable:
          type: boolean
        sortOrder:
          type: integer
      required:
        - nameAr
        - nameEn

    UpdateServiceBody:
      type: object
      properties:
        categoryId:
          type: ["integer", "null"]
        nameAr:
          type: string
        nameEn:
          type: string
        descriptionAr:
          type: ["string", "null"]
        descriptionEn:
          type: ["string", "null"]
        imageUrl:
          type: ["string", "null"]
        price:
          type: ["string", "null"]
        isAvailable:
          type: boolean
        sortOrder:
          type: integer

    ServiceCategory:
      type: object
      properties:
        id:
          type: integer
        nameAr:
          type: string
        nameEn:
          type: string
        sortOrder:
          type: integer
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - nameAr
        - nameEn
        - sortOrder
        - createdAt

    ConversationWithDetails:
      type: object
      properties:
        id:
          type: integer
        nameAr:
          type: ["string", "null"]
        nameEn:
          type: ["string", "null"]
        isGroup:
          type: boolean
        createdBy:
          type: integer
        participants:
          type: array
          items:
            $ref: "#/components/schemas/ParticipantInfo"
        lastMessage:
          $ref: "#/components/schemas/MessageWithSender"
          nullable: true
        unreadCount:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
        - id
        - isGroup
        - createdBy
        - participants
        - unreadCount
        - createdAt
        - updatedAt

    ParticipantInfo:
      type: object
      properties:
        id:
          type: integer
        username:
          type: string
        displayNameAr:
          type: ["string", "null"]
        displayNameEn:
          type: ["string", "null"]
        avatarUrl:
          type: ["string", "null"]
        isAdmin:
          type: boolean
      required:
        - id
        - username
        - isAdmin

    CreateConversationBody:
      type: object
      properties:
        participantIds:
          type: array
          items:
            type: integer
        nameAr:
          type: ["string", "null"]
        nameEn:
          type: ["string", "null"]
        isGroup:
          type: boolean
      required:
        - participantIds

    MessageWithSender:
      type: object
      properties:
        id:
          type: integer
        conversationId:
          type: integer
        senderId:
          type: integer
        content:
          type: string
        sender:
          $ref: "#/components/schemas/ParticipantInfo"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
        - id
        - conversationId
        - senderId
        - content
        - sender
        - createdAt
        - updatedAt

    SendMessageBody:
      type: object
      properties:
        content:
          type: string
      required:
        - content

    Notification:
      type: object
      properties:
        id:
          type: integer
        userId:
          type: integer
        titleAr:
          type: string
        titleEn:
          type: string
        bodyAr:
          type: ["string", "null"]
        bodyEn:
          type: ["string", "null"]
        type:
          type: string
        isRead:
          type: boolean
        relatedId:
          type: ["integer", "null"]
        relatedType:
          type: ["string", "null"]
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - userId
        - titleAr
        - titleEn
        - type
        - isRead
        - createdAt

    AppSettings:
      type: object
      required: [siteNameAr, siteNameEn, registrationOpen, footerTextAr, footerTextEn]
      properties:
        siteNameAr:
          type: string
        siteNameEn:
          type: string
        registrationOpen:
          type: boolean
        footerTextAr:
          type: string
        footerTextEn:
          type: string
        updatedAt:
          type: string
          format: date-time

    UpdateMyAppOrderBody:
      type: object
      required: [order]
      properties:
        order:
          type: array
          items:
            type: integer
          description: App IDs in the desired display order

    UpdateAppSettingsBody:
      type: object
      properties:
        siteNameAr:
          type: string
          minLength: 1
          maxLength: 200
        siteNameEn:
          type: string
          minLength: 1
          maxLength: 200
        registrationOpen:
          type: boolean
        footerTextAr:
          type: string
          minLength: 1
          maxLength: 300
        footerTextEn:
          type: string
          minLength: 1
          maxLength: 300

    RequestUploadUrlBody:
      type: object
      required: [name, size, contentType]
      properties:
        name:
          type: string
          minLength: 1
        size:
          type: integer
          minimum: 1
        contentType:
          type: string
          minLength: 1

    RequestUploadUrlResponse:
      type: object
      required: [uploadURL, objectPath]
      properties:
        uploadURL:
          type: string
          format: uri
        objectPath:
          type: string
        metadata:
          $ref: "#/components/schemas/RequestUploadUrlBody"

    HomeStats:
      type: object
      properties:
        totalApps:
          type: integer
        totalServices:
          type: integer
        unreadNotifications:
          type: integer
        unreadMessages:
          type: integer
        totalUsers:
          type: integer
      required:
        - totalApps
        - totalServices
        - unreadNotifications
        - unreadMessages
        - totalUsers

    AdminStats:
      type: object
      properties:
        newUsersLast7Days:
          type: integer
        newUsersPrev7Days:
          type: integer
        activeServices:
          type: integer
        inactiveServices:
          type: integer
        signupsByDay:
          type: array
          items:
            type: object
            properties:
              date:
                type: string
              count:
                type: integer
            required:
              - date
              - count
      required:
        - newUsersLast7Days
        - newUsersPrev7Days
        - activeServices
        - inactiveServices
        - signupsByDay
