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

159 lines
5.3 KiB
TypeScript

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<ChatChannelCreateVM> {
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<Readonly<{ tenantUsers: readonly ChatTenantUserOptionInput[]; tenantGroups: readonly ChatPermissionGroupOptionInput[] }>> {
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 };
}