Task #511: Fully remove Chat feature
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.
This commit is contained in:
@@ -197,14 +197,6 @@ Omitted from single-user responses (GET /users/:id, PATCH, etc).
|
||||
admin list endpoint (GET /users).
|
||||
*/
|
||||
orderCount?: number;
|
||||
/** Number of conversations created by this user. Populated only by the
|
||||
admin list endpoint (GET /users).
|
||||
*/
|
||||
conversationCount?: number;
|
||||
/** Number of messages authored by this user. Populated only by the
|
||||
admin list endpoint (GET /users).
|
||||
*/
|
||||
messageCount?: number;
|
||||
}
|
||||
|
||||
export interface UpdateUserBody {
|
||||
@@ -371,8 +363,6 @@ export interface UserDeletionConflict {
|
||||
error: string;
|
||||
noteCount: number;
|
||||
orderCount: number;
|
||||
conversationCount: number;
|
||||
messageCount: number;
|
||||
}
|
||||
|
||||
export interface AppDeletionConflict {
|
||||
@@ -643,96 +633,6 @@ export interface BulkDeleteServiceOrdersResponse {
|
||||
failedIds: number[];
|
||||
}
|
||||
|
||||
export interface ParticipantInfo {
|
||||
id: number;
|
||||
username: string;
|
||||
/** @nullable */
|
||||
displayNameAr?: string | null;
|
||||
/** @nullable */
|
||||
displayNameEn?: string | null;
|
||||
/** @nullable */
|
||||
avatarUrl?: string | null;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
export type MessageWithSenderKind =
|
||||
(typeof MessageWithSenderKind)[keyof typeof MessageWithSenderKind];
|
||||
|
||||
export const MessageWithSenderKind = {
|
||||
user: "user",
|
||||
group_renamed: "group_renamed",
|
||||
members_added: "members_added",
|
||||
member_removed: "member_removed",
|
||||
member_left: "member_left",
|
||||
admin_promoted: "admin_promoted",
|
||||
} as const;
|
||||
|
||||
export type MessageWithSenderMeta = { [key: string]: unknown } | null;
|
||||
|
||||
export interface MessageWithSender {
|
||||
id: number;
|
||||
conversationId: number;
|
||||
senderId: number;
|
||||
content: string;
|
||||
kind: MessageWithSenderKind;
|
||||
meta?: MessageWithSenderMeta;
|
||||
sender: ParticipantInfo;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ConversationWithDetails {
|
||||
id: number;
|
||||
/** @nullable */
|
||||
nameAr?: string | null;
|
||||
/** @nullable */
|
||||
nameEn?: string | null;
|
||||
/** @nullable */
|
||||
avatarUrl?: string | null;
|
||||
isGroup: boolean;
|
||||
createdBy: number;
|
||||
participants: ParticipantInfo[];
|
||||
lastMessage?: MessageWithSender | null;
|
||||
unreadCount: number;
|
||||
isMuted: boolean;
|
||||
isArchived: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateConversationBody {
|
||||
participantIds: number[];
|
||||
/** @nullable */
|
||||
nameAr?: string | null;
|
||||
/** @nullable */
|
||||
nameEn?: string | null;
|
||||
/** @nullable */
|
||||
avatarUrl?: string | null;
|
||||
isGroup?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateConversationBody {
|
||||
/** @nullable */
|
||||
avatarUrl?: string | null;
|
||||
/** @nullable */
|
||||
nameAr?: string | null;
|
||||
/** @nullable */
|
||||
nameEn?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateConversationStateBody {
|
||||
isMuted?: boolean;
|
||||
isArchived?: boolean;
|
||||
}
|
||||
|
||||
export interface AddParticipantsBody {
|
||||
userIds: number[];
|
||||
}
|
||||
|
||||
export interface SendMessageBody {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id: number;
|
||||
userId: number;
|
||||
@@ -808,7 +708,6 @@ export interface HomeStats {
|
||||
totalApps: number;
|
||||
totalServices: number;
|
||||
unreadNotifications: number;
|
||||
unreadMessages: number;
|
||||
totalUsers: number;
|
||||
}
|
||||
|
||||
@@ -1224,47 +1123,6 @@ export interface UserDependentOrdersPage {
|
||||
nextOffset: number | null;
|
||||
}
|
||||
|
||||
export interface UserDependentConversationItem {
|
||||
id: number;
|
||||
/** @nullable */
|
||||
nameAr: string | null;
|
||||
/** @nullable */
|
||||
nameEn: string | null;
|
||||
isGroup: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface UserDependentConversationsPage {
|
||||
items: UserDependentConversationItem[];
|
||||
totalCount: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
/** @nullable */
|
||||
nextOffset: number | null;
|
||||
}
|
||||
|
||||
export interface UserDependentMessageItem {
|
||||
id: number;
|
||||
content: string;
|
||||
kind: string;
|
||||
createdAt: string;
|
||||
conversationId: number;
|
||||
/** @nullable */
|
||||
conversationNameAr: string | null;
|
||||
/** @nullable */
|
||||
conversationNameEn: string | null;
|
||||
conversationIsGroup: boolean;
|
||||
}
|
||||
|
||||
export interface UserDependentMessagesPage {
|
||||
items: UserDependentMessageItem[];
|
||||
totalCount: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
/** @nullable */
|
||||
nextOffset: number | null;
|
||||
}
|
||||
|
||||
export interface AuditLogActor {
|
||||
id: number;
|
||||
username: string;
|
||||
@@ -1381,14 +1239,6 @@ export type DeleteServiceParams = {
|
||||
force?: boolean;
|
||||
};
|
||||
|
||||
export type LeaveConversationBody = {
|
||||
/** When the leaver is the only admin, optionally name a specific
|
||||
remaining member to promote. Omit (or send null) to keep the
|
||||
automatic behavior of promoting the longest-tenured member.
|
||||
*/
|
||||
successorId?: number | null;
|
||||
};
|
||||
|
||||
export type ListUsersParams = {
|
||||
/**
|
||||
* Free-text search across username, email, and display names
|
||||
@@ -1822,27 +1672,3 @@ export type GetAdminUserDependentOrdersParams = {
|
||||
*/
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type GetAdminUserDependentConversationsParams = {
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 200
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @minimum 0
|
||||
*/
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
export type GetAdminUserDependentMessagesParams = {
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 200
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @minimum 0
|
||||
*/
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,10 +18,6 @@ tags:
|
||||
description: Internal services (khidmati)
|
||||
- name: orders
|
||||
description: Service orders workflow
|
||||
- name: conversations
|
||||
description: Chat conversations
|
||||
- name: messages
|
||||
description: Chat messages
|
||||
- name: notifications
|
||||
description: Notifications
|
||||
- name: users
|
||||
@@ -928,254 +924,6 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/ServiceCategory"
|
||||
|
||||
# Conversations
|
||||
/conversations:
|
||||
get:
|
||||
operationId: listConversations
|
||||
tags: [conversations]
|
||||
summary: List conversations for current user
|
||||
responses:
|
||||
"200":
|
||||
description: Conversations
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ConversationWithDetails"
|
||||
|
||||
post:
|
||||
operationId: createConversation
|
||||
tags: [conversations]
|
||||
summary: Create a conversation
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateConversationBody"
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConversationWithDetails"
|
||||
|
||||
/conversations/{id}:
|
||||
get:
|
||||
operationId: getConversation
|
||||
tags: [conversations]
|
||||
summary: Get conversation by ID
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: Conversation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConversationWithDetails"
|
||||
|
||||
patch:
|
||||
operationId: updateConversation
|
||||
tags: [conversations]
|
||||
summary: Update conversation properties (admin only)
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpdateConversationBody"
|
||||
responses:
|
||||
"200":
|
||||
description: Updated conversation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConversationWithDetails"
|
||||
|
||||
/conversations/{id}/participants:
|
||||
post:
|
||||
operationId: addConversationParticipants
|
||||
tags: [conversations]
|
||||
summary: Add participants to a group conversation (admin only)
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AddParticipantsBody"
|
||||
responses:
|
||||
"200":
|
||||
description: Updated conversation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConversationWithDetails"
|
||||
|
||||
/conversations/{id}/participants/{userId}:
|
||||
delete:
|
||||
operationId: removeConversationParticipant
|
||||
tags: [conversations]
|
||||
summary: Remove a participant from a group conversation (admin only)
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: userId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: Updated conversation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConversationWithDetails"
|
||||
|
||||
/conversations/{id}/state:
|
||||
patch:
|
||||
operationId: updateConversationState
|
||||
tags: [conversations]
|
||||
summary: Update per-user state (mute, archive)
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpdateConversationStateBody"
|
||||
responses:
|
||||
"200":
|
||||
description: Updated conversation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ConversationWithDetails"
|
||||
|
||||
/conversations/{id}/leave:
|
||||
post:
|
||||
operationId: leaveConversation
|
||||
tags: [conversations]
|
||||
summary: Leave a group conversation
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
successorId:
|
||||
type: integer
|
||||
description: |
|
||||
When the leaver is the only admin, optionally name a specific
|
||||
remaining member to promote. Omit (or send null) to keep the
|
||||
automatic behavior of promoting the longest-tenured member.
|
||||
nullable: true
|
||||
responses:
|
||||
"200":
|
||||
description: Left conversation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SuccessResponse"
|
||||
|
||||
/conversations/{id}/messages:
|
||||
get:
|
||||
operationId: listMessages
|
||||
tags: [messages]
|
||||
summary: List messages in a conversation
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: Messages
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/MessageWithSender"
|
||||
|
||||
post:
|
||||
operationId: sendMessage
|
||||
tags: [messages]
|
||||
summary: Send a message
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SendMessageBody"
|
||||
responses:
|
||||
"201":
|
||||
description: Message sent
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MessageWithSender"
|
||||
|
||||
/conversations/{id}/read:
|
||||
post:
|
||||
operationId: markConversationRead
|
||||
tags: [messages]
|
||||
summary: Mark all messages in conversation as read
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: Marked as read
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SuccessResponse"
|
||||
|
||||
# Notifications
|
||||
/notifications:
|
||||
get:
|
||||
@@ -3074,70 +2822,6 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/admin/users/{id}/dependents/conversations:
|
||||
get:
|
||||
operationId: getAdminUserDependentConversations
|
||||
tags: [users]
|
||||
summary: List conversations created by a user
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: limit
|
||||
required: false
|
||||
schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
|
||||
- in: query
|
||||
name: offset
|
||||
required: false
|
||||
schema: { type: integer, minimum: 0, default: 0 }
|
||||
responses:
|
||||
"200":
|
||||
description: Paged list of conversations
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UserDependentConversationsPage"
|
||||
"404":
|
||||
description: User not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/admin/users/{id}/dependents/messages:
|
||||
get:
|
||||
operationId: getAdminUserDependentMessages
|
||||
tags: [users]
|
||||
summary: List messages sent by a user
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
- in: query
|
||||
name: limit
|
||||
required: false
|
||||
schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
|
||||
- in: query
|
||||
name: offset
|
||||
required: false
|
||||
schema: { type: integer, minimum: 0, default: 0 }
|
||||
responses:
|
||||
"200":
|
||||
description: Paged list of messages
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UserDependentMessagesPage"
|
||||
"404":
|
||||
description: User not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
HealthStatus:
|
||||
@@ -3463,16 +3147,6 @@ components:
|
||||
description: |
|
||||
Number of service orders owned by this user. Populated only by the
|
||||
admin list endpoint (GET /users).
|
||||
conversationCount:
|
||||
type: integer
|
||||
description: |
|
||||
Number of conversations created by this user. Populated only by the
|
||||
admin list endpoint (GET /users).
|
||||
messageCount:
|
||||
type: integer
|
||||
description: |
|
||||
Number of messages authored by this user. Populated only by the
|
||||
admin list endpoint (GET /users).
|
||||
required:
|
||||
- id
|
||||
- username
|
||||
@@ -3817,16 +3491,10 @@ components:
|
||||
type: integer
|
||||
orderCount:
|
||||
type: integer
|
||||
conversationCount:
|
||||
type: integer
|
||||
messageCount:
|
||||
type: integer
|
||||
required:
|
||||
- error
|
||||
- noteCount
|
||||
- orderCount
|
||||
- conversationCount
|
||||
- messageCount
|
||||
|
||||
AppDeletionConflict:
|
||||
type: object
|
||||
@@ -4304,161 +3972,6 @@ components:
|
||||
- deletedIds
|
||||
- failedIds
|
||||
|
||||
ConversationWithDetails:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
nameAr:
|
||||
type: ["string", "null"]
|
||||
nameEn:
|
||||
type: ["string", "null"]
|
||||
avatarUrl:
|
||||
type: ["string", "null"]
|
||||
isGroup:
|
||||
type: boolean
|
||||
createdBy:
|
||||
type: integer
|
||||
participants:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ParticipantInfo"
|
||||
lastMessage:
|
||||
$ref: "#/components/schemas/MessageWithSender"
|
||||
nullable: true
|
||||
unreadCount:
|
||||
type: integer
|
||||
isMuted:
|
||||
type: boolean
|
||||
isArchived:
|
||||
type: boolean
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- id
|
||||
- isGroup
|
||||
- createdBy
|
||||
- participants
|
||||
- unreadCount
|
||||
- isMuted
|
||||
- isArchived
|
||||
- createdAt
|
||||
- updatedAt
|
||||
|
||||
ParticipantInfo:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
username:
|
||||
type: string
|
||||
displayNameAr:
|
||||
type: ["string", "null"]
|
||||
displayNameEn:
|
||||
type: ["string", "null"]
|
||||
avatarUrl:
|
||||
type: ["string", "null"]
|
||||
isAdmin:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- username
|
||||
- isAdmin
|
||||
|
||||
CreateConversationBody:
|
||||
type: object
|
||||
properties:
|
||||
participantIds:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
nameAr:
|
||||
type: ["string", "null"]
|
||||
nameEn:
|
||||
type: ["string", "null"]
|
||||
avatarUrl:
|
||||
type: ["string", "null"]
|
||||
isGroup:
|
||||
type: boolean
|
||||
required:
|
||||
- participantIds
|
||||
|
||||
UpdateConversationBody:
|
||||
type: object
|
||||
properties:
|
||||
avatarUrl:
|
||||
type: ["string", "null"]
|
||||
nameAr:
|
||||
type: ["string", "null"]
|
||||
nameEn:
|
||||
type: ["string", "null"]
|
||||
|
||||
UpdateConversationStateBody:
|
||||
type: object
|
||||
properties:
|
||||
isMuted:
|
||||
type: boolean
|
||||
isArchived:
|
||||
type: boolean
|
||||
|
||||
AddParticipantsBody:
|
||||
type: object
|
||||
properties:
|
||||
userIds:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
required:
|
||||
- userIds
|
||||
|
||||
MessageWithSender:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
conversationId:
|
||||
type: integer
|
||||
senderId:
|
||||
type: integer
|
||||
content:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
enum: [user, group_renamed, members_added, member_removed, member_left, admin_promoted]
|
||||
meta:
|
||||
type: object
|
||||
nullable: true
|
||||
additionalProperties: true
|
||||
sender:
|
||||
$ref: "#/components/schemas/ParticipantInfo"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- id
|
||||
- conversationId
|
||||
- senderId
|
||||
- content
|
||||
- kind
|
||||
- sender
|
||||
- createdAt
|
||||
- updatedAt
|
||||
|
||||
SendMessageBody:
|
||||
type: object
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
required:
|
||||
- content
|
||||
|
||||
Notification:
|
||||
type: object
|
||||
properties:
|
||||
@@ -4579,15 +4092,12 @@ components:
|
||||
type: integer
|
||||
unreadNotifications:
|
||||
type: integer
|
||||
unreadMessages:
|
||||
type: integer
|
||||
totalUsers:
|
||||
type: integer
|
||||
required:
|
||||
- totalApps
|
||||
- totalServices
|
||||
- unreadNotifications
|
||||
- unreadMessages
|
||||
- totalUsers
|
||||
|
||||
AdminStats:
|
||||
@@ -5179,55 +4689,6 @@ components:
|
||||
nextOffset: { type: ["integer", "null"] }
|
||||
required: [items, totalCount, limit, offset, nextOffset]
|
||||
|
||||
UserDependentConversationItem:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
nameAr: { type: ["string", "null"] }
|
||||
nameEn: { type: ["string", "null"] }
|
||||
isGroup: { type: boolean }
|
||||
createdAt: { type: string, format: date-time }
|
||||
required: [id, nameAr, nameEn, isGroup, createdAt]
|
||||
|
||||
UserDependentConversationsPage:
|
||||
type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/UserDependentConversationItem"
|
||||
totalCount: { type: integer }
|
||||
limit: { type: integer }
|
||||
offset: { type: integer }
|
||||
nextOffset: { type: ["integer", "null"] }
|
||||
required: [items, totalCount, limit, offset, nextOffset]
|
||||
|
||||
UserDependentMessageItem:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
content: { type: string }
|
||||
kind: { type: string }
|
||||
createdAt: { type: string, format: date-time }
|
||||
conversationId: { type: integer }
|
||||
conversationNameAr: { type: ["string", "null"] }
|
||||
conversationNameEn: { type: ["string", "null"] }
|
||||
conversationIsGroup: { type: boolean }
|
||||
required: [id, content, kind, createdAt, conversationId, conversationNameAr, conversationNameEn, conversationIsGroup]
|
||||
|
||||
UserDependentMessagesPage:
|
||||
type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/UserDependentMessageItem"
|
||||
totalCount: { type: integer }
|
||||
limit: { type: integer }
|
||||
offset: { type: integer }
|
||||
nextOffset: { type: ["integer", "null"] }
|
||||
required: [items, totalCount, limit, offset, nextOffset]
|
||||
|
||||
TopUserItem:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -1332,456 +1332,6 @@ export const ListServiceCategoriesResponse = zod.array(
|
||||
ListServiceCategoriesResponseItem,
|
||||
);
|
||||
|
||||
/**
|
||||
* @summary List conversations for current user
|
||||
*/
|
||||
export const ListConversationsResponseItem = zod.object({
|
||||
id: zod.number(),
|
||||
nameAr: zod.string().nullish(),
|
||||
nameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isGroup: zod.boolean(),
|
||||
createdBy: zod.number(),
|
||||
participants: zod.array(
|
||||
zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
),
|
||||
lastMessage: zod
|
||||
.object({
|
||||
id: zod.number(),
|
||||
conversationId: zod.number(),
|
||||
senderId: zod.number(),
|
||||
content: zod.string(),
|
||||
kind: zod.enum([
|
||||
"user",
|
||||
"group_renamed",
|
||||
"members_added",
|
||||
"member_removed",
|
||||
"member_left",
|
||||
"admin_promoted",
|
||||
]),
|
||||
meta: zod.record(zod.string(), zod.unknown()).nullish(),
|
||||
sender: zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
})
|
||||
.nullish(),
|
||||
unreadCount: zod.number(),
|
||||
isMuted: zod.boolean(),
|
||||
isArchived: zod.boolean(),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
});
|
||||
export const ListConversationsResponse = zod.array(
|
||||
ListConversationsResponseItem,
|
||||
);
|
||||
|
||||
/**
|
||||
* @summary Create a conversation
|
||||
*/
|
||||
export const CreateConversationBody = zod.object({
|
||||
participantIds: zod.array(zod.number()),
|
||||
nameAr: zod.string().nullish(),
|
||||
nameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isGroup: zod.boolean().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Get conversation by ID
|
||||
*/
|
||||
export const GetConversationParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const GetConversationResponse = zod.object({
|
||||
id: zod.number(),
|
||||
nameAr: zod.string().nullish(),
|
||||
nameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isGroup: zod.boolean(),
|
||||
createdBy: zod.number(),
|
||||
participants: zod.array(
|
||||
zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
),
|
||||
lastMessage: zod
|
||||
.object({
|
||||
id: zod.number(),
|
||||
conversationId: zod.number(),
|
||||
senderId: zod.number(),
|
||||
content: zod.string(),
|
||||
kind: zod.enum([
|
||||
"user",
|
||||
"group_renamed",
|
||||
"members_added",
|
||||
"member_removed",
|
||||
"member_left",
|
||||
"admin_promoted",
|
||||
]),
|
||||
meta: zod.record(zod.string(), zod.unknown()).nullish(),
|
||||
sender: zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
})
|
||||
.nullish(),
|
||||
unreadCount: zod.number(),
|
||||
isMuted: zod.boolean(),
|
||||
isArchived: zod.boolean(),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Update conversation properties (admin only)
|
||||
*/
|
||||
export const UpdateConversationParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const UpdateConversationBody = zod.object({
|
||||
avatarUrl: zod.string().nullish(),
|
||||
nameAr: zod.string().nullish(),
|
||||
nameEn: zod.string().nullish(),
|
||||
});
|
||||
|
||||
export const UpdateConversationResponse = zod.object({
|
||||
id: zod.number(),
|
||||
nameAr: zod.string().nullish(),
|
||||
nameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isGroup: zod.boolean(),
|
||||
createdBy: zod.number(),
|
||||
participants: zod.array(
|
||||
zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
),
|
||||
lastMessage: zod
|
||||
.object({
|
||||
id: zod.number(),
|
||||
conversationId: zod.number(),
|
||||
senderId: zod.number(),
|
||||
content: zod.string(),
|
||||
kind: zod.enum([
|
||||
"user",
|
||||
"group_renamed",
|
||||
"members_added",
|
||||
"member_removed",
|
||||
"member_left",
|
||||
"admin_promoted",
|
||||
]),
|
||||
meta: zod.record(zod.string(), zod.unknown()).nullish(),
|
||||
sender: zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
})
|
||||
.nullish(),
|
||||
unreadCount: zod.number(),
|
||||
isMuted: zod.boolean(),
|
||||
isArchived: zod.boolean(),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Add participants to a group conversation (admin only)
|
||||
*/
|
||||
export const AddConversationParticipantsParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const AddConversationParticipantsBody = zod.object({
|
||||
userIds: zod.array(zod.number()),
|
||||
});
|
||||
|
||||
export const AddConversationParticipantsResponse = zod.object({
|
||||
id: zod.number(),
|
||||
nameAr: zod.string().nullish(),
|
||||
nameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isGroup: zod.boolean(),
|
||||
createdBy: zod.number(),
|
||||
participants: zod.array(
|
||||
zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
),
|
||||
lastMessage: zod
|
||||
.object({
|
||||
id: zod.number(),
|
||||
conversationId: zod.number(),
|
||||
senderId: zod.number(),
|
||||
content: zod.string(),
|
||||
kind: zod.enum([
|
||||
"user",
|
||||
"group_renamed",
|
||||
"members_added",
|
||||
"member_removed",
|
||||
"member_left",
|
||||
"admin_promoted",
|
||||
]),
|
||||
meta: zod.record(zod.string(), zod.unknown()).nullish(),
|
||||
sender: zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
})
|
||||
.nullish(),
|
||||
unreadCount: zod.number(),
|
||||
isMuted: zod.boolean(),
|
||||
isArchived: zod.boolean(),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Remove a participant from a group conversation (admin only)
|
||||
*/
|
||||
export const RemoveConversationParticipantParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
userId: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const RemoveConversationParticipantResponse = zod.object({
|
||||
id: zod.number(),
|
||||
nameAr: zod.string().nullish(),
|
||||
nameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isGroup: zod.boolean(),
|
||||
createdBy: zod.number(),
|
||||
participants: zod.array(
|
||||
zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
),
|
||||
lastMessage: zod
|
||||
.object({
|
||||
id: zod.number(),
|
||||
conversationId: zod.number(),
|
||||
senderId: zod.number(),
|
||||
content: zod.string(),
|
||||
kind: zod.enum([
|
||||
"user",
|
||||
"group_renamed",
|
||||
"members_added",
|
||||
"member_removed",
|
||||
"member_left",
|
||||
"admin_promoted",
|
||||
]),
|
||||
meta: zod.record(zod.string(), zod.unknown()).nullish(),
|
||||
sender: zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
})
|
||||
.nullish(),
|
||||
unreadCount: zod.number(),
|
||||
isMuted: zod.boolean(),
|
||||
isArchived: zod.boolean(),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Update per-user state (mute, archive)
|
||||
*/
|
||||
export const UpdateConversationStateParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const UpdateConversationStateBody = zod.object({
|
||||
isMuted: zod.boolean().optional(),
|
||||
isArchived: zod.boolean().optional(),
|
||||
});
|
||||
|
||||
export const UpdateConversationStateResponse = zod.object({
|
||||
id: zod.number(),
|
||||
nameAr: zod.string().nullish(),
|
||||
nameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isGroup: zod.boolean(),
|
||||
createdBy: zod.number(),
|
||||
participants: zod.array(
|
||||
zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
),
|
||||
lastMessage: zod
|
||||
.object({
|
||||
id: zod.number(),
|
||||
conversationId: zod.number(),
|
||||
senderId: zod.number(),
|
||||
content: zod.string(),
|
||||
kind: zod.enum([
|
||||
"user",
|
||||
"group_renamed",
|
||||
"members_added",
|
||||
"member_removed",
|
||||
"member_left",
|
||||
"admin_promoted",
|
||||
]),
|
||||
meta: zod.record(zod.string(), zod.unknown()).nullish(),
|
||||
sender: zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
})
|
||||
.nullish(),
|
||||
unreadCount: zod.number(),
|
||||
isMuted: zod.boolean(),
|
||||
isArchived: zod.boolean(),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Leave a group conversation
|
||||
*/
|
||||
export const LeaveConversationParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const LeaveConversationBody = zod.object({
|
||||
successorId: zod
|
||||
.number()
|
||||
.nullish()
|
||||
.describe(
|
||||
"When the leaver is the only admin, optionally name a specific\nremaining member to promote. Omit (or send null) to keep the\nautomatic behavior of promoting the longest-tenured member.\n",
|
||||
),
|
||||
});
|
||||
|
||||
export const LeaveConversationResponse = zod.object({
|
||||
success: zod.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary List messages in a conversation
|
||||
*/
|
||||
export const ListMessagesParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const ListMessagesResponseItem = zod.object({
|
||||
id: zod.number(),
|
||||
conversationId: zod.number(),
|
||||
senderId: zod.number(),
|
||||
content: zod.string(),
|
||||
kind: zod.enum([
|
||||
"user",
|
||||
"group_renamed",
|
||||
"members_added",
|
||||
"member_removed",
|
||||
"member_left",
|
||||
"admin_promoted",
|
||||
]),
|
||||
meta: zod.record(zod.string(), zod.unknown()).nullish(),
|
||||
sender: zod.object({
|
||||
id: zod.number(),
|
||||
username: zod.string(),
|
||||
displayNameAr: zod.string().nullish(),
|
||||
displayNameEn: zod.string().nullish(),
|
||||
avatarUrl: zod.string().nullish(),
|
||||
isAdmin: zod.boolean(),
|
||||
}),
|
||||
createdAt: zod.coerce.date(),
|
||||
updatedAt: zod.coerce.date(),
|
||||
});
|
||||
export const ListMessagesResponse = zod.array(ListMessagesResponseItem);
|
||||
|
||||
/**
|
||||
* @summary Send a message
|
||||
*/
|
||||
export const SendMessageParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const SendMessageBody = zod.object({
|
||||
content: zod.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Mark all messages in conversation as read
|
||||
*/
|
||||
export const MarkConversationReadParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const MarkConversationReadResponse = zod.object({
|
||||
success: zod.boolean(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary List notifications for current user
|
||||
*/
|
||||
@@ -1888,18 +1438,6 @@ export const ListUsersResponseItem = zod.object({
|
||||
.describe(
|
||||
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
conversationCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
messageCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
});
|
||||
export const ListUsersResponse = zod.array(ListUsersResponseItem);
|
||||
|
||||
@@ -1972,18 +1510,6 @@ export const GetUserResponse = zod.object({
|
||||
.describe(
|
||||
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
conversationCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
messageCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -2044,18 +1570,6 @@ export const UpdateUserResponse = zod.object({
|
||||
.describe(
|
||||
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
conversationCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
messageCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -2169,18 +1683,6 @@ export const AddUserRoleResponse = zod.object({
|
||||
.describe(
|
||||
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
conversationCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
messageCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -2231,18 +1733,6 @@ export const RemoveUserRoleResponse = zod.object({
|
||||
.describe(
|
||||
"Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
conversationCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
messageCount: zod
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -2878,7 +2368,6 @@ export const GetHomeStatsResponse = zod.object({
|
||||
totalApps: zod.number(),
|
||||
totalServices: zod.number(),
|
||||
unreadNotifications: zod.number(),
|
||||
unreadMessages: zod.number(),
|
||||
totalUsers: zod.number(),
|
||||
});
|
||||
|
||||
@@ -3830,88 +3319,3 @@ export const GetAdminUserDependentOrdersResponse = zod.object({
|
||||
offset: zod.number(),
|
||||
nextOffset: zod.number().nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary List conversations created by a user
|
||||
*/
|
||||
export const GetAdminUserDependentConversationsParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const getAdminUserDependentConversationsQueryLimitDefault = 50;
|
||||
export const getAdminUserDependentConversationsQueryLimitMax = 200;
|
||||
|
||||
export const getAdminUserDependentConversationsQueryOffsetDefault = 0;
|
||||
export const getAdminUserDependentConversationsQueryOffsetMin = 0;
|
||||
|
||||
export const GetAdminUserDependentConversationsQueryParams = zod.object({
|
||||
limit: zod.coerce
|
||||
.number()
|
||||
.min(1)
|
||||
.max(getAdminUserDependentConversationsQueryLimitMax)
|
||||
.default(getAdminUserDependentConversationsQueryLimitDefault),
|
||||
offset: zod.coerce
|
||||
.number()
|
||||
.min(getAdminUserDependentConversationsQueryOffsetMin)
|
||||
.default(getAdminUserDependentConversationsQueryOffsetDefault),
|
||||
});
|
||||
|
||||
export const GetAdminUserDependentConversationsResponse = zod.object({
|
||||
items: zod.array(
|
||||
zod.object({
|
||||
id: zod.number(),
|
||||
nameAr: zod.string().nullable(),
|
||||
nameEn: zod.string().nullable(),
|
||||
isGroup: zod.boolean(),
|
||||
createdAt: zod.coerce.date(),
|
||||
}),
|
||||
),
|
||||
totalCount: zod.number(),
|
||||
limit: zod.number(),
|
||||
offset: zod.number(),
|
||||
nextOffset: zod.number().nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary List messages sent by a user
|
||||
*/
|
||||
export const GetAdminUserDependentMessagesParams = zod.object({
|
||||
id: zod.coerce.number(),
|
||||
});
|
||||
|
||||
export const getAdminUserDependentMessagesQueryLimitDefault = 50;
|
||||
export const getAdminUserDependentMessagesQueryLimitMax = 200;
|
||||
|
||||
export const getAdminUserDependentMessagesQueryOffsetDefault = 0;
|
||||
export const getAdminUserDependentMessagesQueryOffsetMin = 0;
|
||||
|
||||
export const GetAdminUserDependentMessagesQueryParams = zod.object({
|
||||
limit: zod.coerce
|
||||
.number()
|
||||
.min(1)
|
||||
.max(getAdminUserDependentMessagesQueryLimitMax)
|
||||
.default(getAdminUserDependentMessagesQueryLimitDefault),
|
||||
offset: zod.coerce
|
||||
.number()
|
||||
.min(getAdminUserDependentMessagesQueryOffsetMin)
|
||||
.default(getAdminUserDependentMessagesQueryOffsetDefault),
|
||||
});
|
||||
|
||||
export const GetAdminUserDependentMessagesResponse = zod.object({
|
||||
items: zod.array(
|
||||
zod.object({
|
||||
id: zod.number(),
|
||||
content: zod.string(),
|
||||
kind: zod.string(),
|
||||
createdAt: zod.coerce.date(),
|
||||
conversationId: zod.number(),
|
||||
conversationNameAr: zod.string().nullable(),
|
||||
conversationNameEn: zod.string().nullable(),
|
||||
conversationIsGroup: zod.boolean(),
|
||||
}),
|
||||
),
|
||||
totalCount: zod.number(),
|
||||
limit: zod.number(),
|
||||
offset: zod.number(),
|
||||
nextOffset: zod.number().nullable(),
|
||||
});
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { pgTable, text, serial, timestamp, integer, varchar, boolean, primaryKey, jsonb } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod/v4";
|
||||
import { usersTable } from "./users";
|
||||
|
||||
export const conversationsTable = pgTable("conversations", {
|
||||
id: serial("id").primaryKey(),
|
||||
nameAr: varchar("name_ar", { length: 300 }),
|
||||
nameEn: varchar("name_en", { length: 300 }),
|
||||
avatarUrl: text("avatar_url"),
|
||||
isGroup: boolean("is_group").notNull().default(false),
|
||||
createdBy: integer("created_by").notNull().references(() => usersTable.id),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => new Date()),
|
||||
});
|
||||
|
||||
export const conversationParticipantsTable = pgTable("conversation_participants", {
|
||||
conversationId: integer("conversation_id").notNull().references(() => conversationsTable.id, { onDelete: "cascade" }),
|
||||
userId: integer("user_id").notNull().references(() => usersTable.id, { onDelete: "cascade" }),
|
||||
joinedAt: timestamp("joined_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
isAdmin: boolean("is_admin").notNull().default(false),
|
||||
isMuted: boolean("is_muted").notNull().default(false),
|
||||
isArchived: boolean("is_archived").notNull().default(false),
|
||||
}, (t) => [primaryKey({ columns: [t.conversationId, t.userId] })]);
|
||||
|
||||
export const messagesTable = pgTable("messages", {
|
||||
id: serial("id").primaryKey(),
|
||||
conversationId: integer("conversation_id").notNull().references(() => conversationsTable.id, { onDelete: "cascade" }),
|
||||
senderId: integer("sender_id").notNull().references(() => usersTable.id),
|
||||
content: text("content").notNull(),
|
||||
kind: varchar("kind", { length: 32 }).notNull().default("user"),
|
||||
meta: jsonb("meta"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => new Date()),
|
||||
});
|
||||
|
||||
export const messageReadsTable = pgTable("message_reads", {
|
||||
messageId: integer("message_id").notNull().references(() => messagesTable.id, { onDelete: "cascade" }),
|
||||
userId: integer("user_id").notNull().references(() => usersTable.id, { onDelete: "cascade" }),
|
||||
readAt: timestamp("read_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (t) => [primaryKey({ columns: [t.messageId, t.userId] })]);
|
||||
|
||||
export const insertMessageSchema = createInsertSchema(messagesTable).omit({ id: true, createdAt: true, updatedAt: true });
|
||||
export const insertConversationSchema = createInsertSchema(conversationsTable).omit({ id: true, createdAt: true, updatedAt: true });
|
||||
export type InsertMessage = z.infer<typeof insertMessageSchema>;
|
||||
export type Message = typeof messagesTable.$inferSelect;
|
||||
export type Conversation = typeof conversationsTable.$inferSelect;
|
||||
@@ -3,7 +3,6 @@ export * from "./roles";
|
||||
export * from "./apps";
|
||||
export * from "./services";
|
||||
export * from "./service-orders";
|
||||
export * from "./conversations";
|
||||
export * from "./notifications";
|
||||
export * from "./settings";
|
||||
export * from "./user-app-orders";
|
||||
|
||||
Reference in New Issue
Block a user