Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/modules/chat/services/ChatMessageDiscoveryService.server.ts
T

87 lines
3.5 KiB
TypeScript

import { CHAT_PERMISSIONS } from "@hivegrid/chat";
import { buildChatMessageDeepLink } from "@hivegrid/chat/deep-links";
import { listChatMessageDiscovery, type ChatMessageDiscoveryRecord } from "@hivegrid/chat/message-discovery.server";
import { buildChatMessageDiscoveryVM, type ChatMessageDiscoveryMode, type ChatMessageDiscoveryVM } from "@hivegrid/view-models";
import { requireChatPermission, type ChatRouteContext } from "../routes/chatAccess.server";
import { chatIndexPath, chatMentionsPath, chatSearchPath } from "../routes/chatRoutePaths";
export type ChatMessageDiscoveryLabels = Readonly<{
directMessage: string;
unnamedGroup: string;
unnamedProject: string;
unknownUser: string;
}>;
export async function loadChatMessageDiscovery(input: {
readonly context: ChatRouteContext;
readonly tenantSlug: string;
readonly mode: ChatMessageDiscoveryMode;
readonly query?: string;
readonly beforeCursor?: string;
readonly limit?: number;
readonly labels: ChatMessageDiscoveryLabels;
}): Promise<ChatMessageDiscoveryVM> {
const context = requireChatPermission(input.context, CHAT_PERMISSIONS.readOwnChannels);
const result = await listChatMessageDiscovery({
tenantId: context.tenantId,
userId: context.userId,
permissions: context.permissions,
mode: input.mode,
...(input.query !== undefined ? { query: input.query } : {}),
...(input.beforeCursor ? { beforeCursor: input.beforeCursor } : {}),
...(input.limit !== undefined ? { limit: input.limit } : {}),
});
return buildChatMessageDiscoveryVM({
mode: input.mode,
query: input.query,
nextCursor: result.nextCursor,
hrefs: {
chat: chatIndexPath(input.tenantSlug),
search: chatSearchPath(input.tenantSlug),
mentions: chatMentionsPath(input.tenantSlug),
},
rows: result.messages.map((message) => toRow(message, context.userId, input.tenantSlug, input.labels)),
});
}
function toRow(message: ChatMessageDiscoveryRecord, currentUserId: string, tenantSlug: string, labels: ChatMessageDiscoveryLabels) {
const author = message.participants.find((participant) => participant.userId === message.authorUserId);
const authorLabel = author?.displayName ?? labels.unknownUser;
const channelTitle = resolveChannelTitle(message, currentUserId, labels);
return {
id: message.id,
chatId: message.chatId,
channelKind: message.channelKind,
channelTitle,
authorLabel,
authorAvatar: {
imageUrl: author?.avatarUrl ?? null,
fallbackLabel: initials(authorLabel),
},
body: message.body,
createdAtIso: message.createdAtIso,
detailHref: buildChatMessageDeepLink({
tenantSlug,
chatId: message.chatId,
messageId: message.id,
parentMessageId: message.parentMessageId,
}),
isThreadReply: message.parentMessageId !== null,
} as const;
}
function resolveChannelTitle(message: ChatMessageDiscoveryRecord, currentUserId: string, labels: ChatMessageDiscoveryLabels): string {
if (message.channelKind === "dm") {
return message.participants.find((participant) => participant.userId !== currentUserId)?.displayName ?? labels.directMessage;
}
return message.channelName ?? (message.channelKind === "project" ? labels.unnamedProject : labels.unnamedGroup);
}
function initials(label: string): string {
const normalized = label.trim();
const parts = normalized.split(/\s+/).filter(Boolean);
return (parts.length > 1 ? `${parts[0]?.[0] ?? ""}${parts.at(-1)?.[0] ?? ""}` : normalized.slice(0, 2)).toUpperCase() || "?";
}