Files
thbd-portal/src/app/(admin)/chat/mappers.ts
T
ogrechkoandClaude Sonnet 5 579528edf7 Initial commit: thbd-portal, duplicated from elmi-portal
Rebranded for Thunderbird Portal (THBD): numbering prefix EL->TH, all
"ELMI"/"ЭЛМИ" UI strings genericized, ELMI's real contract templates and
company legal/bank details removed (not applicable to this company), fresh
VAPID keypair and encryption key, fresh single-migration schema history.
Same architecture as elmi-portal: Web Push notifications, unread-chat app
badge, cookie-session auth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gh2UXUQUVBWroWEnn1FLFG
2026-08-31 16:33:30 +00:00

98 lines
2.7 KiB
TypeScript

import type { ChatMessage, ConversationSummary } from "./types";
// The chat API returns Drizzle relational shapes (nested `user`/`author`
// objects, ISO date strings after JSON serialization) — these adapt that
// wire shape into the flat, timestamp-as-number types the UI works with.
interface RawUser {
id: string;
name: string;
avatarPath: string | null;
lastSeenAt: string | null;
}
interface RawParticipant {
userId: string;
user: RawUser;
}
interface RawAttachment {
id: string;
kind: "file" | "image" | "voice";
fileName: string;
mimeType: string;
durationSeconds: number | null;
}
interface RawReaction {
userId: string;
emoji: string;
user: RawUser;
}
interface RawMessage {
id: string;
conversationId: string;
authorId: string;
author: RawUser;
body: string | null;
createdAt: string;
replyTo: { id: string; body: string | null; author: RawUser } | null;
attachments: RawAttachment[];
reactions: RawReaction[];
}
interface RawConversation {
id: string;
isGroup: boolean;
name: string | null;
unreadCount: number;
participants: RawParticipant[];
lastMessage: { id: string; body: string | null; authorId: string; createdAt: string; attachments: unknown[] } | null;
}
export function mapMessage(raw: RawMessage): ChatMessage {
return {
id: raw.id,
conversationId: raw.conversationId,
authorId: raw.authorId,
authorName: raw.author?.name ?? "?",
authorHasAvatar: Boolean(raw.author?.avatarPath),
body: raw.body,
createdAt: new Date(raw.createdAt).getTime(),
replyTo: raw.replyTo ? { id: raw.replyTo.id, body: raw.replyTo.body, authorName: raw.replyTo.author?.name ?? "?" } : null,
attachments: (raw.attachments ?? []).map((a) => ({
id: a.id,
kind: a.kind,
fileName: a.fileName,
mimeType: a.mimeType,
durationSeconds: a.durationSeconds,
})),
reactions: (raw.reactions ?? []).map((r) => ({ emoji: r.emoji, userId: r.userId, userName: r.user?.name ?? "?" })),
};
}
export function mapConversation(raw: RawConversation): ConversationSummary {
return {
id: raw.id,
isGroup: raw.isGroup,
name: raw.name,
unreadCount: raw.unreadCount,
participants: raw.participants.map((p) => ({
userId: p.userId,
name: p.user?.name ?? "?",
hasAvatar: Boolean(p.user?.avatarPath),
lastSeenAt: p.user?.lastSeenAt ? new Date(p.user.lastSeenAt).getTime() : null,
})),
lastMessage: raw.lastMessage
? {
id: raw.lastMessage.id,
body: raw.lastMessage.body,
authorId: raw.lastMessage.authorId,
createdAt: new Date(raw.lastMessage.createdAt).getTime(),
hasAttachment: (raw.lastMessage.attachments ?? []).length > 0,
}
: null,
};
}