2026-04-18 02:00:09 +00:00
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
2026-04-20 09:20:50 +00:00
description : TeaBoy OS API specification
2026-04-18 02:00:09 +00:00
servers :
- url : /api
description : Base API path
tags :
- name : health
description : Health operations
2026-04-20 09:20:50 +00:00
- name : auth
description : Authentication
- name : apps
description : App management
- name : services
description : Internal services (khidmati)
2026-04-21 18:24:20 +00:00
- name : orders
description : Service orders workflow
2026-04-20 09:20:50 +00:00
- name : conversations
description : Chat conversations
- name : messages
description : Chat messages
- name : notifications
description : Notifications
- name : users
description : User management
2026-04-21 18:58:19 +00:00
- name : roles
description : Role assignment
2026-04-20 09:20:50 +00:00
- name : stats
description : Dashboard stats
2026-04-20 10:55:11 +00:00
- name : storage
description : Object storage upload and serving endpoints
2026-04-20 09:20:50 +00:00
2026-04-18 02:00:09 +00:00
paths :
/healthz :
get :
operationId : healthCheck
tags : [ health]
summary : Health check
responses :
"200" :
description : Healthy
content :
application/json :
schema :
$ref : "#/components/schemas/HealthStatus"
2026-04-20 09:20:50 +00:00
# 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"
2026-04-20 16:28:50 +00:00
/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"
2026-04-20 09:20:50 +00:00
/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"
2026-04-20 16:40:49 +00:00
/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"
2026-04-21 07:16:50 +00:00
/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"
2026-04-20 09:20:50 +00:00
# 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
2026-04-20 15:16:19 +00:00
/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"
2026-04-20 12:09:14 +00:00
/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"
2026-04-20 11:06:15 +00:00
# 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"
2026-04-20 10:55:11 +00:00
# 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"
2026-04-20 09:20:50 +00:00
# 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
2026-04-21 18:24:20 +00:00
/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]
2026-04-21 18:34:14 +00:00
summary : List active orders for receivers (requires orders.receive permission). Returns pending+unassigned orders plus orders assigned to me that are not completed/cancelled.
2026-04-21 18:24:20 +00:00
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]
2026-04-21 18:34:14 +00:00
summary : Update order status to preparing, completed, or cancelled. Preparing/completed restricted to assigned receiver or admin; cancelled allowed for owner (while pending|received) or admin (any time).
2026-04-21 18:24:20 +00:00
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"
2026-04-21 18:36:25 +00:00
"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
2026-04-21 18:24:20 +00:00
content :
application/json :
schema :
$ref : "#/components/schemas/ErrorResponse"
/orders/{id}/confirm-receipt:
patch :
operationId : confirmServiceOrderReceipt
tags : [ orders]
2026-04-21 18:34:14 +00:00
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.
2026-04-21 18:24:20 +00:00
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"
2026-04-20 09:20:50 +00:00
/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"
2026-04-21 07:46:16 +00:00
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"
2026-04-21 08:07:15 +00:00
/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"
2026-04-21 08:29:01 +00:00
/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
2026-04-21 11:29:39 +00:00
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
2026-04-21 08:29:01 +00:00
responses :
"200" :
description : Left conversation
content :
application/json :
schema :
$ref : "#/components/schemas/SuccessResponse"
2026-04-20 09:20:50 +00:00
/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"
2026-04-20 10:02:09 +00:00
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
2026-04-20 09:20:50 +00:00
/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
2026-04-21 18:58:19 +00:00
/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"
2026-04-20 09:20:50 +00:00
# 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"
2026-04-20 12:16:55 +00:00
/stats/admin :
get :
operationId : getAdminStats
tags : [ stats]
summary : Get admin dashboard trend stats
2026-04-20 15:38:28 +00:00
parameters :
- in : query
name : range
required : false
schema :
type : string
2026-04-21 07:01:43 +00:00
enum : [ "7d" , "30d" , "90d" , "custom" ]
2026-04-20 15:38:28 +00:00
default : "7d"
2026-04-21 07:01:43 +00:00
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
2026-04-20 12:16:55 +00:00
responses :
"200" :
description : Admin stats
content :
application/json :
schema :
$ref : "#/components/schemas/AdminStats"
2026-04-21 09:01:03 +00:00
"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"
2026-04-20 12:16:55 +00:00
2026-04-21 10:17:41 +00:00
/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
2026-04-21 11:48:40 +00:00
- 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
2026-04-21 10:17:41 +00:00
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
2026-04-21 11:48:40 +00:00
- 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
2026-04-21 10:17:41 +00:00
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"
2026-04-18 02:00:09 +00:00
components :
schemas :
HealthStatus :
type : object
properties :
status :
type : string
required :
- status
2026-04-20 09:20:50 +00:00
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
2026-04-20 16:28:50 +00:00
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
2026-04-20 09:20:50 +00:00
UpdateLanguageBody :
type : object
properties :
language :
type : string
required :
- language
2026-04-20 16:40:49 +00:00
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
2026-04-21 07:16:50 +00:00
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
2026-04-20 09:20:50 +00:00
AuthUser :
type : object
properties :
id :
type : integer
username :
type : string
email :
type : string
displayNameAr :
type : [ "string" , "null" ]
displayNameEn :
type : [ "string" , "null" ]
preferredLanguage :
type : string
2026-04-20 16:40:49 +00:00
clockStyle :
oneOf :
- $ref : "#/components/schemas/ClockStyle"
- type : "null"
2026-04-21 07:16:50 +00:00
clockHour12 :
type : [ "boolean" , "null" ]
2026-04-20 09:20:50 +00:00
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
2026-04-20 16:40:49 +00:00
clockStyle :
oneOf :
- $ref : "#/components/schemas/ClockStyle"
- type : "null"
2026-04-21 07:16:50 +00:00
clockHour12 :
type : [ "boolean" , "null" ]
2026-04-20 09:20:50 +00:00
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
2026-04-21 18:58:19 +00:00
AddUserRoleBody :
type : object
properties :
roleName :
type : string
minLength : 1
required :
- roleName
2026-04-20 09:20:50 +00:00
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
2026-04-21 18:24:20 +00:00
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
2026-04-21 18:34:14 +00:00
enum : [ pending, received, preparing, completed, cancelled]
2026-04-21 18:24:20 +00:00
ServiceOrder :
type : object
properties :
id :
type : integer
serviceId :
type : integer
2026-04-21 18:34:14 +00:00
userId :
2026-04-21 18:24:20 +00:00
type : integer
assignedTo :
type : [ "integer" , "null" ]
notes :
type : [ "string" , "null" ]
status :
$ref : "#/components/schemas/ServiceOrderStatus"
createdAt :
type : string
format : date-time
2026-04-21 18:34:14 +00:00
updatedAt :
type : string
2026-04-21 18:24:20 +00:00
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
2026-04-21 18:34:14 +00:00
- userId
2026-04-21 18:24:20 +00:00
- status
- createdAt
2026-04-21 18:34:14 +00:00
- updatedAt
2026-04-21 18:24:20 +00:00
- service
CreateServiceOrderBody :
type : object
properties :
serviceId :
type : integer
notes :
type : [ "string" , "null" ]
maxLength : 500
required :
- serviceId
UpdateServiceOrderStatusBody :
type : object
properties :
status :
type : string
2026-04-21 18:34:14 +00:00
enum : [ preparing, completed, cancelled]
2026-04-21 18:24:20 +00:00
required :
- status
2026-04-20 09:20:50 +00:00
ConversationWithDetails :
type : object
properties :
id :
type : integer
nameAr :
type : [ "string" , "null" ]
nameEn :
type : [ "string" , "null" ]
2026-04-21 07:46:16 +00:00
avatarUrl :
type : [ "string" , "null" ]
2026-04-20 09:20:50 +00:00
isGroup :
type : boolean
createdBy :
type : integer
participants :
type : array
items :
$ref : "#/components/schemas/ParticipantInfo"
lastMessage :
$ref : "#/components/schemas/MessageWithSender"
nullable : true
unreadCount :
type : integer
2026-04-21 08:29:01 +00:00
isMuted :
type : boolean
isArchived :
type : boolean
2026-04-20 09:20:50 +00:00
createdAt :
type : string
format : date-time
updatedAt :
type : string
format : date-time
required :
- id
- isGroup
- createdBy
- participants
- unreadCount
2026-04-21 08:29:01 +00:00
- isMuted
- isArchived
2026-04-20 09:20:50 +00:00
- 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" ]
2026-04-21 07:46:16 +00:00
avatarUrl :
type : [ "string" , "null" ]
2026-04-20 09:20:50 +00:00
isGroup :
type : boolean
required :
- participantIds
2026-04-21 07:46:16 +00:00
UpdateConversationBody :
type : object
properties :
avatarUrl :
type : [ "string" , "null" ]
2026-04-21 08:07:15 +00:00
nameAr :
type : [ "string" , "null" ]
nameEn :
type : [ "string" , "null" ]
2026-04-21 08:29:01 +00:00
UpdateConversationStateBody :
type : object
properties :
isMuted :
type : boolean
isArchived :
type : boolean
2026-04-21 08:07:15 +00:00
AddParticipantsBody :
type : object
properties :
userIds :
type : array
items :
type : integer
required :
- userIds
2026-04-21 07:46:16 +00:00
2026-04-20 09:20:50 +00:00
MessageWithSender :
type : object
properties :
id :
type : integer
conversationId :
type : integer
senderId :
type : integer
content :
type : string
2026-04-21 09:35:32 +00:00
kind :
type : string
2026-04-21 10:20:22 +00:00
enum : [ user, group_renamed, members_added, member_removed, member_left, admin_promoted]
2026-04-21 09:35:32 +00:00
meta :
type : object
nullable : true
additionalProperties : true
2026-04-20 09:20:50 +00:00
sender :
$ref : "#/components/schemas/ParticipantInfo"
createdAt :
type : string
format : date-time
updatedAt :
type : string
format : date-time
required :
- id
- conversationId
- senderId
- content
2026-04-21 09:35:32 +00:00
- kind
2026-04-20 09:20:50 +00:00
- 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
2026-04-20 11:06:15 +00:00
AppSettings :
type : object
2026-04-20 12:28:25 +00:00
required : [ siteNameAr, siteNameEn, registrationOpen, footerTextAr, footerTextEn]
2026-04-20 11:06:15 +00:00
properties :
siteNameAr :
type : string
siteNameEn :
type : string
2026-04-20 11:33:06 +00:00
registrationOpen :
type : boolean
2026-04-20 12:28:25 +00:00
footerTextAr :
type : string
footerTextEn :
type : string
2026-04-20 11:06:15 +00:00
updatedAt :
type : string
format : date-time
2026-04-20 12:09:14 +00:00
UpdateMyAppOrderBody :
type : object
required : [ order]
properties :
order :
type : array
items :
type : integer
description : App IDs in the desired display order
2026-04-20 11:06:15 +00:00
UpdateAppSettingsBody :
type : object
properties :
siteNameAr :
type : string
minLength : 1
maxLength : 200
siteNameEn :
type : string
minLength : 1
maxLength : 200
2026-04-20 11:33:06 +00:00
registrationOpen :
type : boolean
2026-04-20 12:28:25 +00:00
footerTextAr :
type : string
minLength : 1
maxLength : 300
footerTextEn :
type : string
minLength : 1
maxLength : 300
2026-04-20 11:06:15 +00:00
2026-04-20 10:55:11 +00:00
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"
2026-04-20 09:20:50 +00:00
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
2026-04-20 12:16:55 +00:00
AdminStats :
type : object
properties :
2026-04-20 15:38:28 +00:00
range :
type : string
2026-04-21 07:01:43 +00:00
enum : [ "7d" , "30d" , "90d" , "custom" ]
2026-04-20 15:38:28 +00:00
rangeDays :
type : integer
2026-04-21 07:01:43 +00:00
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
2026-04-20 15:38:28 +00:00
newUsersInRange :
2026-04-20 12:16:55 +00:00
type : integer
2026-04-20 15:38:28 +00:00
newUsersPrevRange :
2026-04-20 12:16:55 +00:00
type : integer
activeServices :
type : integer
inactiveServices :
type : integer
signupsByDay :
type : array
items :
type : object
properties :
date :
type : string
count :
type : integer
required :
- date
- count
2026-04-20 15:16:19 +00:00
appOpensByDay :
type : array
items :
type : object
properties :
date :
type : string
count :
type : integer
required :
- date
- count
2026-04-20 15:38:28 +00:00
appOpensInRange :
2026-04-20 15:16:19 +00:00
type : integer
2026-04-20 15:38:28 +00:00
appOpensPrevRange :
2026-04-20 15:16:19 +00:00
type : integer
servicesCreatedByDay :
type : array
items :
type : object
properties :
date :
type : string
count :
type : integer
required :
- date
- count
2026-04-20 15:38:28 +00:00
servicesCreatedInRange :
2026-04-20 15:16:19 +00:00
type : integer
2026-04-21 06:11:28 +00:00
topApps :
type : array
items :
$ref : "#/components/schemas/TopAppItem"
mostActiveUsers :
type : array
items :
$ref : "#/components/schemas/TopUserItem"
2026-04-20 12:16:55 +00:00
required :
2026-04-20 15:38:28 +00:00
- range
- rangeDays
- newUsersInRange
- newUsersPrevRange
2026-04-20 12:16:55 +00:00
- activeServices
- inactiveServices
- signupsByDay
2026-04-20 15:16:19 +00:00
- appOpensByDay
2026-04-20 15:38:28 +00:00
- appOpensInRange
- appOpensPrevRange
2026-04-20 15:16:19 +00:00
- servicesCreatedByDay
2026-04-20 15:38:28 +00:00
- servicesCreatedInRange
2026-04-21 06:11:28 +00:00
- 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
2026-04-21 10:17:41 +00:00
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
2026-04-21 11:48:40 +00:00
limit :
type : integer
offset :
type : integer
nextOffset :
type : [ "integer" , "null" ]
2026-04-21 10:17:41 +00:00
opens :
type : array
items :
$ref : "#/components/schemas/AppOpenByAppEntry"
required :
- appId
- slug
- nameAr
- nameEn
- iconName
- color
- range
- rangeDays
- rangeFrom
- rangeTo
- totalCount
2026-04-21 11:48:40 +00:00
- limit
- offset
- nextOffset
2026-04-21 10:17:41 +00:00
- 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
2026-04-21 11:48:40 +00:00
limit :
type : integer
offset :
type : integer
nextOffset :
type : [ "integer" , "null" ]
2026-04-21 10:17:41 +00:00
opens :
type : array
items :
$ref : "#/components/schemas/AppOpenByUserEntry"
required :
- userId
- username
- range
- rangeDays
- rangeFrom
- rangeTo
- totalCount
2026-04-21 11:48:40 +00:00
- limit
- offset
- nextOffset
2026-04-21 10:17:41 +00:00
- opens
2026-04-21 06:11:28 +00:00
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