forked from bai/curriculum-project-hub
feat: add organization tenant model
This commit is contained in:
@@ -13,6 +13,9 @@ export interface ExternalPrincipalMembershipInput {
|
||||
}
|
||||
|
||||
export interface SyncExternalPrincipalMembershipsInput {
|
||||
readonly organizationId: string;
|
||||
readonly provider?: string | undefined;
|
||||
readonly providerTenantId?: string | undefined;
|
||||
readonly source: string;
|
||||
readonly replaceSource?: boolean | undefined;
|
||||
readonly memberships: readonly ExternalPrincipalMembershipInput[];
|
||||
@@ -30,6 +33,26 @@ export async function syncExternalPrincipalMemberships(
|
||||
const syncedAt = new Date();
|
||||
const incomingKeys = new Set<string>();
|
||||
let synced = 0;
|
||||
const connection = await prisma.externalDirectoryConnection.upsert({
|
||||
where: {
|
||||
organizationId_provider_source: {
|
||||
organizationId: input.organizationId,
|
||||
provider: input.provider ?? "FEISHU",
|
||||
source: input.source,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
revokedAt: null,
|
||||
...(input.providerTenantId !== undefined ? { providerTenantId: input.providerTenantId } : {}),
|
||||
},
|
||||
create: {
|
||||
organizationId: input.organizationId,
|
||||
provider: input.provider ?? "FEISHU",
|
||||
...(input.providerTenantId !== undefined ? { providerTenantId: input.providerTenantId } : {}),
|
||||
source: input.source,
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
for (const item of input.memberships) {
|
||||
assertExternalPrincipalType(item.principalType);
|
||||
@@ -40,15 +63,17 @@ export async function syncExternalPrincipalMemberships(
|
||||
feishuOpenId: item.feishuOpenId,
|
||||
displayName: item.displayName,
|
||||
platformRoles: { create: { role: "TEACHER" } },
|
||||
organizationMemberships: { create: { organizationId: input.organizationId, role: "MEMBER" } },
|
||||
},
|
||||
});
|
||||
incomingKeys.add(externalMembershipKey(user.id, item.principalType, item.principalId));
|
||||
await ensureOrganizationMembership(prisma, input.organizationId, user.id);
|
||||
incomingKeys.add(externalMembershipKey(user.id, item.principalType, item.principalId, connection.id));
|
||||
const existing = await prisma.externalPrincipalMembership.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
principalType: item.principalType,
|
||||
principalId: item.principalId,
|
||||
source: input.source,
|
||||
connectionId: connection.id,
|
||||
revokedAt: null,
|
||||
},
|
||||
select: { id: true },
|
||||
@@ -57,9 +82,9 @@ export async function syncExternalPrincipalMemberships(
|
||||
await prisma.externalPrincipalMembership.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
connectionId: connection.id,
|
||||
principalType: item.principalType,
|
||||
principalId: item.principalId,
|
||||
source: input.source,
|
||||
syncedAt,
|
||||
},
|
||||
});
|
||||
@@ -75,14 +100,15 @@ export async function syncExternalPrincipalMemberships(
|
||||
let revoked = 0;
|
||||
if (input.replaceSource === true) {
|
||||
const active = await prisma.externalPrincipalMembership.findMany({
|
||||
where: { source: input.source, revokedAt: null },
|
||||
select: { id: true, userId: true, principalType: true, principalId: true },
|
||||
where: { connectionId: connection.id, revokedAt: null },
|
||||
select: { id: true, userId: true, principalType: true, principalId: true, connectionId: true },
|
||||
});
|
||||
const revokeIds = active
|
||||
.filter((membership) => !incomingKeys.has(externalMembershipKey(
|
||||
membership.userId,
|
||||
membership.principalType,
|
||||
membership.principalId,
|
||||
membership.connectionId,
|
||||
)))
|
||||
.map((membership) => membership.id);
|
||||
if (revokeIds.length > 0) {
|
||||
@@ -97,8 +123,23 @@ export async function syncExternalPrincipalMemberships(
|
||||
return { synced, revoked };
|
||||
}
|
||||
|
||||
function externalMembershipKey(userId: string, type: PrincipalType, id: string): string {
|
||||
return `${userId}:${type}:${id}`;
|
||||
async function ensureOrganizationMembership(
|
||||
prisma: PrismaClient,
|
||||
organizationId: string,
|
||||
userId: string,
|
||||
): Promise<void> {
|
||||
const active = await prisma.organizationMembership.findFirst({
|
||||
where: { organizationId, userId, revokedAt: null },
|
||||
select: { id: true },
|
||||
});
|
||||
if (active !== null) return;
|
||||
await prisma.organizationMembership.create({
|
||||
data: { organizationId, userId, role: "MEMBER" },
|
||||
});
|
||||
}
|
||||
|
||||
function externalMembershipKey(userId: string, type: PrincipalType, id: string, connectionId: string): string {
|
||||
return `${connectionId}:${userId}:${type}:${id}`;
|
||||
}
|
||||
|
||||
function assertExternalPrincipalType(type: PrincipalType): asserts type is ExternalPrincipalType {
|
||||
|
||||
Reference in New Issue
Block a user