84398de390
Destructive removal per user confirmation ("حذف نهائي ما يرجع").
Removed:
- API: routes/conversations.ts, schema/conversations.ts, all chat
socket handlers in src/index.ts, /admin/users/:id/dependents/
conversations+messages endpoints, conversation/message dependency
counts in users/stats routes.
- Web: pages/chat.tsx, /chat route, dock chat filter, MessageSquare
icon and messages StatCard on home, all chat-related UI in
notifications + admin (dependency badges, delete-dialog rows,
UserDependentConversations/Messages sections, count map keys).
- Locales: nav.chat, home.stats.messages, full chat.* block,
admin.deleteUser conv/msgCount, admin.users.counts.conv/msg,
admin.audit.unit.conversation_*/message_*, admin.dependents.user*.
- OpenAPI spec: tags, all /conversations/* paths, conv/msg dependent
paths, related schemas (ConversationWithDetails, MessageWithSender,
UserDependentConversation/MessageItem+Page, etc.), UserProfile and
UserDeletionConflict conv/msg fields, HomeStats.unreadMessages.
Regenerated client via orval.
- Database: dropped message_reads, messages,
conversation_participants, conversations (CASCADE); deleted
notifications with related_type='conversation' or type='chat';
deleted apps row with slug='chat'; ran drizzle push-force.
- Seed: removed chat:access permission + user-role assignment +
seeded chat app entry from scripts/src/seed.ts.
- Tests: deleted conversations-leave.test.mjs; cleaned chat refs from
list-dependency-counts, delete-force-warnings, audit-log-coverage,
and admin-inline-dependency-counts (e2e) — replaced chat dependents
with note dependents where needed for force-delete coverage.
Notes preserved: notes.tsx noConversationsYet/conversationWith refer
to NOTE THREADS (not chat) and were intentionally NOT touched.
executive-meetings.ts not modified per replit.md restriction.
Pre-existing flaky test failures in executive-meetings/group/etc
suites remain unrelated to this task.
3322 lines
91 KiB
TypeScript
3322 lines
91 KiB
TypeScript
/**
|
|
* Generated by orval v8.5.3 🍺
|
|
* Do not edit manually.
|
|
* Api
|
|
* Tx OS API specification
|
|
* OpenAPI spec version: 0.1.0
|
|
*/
|
|
import * as zod from "zod";
|
|
|
|
/**
|
|
* @summary Health check
|
|
*/
|
|
export const HealthCheckResponse = zod.object({
|
|
status: zod.string(),
|
|
});
|
|
|
|
/**
|
|
* @summary Register a new user
|
|
*/
|
|
export const registerBodyPreferredLanguageDefault = `ar`;
|
|
|
|
export const RegisterBody = zod.object({
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
password: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string().default(registerBodyPreferredLanguageDefault),
|
|
});
|
|
|
|
/**
|
|
* @summary Login
|
|
*/
|
|
export const LoginBody = zod.object({
|
|
username: zod.string(),
|
|
password: zod.string(),
|
|
});
|
|
|
|
export const LoginResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
notificationSoundOrder: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundMeeting: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundNote: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notifyOrdersEnabled: zod.boolean(),
|
|
notifyMeetingsEnabled: zod.boolean(),
|
|
notifyNotesEnabled: zod.boolean(),
|
|
vibrationEnabledOrder: zod.boolean(),
|
|
vibrationEnabledMeeting: zod.boolean(),
|
|
vibrationEnabledNote: zod.boolean(),
|
|
notificationsMuted: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Logout
|
|
*/
|
|
export const LogoutResponse = zod.object({
|
|
success: zod.boolean(),
|
|
});
|
|
|
|
/**
|
|
* @summary Request a password reset link
|
|
*/
|
|
export const ForgotPasswordBody = zod.object({
|
|
identifier: zod.string().describe("Username or email"),
|
|
});
|
|
|
|
export const ForgotPasswordResponse = zod.object({
|
|
success: zod.boolean(),
|
|
});
|
|
|
|
/**
|
|
* @summary Set a new password using a reset token
|
|
*/
|
|
export const resetPasswordBodyNewPasswordMin = 6;
|
|
|
|
export const ResetPasswordBody = zod.object({
|
|
token: zod.string(),
|
|
newPassword: zod.string().min(resetPasswordBodyNewPasswordMin),
|
|
});
|
|
|
|
export const ResetPasswordResponse = zod.object({
|
|
success: zod.boolean(),
|
|
});
|
|
|
|
/**
|
|
* @summary Check whether a reset token is valid
|
|
*/
|
|
export const VerifyResetTokenBody = zod.object({
|
|
token: zod.string(),
|
|
});
|
|
|
|
export const VerifyResetTokenResponse = zod.object({
|
|
valid: zod.boolean(),
|
|
});
|
|
|
|
/**
|
|
* @summary Admin generates a one-time password reset link for a user
|
|
*/
|
|
export const AdminIssueResetLinkParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const AdminIssueResetLinkResponse = zod.object({
|
|
resetUrl: zod.string(),
|
|
expiresAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Get current user
|
|
*/
|
|
export const GetMeResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
notificationSoundOrder: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundMeeting: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundNote: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notifyOrdersEnabled: zod.boolean(),
|
|
notifyMeetingsEnabled: zod.boolean(),
|
|
notifyNotesEnabled: zod.boolean(),
|
|
vibrationEnabledOrder: zod.boolean(),
|
|
vibrationEnabledMeeting: zod.boolean(),
|
|
vibrationEnabledNote: zod.boolean(),
|
|
notificationsMuted: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Update preferred language
|
|
*/
|
|
export const UpdateLanguageBody = zod.object({
|
|
language: zod.string(),
|
|
});
|
|
|
|
export const UpdateLanguageResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
notificationSoundOrder: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundMeeting: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundNote: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notifyOrdersEnabled: zod.boolean(),
|
|
notifyMeetingsEnabled: zod.boolean(),
|
|
notifyNotesEnabled: zod.boolean(),
|
|
vibrationEnabledOrder: zod.boolean(),
|
|
vibrationEnabledMeeting: zod.boolean(),
|
|
vibrationEnabledNote: zod.boolean(),
|
|
notificationsMuted: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Update preferred home-screen clock style
|
|
*/
|
|
export const UpdateClockStyleBody = zod.object({
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.describe("Clock style; pass null to clear and use the default"),
|
|
});
|
|
|
|
export const UpdateClockStyleResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
notificationSoundOrder: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundMeeting: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundNote: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notifyOrdersEnabled: zod.boolean(),
|
|
notifyMeetingsEnabled: zod.boolean(),
|
|
notifyNotesEnabled: zod.boolean(),
|
|
vibrationEnabledOrder: zod.boolean(),
|
|
vibrationEnabledMeeting: zod.boolean(),
|
|
vibrationEnabledNote: zod.boolean(),
|
|
notificationsMuted: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Update notification sound, vibration & mute preferences
|
|
*/
|
|
export const UpdateNotificationPreferencesBody = zod.object({
|
|
notificationSoundOrder: zod
|
|
.enum(["ding", "chime", "bell", "knock", "pop", "alert", "beep", "soft"])
|
|
.optional(),
|
|
notificationSoundMeeting: zod
|
|
.enum(["ding", "chime", "bell", "knock", "pop", "alert", "beep", "soft"])
|
|
.optional(),
|
|
notificationSoundNote: zod
|
|
.enum(["ding", "chime", "bell", "knock", "pop", "alert", "beep", "soft"])
|
|
.optional(),
|
|
notifyOrdersEnabled: zod.boolean().optional(),
|
|
notifyMeetingsEnabled: zod.boolean().optional(),
|
|
notifyNotesEnabled: zod.boolean().optional(),
|
|
vibrationEnabledOrder: zod.boolean().optional(),
|
|
vibrationEnabledMeeting: zod.boolean().optional(),
|
|
vibrationEnabledNote: zod.boolean().optional(),
|
|
notificationsMuted: zod.boolean().optional(),
|
|
});
|
|
|
|
export const UpdateNotificationPreferencesResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
notificationSoundOrder: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundMeeting: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundNote: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notifyOrdersEnabled: zod.boolean(),
|
|
notifyMeetingsEnabled: zod.boolean(),
|
|
notifyNotesEnabled: zod.boolean(),
|
|
vibrationEnabledOrder: zod.boolean(),
|
|
vibrationEnabledMeeting: zod.boolean(),
|
|
vibrationEnabledNote: zod.boolean(),
|
|
notificationsMuted: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Update preferred 12/24-hour clock format
|
|
*/
|
|
export const UpdateClockHour12Body = zod.object({
|
|
clockHour12: zod
|
|
.boolean()
|
|
.nullable()
|
|
.describe(
|
|
"Whether to use 12-hour (AM\/PM) clock format; null clears and uses the default (24-hour)",
|
|
),
|
|
});
|
|
|
|
export const UpdateClockHour12Response = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
notificationSoundOrder: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundMeeting: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notificationSoundNote: zod.enum([
|
|
"ding",
|
|
"chime",
|
|
"bell",
|
|
"knock",
|
|
"pop",
|
|
"alert",
|
|
"beep",
|
|
"soft",
|
|
]),
|
|
notifyOrdersEnabled: zod.boolean(),
|
|
notifyMeetingsEnabled: zod.boolean(),
|
|
notifyNotesEnabled: zod.boolean(),
|
|
vibrationEnabledOrder: zod.boolean(),
|
|
vibrationEnabledMeeting: zod.boolean(),
|
|
vibrationEnabledNote: zod.boolean(),
|
|
notificationsMuted: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary List all active apps
|
|
*/
|
|
export const ListAppsResponseItem = zod.object({
|
|
id: zod.number(),
|
|
slug: zod.string(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
iconName: zod.string(),
|
|
route: zod.string(),
|
|
color: zod.string(),
|
|
isActive: zod.boolean(),
|
|
isSystem: zod.boolean(),
|
|
sortOrder: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
groupCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
|
|
),
|
|
restrictionCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
|
|
),
|
|
openCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
|
|
),
|
|
});
|
|
export const ListAppsResponse = zod.array(ListAppsResponseItem);
|
|
|
|
/**
|
|
* @summary Create a new app (admin)
|
|
*/
|
|
export const CreateAppBody = zod.object({
|
|
slug: zod.string(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
iconName: zod.string(),
|
|
route: zod.string(),
|
|
color: zod.string(),
|
|
isActive: zod.boolean().optional(),
|
|
isSystem: zod.boolean().optional(),
|
|
sortOrder: zod.number().optional(),
|
|
permissionIds: zod
|
|
.array(zod.number())
|
|
.optional()
|
|
.describe(
|
|
"Optional set of permission IDs to gate the new app on. Inserted with onConflictDoNothing so duplicates are tolerated.",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Get app by ID
|
|
*/
|
|
export const GetAppParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const GetAppResponse = zod.object({
|
|
id: zod.number(),
|
|
slug: zod.string(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
iconName: zod.string(),
|
|
route: zod.string(),
|
|
color: zod.string(),
|
|
isActive: zod.boolean(),
|
|
isSystem: zod.boolean(),
|
|
sortOrder: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
groupCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
|
|
),
|
|
restrictionCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
|
|
),
|
|
openCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Update app (admin)
|
|
*/
|
|
export const UpdateAppParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const UpdateAppBody = zod.object({
|
|
nameAr: zod.string().optional(),
|
|
nameEn: zod.string().optional(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
iconName: zod.string().optional(),
|
|
route: zod.string().optional(),
|
|
color: zod.string().optional(),
|
|
isActive: zod.boolean().optional(),
|
|
sortOrder: zod.number().optional(),
|
|
});
|
|
|
|
export const UpdateAppResponse = zod.object({
|
|
id: zod.number(),
|
|
slug: zod.string(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
iconName: zod.string(),
|
|
route: zod.string(),
|
|
color: zod.string(),
|
|
isActive: zod.boolean(),
|
|
isSystem: zod.boolean(),
|
|
sortOrder: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
groupCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
|
|
),
|
|
restrictionCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
|
|
),
|
|
openCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Delete app (admin)
|
|
*/
|
|
export const DeleteAppParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const deleteAppQueryForceDefault = false;
|
|
|
|
export const DeleteAppQueryParams = zod.object({
|
|
force: zod.coerce
|
|
.boolean()
|
|
.default(deleteAppQueryForceDefault)
|
|
.describe(
|
|
"Force deletion of an app that has dependent records (records an audit log entry).",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* Returns the permissions currently required for users to see this app
|
|
in their launcher. An app with no rows here is unrestricted (visible
|
|
to anyone). When more than one permission is configured, holding any
|
|
one of them is sufficient.
|
|
|
|
* @summary List the permissions that gate this app (admin)
|
|
*/
|
|
export const ListAppPermissionsParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const ListAppPermissionsResponseItem = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
});
|
|
export const ListAppPermissionsResponse = zod.array(
|
|
ListAppPermissionsResponseItem,
|
|
);
|
|
|
|
/**
|
|
* Adds a row to `app_permissions` for the (app_id, permission_id) pair.
|
|
Uses `ON CONFLICT DO NOTHING` against the composite primary key so
|
|
re-adding an existing pair is a no-op.
|
|
|
|
* @summary Add a required permission to this app (admin)
|
|
*/
|
|
export const AddAppPermissionParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const AddAppPermissionBody = zod.object({
|
|
permissionId: zod
|
|
.number()
|
|
.describe("ID of the permission to require for this app."),
|
|
});
|
|
|
|
/**
|
|
* Given a candidate set of permission IDs that would gate this app,
|
|
returns how many users would lose visibility of the app (excluding
|
|
admins, who always see every app), the count of users who currently
|
|
see the app, and the groups that grant the app via `group_apps`. A
|
|
member of any returned group always sees the app regardless of
|
|
permission changes, so listing them lets the admin see which
|
|
populations are protected from the loss.
|
|
|
|
Mirrors `POST /roles/{id}/permissions/impact-preview` so the admin
|
|
UI can show the same kind of warning before committing the change.
|
|
|
|
* @summary Preview the impact of changing the permissions required for an app (admin)
|
|
*/
|
|
export const PreviewAppPermissionsImpactParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const PreviewAppPermissionsImpactBody = zod.object({
|
|
permissionIds: zod
|
|
.array(zod.number())
|
|
.describe(
|
|
'Candidate set of permission IDs that would gate this app. Pass an empty array to model \"remove every requirement\" (i.e. make the app unrestricted).',
|
|
),
|
|
});
|
|
|
|
export const PreviewAppPermissionsImpactResponse = zod.object({
|
|
affectedUserCount: zod
|
|
.number()
|
|
.describe(
|
|
"Distinct non-admin users who currently see the app and would lose visibility under the candidate permission set. Already accounts for users who keep access via `group_apps` group-grants.",
|
|
),
|
|
currentlyVisibleUserCount: zod
|
|
.number()
|
|
.describe(
|
|
'Distinct non-admin users who currently see the app under the existing permission set. Useful for showing the warning as a fraction (e.g. \"12 of 80 will lose access\").',
|
|
),
|
|
groups: zod
|
|
.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
}),
|
|
)
|
|
.describe(
|
|
"Groups that currently grant this app via `group_apps`. Members of any of these groups keep access regardless of permission changes.",
|
|
),
|
|
noChange: zod
|
|
.boolean()
|
|
.describe(
|
|
"True when the candidate set is identical to the current permission set so no change would actually be saved.",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Remove a required permission from this app (admin)
|
|
*/
|
|
export const RemoveAppPermissionParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
permissionId: zod.coerce.number(),
|
|
});
|
|
|
|
/**
|
|
* @summary Log an app open event for the current user
|
|
*/
|
|
export const LogAppOpenParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
/**
|
|
* @summary Set the current user's preferred home apps order
|
|
*/
|
|
export const UpdateMyAppOrderBody = zod.object({
|
|
order: zod
|
|
.array(zod.number())
|
|
.describe("App IDs in the desired display order"),
|
|
});
|
|
|
|
export const UpdateMyAppOrderResponseItem = zod.object({
|
|
id: zod.number(),
|
|
slug: zod.string(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
iconName: zod.string(),
|
|
route: zod.string(),
|
|
color: zod.string(),
|
|
isActive: zod.boolean(),
|
|
isSystem: zod.boolean(),
|
|
sortOrder: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
groupCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
|
|
),
|
|
restrictionCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
|
|
),
|
|
openCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
|
|
),
|
|
});
|
|
export const UpdateMyAppOrderResponse = zod.array(UpdateMyAppOrderResponseItem);
|
|
|
|
/**
|
|
* @summary Get application settings (public)
|
|
*/
|
|
export const GetAppSettingsResponse = zod.object({
|
|
siteNameAr: zod.string(),
|
|
siteNameEn: zod.string(),
|
|
registrationOpen: zod.boolean(),
|
|
footerTextAr: zod.string(),
|
|
footerTextEn: zod.string(),
|
|
updatedAt: zod.coerce.date().optional(),
|
|
});
|
|
|
|
/**
|
|
* @summary Update application settings (admin)
|
|
*/
|
|
export const updateAppSettingsBodySiteNameArMax = 200;
|
|
|
|
export const updateAppSettingsBodySiteNameEnMax = 200;
|
|
|
|
export const updateAppSettingsBodyFooterTextArMax = 300;
|
|
|
|
export const updateAppSettingsBodyFooterTextEnMax = 300;
|
|
|
|
export const UpdateAppSettingsBody = zod.object({
|
|
siteNameAr: zod
|
|
.string()
|
|
.min(1)
|
|
.max(updateAppSettingsBodySiteNameArMax)
|
|
.optional(),
|
|
siteNameEn: zod
|
|
.string()
|
|
.min(1)
|
|
.max(updateAppSettingsBodySiteNameEnMax)
|
|
.optional(),
|
|
registrationOpen: zod.boolean().optional(),
|
|
footerTextAr: zod
|
|
.string()
|
|
.min(1)
|
|
.max(updateAppSettingsBodyFooterTextArMax)
|
|
.optional(),
|
|
footerTextEn: zod
|
|
.string()
|
|
.min(1)
|
|
.max(updateAppSettingsBodyFooterTextEnMax)
|
|
.optional(),
|
|
});
|
|
|
|
export const UpdateAppSettingsResponse = zod.object({
|
|
siteNameAr: zod.string(),
|
|
siteNameEn: zod.string(),
|
|
registrationOpen: zod.boolean(),
|
|
footerTextAr: zod.string(),
|
|
footerTextEn: zod.string(),
|
|
updatedAt: zod.coerce.date().optional(),
|
|
});
|
|
|
|
/**
|
|
* @summary Request a presigned URL for file upload
|
|
*/
|
|
|
|
export const RequestUploadUrlBody = zod.object({
|
|
name: zod.string().min(1),
|
|
size: zod.number().min(1),
|
|
contentType: zod.string().min(1),
|
|
});
|
|
|
|
export const RequestUploadUrlResponse = zod.object({
|
|
uploadURL: zod.string().url(),
|
|
objectPath: zod.string(),
|
|
metadata: zod
|
|
.object({
|
|
name: zod.string().min(1),
|
|
size: zod.number().min(1),
|
|
contentType: zod.string().min(1),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
/**
|
|
* @summary List all services
|
|
*/
|
|
export const ListServicesResponseItem = zod.object({
|
|
id: zod.number(),
|
|
categoryId: zod.number().nullish(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
imageUrl: zod.string().nullish(),
|
|
price: zod.string().nullish(),
|
|
isAvailable: zod.boolean(),
|
|
sortOrder: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
orderCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of service_orders rows referencing this service. Populated\nonly by the list endpoint (GET \/services) so the admin delete\ndialog can warn before the first click. Omitted from single-service\nresponses.\n",
|
|
),
|
|
});
|
|
export const ListServicesResponse = zod.array(ListServicesResponseItem);
|
|
|
|
/**
|
|
* @summary Create a service (admin)
|
|
*/
|
|
export const CreateServiceBody = zod.object({
|
|
categoryId: zod.number().nullish(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
imageUrl: zod.string().nullish(),
|
|
price: zod.string().nullish(),
|
|
isAvailable: zod.boolean().optional(),
|
|
sortOrder: zod.number().optional(),
|
|
});
|
|
|
|
/**
|
|
* @summary Get service by ID
|
|
*/
|
|
export const GetServiceParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const GetServiceResponse = zod.object({
|
|
id: zod.number(),
|
|
categoryId: zod.number().nullish(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
imageUrl: zod.string().nullish(),
|
|
price: zod.string().nullish(),
|
|
isAvailable: zod.boolean(),
|
|
sortOrder: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
orderCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of service_orders rows referencing this service. Populated\nonly by the list endpoint (GET \/services) so the admin delete\ndialog can warn before the first click. Omitted from single-service\nresponses.\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Update service (admin)
|
|
*/
|
|
export const UpdateServiceParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const UpdateServiceBody = zod.object({
|
|
categoryId: zod.number().nullish(),
|
|
nameAr: zod.string().optional(),
|
|
nameEn: zod.string().optional(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
imageUrl: zod.string().nullish(),
|
|
price: zod.string().nullish(),
|
|
isAvailable: zod.boolean().optional(),
|
|
sortOrder: zod.number().optional(),
|
|
});
|
|
|
|
export const UpdateServiceResponse = zod.object({
|
|
id: zod.number(),
|
|
categoryId: zod.number().nullish(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
imageUrl: zod.string().nullish(),
|
|
price: zod.string().nullish(),
|
|
isAvailable: zod.boolean(),
|
|
sortOrder: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
orderCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of service_orders rows referencing this service. Populated\nonly by the list endpoint (GET \/services) so the admin delete\ndialog can warn before the first click. Omitted from single-service\nresponses.\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Delete service (admin)
|
|
*/
|
|
export const DeleteServiceParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const deleteServiceQueryForceDefault = false;
|
|
|
|
export const DeleteServiceQueryParams = zod.object({
|
|
force: zod.coerce
|
|
.boolean()
|
|
.default(deleteServiceQueryForceDefault)
|
|
.describe(
|
|
"Force deletion of a service that has existing orders (records an audit log entry).",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Place a service order
|
|
*/
|
|
export const createServiceOrderBodyNotesMax = 500;
|
|
|
|
export const CreateServiceOrderBody = zod.object({
|
|
serviceId: zod.number(),
|
|
notes: zod.string().max(createServiceOrderBodyNotesMax).nullish(),
|
|
});
|
|
|
|
/**
|
|
* @summary List orders placed by the current user
|
|
*/
|
|
export const ListMyServiceOrdersResponseItem = zod.object({
|
|
id: zod.number(),
|
|
serviceId: zod.number(),
|
|
userId: zod.number(),
|
|
assignedTo: zod.number().nullish(),
|
|
notes: zod.string().nullish(),
|
|
status: zod.enum([
|
|
"pending",
|
|
"received",
|
|
"preparing",
|
|
"completed",
|
|
"cancelled",
|
|
]),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
service: zod.object({
|
|
id: zod.number(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
imageUrl: zod.string().nullish(),
|
|
}),
|
|
requester: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
assignee: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
});
|
|
export const ListMyServiceOrdersResponse = zod.array(
|
|
ListMyServiceOrdersResponseItem,
|
|
);
|
|
|
|
/**
|
|
* @summary List active orders for receivers (requires orders.receive permission). Returns pending+unassigned orders plus orders assigned to me that are not completed/cancelled.
|
|
*/
|
|
export const ListIncomingServiceOrdersResponseItem = zod.object({
|
|
id: zod.number(),
|
|
serviceId: zod.number(),
|
|
userId: zod.number(),
|
|
assignedTo: zod.number().nullish(),
|
|
notes: zod.string().nullish(),
|
|
status: zod.enum([
|
|
"pending",
|
|
"received",
|
|
"preparing",
|
|
"completed",
|
|
"cancelled",
|
|
]),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
service: zod.object({
|
|
id: zod.number(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
imageUrl: zod.string().nullish(),
|
|
}),
|
|
requester: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
assignee: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
});
|
|
export const ListIncomingServiceOrdersResponse = zod.array(
|
|
ListIncomingServiceOrdersResponseItem,
|
|
);
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
export const UpdateServiceOrderStatusParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const UpdateServiceOrderStatusBody = zod.object({
|
|
status: zod.enum([
|
|
"pending",
|
|
"received",
|
|
"preparing",
|
|
"completed",
|
|
"cancelled",
|
|
]),
|
|
});
|
|
|
|
export const UpdateServiceOrderStatusResponse = zod.object({
|
|
id: zod.number(),
|
|
serviceId: zod.number(),
|
|
userId: zod.number(),
|
|
assignedTo: zod.number().nullish(),
|
|
notes: zod.string().nullish(),
|
|
status: zod.enum([
|
|
"pending",
|
|
"received",
|
|
"preparing",
|
|
"completed",
|
|
"cancelled",
|
|
]),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
service: zod.object({
|
|
id: zod.number(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
imageUrl: zod.string().nullish(),
|
|
}),
|
|
requester: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
assignee: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
});
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
export const ConfirmServiceOrderReceiptParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const ConfirmServiceOrderReceiptResponse = zod.object({
|
|
id: zod.number(),
|
|
serviceId: zod.number(),
|
|
userId: zod.number(),
|
|
assignedTo: zod.number().nullish(),
|
|
notes: zod.string().nullish(),
|
|
status: zod.enum([
|
|
"pending",
|
|
"received",
|
|
"preparing",
|
|
"completed",
|
|
"cancelled",
|
|
]),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
service: zod.object({
|
|
id: zod.number(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
imageUrl: zod.string().nullish(),
|
|
}),
|
|
requester: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
assignee: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
});
|
|
|
|
/**
|
|
* @summary List service categories
|
|
*/
|
|
export const ListServiceCategoriesResponseItem = zod.object({
|
|
id: zod.number(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
sortOrder: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
export const ListServiceCategoriesResponse = zod.array(
|
|
ListServiceCategoriesResponseItem,
|
|
);
|
|
|
|
/**
|
|
* @summary List notifications for current user
|
|
*/
|
|
export const ListNotificationsResponseItem = zod.object({
|
|
id: zod.number(),
|
|
userId: zod.number(),
|
|
titleAr: zod.string(),
|
|
titleEn: zod.string(),
|
|
bodyAr: zod.string().nullish(),
|
|
bodyEn: zod.string().nullish(),
|
|
type: zod.string(),
|
|
isRead: zod.boolean(),
|
|
relatedId: zod.number().nullish(),
|
|
relatedType: zod.string().nullish(),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
export const ListNotificationsResponse = zod.array(
|
|
ListNotificationsResponseItem,
|
|
);
|
|
|
|
/**
|
|
* @summary Mark notification as read
|
|
*/
|
|
export const MarkNotificationReadParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const MarkNotificationReadResponse = zod.object({
|
|
id: zod.number(),
|
|
userId: zod.number(),
|
|
titleAr: zod.string(),
|
|
titleEn: zod.string(),
|
|
bodyAr: zod.string().nullish(),
|
|
bodyEn: zod.string().nullish(),
|
|
type: zod.string(),
|
|
isRead: zod.boolean(),
|
|
relatedId: zod.number().nullish(),
|
|
relatedType: zod.string().nullish(),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Mark all notifications as read
|
|
*/
|
|
export const MarkAllNotificationsReadResponse = zod.object({
|
|
success: zod.boolean(),
|
|
});
|
|
|
|
/**
|
|
* @summary List all users (admin)
|
|
*/
|
|
export const ListUsersQueryParams = zod.object({
|
|
q: zod.coerce
|
|
.string()
|
|
.optional()
|
|
.describe("Free-text search across username, email, and display names"),
|
|
groupId: zod.coerce
|
|
.number()
|
|
.optional()
|
|
.describe("Filter to users that belong to the given group"),
|
|
status: zod
|
|
.enum(["active", "disabled"])
|
|
.optional()
|
|
.describe("Filter by active\/disabled state"),
|
|
});
|
|
|
|
export const ListUsersResponseItem = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
noteCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
|
|
),
|
|
orderCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
|
),
|
|
});
|
|
export const ListUsersResponse = zod.array(ListUsersResponseItem);
|
|
|
|
/**
|
|
* @summary Create a user (admin)
|
|
*/
|
|
export const createUserBodyPreferredLanguageDefault = `ar`;
|
|
|
|
export const CreateUserBody = zod.object({
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
password: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod
|
|
.string()
|
|
.default(createUserBodyPreferredLanguageDefault),
|
|
groupIds: zod
|
|
.array(zod.number())
|
|
.optional()
|
|
.describe(
|
|
"Initial group memberships in addition to the auto-assigned Everyone group.",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Get user by ID (admin)
|
|
*/
|
|
export const GetUserParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const GetUserResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
noteCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
|
|
),
|
|
orderCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Update user (admin)
|
|
*/
|
|
export const UpdateUserParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const UpdateUserBody = zod.object({
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
isActive: zod.boolean().optional(),
|
|
preferredLanguage: zod.string().optional(),
|
|
groupIds: zod
|
|
.array(zod.number())
|
|
.optional()
|
|
.describe("If provided, replaces the user's group memberships"),
|
|
});
|
|
|
|
export const UpdateUserResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
noteCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
|
|
),
|
|
orderCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Delete user (admin)
|
|
*/
|
|
export const DeleteUserParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const deleteUserQueryForceDefault = false;
|
|
|
|
export const DeleteUserQueryParams = zod.object({
|
|
force: zod.coerce
|
|
.boolean()
|
|
.default(deleteUserQueryForceDefault)
|
|
.describe(
|
|
"Force deletion of a user that owns dependent records (records an audit log entry).",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* Deletes several orders in one round-trip. Authorization is still
|
|
enforced per id using the same rules as DELETE /orders/{id}, so the
|
|
client cannot bypass the permission check by batching. Returns a
|
|
summary of which ids were deleted and which were rejected.
|
|
|
|
* @summary Delete multiple service orders in one request
|
|
*/
|
|
export const bulkDeleteServiceOrdersBodyIdsMax = 200;
|
|
|
|
export const BulkDeleteServiceOrdersBody = zod.object({
|
|
ids: zod.array(zod.number()).min(1).max(bulkDeleteServiceOrdersBodyIdsMax),
|
|
});
|
|
|
|
export const BulkDeleteServiceOrdersResponse = zod.object({
|
|
deletedIds: zod
|
|
.array(zod.number())
|
|
.describe("Ids that were successfully deleted."),
|
|
failedIds: zod
|
|
.array(zod.number())
|
|
.describe(
|
|
"Ids that were skipped (not found, or caller is not authorized).",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* Permanently deletes the order. Allowed when:
|
|
- the caller is an admin (any order, any status); or
|
|
- the caller owns the order AND it is in a terminal status
|
|
(completed/cancelled); or
|
|
- the caller holds the `orders.receive` permission AND the order
|
|
is currently visible in their own incoming view — i.e. an
|
|
unclaimed pending order, or one they have themselves claimed
|
|
and is still active (received/preparing). Receivers cannot
|
|
delete another receiver's claimed order, nor terminal orders.
|
|
|
|
* @summary Delete a service order
|
|
*/
|
|
export const DeleteServiceOrderParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
/**
|
|
* @summary Idempotently add a role to a user (admin)
|
|
*/
|
|
export const AddUserRoleParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const AddUserRoleBody = zod.object({
|
|
roleName: zod.string().min(1),
|
|
});
|
|
|
|
export const AddUserRoleResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
noteCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
|
|
),
|
|
orderCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Idempotently remove a role from a user (admin)
|
|
*/
|
|
export const RemoveUserRoleParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
roleName: zod.coerce.string(),
|
|
});
|
|
|
|
export const RemoveUserRoleResponse = zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
email: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
preferredLanguage: zod.string(),
|
|
clockStyle: zod
|
|
.union([
|
|
zod
|
|
.enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
|
|
.describe("Per-user home-screen clock style preference"),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
clockHour12: zod.boolean().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean(),
|
|
roles: zod.array(zod.string()),
|
|
groups: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
}),
|
|
),
|
|
createdAt: zod.coerce.date(),
|
|
noteCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
|
|
),
|
|
orderCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary List all available permissions (admin)
|
|
*/
|
|
export const ListPermissionsResponseItem = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
});
|
|
export const ListPermissionsResponse = zod.array(ListPermissionsResponseItem);
|
|
|
|
/**
|
|
* @summary List roles (admin)
|
|
*/
|
|
export const ListRolesResponseItem = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
isSystem: zod.boolean(),
|
|
createdAt: zod.coerce.date(),
|
|
userCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of users assigned this role. Populated only by the admin\nlist endpoint (GET \/admin\/roles) so the admin list can show the\nsame dependency counts inline as the Apps\/Services\/Users\/Groups\npanels. Omitted from create\/update responses.\n",
|
|
),
|
|
groupCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of groups granting this role. Populated only by the admin\nlist endpoint (GET \/admin\/roles).\n",
|
|
),
|
|
});
|
|
export const ListRolesResponse = zod.array(ListRolesResponseItem);
|
|
|
|
/**
|
|
* @summary Create role (admin)
|
|
*/
|
|
|
|
export const CreateRoleBody = zod.object({
|
|
name: zod.string().min(1),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
});
|
|
|
|
/**
|
|
* @summary Update role name and descriptions (admin)
|
|
*/
|
|
export const UpdateRoleParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const UpdateRoleBody = zod.object({
|
|
name: zod.string().min(1).optional(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
});
|
|
|
|
export const UpdateRoleResponse = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
isSystem: zod.boolean(),
|
|
createdAt: zod.coerce.date(),
|
|
userCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of users assigned this role. Populated only by the admin\nlist endpoint (GET \/admin\/roles) so the admin list can show the\nsame dependency counts inline as the Apps\/Services\/Users\/Groups\npanels. Omitted from create\/update responses.\n",
|
|
),
|
|
groupCount: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Number of groups granting this role. Populated only by the admin\nlist endpoint (GET \/admin\/roles).\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Delete role (admin)
|
|
*/
|
|
export const DeleteRoleParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
/**
|
|
* @summary Count users and groups currently assigned to a role (admin)
|
|
*/
|
|
export const GetRoleUsageParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const GetRoleUsageResponse = zod.object({
|
|
userCount: zod.number(),
|
|
groupCount: zod.number(),
|
|
});
|
|
|
|
/**
|
|
* Returns permission-change audit records for the given role, newest
|
|
first. Supports offset-based pagination via `limit`/`offset` and
|
|
optional filters by acting user (`actorUserId`) and a date range
|
|
(`from`/`to`, inclusive UTC days). The response includes the total
|
|
matching count and the next offset to load (or `null` when the end
|
|
has been reached). Each entry captures the actor (if known), the
|
|
previous permission IDs, the new permission IDs, and the timestamp.
|
|
|
|
* @summary List recent permission-change audit entries for a role (admin)
|
|
*/
|
|
export const GetRolePermissionAuditParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getRolePermissionAuditQueryLimitDefault = 10;
|
|
export const getRolePermissionAuditQueryLimitMax = 200;
|
|
|
|
export const getRolePermissionAuditQueryOffsetDefault = 0;
|
|
export const getRolePermissionAuditQueryOffsetMin = 0;
|
|
|
|
export const getRolePermissionAuditQueryActorUserIdMin = 0;
|
|
|
|
export const GetRolePermissionAuditQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getRolePermissionAuditQueryLimitMax)
|
|
.default(getRolePermissionAuditQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getRolePermissionAuditQueryOffsetMin)
|
|
.default(getRolePermissionAuditQueryOffsetDefault),
|
|
actorUserId: zod.coerce
|
|
.number()
|
|
.min(getRolePermissionAuditQueryActorUserIdMin)
|
|
.optional()
|
|
.describe(
|
|
"Restrict results to entries authored by the given user. Use\n`0` or omit to return entries from any actor.\n",
|
|
),
|
|
from: zod
|
|
.date()
|
|
.optional()
|
|
.describe("Start date (inclusive, YYYY-MM-DD UTC)."),
|
|
to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
|
|
});
|
|
|
|
export const GetRolePermissionAuditResponse = zod.object({
|
|
entries: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
roleId: zod.number(),
|
|
previousPermissionIds: zod.array(zod.number()),
|
|
newPermissionIds: zod.array(zod.number()),
|
|
addedPermissionIds: zod.array(zod.number()),
|
|
removedPermissionIds: zod.array(zod.number()),
|
|
createdAt: zod.coerce.date(),
|
|
actor: zod.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
]),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* Returns the same audit rows as `GET /roles/{id}/audit` but as a
|
|
downloadable CSV file with permission IDs resolved to names.
|
|
Honors the optional `actorUserId`, `from`, and `to` filters
|
|
identically. Pagination parameters are NOT accepted: the file
|
|
contains all matching rows up to a server-side cap (currently
|
|
10,000 — narrow the date range for a longer history).
|
|
|
|
* @summary Download permission-change audit entries for a role as CSV (admin)
|
|
*/
|
|
export const ExportRolePermissionAuditCsvParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const exportRolePermissionAuditCsvQueryActorUserIdMin = 0;
|
|
|
|
export const ExportRolePermissionAuditCsvQueryParams = zod.object({
|
|
actorUserId: zod.coerce
|
|
.number()
|
|
.min(exportRolePermissionAuditCsvQueryActorUserIdMin)
|
|
.optional()
|
|
.describe(
|
|
"Restrict results to entries authored by the given user. Use\n`0` or omit to return entries from any actor.\n",
|
|
),
|
|
from: zod
|
|
.date()
|
|
.optional()
|
|
.describe("Start date (inclusive, YYYY-MM-DD UTC)."),
|
|
to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
|
|
});
|
|
|
|
/**
|
|
* Given a candidate set of permission IDs for the role, returns the list of
|
|
permissions that would be removed and, for each one, how many users would
|
|
lose the permission (because they hold this role directly or via a group
|
|
and have no other role granting the permission), plus the groups that
|
|
currently grant this role.
|
|
|
|
* @summary Preview the impact of changing a role's permission set (admin)
|
|
*/
|
|
export const PreviewRolePermissionsImpactParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const PreviewRolePermissionsImpactBody = zod.object({
|
|
permissionIds: zod
|
|
.array(zod.number())
|
|
.describe(
|
|
"Candidate set of permission IDs to evaluate against the role's current assignments.",
|
|
),
|
|
});
|
|
|
|
export const PreviewRolePermissionsImpactResponse = zod.object({
|
|
removed: zod
|
|
.array(
|
|
zod.object({
|
|
permissionId: zod.number(),
|
|
permissionName: zod.string(),
|
|
userCount: zod
|
|
.number()
|
|
.describe(
|
|
"Distinct users who currently have this permission via this role and would lose it (no other role they hold grants the permission).",
|
|
),
|
|
groupCount: zod
|
|
.number()
|
|
.describe("Number of groups that currently grant this role."),
|
|
groups: zod
|
|
.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
}),
|
|
)
|
|
.describe(
|
|
"Groups that currently grant this role and therefore propagate the removal to their members.",
|
|
),
|
|
}),
|
|
)
|
|
.describe(
|
|
"One entry per permission that is currently assigned to the role but not in the candidate set.",
|
|
),
|
|
totalAffectedUsers: zod
|
|
.number()
|
|
.describe(
|
|
"Distinct users that would lose at least one permission across all removals.",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary List permissions assigned to a role (admin)
|
|
*/
|
|
export const GetRolePermissionsParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const GetRolePermissionsResponseItem = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
});
|
|
export const GetRolePermissionsResponse = zod.array(
|
|
GetRolePermissionsResponseItem,
|
|
);
|
|
|
|
/**
|
|
* @summary Atomically replace the permissions assigned to a role (admin)
|
|
*/
|
|
export const ReplaceRolePermissionsParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const ReplaceRolePermissionsBody = zod.object({
|
|
permissionIds: zod
|
|
.array(zod.number())
|
|
.describe(
|
|
"Full set of permission IDs the role should grant. Existing assignments not present here are removed.",
|
|
),
|
|
});
|
|
|
|
export const ReplaceRolePermissionsResponseItem = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
});
|
|
export const ReplaceRolePermissionsResponse = zod.array(
|
|
ReplaceRolePermissionsResponseItem,
|
|
);
|
|
|
|
/**
|
|
* @summary List groups (admin)
|
|
*/
|
|
export const ListGroupsQueryParams = zod.object({
|
|
q: zod.coerce.string().optional(),
|
|
});
|
|
|
|
export const ListGroupsResponseItem = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
isSystem: zod.boolean(),
|
|
memberCount: zod.number(),
|
|
appCount: zod.number(),
|
|
roleCount: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
export const ListGroupsResponse = zod.array(ListGroupsResponseItem);
|
|
|
|
/**
|
|
* @summary Create group (admin)
|
|
*/
|
|
|
|
export const CreateGroupBody = zod.object({
|
|
name: zod.string().min(1),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
appIds: zod.array(zod.number()).optional(),
|
|
roleIds: zod.array(zod.number()).optional(),
|
|
userIds: zod.array(zod.number()).optional(),
|
|
});
|
|
|
|
/**
|
|
* @summary Get group with members and apps (admin)
|
|
*/
|
|
export const GetGroupParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const GetGroupResponse = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
isSystem: zod.boolean(),
|
|
appIds: zod.array(zod.number()),
|
|
roleIds: zod.array(zod.number()),
|
|
userIds: zod.array(zod.number()),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Update group (admin)
|
|
*/
|
|
export const UpdateGroupParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const UpdateGroupBody = zod.object({
|
|
name: zod.string().min(1).optional(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
appIds: zod.array(zod.number()).optional(),
|
|
roleIds: zod.array(zod.number()).optional(),
|
|
userIds: zod.array(zod.number()).optional(),
|
|
});
|
|
|
|
export const UpdateGroupResponse = zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullish(),
|
|
descriptionEn: zod.string().nullish(),
|
|
isSystem: zod.boolean(),
|
|
appIds: zod.array(zod.number()),
|
|
roleIds: zod.array(zod.number()),
|
|
userIds: zod.array(zod.number()),
|
|
createdAt: zod.coerce.date(),
|
|
});
|
|
|
|
/**
|
|
* @summary Delete group (admin)
|
|
*/
|
|
export const DeleteGroupParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const deleteGroupQueryForceDefault = false;
|
|
|
|
export const DeleteGroupQueryParams = zod.object({
|
|
force: zod.coerce
|
|
.boolean()
|
|
.default(deleteGroupQueryForceDefault)
|
|
.describe(
|
|
"Force deletion of a non-empty group (records an audit log entry).",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* Returns permission-change audit records for the given user, newest
|
|
first. Mirrors the role audit endpoint shape and supports the same
|
|
`limit`/`offset`/`actorUserId`/`from`/`to` filters. Each entry
|
|
captures the actor (if known), the previous and new id sets, and
|
|
the change kind (`user.roles` or `user.groups`).
|
|
|
|
* @summary List recent permission-change audit entries for a user (admin)
|
|
*/
|
|
export const GetUserPermissionAuditParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getUserPermissionAuditQueryLimitDefault = 10;
|
|
export const getUserPermissionAuditQueryLimitMax = 200;
|
|
|
|
export const getUserPermissionAuditQueryOffsetDefault = 0;
|
|
export const getUserPermissionAuditQueryOffsetMin = 0;
|
|
|
|
export const getUserPermissionAuditQueryActorUserIdMin = 0;
|
|
|
|
export const GetUserPermissionAuditQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getUserPermissionAuditQueryLimitMax)
|
|
.default(getUserPermissionAuditQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getUserPermissionAuditQueryOffsetMin)
|
|
.default(getUserPermissionAuditQueryOffsetDefault),
|
|
actorUserId: zod.coerce
|
|
.number()
|
|
.min(getUserPermissionAuditQueryActorUserIdMin)
|
|
.optional(),
|
|
from: zod.date().optional(),
|
|
to: zod.date().optional(),
|
|
});
|
|
|
|
export const GetUserPermissionAuditResponse = zod.object({
|
|
entries: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
targetKind: zod.enum(["user", "group", "app"]),
|
|
targetId: zod.number(),
|
|
changeKind: zod.enum([
|
|
"user.roles",
|
|
"user.groups",
|
|
"group.users",
|
|
"group.roles",
|
|
"group.apps",
|
|
"app.permissions",
|
|
]),
|
|
previousIds: zod.array(zod.number()),
|
|
newIds: zod.array(zod.number()),
|
|
addedIds: zod.array(zod.number()),
|
|
removedIds: zod.array(zod.number()),
|
|
createdAt: zod.coerce.date(),
|
|
actor: zod.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
]),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* Returns permission-change audit records for the given group, newest
|
|
first. Captures changes to the group's user, role and app sets via
|
|
the discriminator field `changeKind`.
|
|
|
|
* @summary List recent permission-change audit entries for a group (admin)
|
|
*/
|
|
export const GetGroupPermissionAuditParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getGroupPermissionAuditQueryLimitDefault = 10;
|
|
export const getGroupPermissionAuditQueryLimitMax = 200;
|
|
|
|
export const getGroupPermissionAuditQueryOffsetDefault = 0;
|
|
export const getGroupPermissionAuditQueryOffsetMin = 0;
|
|
|
|
export const getGroupPermissionAuditQueryActorUserIdMin = 0;
|
|
|
|
export const GetGroupPermissionAuditQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getGroupPermissionAuditQueryLimitMax)
|
|
.default(getGroupPermissionAuditQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getGroupPermissionAuditQueryOffsetMin)
|
|
.default(getGroupPermissionAuditQueryOffsetDefault),
|
|
actorUserId: zod.coerce
|
|
.number()
|
|
.min(getGroupPermissionAuditQueryActorUserIdMin)
|
|
.optional(),
|
|
from: zod.date().optional(),
|
|
to: zod.date().optional(),
|
|
});
|
|
|
|
export const GetGroupPermissionAuditResponse = zod.object({
|
|
entries: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
targetKind: zod.enum(["user", "group", "app"]),
|
|
targetId: zod.number(),
|
|
changeKind: zod.enum([
|
|
"user.roles",
|
|
"user.groups",
|
|
"group.users",
|
|
"group.roles",
|
|
"group.apps",
|
|
"app.permissions",
|
|
]),
|
|
previousIds: zod.array(zod.number()),
|
|
newIds: zod.array(zod.number()),
|
|
addedIds: zod.array(zod.number()),
|
|
removedIds: zod.array(zod.number()),
|
|
createdAt: zod.coerce.date(),
|
|
actor: zod.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
]),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* Returns permission-change audit records for the given app, newest
|
|
first. Captures changes to the set of permissions required to open
|
|
the app (`changeKind: app.permissions`).
|
|
|
|
* @summary List recent permission-change audit entries for an app (admin)
|
|
*/
|
|
export const GetAppPermissionAuditParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAppPermissionAuditQueryLimitDefault = 10;
|
|
export const getAppPermissionAuditQueryLimitMax = 200;
|
|
|
|
export const getAppPermissionAuditQueryOffsetDefault = 0;
|
|
export const getAppPermissionAuditQueryOffsetMin = 0;
|
|
|
|
export const getAppPermissionAuditQueryActorUserIdMin = 0;
|
|
|
|
export const GetAppPermissionAuditQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAppPermissionAuditQueryLimitMax)
|
|
.default(getAppPermissionAuditQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAppPermissionAuditQueryOffsetMin)
|
|
.default(getAppPermissionAuditQueryOffsetDefault),
|
|
actorUserId: zod.coerce
|
|
.number()
|
|
.min(getAppPermissionAuditQueryActorUserIdMin)
|
|
.optional(),
|
|
from: zod.date().optional(),
|
|
to: zod.date().optional(),
|
|
});
|
|
|
|
export const GetAppPermissionAuditResponse = zod.object({
|
|
entries: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
targetKind: zod.enum(["user", "group", "app"]),
|
|
targetId: zod.number(),
|
|
changeKind: zod.enum([
|
|
"user.roles",
|
|
"user.groups",
|
|
"group.users",
|
|
"group.roles",
|
|
"group.apps",
|
|
"app.permissions",
|
|
]),
|
|
previousIds: zod.array(zod.number()),
|
|
newIds: zod.array(zod.number()),
|
|
addedIds: zod.array(zod.number()),
|
|
removedIds: zod.array(zod.number()),
|
|
createdAt: zod.coerce.date(),
|
|
actor: zod.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
]),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* @summary Get home screen stats
|
|
*/
|
|
export const GetHomeStatsResponse = zod.object({
|
|
totalApps: zod.number(),
|
|
totalServices: zod.number(),
|
|
unreadNotifications: zod.number(),
|
|
totalUsers: zod.number(),
|
|
});
|
|
|
|
/**
|
|
* @summary Get admin dashboard trend stats
|
|
*/
|
|
export const getAdminStatsQueryRangeDefault = `7d`;
|
|
|
|
export const GetAdminStatsQueryParams = zod.object({
|
|
range: zod
|
|
.enum(["7d", "30d", "90d", "custom"])
|
|
.default(getAdminStatsQueryRangeDefault)
|
|
.describe('Time range for trend stats. Use \"custom\" with from\/to.'),
|
|
from: zod
|
|
.date()
|
|
.optional()
|
|
.describe("Start date (inclusive, YYYY-MM-DD UTC) when range=custom"),
|
|
to: zod
|
|
.date()
|
|
.optional()
|
|
.describe("End date (inclusive, YYYY-MM-DD UTC) when range=custom"),
|
|
});
|
|
|
|
export const GetAdminStatsResponse = zod.object({
|
|
range: zod.enum(["7d", "30d", "90d", "custom"]),
|
|
rangeDays: zod.number(),
|
|
rangeFrom: zod
|
|
.string()
|
|
.optional()
|
|
.describe("Start date (YYYY-MM-DD UTC) of the selected window"),
|
|
rangeTo: zod
|
|
.string()
|
|
.optional()
|
|
.describe("End date (YYYY-MM-DD UTC) of the selected window"),
|
|
newUsersInRange: zod.number(),
|
|
newUsersPrevRange: zod.number(),
|
|
activeServices: zod.number(),
|
|
inactiveServices: zod.number(),
|
|
signupsByDay: zod.array(
|
|
zod.object({
|
|
date: zod.string(),
|
|
count: zod.number(),
|
|
}),
|
|
),
|
|
appOpensByDay: zod.array(
|
|
zod.object({
|
|
date: zod.string(),
|
|
count: zod.number(),
|
|
}),
|
|
),
|
|
appOpensInRange: zod.number(),
|
|
appOpensPrevRange: zod.number(),
|
|
servicesCreatedByDay: zod.array(
|
|
zod.object({
|
|
date: zod.string(),
|
|
count: zod.number(),
|
|
}),
|
|
),
|
|
servicesCreatedInRange: zod.number(),
|
|
topApps: zod.array(
|
|
zod.object({
|
|
appId: zod.number(),
|
|
slug: zod.string(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
iconName: zod.string(),
|
|
color: zod.string(),
|
|
count: zod.number(),
|
|
}),
|
|
),
|
|
mostActiveUsers: zod.array(
|
|
zod.object({
|
|
userId: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
count: zod.number(),
|
|
}),
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Recent opens of a single app within the selected range
|
|
*/
|
|
export const GetAdminAppOpensByAppParams = zod.object({
|
|
appId: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAdminAppOpensByAppQueryRangeDefault = `7d`;
|
|
export const getAdminAppOpensByAppQueryLimitDefault = 100;
|
|
export const getAdminAppOpensByAppQueryLimitMax = 200;
|
|
|
|
export const getAdminAppOpensByAppQueryOffsetDefault = 0;
|
|
export const getAdminAppOpensByAppQueryOffsetMin = 0;
|
|
|
|
export const GetAdminAppOpensByAppQueryParams = zod.object({
|
|
range: zod
|
|
.enum(["7d", "30d", "90d", "custom"])
|
|
.default(getAdminAppOpensByAppQueryRangeDefault),
|
|
from: zod.date().optional(),
|
|
to: zod.date().optional(),
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAdminAppOpensByAppQueryLimitMax)
|
|
.default(getAdminAppOpensByAppQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAdminAppOpensByAppQueryOffsetMin)
|
|
.default(getAdminAppOpensByAppQueryOffsetDefault),
|
|
});
|
|
|
|
export const GetAdminAppOpensByAppResponse = zod.object({
|
|
appId: zod.number(),
|
|
slug: zod.string(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
iconName: zod.string(),
|
|
color: zod.string(),
|
|
range: zod.enum(["7d", "30d", "90d", "custom"]),
|
|
rangeDays: zod.number(),
|
|
rangeFrom: zod.string(),
|
|
rangeTo: zod.string(),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
opens: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
userId: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Recent app opens by a single user within the selected range
|
|
*/
|
|
export const GetAdminAppOpensByUserParams = zod.object({
|
|
userId: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAdminAppOpensByUserQueryRangeDefault = `7d`;
|
|
export const getAdminAppOpensByUserQueryLimitDefault = 100;
|
|
export const getAdminAppOpensByUserQueryLimitMax = 200;
|
|
|
|
export const getAdminAppOpensByUserQueryOffsetDefault = 0;
|
|
export const getAdminAppOpensByUserQueryOffsetMin = 0;
|
|
|
|
export const GetAdminAppOpensByUserQueryParams = zod.object({
|
|
range: zod
|
|
.enum(["7d", "30d", "90d", "custom"])
|
|
.default(getAdminAppOpensByUserQueryRangeDefault),
|
|
from: zod.date().optional(),
|
|
to: zod.date().optional(),
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAdminAppOpensByUserQueryLimitMax)
|
|
.default(getAdminAppOpensByUserQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAdminAppOpensByUserQueryOffsetMin)
|
|
.default(getAdminAppOpensByUserQueryOffsetDefault),
|
|
});
|
|
|
|
export const GetAdminAppOpensByUserResponse = zod.object({
|
|
userId: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
range: zod.enum(["7d", "30d", "90d", "custom"]),
|
|
rangeDays: zod.number(),
|
|
rangeFrom: zod.string(),
|
|
rangeTo: zod.string(),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
opens: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
appId: zod.number(),
|
|
slug: zod.string(),
|
|
nameAr: zod.string(),
|
|
nameEn: zod.string(),
|
|
iconName: zod.string(),
|
|
color: zod.string(),
|
|
}),
|
|
),
|
|
});
|
|
|
|
/**
|
|
* Returns recent entries from the audit log, newest first. Admin only.
|
|
Filter by action (exact match) and/or a date range (inclusive).
|
|
|
|
* @summary List audit log entries (admin)
|
|
*/
|
|
export const listAuditLogsQueryForcedOnlyDefault = false;
|
|
|
|
export const listAuditLogsQueryLimitDefault = 50;
|
|
export const listAuditLogsQueryLimitMax = 200;
|
|
|
|
export const listAuditLogsQueryOffsetDefault = 0;
|
|
export const listAuditLogsQueryOffsetMin = 0;
|
|
|
|
export const ListAuditLogsQueryParams = zod.object({
|
|
action: zod.coerce
|
|
.string()
|
|
.optional()
|
|
.describe('Exact action name to filter by (e.g. \"group.force_delete\").'),
|
|
forcedOnly: zod.coerce
|
|
.boolean()
|
|
.default(listAuditLogsQueryForcedOnlyDefault)
|
|
.describe(
|
|
"When true, restrict results to forced deletions only — i.e. rows\nwith action `group.force_delete`, `user.force_delete`,\n`app.force_delete`, `service.force_delete`, or any\n`group.delete`\/`user.delete`\/`app.delete` row whose metadata\ncontains `force: true`. Overrides the `action` filter when set.\n",
|
|
),
|
|
from: zod
|
|
.date()
|
|
.optional()
|
|
.describe("Start date (inclusive, YYYY-MM-DD UTC)."),
|
|
to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
|
|
targetType: zod.coerce
|
|
.string()
|
|
.optional()
|
|
.describe(
|
|
'Restrict results to entries whose `target_type` exactly matches\n(e.g. \"group\", \"app\", \"role\", \"user\", \"service\", \"settings\").\nCombines with `targetId` and the other filters.\n',
|
|
),
|
|
targetId: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.optional()
|
|
.describe(
|
|
"Restrict results to entries whose `target_id` exactly matches.\nTypically used together with `targetType` to focus on the history\nof a single entity.\n",
|
|
),
|
|
actorUserId: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.optional()
|
|
.describe(
|
|
"Restrict results to entries written by a specific acting user\n(matches `actor_user_id`). Combines with the other filters.\n",
|
|
),
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(listAuditLogsQueryLimitMax)
|
|
.default(listAuditLogsQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(listAuditLogsQueryOffsetMin)
|
|
.default(listAuditLogsQueryOffsetDefault),
|
|
});
|
|
|
|
export const ListAuditLogsResponse = zod.object({
|
|
entries: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
action: zod.string(),
|
|
targetType: zod.string(),
|
|
targetId: zod.number().nullable(),
|
|
metadata: zod.record(zod.string(), zod.unknown()).nullable(),
|
|
createdAt: zod.coerce.date(),
|
|
actor: zod.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullish(),
|
|
displayNameEn: zod.string().nullish(),
|
|
avatarUrl: zod.string().nullish(),
|
|
}),
|
|
zod.null(),
|
|
]),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
actions: zod
|
|
.array(zod.string())
|
|
.describe(
|
|
"All distinct action names present in the audit log (for building filter UI).",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* Streams the filtered audit log as a CSV download.
|
|
Honors the same `action`, `forcedOnly`, `from`, `to`, `targetType`,
|
|
`targetId`, and `actorUserId` filters as the list endpoint, so an
|
|
export always matches whatever the admin is currently viewing.
|
|
Columns: action, actor_username, target_type, target_id, created_at, metadata.
|
|
Capped at 50,000 rows per export to bound response size.
|
|
|
|
* @summary Export filtered audit log as CSV (admin)
|
|
*/
|
|
export const exportAuditLogsCsvQueryForcedOnlyDefault = false;
|
|
|
|
export const ExportAuditLogsCsvQueryParams = zod.object({
|
|
action: zod.coerce
|
|
.string()
|
|
.optional()
|
|
.describe('Exact action name to filter by (e.g. \"group.force_delete\").'),
|
|
forcedOnly: zod.coerce
|
|
.boolean()
|
|
.default(exportAuditLogsCsvQueryForcedOnlyDefault)
|
|
.describe(
|
|
"When true, restrict the export to forced deletions only.\nSee the list endpoint for the exact set of matching rows.\nOverrides the `action` filter when set.\n",
|
|
),
|
|
from: zod
|
|
.date()
|
|
.optional()
|
|
.describe("Start date (inclusive, YYYY-MM-DD UTC)."),
|
|
to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
|
|
targetType: zod.coerce
|
|
.string()
|
|
.optional()
|
|
.describe(
|
|
"Restrict the export to entries whose `target_type` exactly matches.\nCombines with `targetId` and the other filters.\n",
|
|
),
|
|
targetId: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.optional()
|
|
.describe(
|
|
"Restrict the export to entries whose `target_id` exactly matches.\n",
|
|
),
|
|
actorUserId: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.optional()
|
|
.describe(
|
|
"Restrict the export to entries written by a specific acting user.\n",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* Returns the groups behind the inline "<n> groups" badge on the
|
|
admin Apps row, paginated. Admin only.
|
|
|
|
* @summary List groups granting access to an app
|
|
*/
|
|
export const GetAdminAppDependentGroupsParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAdminAppDependentGroupsQueryLimitDefault = 50;
|
|
export const getAdminAppDependentGroupsQueryLimitMax = 200;
|
|
|
|
export const getAdminAppDependentGroupsQueryOffsetDefault = 0;
|
|
export const getAdminAppDependentGroupsQueryOffsetMin = 0;
|
|
|
|
export const GetAdminAppDependentGroupsQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAdminAppDependentGroupsQueryLimitMax)
|
|
.default(getAdminAppDependentGroupsQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAdminAppDependentGroupsQueryOffsetMin)
|
|
.default(getAdminAppDependentGroupsQueryOffsetDefault),
|
|
});
|
|
|
|
export const GetAdminAppDependentGroupsResponse = zod.object({
|
|
items: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullable(),
|
|
descriptionEn: zod.string().nullable(),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* Returns the legacy permissions gating an app, with the role names
|
|
currently holding each permission, paginated. Admin only.
|
|
|
|
* @summary List permission restrictions on an app
|
|
*/
|
|
export const GetAdminAppDependentRestrictionsParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAdminAppDependentRestrictionsQueryLimitDefault = 50;
|
|
export const getAdminAppDependentRestrictionsQueryLimitMax = 200;
|
|
|
|
export const getAdminAppDependentRestrictionsQueryOffsetDefault = 0;
|
|
export const getAdminAppDependentRestrictionsQueryOffsetMin = 0;
|
|
|
|
export const GetAdminAppDependentRestrictionsQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAdminAppDependentRestrictionsQueryLimitMax)
|
|
.default(getAdminAppDependentRestrictionsQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAdminAppDependentRestrictionsQueryOffsetMin)
|
|
.default(getAdminAppDependentRestrictionsQueryOffsetDefault),
|
|
});
|
|
|
|
export const GetAdminAppDependentRestrictionsResponse = zod.object({
|
|
items: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
name: zod.string(),
|
|
descriptionAr: zod.string().nullable(),
|
|
descriptionEn: zod.string().nullable(),
|
|
roles: zod
|
|
.array(zod.string())
|
|
.describe("Names of roles currently holding this permission."),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* Returns every recorded open for the app newest-first, paginated.
|
|
Unlike /stats/admin/app-opens/by-app/{appId}, this endpoint does
|
|
not apply a date range so the badge total and the list match.
|
|
|
|
* @summary List recent opens for an app (no time filter)
|
|
*/
|
|
export const GetAdminAppDependentOpensParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAdminAppDependentOpensQueryLimitDefault = 50;
|
|
export const getAdminAppDependentOpensQueryLimitMax = 200;
|
|
|
|
export const getAdminAppDependentOpensQueryOffsetDefault = 0;
|
|
export const getAdminAppDependentOpensQueryOffsetMin = 0;
|
|
|
|
export const GetAdminAppDependentOpensQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAdminAppDependentOpensQueryLimitMax)
|
|
.default(getAdminAppDependentOpensQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAdminAppDependentOpensQueryOffsetMin)
|
|
.default(getAdminAppDependentOpensQueryOffsetDefault),
|
|
});
|
|
|
|
export const GetAdminAppDependentOpensResponse = zod.object({
|
|
items: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
createdAt: zod.coerce.date(),
|
|
userId: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullable(),
|
|
displayNameEn: zod.string().nullable(),
|
|
avatarUrl: zod.string().nullable(),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* Returns the service orders behind the inline "<n> orders" badge
|
|
on the admin Services row, paginated, newest-first. Admin only.
|
|
|
|
* @summary List orders placed against a service
|
|
*/
|
|
export const GetAdminServiceDependentOrdersParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAdminServiceDependentOrdersQueryLimitDefault = 50;
|
|
export const getAdminServiceDependentOrdersQueryLimitMax = 200;
|
|
|
|
export const getAdminServiceDependentOrdersQueryOffsetDefault = 0;
|
|
export const getAdminServiceDependentOrdersQueryOffsetMin = 0;
|
|
|
|
export const GetAdminServiceDependentOrdersQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAdminServiceDependentOrdersQueryLimitMax)
|
|
.default(getAdminServiceDependentOrdersQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAdminServiceDependentOrdersQueryOffsetMin)
|
|
.default(getAdminServiceDependentOrdersQueryOffsetDefault),
|
|
});
|
|
|
|
export const GetAdminServiceDependentOrdersResponse = zod.object({
|
|
items: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
status: zod.string(),
|
|
notes: zod.string().nullable(),
|
|
createdAt: zod.coerce.date(),
|
|
userId: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullable(),
|
|
displayNameEn: zod.string().nullable(),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* @summary Alias of GET /notes — list the caller's own notes
|
|
*/
|
|
export const listMyNotesAliasQueryArchivedDefault = false;
|
|
|
|
export const ListMyNotesAliasQueryParams = zod.object({
|
|
archived: zod.coerce.boolean().default(listMyNotesAliasQueryArchivedDefault),
|
|
});
|
|
|
|
export const ListMyNotesAliasResponseItem = zod
|
|
.object({
|
|
id: zod.number(),
|
|
userId: zod.number(),
|
|
title: zod.string(),
|
|
content: zod.string(),
|
|
color: zod.string(),
|
|
isPinned: zod.boolean(),
|
|
isArchived: zod.boolean(),
|
|
folderId: zod
|
|
.number()
|
|
.nullable()
|
|
.describe("Owning folder id, or null for Unfiled."),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
labelIds: zod.array(zod.number()),
|
|
})
|
|
.describe("A note owned by the caller (My Notes \/ Archive view).");
|
|
export const ListMyNotesAliasResponse = zod.array(ListMyNotesAliasResponseItem);
|
|
|
|
/**
|
|
* @summary List notes the caller has sent
|
|
*/
|
|
export const ListSentNotesResponseItem = zod.object({
|
|
id: zod.number(),
|
|
userId: zod.number(),
|
|
title: zod.string(),
|
|
content: zod.string(),
|
|
color: zod.string(),
|
|
isPinned: zod.boolean().optional(),
|
|
isArchived: zod.boolean().optional(),
|
|
folderId: zod
|
|
.number()
|
|
.nullish()
|
|
.describe("Owning folder id, or null for Unfiled."),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
recipients: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
noteId: zod.number().optional(),
|
|
recipientUserId: zod.number(),
|
|
status: zod.enum(["unread", "read", "replied", "archived"]),
|
|
sentAt: zod.coerce.date(),
|
|
readAt: zod.coerce.date().nullable(),
|
|
archivedAt: zod.coerce.date().nullable(),
|
|
recipient: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullable(),
|
|
displayNameEn: zod.string().nullable(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean().optional(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
}),
|
|
),
|
|
replyCount: zod.number(),
|
|
});
|
|
export const ListSentNotesResponse = zod.array(ListSentNotesResponseItem);
|
|
|
|
/**
|
|
* @summary List notes sent to the caller
|
|
*/
|
|
export const listReceivedNotesQueryArchivedDefault = false;
|
|
|
|
export const ListReceivedNotesQueryParams = zod.object({
|
|
archived: zod.coerce.boolean().default(listReceivedNotesQueryArchivedDefault),
|
|
});
|
|
|
|
export const ListReceivedNotesResponseItem = zod.object({
|
|
id: zod.number(),
|
|
title: zod.string(),
|
|
content: zod.string(),
|
|
color: zod.string(),
|
|
folderId: zod
|
|
.number()
|
|
.nullish()
|
|
.describe("Owning folder id on the recipient's side, or null for Unfiled."),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
sender: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullable(),
|
|
displayNameEn: zod.string().nullable(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean().optional(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
recipientRowId: zod.number(),
|
|
status: zod.enum(["unread", "read", "replied", "archived"]),
|
|
sentAt: zod.coerce.date(),
|
|
readAt: zod.coerce.date().nullable(),
|
|
archivedAt: zod.coerce.date().nullable(),
|
|
});
|
|
export const ListReceivedNotesResponse = zod.array(
|
|
ListReceivedNotesResponseItem,
|
|
);
|
|
|
|
/**
|
|
* @summary Get a note's full thread (note + recipients + replies)
|
|
*/
|
|
export const GetNoteDetailParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const GetNoteDetailResponse = zod.object({
|
|
id: zod.number(),
|
|
title: zod.string(),
|
|
content: zod.string(),
|
|
color: zod.string(),
|
|
createdAt: zod.coerce.date().nullish(),
|
|
updatedAt: zod.coerce.date().nullish(),
|
|
senderUserId: zod.number(),
|
|
sender: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullable(),
|
|
displayNameEn: zod.string().nullable(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean().optional(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
isOwner: zod.boolean(),
|
|
isAdmin: zod.boolean(),
|
|
myStatus: zod.union([
|
|
zod.enum(["unread", "read", "replied", "archived"]),
|
|
zod.null(),
|
|
]),
|
|
recipients: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
noteId: zod.number().optional(),
|
|
recipientUserId: zod.number(),
|
|
status: zod.enum(["unread", "read", "replied", "archived"]),
|
|
sentAt: zod.coerce.date(),
|
|
readAt: zod.coerce.date().nullable(),
|
|
archivedAt: zod.coerce.date().nullable(),
|
|
recipient: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullable(),
|
|
displayNameEn: zod.string().nullable(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean().optional(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
}),
|
|
),
|
|
replies: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
noteId: zod.number(),
|
|
senderUserId: zod.number(),
|
|
recipientUserId: zod.number(),
|
|
content: zod.string(),
|
|
createdAt: zod.coerce.date(),
|
|
sender: zod
|
|
.union([
|
|
zod.object({
|
|
id: zod.number(),
|
|
username: zod.string(),
|
|
displayNameAr: zod.string().nullable(),
|
|
displayNameEn: zod.string().nullable(),
|
|
avatarUrl: zod.string().nullish(),
|
|
isActive: zod.boolean().optional(),
|
|
}),
|
|
zod.null(),
|
|
])
|
|
.optional(),
|
|
}),
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Owner sends an existing note to a list of recipients
|
|
*/
|
|
export const SendNoteParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const SendNoteBody = zod.object({
|
|
recipientUserIds: zod.array(zod.number()),
|
|
});
|
|
|
|
export const SendNoteResponse = zod.object({
|
|
success: zod.boolean(),
|
|
sent: zod.number(),
|
|
});
|
|
|
|
/**
|
|
* @summary Recipient marks their copy of a note read
|
|
*/
|
|
export const MarkNoteReadParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const MarkNoteReadResponse = zod.object({
|
|
success: zod.boolean(),
|
|
});
|
|
|
|
/**
|
|
* @summary Recipient archives or unarchives their copy of a note
|
|
*/
|
|
export const ArchiveReceivedNoteParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const ArchiveReceivedNoteBody = zod.object({
|
|
archived: zod.boolean(),
|
|
});
|
|
|
|
export const ArchiveReceivedNoteResponse = zod.object({
|
|
success: zod.boolean(),
|
|
});
|
|
|
|
/**
|
|
* @summary Sender or recipient adds a reply to a note's thread
|
|
*/
|
|
export const ReplyToNoteParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const ReplyToNoteBody = zod.object({
|
|
content: zod.string(),
|
|
recipientUserId: zod
|
|
.number()
|
|
.optional()
|
|
.describe(
|
|
"Required when the note owner replies and there is more than one recipient.",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary List the caller's note folders with per-tab note counts
|
|
*/
|
|
export const listNoteFoldersQueryArchivedDefault = false;
|
|
|
|
export const ListNoteFoldersQueryParams = zod.object({
|
|
archived: zod.coerce
|
|
.boolean()
|
|
.default(listNoteFoldersQueryArchivedDefault)
|
|
.describe("Tab scope for noteCount (false = My Notes, true = Archive)"),
|
|
});
|
|
|
|
export const ListNoteFoldersResponseItem = zod.object({
|
|
id: zod.number(),
|
|
userId: zod.number(),
|
|
name: zod.string(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
noteCount: zod
|
|
.number()
|
|
.describe(
|
|
"Number of notes in this folder, scoped to the requested tab (active vs archived).",
|
|
),
|
|
});
|
|
export const ListNoteFoldersResponse = zod.array(ListNoteFoldersResponseItem);
|
|
|
|
/**
|
|
* @summary Create a new note folder owned by the caller
|
|
*/
|
|
export const createNoteFolderBodyNameMax = 80;
|
|
|
|
export const CreateNoteFolderBody = zod.object({
|
|
name: zod.string().min(1).max(createNoteFolderBodyNameMax),
|
|
});
|
|
|
|
/**
|
|
* @summary Rename a note folder
|
|
*/
|
|
export const UpdateNoteFolderParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const updateNoteFolderQueryArchivedDefault = false;
|
|
|
|
export const UpdateNoteFolderQueryParams = zod.object({
|
|
archived: zod.coerce
|
|
.boolean()
|
|
.default(updateNoteFolderQueryArchivedDefault)
|
|
.describe(
|
|
"Tab scope for the returned noteCount (false = My Notes, true = Archive)",
|
|
),
|
|
});
|
|
|
|
export const updateNoteFolderBodyNameMax = 80;
|
|
|
|
export const UpdateNoteFolderBody = zod.object({
|
|
name: zod.string().min(1).max(updateNoteFolderBodyNameMax),
|
|
});
|
|
|
|
export const UpdateNoteFolderResponse = zod.object({
|
|
id: zod.number(),
|
|
userId: zod.number(),
|
|
name: zod.string(),
|
|
createdAt: zod.coerce.date(),
|
|
updatedAt: zod.coerce.date(),
|
|
noteCount: zod
|
|
.number()
|
|
.describe(
|
|
"Number of notes in this folder, scoped to the requested tab (active vs archived).",
|
|
),
|
|
});
|
|
|
|
/**
|
|
* @summary Delete a note folder. Notes inside are preserved (folderId set to null).
|
|
*/
|
|
export const DeleteNoteFolderParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const DeleteNoteFolderResponse = zod.object({
|
|
success: zod.boolean(),
|
|
});
|
|
|
|
/**
|
|
* @summary List notes owned by a user
|
|
*/
|
|
export const GetAdminUserDependentNotesParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAdminUserDependentNotesQueryLimitDefault = 50;
|
|
export const getAdminUserDependentNotesQueryLimitMax = 200;
|
|
|
|
export const getAdminUserDependentNotesQueryOffsetDefault = 0;
|
|
export const getAdminUserDependentNotesQueryOffsetMin = 0;
|
|
|
|
export const GetAdminUserDependentNotesQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAdminUserDependentNotesQueryLimitMax)
|
|
.default(getAdminUserDependentNotesQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAdminUserDependentNotesQueryOffsetMin)
|
|
.default(getAdminUserDependentNotesQueryOffsetDefault),
|
|
});
|
|
|
|
export const GetAdminUserDependentNotesResponse = zod.object({
|
|
items: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
title: zod.string(),
|
|
isPinned: zod.boolean(),
|
|
isArchived: zod.boolean(),
|
|
updatedAt: zod.coerce.date(),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|
|
|
|
/**
|
|
* @summary List service orders placed by a user
|
|
*/
|
|
export const GetAdminUserDependentOrdersParams = zod.object({
|
|
id: zod.coerce.number(),
|
|
});
|
|
|
|
export const getAdminUserDependentOrdersQueryLimitDefault = 50;
|
|
export const getAdminUserDependentOrdersQueryLimitMax = 200;
|
|
|
|
export const getAdminUserDependentOrdersQueryOffsetDefault = 0;
|
|
export const getAdminUserDependentOrdersQueryOffsetMin = 0;
|
|
|
|
export const GetAdminUserDependentOrdersQueryParams = zod.object({
|
|
limit: zod.coerce
|
|
.number()
|
|
.min(1)
|
|
.max(getAdminUserDependentOrdersQueryLimitMax)
|
|
.default(getAdminUserDependentOrdersQueryLimitDefault),
|
|
offset: zod.coerce
|
|
.number()
|
|
.min(getAdminUserDependentOrdersQueryOffsetMin)
|
|
.default(getAdminUserDependentOrdersQueryOffsetDefault),
|
|
});
|
|
|
|
export const GetAdminUserDependentOrdersResponse = zod.object({
|
|
items: zod.array(
|
|
zod.object({
|
|
id: zod.number(),
|
|
status: zod.string(),
|
|
notes: zod.string().nullable(),
|
|
createdAt: zod.coerce.date(),
|
|
serviceId: zod.number(),
|
|
serviceNameAr: zod.string(),
|
|
serviceNameEn: zod.string(),
|
|
}),
|
|
),
|
|
totalCount: zod.number(),
|
|
limit: zod.number(),
|
|
offset: zod.number(),
|
|
nextOffset: zod.number().nullable(),
|
|
});
|