159 lines
5.3 KiB
TypeScript
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 };
|
|
}
|