Add internal org chat (phase 1) with DM, groups, and org settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-29 10:34:01 +03:30
co-authored by Cursor
parent 589c763ef8
commit a1655432fc
21 changed files with 1912 additions and 8 deletions
@@ -0,0 +1,88 @@
import { BadGatewayException, Injectable } from '@nestjs/common';
export type ExternalRoomRow = Record<string, unknown>;
@Injectable()
export class ChatExternalClient {
normalizeHost(host: string): string {
return host
.trim()
.replace(/^wss?:\/\//, '')
.replace(/^https?:\/\//, '')
.replace(/\/+$/, '');
}
async createRoom(
host: string,
apiKey: string,
payload: {
name: string;
type: string;
members: string[];
},
): Promise<{ room_id: string }> {
const base = this.normalizeHost(host);
const url = `https://${base}/api/rooms/create`;
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({
api_key: apiKey,
name: payload.name,
type: payload.type,
members: payload.members,
}),
});
if (!response.ok) {
const body = await response.text().catch(() => '');
throw new BadGatewayException(
`سرویس گفتگو room ایجاد نکرد (${response.status})${body ? `: ${body.slice(0, 200)}` : ''}`,
);
}
const data = (await response.json()) as { room_id?: string; id?: string };
const roomId = String(data.room_id ?? data.id ?? '').trim();
if (!roomId) {
throw new BadGatewayException('پاسخ سرویس گفتگو فاقد room_id است');
}
return { room_id: roomId };
}
async listUserRooms(
host: string,
apiKey: string,
chatUserId: string,
): Promise<ExternalRoomRow[]> {
const base = this.normalizeHost(host);
const params = new URLSearchParams({
api_key: apiKey,
user_id: chatUserId,
});
const url = `https://${base}/api/users/rooms?${params.toString()}`;
const response = await fetch(url, {
method: 'GET',
headers: { Accept: 'application/json' },
cache: 'no-store',
});
if (!response.ok) {
const body = await response.text().catch(() => '');
throw new BadGatewayException(
`دریافت لیست گفتگو ناموفق (${response.status})${body ? `: ${body.slice(0, 200)}` : ''}`,
);
}
const result = await response.json();
if (Array.isArray(result)) return result as ExternalRoomRow[];
if (Array.isArray(result?.data)) return result.data as ExternalRoomRow[];
if (Array.isArray(result?.rooms)) return result.rooms as ExternalRoomRow[];
if (Array.isArray(result?.items)) return result.items as ExternalRoomRow[];
return [];
}
async ping(host: string, apiKey: string, chatUserId: string): Promise<boolean> {
try {
await this.listUserRooms(host, apiKey, chatUserId);
return true;
} catch {
return false;
}
}
}