1f23e65c0b
Replaces all user-facing instances of "TeaBoy" with "Tx" across the application, including titles, locale files, database settings, seed data, and API documentation. Also updates internal storage keys and session secrets to remove the old branding. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 07b19cb0-11b5-4be9-8932-ae4820eb73b8 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/HxTkDPZ Replit-Helium-Checkpoint-Created: true
2876 lines
68 KiB
YAML
2876 lines
68 KiB
YAML
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: Tx 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: orders
|
|
description: Service orders workflow
|
|
- name: conversations
|
|
description: Chat conversations
|
|
- name: messages
|
|
description: Chat messages
|
|
- name: notifications
|
|
description: Notifications
|
|
- name: users
|
|
description: User management
|
|
- name: roles
|
|
description: Role assignment
|
|
- 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/forgot-password:
|
|
post:
|
|
operationId: forgotPassword
|
|
tags: [auth]
|
|
summary: Request a password reset link
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ForgotPasswordBody"
|
|
responses:
|
|
"200":
|
|
description: Reset request accepted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ForgotPasswordResponse"
|
|
"400":
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/auth/reset-password:
|
|
post:
|
|
operationId: resetPassword
|
|
tags: [auth]
|
|
summary: Set a new password using a reset token
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ResetPasswordBody"
|
|
responses:
|
|
"200":
|
|
description: Password updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessResponse"
|
|
"400":
|
|
description: Invalid or expired token
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/auth/reset-password/verify:
|
|
post:
|
|
operationId: verifyResetToken
|
|
tags: [auth]
|
|
summary: Check whether a reset token is valid
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/VerifyResetTokenBody"
|
|
responses:
|
|
"200":
|
|
description: Token check result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/VerifyResetTokenResponse"
|
|
|
|
/auth/admin/users/{id}/issue-reset-link:
|
|
post:
|
|
operationId: adminIssueResetLink
|
|
tags: [auth]
|
|
summary: Admin generates a one-time password reset link for a user
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"200":
|
|
description: Reset link issued
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AdminResetLinkResponse"
|
|
"403":
|
|
description: Forbidden
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
"404":
|
|
description: User not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/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"
|
|
|
|
/auth/me/clock-style:
|
|
patch:
|
|
operationId: updateClockStyle
|
|
tags: [auth]
|
|
summary: Update preferred home-screen clock style
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateClockStyleBody"
|
|
responses:
|
|
"200":
|
|
description: Updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AuthUser"
|
|
"400":
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/auth/me/clock-hour12:
|
|
patch:
|
|
operationId: updateClockHour12
|
|
tags: [auth]
|
|
summary: Update preferred 12/24-hour clock format
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateClockHour12Body"
|
|
responses:
|
|
"200":
|
|
description: Updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AuthUser"
|
|
"400":
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
# 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
|
|
|
|
/apps/{id}/open:
|
|
post:
|
|
operationId: logAppOpen
|
|
tags: [apps]
|
|
summary: Log an app open event for the current user
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"204":
|
|
description: Logged
|
|
"404":
|
|
description: App not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/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
|
|
|
|
/orders:
|
|
post:
|
|
operationId: createServiceOrder
|
|
tags: [orders]
|
|
summary: Place a service order
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateServiceOrderBody"
|
|
responses:
|
|
"201":
|
|
description: Created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ServiceOrder"
|
|
"400":
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/orders/my:
|
|
get:
|
|
operationId: listMyServiceOrders
|
|
tags: [orders]
|
|
summary: List orders placed by the current user
|
|
responses:
|
|
"200":
|
|
description: My orders
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ServiceOrder"
|
|
|
|
/orders/incoming:
|
|
get:
|
|
operationId: listIncomingServiceOrders
|
|
tags: [orders]
|
|
summary: List active orders for receivers (requires orders.receive permission). Returns pending+unassigned orders plus orders assigned to me that are not completed/cancelled.
|
|
responses:
|
|
"200":
|
|
description: Incoming orders
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ServiceOrder"
|
|
"403":
|
|
description: Forbidden
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/orders/{id}/status:
|
|
patch:
|
|
operationId: updateServiceOrderStatus
|
|
tags: [orders]
|
|
summary: Update order status. Preparing/completed restricted to assigned receiver or admin; cancelled allowed for owner (while pending|received) or admin (any time); pending/received are restore-from-cancelled transitions allowed for owner or admin.
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateServiceOrderStatusBody"
|
|
responses:
|
|
"200":
|
|
description: Updated order
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ServiceOrder"
|
|
"400":
|
|
description: Invalid transition or validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
"403":
|
|
description: Forbidden — caller is not the assignee, owner, or admin per the transition rules
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
"404":
|
|
description: Order not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/orders/{id}/confirm-receipt:
|
|
patch:
|
|
operationId: confirmServiceOrderReceipt
|
|
tags: [orders]
|
|
summary: Receiver atomically claims a pending order (sets status=received, assigned_to=current user). Requires orders.receive permission. Returns 409 already_claimed if the order is no longer pending+unassigned.
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"200":
|
|
description: Order received
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ServiceOrder"
|
|
"409":
|
|
description: Invalid transition
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/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"
|
|
|
|
patch:
|
|
operationId: updateConversation
|
|
tags: [conversations]
|
|
summary: Update conversation properties (admin only)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateConversationBody"
|
|
responses:
|
|
"200":
|
|
description: Updated conversation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ConversationWithDetails"
|
|
|
|
/conversations/{id}/participants:
|
|
post:
|
|
operationId: addConversationParticipants
|
|
tags: [conversations]
|
|
summary: Add participants to a group conversation (admin only)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AddParticipantsBody"
|
|
responses:
|
|
"200":
|
|
description: Updated conversation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ConversationWithDetails"
|
|
|
|
/conversations/{id}/participants/{userId}:
|
|
delete:
|
|
operationId: removeConversationParticipant
|
|
tags: [conversations]
|
|
summary: Remove a participant from a group conversation (admin only)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
- name: userId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"200":
|
|
description: Updated conversation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ConversationWithDetails"
|
|
|
|
/conversations/{id}/state:
|
|
patch:
|
|
operationId: updateConversationState
|
|
tags: [conversations]
|
|
summary: Update per-user state (mute, archive)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateConversationStateBody"
|
|
responses:
|
|
"200":
|
|
description: Updated conversation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ConversationWithDetails"
|
|
|
|
/conversations/{id}/leave:
|
|
post:
|
|
operationId: leaveConversation
|
|
tags: [conversations]
|
|
summary: Leave a group conversation
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
successorId:
|
|
type: integer
|
|
description: |
|
|
When the leaver is the only admin, optionally name a specific
|
|
remaining member to promote. Omit (or send null) to keep the
|
|
automatic behavior of promoting the longest-tenured member.
|
|
nullable: true
|
|
responses:
|
|
"200":
|
|
description: Left conversation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessResponse"
|
|
|
|
/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)
|
|
parameters:
|
|
- in: query
|
|
name: q
|
|
required: false
|
|
schema:
|
|
type: string
|
|
description: Free-text search across username, email, and display names
|
|
- in: query
|
|
name: groupId
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
description: Filter to users that belong to the given group
|
|
- in: query
|
|
name: status
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: ["active", "disabled"]
|
|
description: Filter by active/disabled state
|
|
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/CreateUserBody"
|
|
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
|
|
|
|
/orders/{id}:
|
|
delete:
|
|
operationId: deleteServiceOrder
|
|
tags: [orders]
|
|
summary: Delete a service order
|
|
description: |
|
|
Permanently deletes the order. Allowed when the caller owns the order
|
|
AND it is in a terminal status (completed/cancelled), OR the caller is
|
|
an admin (any status).
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"204":
|
|
description: Deleted
|
|
"403":
|
|
description: Forbidden
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
"404":
|
|
description: Order not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/users/{id}/roles:
|
|
post:
|
|
operationId: addUserRole
|
|
tags: [roles]
|
|
summary: Idempotently add a role to a user (admin)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AddUserRoleBody"
|
|
responses:
|
|
"200":
|
|
description: Role added (or already present)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UserProfile"
|
|
"400":
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
"404":
|
|
description: User or role not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/users/{id}/roles/{roleName}:
|
|
delete:
|
|
operationId: removeUserRole
|
|
tags: [roles]
|
|
summary: Idempotently remove a role from a user (admin)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
- name: roleName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Role removed (or already absent)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UserProfile"
|
|
"404":
|
|
description: User not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
# Roles (admin)
|
|
/roles:
|
|
get:
|
|
operationId: listRoles
|
|
tags: [users]
|
|
summary: List roles (admin)
|
|
responses:
|
|
"200":
|
|
description: Roles
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Role"
|
|
|
|
# Groups (admin)
|
|
/groups:
|
|
get:
|
|
operationId: listGroups
|
|
tags: [users]
|
|
summary: List groups (admin)
|
|
parameters:
|
|
- in: query
|
|
name: q
|
|
required: false
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Groups
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Group"
|
|
post:
|
|
operationId: createGroup
|
|
tags: [users]
|
|
summary: Create group (admin)
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateGroupBody"
|
|
responses:
|
|
"201":
|
|
description: Created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Group"
|
|
|
|
/groups/{id}:
|
|
get:
|
|
operationId: getGroup
|
|
tags: [users]
|
|
summary: Get group with members and apps (admin)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"200":
|
|
description: Group detail
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/GroupDetail"
|
|
"404":
|
|
description: Not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
patch:
|
|
operationId: updateGroup
|
|
tags: [users]
|
|
summary: Update group (admin)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateGroupBody"
|
|
responses:
|
|
"200":
|
|
description: Updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/GroupDetail"
|
|
|
|
delete:
|
|
operationId: deleteGroup
|
|
tags: [users]
|
|
summary: Delete group (admin)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"204":
|
|
description: Deleted
|
|
"400":
|
|
description: Cannot delete a system group
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
# 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
|
|
parameters:
|
|
- in: query
|
|
name: range
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: ["7d", "30d", "90d", "custom"]
|
|
default: "7d"
|
|
description: Time range for trend stats. Use "custom" with from/to.
|
|
- in: query
|
|
name: from
|
|
required: false
|
|
schema:
|
|
type: string
|
|
format: date
|
|
description: Start date (inclusive, YYYY-MM-DD UTC) when range=custom
|
|
- in: query
|
|
name: to
|
|
required: false
|
|
schema:
|
|
type: string
|
|
format: date
|
|
description: End date (inclusive, YYYY-MM-DD UTC) when range=custom
|
|
responses:
|
|
"200":
|
|
description: Admin stats
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AdminStats"
|
|
"400":
|
|
description: Invalid range parameters (e.g. range=custom with missing/malformed from/to or inverted dates)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/stats/admin/app-opens/by-app/{appId}:
|
|
get:
|
|
operationId: getAdminAppOpensByApp
|
|
tags: [stats]
|
|
summary: Recent opens of a single app within the selected range
|
|
parameters:
|
|
- in: path
|
|
name: appId
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
- in: query
|
|
name: range
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: ["7d", "30d", "90d", "custom"]
|
|
default: "7d"
|
|
- in: query
|
|
name: from
|
|
required: false
|
|
schema:
|
|
type: string
|
|
format: date
|
|
- in: query
|
|
name: to
|
|
required: false
|
|
schema:
|
|
type: string
|
|
format: date
|
|
- in: query
|
|
name: limit
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 200
|
|
default: 100
|
|
- in: query
|
|
name: offset
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 0
|
|
default: 0
|
|
responses:
|
|
"200":
|
|
description: Recent opens for the app
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AdminAppOpensByApp"
|
|
"400":
|
|
description: Invalid range parameters
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
"404":
|
|
description: App not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
/stats/admin/app-opens/by-user/{userId}:
|
|
get:
|
|
operationId: getAdminAppOpensByUser
|
|
tags: [stats]
|
|
summary: Recent app opens by a single user within the selected range
|
|
parameters:
|
|
- in: path
|
|
name: userId
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
- in: query
|
|
name: range
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: ["7d", "30d", "90d", "custom"]
|
|
default: "7d"
|
|
- in: query
|
|
name: from
|
|
required: false
|
|
schema:
|
|
type: string
|
|
format: date
|
|
- in: query
|
|
name: to
|
|
required: false
|
|
schema:
|
|
type: string
|
|
format: date
|
|
- in: query
|
|
name: limit
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 200
|
|
default: 100
|
|
- in: query
|
|
name: offset
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 0
|
|
default: 0
|
|
responses:
|
|
"200":
|
|
description: Recent app opens by the user
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AdminAppOpensByUser"
|
|
"400":
|
|
description: Invalid range parameters
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
"404":
|
|
description: User not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
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
|
|
|
|
CreateUserBody:
|
|
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"
|
|
groupIds:
|
|
type: array
|
|
description: Initial group memberships in addition to the auto-assigned Everyone group.
|
|
items:
|
|
type: integer
|
|
required:
|
|
- username
|
|
- email
|
|
- password
|
|
|
|
LoginBody:
|
|
type: object
|
|
properties:
|
|
username:
|
|
type: string
|
|
password:
|
|
type: string
|
|
required:
|
|
- username
|
|
- password
|
|
|
|
ForgotPasswordBody:
|
|
type: object
|
|
properties:
|
|
identifier:
|
|
type: string
|
|
description: Username or email
|
|
required:
|
|
- identifier
|
|
|
|
ForgotPasswordResponse:
|
|
type: object
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
required:
|
|
- success
|
|
|
|
ResetPasswordBody:
|
|
type: object
|
|
properties:
|
|
token:
|
|
type: string
|
|
newPassword:
|
|
type: string
|
|
minLength: 6
|
|
required:
|
|
- token
|
|
- newPassword
|
|
|
|
VerifyResetTokenBody:
|
|
type: object
|
|
properties:
|
|
token:
|
|
type: string
|
|
required:
|
|
- token
|
|
|
|
AdminResetLinkResponse:
|
|
type: object
|
|
properties:
|
|
resetUrl:
|
|
type: string
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- resetUrl
|
|
- expiresAt
|
|
|
|
VerifyResetTokenResponse:
|
|
type: object
|
|
properties:
|
|
valid:
|
|
type: boolean
|
|
required:
|
|
- valid
|
|
|
|
UpdateLanguageBody:
|
|
type: object
|
|
properties:
|
|
language:
|
|
type: string
|
|
required:
|
|
- language
|
|
|
|
ClockStyle:
|
|
type: string
|
|
enum: [full, digital, digital-no-seconds, analog, minimal]
|
|
description: Per-user home-screen clock style preference
|
|
|
|
UpdateClockStyleBody:
|
|
type: object
|
|
properties:
|
|
clockStyle:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/ClockStyle"
|
|
- type: "null"
|
|
description: Clock style; pass null to clear and use the default
|
|
required:
|
|
- clockStyle
|
|
|
|
UpdateClockHour12Body:
|
|
type: object
|
|
properties:
|
|
clockHour12:
|
|
type: ["boolean", "null"]
|
|
description: Whether to use 12-hour (AM/PM) clock format; null clears and uses the default (24-hour)
|
|
required:
|
|
- clockHour12
|
|
|
|
AuthUser:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
username:
|
|
type: string
|
|
email:
|
|
type: string
|
|
displayNameAr:
|
|
type: ["string", "null"]
|
|
displayNameEn:
|
|
type: ["string", "null"]
|
|
preferredLanguage:
|
|
type: string
|
|
clockStyle:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/ClockStyle"
|
|
- type: "null"
|
|
clockHour12:
|
|
type: ["boolean", "null"]
|
|
avatarUrl:
|
|
type: ["string", "null"]
|
|
isActive:
|
|
type: boolean
|
|
roles:
|
|
type: array
|
|
items:
|
|
type: string
|
|
groups:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/GroupSummary"
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- id
|
|
- username
|
|
- email
|
|
- preferredLanguage
|
|
- isActive
|
|
- roles
|
|
- groups
|
|
- 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
|
|
clockStyle:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/ClockStyle"
|
|
- type: "null"
|
|
clockHour12:
|
|
type: ["boolean", "null"]
|
|
avatarUrl:
|
|
type: ["string", "null"]
|
|
isActive:
|
|
type: boolean
|
|
roles:
|
|
type: array
|
|
items:
|
|
type: string
|
|
groups:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/GroupSummary"
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- id
|
|
- username
|
|
- email
|
|
- preferredLanguage
|
|
- isActive
|
|
- roles
|
|
- groups
|
|
- createdAt
|
|
|
|
UpdateUserBody:
|
|
type: object
|
|
properties:
|
|
displayNameAr:
|
|
type: ["string", "null"]
|
|
displayNameEn:
|
|
type: ["string", "null"]
|
|
isActive:
|
|
type: boolean
|
|
preferredLanguage:
|
|
type: string
|
|
groupIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
description: If provided, replaces the user's group memberships
|
|
|
|
AddUserRoleBody:
|
|
type: object
|
|
properties:
|
|
roleName:
|
|
type: string
|
|
minLength: 1
|
|
required:
|
|
- roleName
|
|
|
|
Role:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
descriptionAr:
|
|
type: ["string", "null"]
|
|
descriptionEn:
|
|
type: ["string", "null"]
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- id
|
|
- name
|
|
- createdAt
|
|
|
|
GroupSummary:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
descriptionAr:
|
|
type: ["string", "null"]
|
|
descriptionEn:
|
|
type: ["string", "null"]
|
|
required:
|
|
- id
|
|
- name
|
|
|
|
Group:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
descriptionAr:
|
|
type: ["string", "null"]
|
|
descriptionEn:
|
|
type: ["string", "null"]
|
|
isSystem:
|
|
type: boolean
|
|
memberCount:
|
|
type: integer
|
|
appCount:
|
|
type: integer
|
|
roleCount:
|
|
type: integer
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- id
|
|
- name
|
|
- isSystem
|
|
- memberCount
|
|
- appCount
|
|
- roleCount
|
|
- createdAt
|
|
|
|
GroupDetail:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
descriptionAr:
|
|
type: ["string", "null"]
|
|
descriptionEn:
|
|
type: ["string", "null"]
|
|
isSystem:
|
|
type: boolean
|
|
appIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
roleIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
userIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- id
|
|
- name
|
|
- isSystem
|
|
- appIds
|
|
- roleIds
|
|
- userIds
|
|
- createdAt
|
|
|
|
CreateGroupBody:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
descriptionAr:
|
|
type: ["string", "null"]
|
|
descriptionEn:
|
|
type: ["string", "null"]
|
|
appIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
roleIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
userIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
required:
|
|
- name
|
|
|
|
UpdateGroupBody:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
descriptionAr:
|
|
type: ["string", "null"]
|
|
descriptionEn:
|
|
type: ["string", "null"]
|
|
appIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
roleIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
userIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
|
|
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
|
|
|
|
ServiceOrderUser:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
username:
|
|
type: string
|
|
displayNameAr:
|
|
type: ["string", "null"]
|
|
displayNameEn:
|
|
type: ["string", "null"]
|
|
avatarUrl:
|
|
type: ["string", "null"]
|
|
required:
|
|
- id
|
|
- username
|
|
|
|
ServiceOrderService:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
nameAr:
|
|
type: string
|
|
nameEn:
|
|
type: string
|
|
imageUrl:
|
|
type: ["string", "null"]
|
|
required:
|
|
- id
|
|
- nameAr
|
|
- nameEn
|
|
|
|
ServiceOrderStatus:
|
|
type: string
|
|
enum: [pending, received, preparing, completed, cancelled]
|
|
|
|
ServiceOrder:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
serviceId:
|
|
type: integer
|
|
userId:
|
|
type: integer
|
|
assignedTo:
|
|
type: ["integer", "null"]
|
|
notes:
|
|
type: ["string", "null"]
|
|
status:
|
|
$ref: "#/components/schemas/ServiceOrderStatus"
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
service:
|
|
$ref: "#/components/schemas/ServiceOrderService"
|
|
requester:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/ServiceOrderUser"
|
|
- type: "null"
|
|
assignee:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/ServiceOrderUser"
|
|
- type: "null"
|
|
required:
|
|
- id
|
|
- serviceId
|
|
- userId
|
|
- status
|
|
- createdAt
|
|
- updatedAt
|
|
- service
|
|
|
|
CreateServiceOrderBody:
|
|
type: object
|
|
properties:
|
|
serviceId:
|
|
type: integer
|
|
notes:
|
|
type: ["string", "null"]
|
|
maxLength: 500
|
|
required:
|
|
- serviceId
|
|
|
|
UpdateServiceOrderStatusBody:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
enum: [pending, received, preparing, completed, cancelled]
|
|
required:
|
|
- status
|
|
|
|
ConversationWithDetails:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
nameAr:
|
|
type: ["string", "null"]
|
|
nameEn:
|
|
type: ["string", "null"]
|
|
avatarUrl:
|
|
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
|
|
isMuted:
|
|
type: boolean
|
|
isArchived:
|
|
type: boolean
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- id
|
|
- isGroup
|
|
- createdBy
|
|
- participants
|
|
- unreadCount
|
|
- isMuted
|
|
- isArchived
|
|
- 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"]
|
|
avatarUrl:
|
|
type: ["string", "null"]
|
|
isGroup:
|
|
type: boolean
|
|
required:
|
|
- participantIds
|
|
|
|
UpdateConversationBody:
|
|
type: object
|
|
properties:
|
|
avatarUrl:
|
|
type: ["string", "null"]
|
|
nameAr:
|
|
type: ["string", "null"]
|
|
nameEn:
|
|
type: ["string", "null"]
|
|
|
|
UpdateConversationStateBody:
|
|
type: object
|
|
properties:
|
|
isMuted:
|
|
type: boolean
|
|
isArchived:
|
|
type: boolean
|
|
|
|
AddParticipantsBody:
|
|
type: object
|
|
properties:
|
|
userIds:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
required:
|
|
- userIds
|
|
|
|
MessageWithSender:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
conversationId:
|
|
type: integer
|
|
senderId:
|
|
type: integer
|
|
content:
|
|
type: string
|
|
kind:
|
|
type: string
|
|
enum: [user, group_renamed, members_added, member_removed, member_left, admin_promoted]
|
|
meta:
|
|
type: object
|
|
nullable: true
|
|
additionalProperties: true
|
|
sender:
|
|
$ref: "#/components/schemas/ParticipantInfo"
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- id
|
|
- conversationId
|
|
- senderId
|
|
- content
|
|
- kind
|
|
- 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:
|
|
range:
|
|
type: string
|
|
enum: ["7d", "30d", "90d", "custom"]
|
|
rangeDays:
|
|
type: integer
|
|
rangeFrom:
|
|
type: string
|
|
description: Start date (YYYY-MM-DD UTC) of the selected window
|
|
rangeTo:
|
|
type: string
|
|
description: End date (YYYY-MM-DD UTC) of the selected window
|
|
newUsersInRange:
|
|
type: integer
|
|
newUsersPrevRange:
|
|
type: integer
|
|
activeServices:
|
|
type: integer
|
|
inactiveServices:
|
|
type: integer
|
|
signupsByDay:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
date:
|
|
type: string
|
|
count:
|
|
type: integer
|
|
required:
|
|
- date
|
|
- count
|
|
appOpensByDay:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
date:
|
|
type: string
|
|
count:
|
|
type: integer
|
|
required:
|
|
- date
|
|
- count
|
|
appOpensInRange:
|
|
type: integer
|
|
appOpensPrevRange:
|
|
type: integer
|
|
servicesCreatedByDay:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
date:
|
|
type: string
|
|
count:
|
|
type: integer
|
|
required:
|
|
- date
|
|
- count
|
|
servicesCreatedInRange:
|
|
type: integer
|
|
topApps:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/TopAppItem"
|
|
mostActiveUsers:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/TopUserItem"
|
|
required:
|
|
- range
|
|
- rangeDays
|
|
- newUsersInRange
|
|
- newUsersPrevRange
|
|
- activeServices
|
|
- inactiveServices
|
|
- signupsByDay
|
|
- appOpensByDay
|
|
- appOpensInRange
|
|
- appOpensPrevRange
|
|
- servicesCreatedByDay
|
|
- servicesCreatedInRange
|
|
- topApps
|
|
- mostActiveUsers
|
|
|
|
TopAppItem:
|
|
type: object
|
|
properties:
|
|
appId:
|
|
type: integer
|
|
slug:
|
|
type: string
|
|
nameAr:
|
|
type: string
|
|
nameEn:
|
|
type: string
|
|
iconName:
|
|
type: string
|
|
color:
|
|
type: string
|
|
count:
|
|
type: integer
|
|
required:
|
|
- appId
|
|
- slug
|
|
- nameAr
|
|
- nameEn
|
|
- iconName
|
|
- color
|
|
- count
|
|
|
|
AppOpenByAppEntry:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
userId:
|
|
type: integer
|
|
username:
|
|
type: string
|
|
displayNameAr:
|
|
type: ["string", "null"]
|
|
displayNameEn:
|
|
type: ["string", "null"]
|
|
avatarUrl:
|
|
type: ["string", "null"]
|
|
required:
|
|
- id
|
|
- createdAt
|
|
- userId
|
|
- username
|
|
|
|
AdminAppOpensByApp:
|
|
type: object
|
|
properties:
|
|
appId:
|
|
type: integer
|
|
slug:
|
|
type: string
|
|
nameAr:
|
|
type: string
|
|
nameEn:
|
|
type: string
|
|
iconName:
|
|
type: string
|
|
color:
|
|
type: string
|
|
range:
|
|
type: string
|
|
enum: ["7d", "30d", "90d", "custom"]
|
|
rangeDays:
|
|
type: integer
|
|
rangeFrom:
|
|
type: string
|
|
rangeTo:
|
|
type: string
|
|
totalCount:
|
|
type: integer
|
|
limit:
|
|
type: integer
|
|
offset:
|
|
type: integer
|
|
nextOffset:
|
|
type: ["integer", "null"]
|
|
opens:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/AppOpenByAppEntry"
|
|
required:
|
|
- appId
|
|
- slug
|
|
- nameAr
|
|
- nameEn
|
|
- iconName
|
|
- color
|
|
- range
|
|
- rangeDays
|
|
- rangeFrom
|
|
- rangeTo
|
|
- totalCount
|
|
- limit
|
|
- offset
|
|
- nextOffset
|
|
- opens
|
|
|
|
AppOpenByUserEntry:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
appId:
|
|
type: integer
|
|
slug:
|
|
type: string
|
|
nameAr:
|
|
type: string
|
|
nameEn:
|
|
type: string
|
|
iconName:
|
|
type: string
|
|
color:
|
|
type: string
|
|
required:
|
|
- id
|
|
- createdAt
|
|
- appId
|
|
- slug
|
|
- nameAr
|
|
- nameEn
|
|
- iconName
|
|
- color
|
|
|
|
AdminAppOpensByUser:
|
|
type: object
|
|
properties:
|
|
userId:
|
|
type: integer
|
|
username:
|
|
type: string
|
|
displayNameAr:
|
|
type: ["string", "null"]
|
|
displayNameEn:
|
|
type: ["string", "null"]
|
|
avatarUrl:
|
|
type: ["string", "null"]
|
|
range:
|
|
type: string
|
|
enum: ["7d", "30d", "90d", "custom"]
|
|
rangeDays:
|
|
type: integer
|
|
rangeFrom:
|
|
type: string
|
|
rangeTo:
|
|
type: string
|
|
totalCount:
|
|
type: integer
|
|
limit:
|
|
type: integer
|
|
offset:
|
|
type: integer
|
|
nextOffset:
|
|
type: ["integer", "null"]
|
|
opens:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/AppOpenByUserEntry"
|
|
required:
|
|
- userId
|
|
- username
|
|
- range
|
|
- rangeDays
|
|
- rangeFrom
|
|
- rangeTo
|
|
- totalCount
|
|
- limit
|
|
- offset
|
|
- nextOffset
|
|
- opens
|
|
|
|
TopUserItem:
|
|
type: object
|
|
properties:
|
|
userId:
|
|
type: integer
|
|
username:
|
|
type: string
|
|
displayNameAr:
|
|
type: ["string", "null"]
|
|
displayNameEn:
|
|
type: ["string", "null"]
|
|
avatarUrl:
|
|
type: ["string", "null"]
|
|
count:
|
|
type: integer
|
|
required:
|
|
- userId
|
|
- username
|
|
- count
|