import { CHAT_PERMISSIONS } from "@hivegrid/chat"; import { createGroupChat, getOrCreateDirectMessage } from "@hivegrid/chat/lifecycle.server"; import { withTenantScope } from "@hivegrid/foundation"; import { buildChatChannelCreateVM, type ChatChannelCreateMode, type ChatChannelCreateSubmission, type ChatChannelCreateVM, type ChatPermissionGroupOptionInput, type ChatTenantUserOptionInput, } from "@hivegrid/view-models"; import { TenantUserStatus } from "~/application/enums/tenants/TenantUserStatus"; import { withTenantRLS } from "~/utils/db/.server/tenantRls.server"; import { chatHasPermission, requireChatPermission, type ChatRouteContext } from "../routes/chatAccess.server"; export async function loadChatChannelCreatePage(input: { readonly context: ChatRouteContext; readonly requestedMode?: ChatChannelCreateMode; }): Promise { const permissions = { canCreateGroup: chatHasPermission(input.context, CHAT_PERMISSIONS.createChannel), canCreateDm: chatHasPermission(input.context, CHAT_PERMISSIONS.createDm), }; const options = permissions.canCreateGroup || permissions.canCreateDm ? await loadChatChannelCreateOptions(input.context, permissions.canCreateGroup) : { tenantUsers: [], tenantGroups: [] }; return buildChatChannelCreateVM({ currentUserId: input.context.userId, tenantUsers: options.tenantUsers, tenantGroups: options.tenantGroups, permissions, requestedMode: input.requestedMode, }); } export async function createChatChannel(input: { readonly context: ChatRouteContext; readonly requestId: string; readonly submission: ChatChannelCreateSubmission; }): Promise<{ readonly chatId: string }> { if (input.submission.mode === "group") { const context = requireChatPermission(input.context, CHAT_PERMISSIONS.createChannel); return createGroupChat({ tenantId: context.tenantId, userId: context.userId, permissions: context.permissions, requestId: input.requestId, name: input.submission.name, description: input.submission.description, isPrivate: input.submission.privacy === "private", notificationDefault: "all_messages", initialMemberUserIds: input.submission.initialUserIds, initialGroupIds: input.submission.initialGroupIds, syncGroupMembership: input.submission.syncGroupMembership, }); } const context = requireChatPermission(input.context, CHAT_PERMISSIONS.createDm); return getOrCreateDirectMessage({ tenantId: context.tenantId, userId: context.userId, permissions: context.permissions, requestId: input.requestId, peerUserId: input.submission.peerUserId, }); } async function loadChatChannelCreateOptions( context: ChatRouteContext, includeGroups: boolean ): Promise> { const { memberships, groups } = await withTenantRLS(context.tenantId, async (tx) => { const membershipsPromise = tx.tenantUser.findMany({ where: { tenantId: context.tenantId, status: TenantUserStatus.ACTIVE, user: { active: true, pendingDeletion: false, pseudonymized: false, }, }, select: { userId: true, user: { select: { email: true, firstName: true, lastName: true, avatar: true, }, }, }, orderBy: [{ user: { firstName: "asc" } }, { user: { lastName: "asc" } }, { user: { email: "asc" } }], }); const groupsPromise = includeGroups ? tx.group.findMany({ where: withTenantScope(context.tenantId, {}), select: { id: true, name: true, description: true, users: { where: { user: { active: true, pendingDeletion: false, pseudonymized: false, tenants: { some: withTenantScope(context.tenantId, { status: TenantUserStatus.ACTIVE }) }, }, }, select: { userId: true }, }, }, orderBy: [{ name: "asc" }, { id: "asc" }], }) : Promise.resolve([]); const [memberships, groups] = await Promise.all([membershipsPromise, groupsPromise]); return { memberships, groups }; }); const tenantUsers = memberships.map(({ userId, user }) => { const displayName = [user.firstName, user.lastName] .map((part) => part?.trim()) .filter((part): part is string => Boolean(part)) .join(" ") || user.email; const fallbackLabel = [user.firstName?.trim().slice(0, 1), user.lastName?.trim().slice(0, 1)] .filter((part): part is string => Boolean(part)) .join("") .toLocaleUpperCase() || user.email.slice(0, 1).toLocaleUpperCase() || "?"; return { userId, displayName, email: user.email, avatar: { imageUrl: user.avatar?.trim() || null, fallbackLabel, }, }; }); const tenantGroups = groups.map((group) => ({ groupId: group.id, name: group.name, description: group.description, memberCount: group.users.length, })); return { tenantUsers, tenantGroups }; }