mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 09:07:46 +02:00
Backfill principal access compatibility
This commit is contained in:
1 parent
996c0e95e0
commit
bca338c19a
11 files changed
+466
-2
No files matched your search
@@ -0,0 +1,75 @@
|
||||
INSERT INTO "company_memberships" (
|
||||
"company_id",
|
||||
"principal_type",
|
||||
"principal_id",
|
||||
"status",
|
||||
"membership_role",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
)
|
||||
SELECT
|
||||
"company_id",
|
||||
'agent',
|
||||
"id",
|
||||
'active',
|
||||
'member',
|
||||
now(),
|
||||
now()
|
||||
FROM "agents"
|
||||
WHERE "status" NOT IN ('pending_approval', 'terminated')
|
||||
ON CONFLICT (
|
||||
"company_id",
|
||||
"principal_type",
|
||||
"principal_id"
|
||||
) DO NOTHING;
|
||||
|
||||
INSERT INTO "principal_permission_grants" (
|
||||
"company_id",
|
||||
"principal_type",
|
||||
"principal_id",
|
||||
"permission_key",
|
||||
"scope",
|
||||
"granted_by_user_id",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
)
|
||||
SELECT
|
||||
memberships."company_id",
|
||||
'user',
|
||||
memberships."principal_id",
|
||||
role_defaults."permission_key",
|
||||
NULL,
|
||||
NULL,
|
||||
now(),
|
||||
now()
|
||||
FROM "company_memberships" memberships
|
||||
JOIN (
|
||||
VALUES
|
||||
('owner', 'agents:create'),
|
||||
('owner', 'environments:manage'),
|
||||
('owner', 'users:invite'),
|
||||
('owner', 'users:manage_permissions'),
|
||||
('owner', 'tasks:assign'),
|
||||
('owner', 'joins:approve'),
|
||||
('admin', 'agents:create'),
|
||||
('admin', 'environments:manage'),
|
||||
('admin', 'users:invite'),
|
||||
('admin', 'tasks:assign'),
|
||||
('admin', 'joins:approve'),
|
||||
('operator', 'tasks:assign')
|
||||
) AS role_defaults("membership_role", "permission_key")
|
||||
ON role_defaults."membership_role" = CASE
|
||||
WHEN memberships."membership_role" = 'owner' THEN 'owner'
|
||||
WHEN memberships."membership_role" = 'admin' THEN 'admin'
|
||||
WHEN memberships."membership_role" = 'viewer' THEN 'viewer'
|
||||
WHEN memberships."membership_role" = 'member' THEN 'operator'
|
||||
ELSE 'operator'
|
||||
END
|
||||
WHERE memberships."principal_type" = 'user'
|
||||
AND memberships."status" = 'active'
|
||||
ON CONFLICT (
|
||||
"company_id",
|
||||
"principal_type",
|
||||
"principal_id",
|
||||
"permission_key"
|
||||
) DO NOTHING;
|
||||
@@ -617,6 +617,13 @@
|
||||
"when": 1779360000000,
|
||||
"tag": "0087_backfill_environment_manage_human_defaults",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 88,
|
||||
"version": "7",
|
||||
"when": 1779446400000,
|
||||
"tag": "0088_backfill_principal_access_compatibility",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
||||
import {
|
||||
agents,
|
||||
companies,
|
||||
companyMemberships,
|
||||
createDb,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
} from "./helpers/embedded-postgres.js";
|
||||
import { accessService } from "../services/access.js";
|
||||
import { grantsForHumanRole } from "../services/company-member-roles.js";
|
||||
import { backfillPrincipalAccessCompatibility } from "../services/principal-access-compatibility.js";
|
||||
|
||||
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
|
||||
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip;
|
||||
@@ -57,6 +59,7 @@ describeEmbeddedPostgres("access service", () => {
|
||||
await db.delete(issues);
|
||||
await db.delete(principalPermissionGrants);
|
||||
await db.delete(instanceUserRoles);
|
||||
await db.delete(agents);
|
||||
await db.delete(companyMemberships);
|
||||
await db.delete(companies);
|
||||
});
|
||||
@@ -267,6 +270,184 @@ describeEmbeddedPostgres("access service", () => {
|
||||
await expect(access.canUser(company.id, viewer.principalId, "environments:manage")).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("backfills pre-upgrade human memberships with missing role grants without replacing custom grants", async () => {
|
||||
const { company, owner } = await createCompanyWithOwner(db);
|
||||
const scopedEnvironmentGrant = { environmentId: "env-1" };
|
||||
const humanRows = await db
|
||||
.insert(companyMemberships)
|
||||
.values([
|
||||
{
|
||||
companyId: company.id,
|
||||
principalType: "user",
|
||||
principalId: `admin-${randomUUID()}`,
|
||||
status: "active",
|
||||
membershipRole: "admin",
|
||||
},
|
||||
{
|
||||
companyId: company.id,
|
||||
principalType: "user",
|
||||
principalId: `operator-${randomUUID()}`,
|
||||
status: "active",
|
||||
membershipRole: "operator",
|
||||
},
|
||||
{
|
||||
companyId: company.id,
|
||||
principalType: "user",
|
||||
principalId: `viewer-${randomUUID()}`,
|
||||
status: "active",
|
||||
membershipRole: "viewer",
|
||||
},
|
||||
{
|
||||
companyId: company.id,
|
||||
principalType: "user",
|
||||
principalId: `legacy-${randomUUID()}`,
|
||||
status: "active",
|
||||
membershipRole: null,
|
||||
},
|
||||
])
|
||||
.returning();
|
||||
const admin = humanRows[0]!;
|
||||
const operator = humanRows[1]!;
|
||||
const viewer = humanRows[2]!;
|
||||
const legacyMember = humanRows[3]!;
|
||||
|
||||
await db.insert(principalPermissionGrants).values({
|
||||
companyId: company.id,
|
||||
principalType: "user",
|
||||
principalId: owner.principalId,
|
||||
permissionKey: "environments:manage",
|
||||
scope: scopedEnvironmentGrant,
|
||||
grantedByUserId: "custom-author",
|
||||
});
|
||||
|
||||
const first = await backfillPrincipalAccessCompatibility(db);
|
||||
const second = await backfillPrincipalAccessCompatibility(db);
|
||||
|
||||
expect(first.humanGrantsInserted).toBeGreaterThan(0);
|
||||
expect(second.humanGrantsInserted).toBe(0);
|
||||
await expect(accessService(db).canUser(company.id, admin.principalId, "environments:manage")).resolves.toBe(true);
|
||||
await expect(accessService(db).canUser(company.id, operator.principalId, "tasks:assign")).resolves.toBe(true);
|
||||
await expect(accessService(db).canUser(company.id, legacyMember.principalId, "tasks:assign")).resolves.toBe(true);
|
||||
await expect(accessService(db).canUser(company.id, viewer.principalId, "tasks:assign")).resolves.toBe(false);
|
||||
|
||||
const ownerEnvironmentGrants = await db
|
||||
.select()
|
||||
.from(principalPermissionGrants)
|
||||
.where(
|
||||
and(
|
||||
eq(principalPermissionGrants.companyId, company.id),
|
||||
eq(principalPermissionGrants.principalId, owner.principalId),
|
||||
eq(principalPermissionGrants.permissionKey, "environments:manage"),
|
||||
),
|
||||
);
|
||||
expect(ownerEnvironmentGrants).toHaveLength(1);
|
||||
expect(ownerEnvironmentGrants[0]?.scope).toEqual(scopedEnvironmentGrant);
|
||||
expect(ownerEnvironmentGrants[0]?.grantedByUserId).toBe("custom-author");
|
||||
});
|
||||
|
||||
it("backfills non-terminal agents as active company members without reviving pending or terminated agents", async () => {
|
||||
const { company } = await createCompanyWithOwner(db);
|
||||
const agentRows = await db
|
||||
.insert(agents)
|
||||
.values([
|
||||
{
|
||||
companyId: company.id,
|
||||
name: `Idle ${randomUUID()}`,
|
||||
role: "engineer",
|
||||
status: "idle",
|
||||
adapterType: "process",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
},
|
||||
{
|
||||
companyId: company.id,
|
||||
name: `Running ${randomUUID()}`,
|
||||
role: "engineer",
|
||||
status: "running",
|
||||
adapterType: "process",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
},
|
||||
{
|
||||
companyId: company.id,
|
||||
name: `Pending ${randomUUID()}`,
|
||||
role: "engineer",
|
||||
status: "pending_approval",
|
||||
adapterType: "process",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
},
|
||||
{
|
||||
companyId: company.id,
|
||||
name: `Terminated ${randomUUID()}`,
|
||||
role: "engineer",
|
||||
status: "terminated",
|
||||
adapterType: "process",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
},
|
||||
])
|
||||
.returning();
|
||||
const idleAgent = agentRows[0]!;
|
||||
const runningAgent = agentRows[1]!;
|
||||
const pendingAgent = agentRows[2]!;
|
||||
const terminatedAgent = agentRows[3]!;
|
||||
|
||||
const first = await backfillPrincipalAccessCompatibility(db);
|
||||
const second = await backfillPrincipalAccessCompatibility(db);
|
||||
|
||||
expect(first.agentMembershipsInserted).toBe(2);
|
||||
expect(second.agentMembershipsInserted).toBe(0);
|
||||
const memberships = await db
|
||||
.select()
|
||||
.from(companyMemberships)
|
||||
.where(eq(companyMemberships.principalType, "agent"));
|
||||
expect(memberships.map((membership) => membership.principalId).sort()).toEqual([
|
||||
idleAgent.id,
|
||||
runningAgent.id,
|
||||
].sort());
|
||||
expect(memberships.every((membership) => membership.status === "active")).toBe(true);
|
||||
expect(memberships.every((membership) => membership.membershipRole === "member")).toBe(true);
|
||||
expect(memberships.some((membership) => membership.principalId === pendingAgent.id)).toBe(false);
|
||||
expect(memberships.some((membership) => membership.principalId === terminatedAgent.id)).toBe(false);
|
||||
});
|
||||
|
||||
it("copies active user memberships with role-default grants for safe company imports", async () => {
|
||||
const source = await createCompanyWithOwner(db);
|
||||
const target = await createCompanyWithOwner(db);
|
||||
const admin = await db
|
||||
.insert(companyMemberships)
|
||||
.values({
|
||||
companyId: source.company.id,
|
||||
principalType: "user",
|
||||
principalId: `admin-${randomUUID()}`,
|
||||
status: "active",
|
||||
membershipRole: "admin",
|
||||
})
|
||||
.returning()
|
||||
.then((rows) => rows[0]!);
|
||||
|
||||
const access = accessService(db);
|
||||
await access.copyActiveUserMemberships(source.company.id, target.company.id);
|
||||
|
||||
const copiedOwnerGrants = await access.listPrincipalGrants(
|
||||
target.company.id,
|
||||
"user",
|
||||
source.owner.principalId,
|
||||
);
|
||||
const copiedAdminGrants = await access.listPrincipalGrants(
|
||||
target.company.id,
|
||||
"user",
|
||||
admin.principalId,
|
||||
);
|
||||
expect(copiedOwnerGrants.map((grant) => grant.permissionKey)).toEqual(
|
||||
grantsForHumanRole("owner").map((grant) => grant.permissionKey).sort(),
|
||||
);
|
||||
expect(copiedAdminGrants.map((grant) => grant.permissionKey)).toEqual(
|
||||
grantsForHumanRole("admin").map((grant) => grant.permissionKey).sort(),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves explicit scoped environment grants when backfilling owner and admin defaults", async () => {
|
||||
const { company, owner } = await createCompanyWithOwner(db);
|
||||
const scopedGrant = { environmentId: "env-1" };
|
||||
|
||||
@@ -20,6 +20,7 @@ const agentSvc = {
|
||||
|
||||
const accessSvc = {
|
||||
ensureMembership: vi.fn(),
|
||||
ensureRoleDefaultGrants: vi.fn(),
|
||||
listActiveUserMemberships: vi.fn(),
|
||||
copyActiveUserMemberships: vi.fn(),
|
||||
setPrincipalPermission: vi.fn(),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { and, eq } from "drizzle-orm";
|
||||
import type { Db } from "@paperclipai/db";
|
||||
import { companies, companyMemberships, instanceUserRoles } from "@paperclipai/db";
|
||||
import type { DeploymentMode } from "@paperclipai/shared";
|
||||
import { ensureHumanRoleDefaultGrants } from "./services/principal-access-compatibility.js";
|
||||
|
||||
const LOCAL_BOARD_USER_ID = "local-board";
|
||||
const CLAIM_TTL_MS = 1000 * 60 * 60 * 24;
|
||||
@@ -89,6 +90,7 @@ export async function claimBoardOwnership(
|
||||
const status = getChallengeStatus(opts.token, opts.code);
|
||||
if (status !== "available") return { status };
|
||||
|
||||
const claimedCompanyIds: string[] = [];
|
||||
await db.transaction(async (tx) => {
|
||||
const existingTargetAdmin = await tx
|
||||
.select({ id: instanceUserRoles.id })
|
||||
@@ -108,6 +110,7 @@ export async function claimBoardOwnership(
|
||||
|
||||
const allCompanies = await tx.select({ id: companies.id }).from(companies);
|
||||
for (const company of allCompanies) {
|
||||
claimedCompanyIds.push(company.id);
|
||||
const existing = await tx
|
||||
.select({ id: companyMemberships.id, status: companyMemberships.status })
|
||||
.from(companyMemberships)
|
||||
@@ -140,6 +143,15 @@ export async function claimBoardOwnership(
|
||||
}
|
||||
});
|
||||
|
||||
for (const companyId of claimedCompanyIds) {
|
||||
await ensureHumanRoleDefaultGrants(db, {
|
||||
companyId,
|
||||
principalId: opts.userId,
|
||||
membershipRole: "owner",
|
||||
grantedByUserId: opts.userId,
|
||||
});
|
||||
}
|
||||
|
||||
if (activeChallenge && activeChallenge.token === opts.token) {
|
||||
activeChallenge.claimedAt = new Date();
|
||||
activeChallenge.claimedByUserId = opts.userId;
|
||||
|
||||
@@ -30,6 +30,7 @@ import { logger } from "./middleware/logger.js";
|
||||
import { setupLiveEventsWebSocketServer } from "./realtime/live-events-ws.js";
|
||||
import {
|
||||
feedbackService,
|
||||
backfillPrincipalAccessCompatibility,
|
||||
heartbeatService,
|
||||
instanceSettingsService,
|
||||
reconcilePersistedRuntimeServicesOnStartup,
|
||||
@@ -512,6 +513,10 @@ export async function startServer(): Promise<StartedServer> {
|
||||
if (config.deploymentMode === "local_trusted") {
|
||||
await ensureLocalTrustedBoardPrincipal(db as any);
|
||||
}
|
||||
const accessBackfill = await backfillPrincipalAccessCompatibility(db as any);
|
||||
if (accessBackfill.agentMembershipsInserted > 0 || accessBackfill.humanGrantsInserted > 0) {
|
||||
logger.info(accessBackfill, "Backfilled principal access compatibility records");
|
||||
}
|
||||
if (config.deploymentMode === "authenticated") {
|
||||
const {
|
||||
createBetterAuthHandler,
|
||||
|
||||
@@ -271,7 +271,14 @@ export function companyRoutes(db: Db, storage?: StorageService) {
|
||||
throw forbidden("Instance admin required");
|
||||
}
|
||||
const company = await svc.create(req.body);
|
||||
await access.ensureMembership(company.id, "user", req.actor.userId ?? "local-board", "owner", "active");
|
||||
const ownerPrincipalId = req.actor.userId ?? "local-board";
|
||||
await access.ensureMembership(company.id, "user", ownerPrincipalId, "owner", "active");
|
||||
await access.ensureRoleDefaultGrants(
|
||||
company.id,
|
||||
ownerPrincipalId,
|
||||
"owner",
|
||||
req.actor.userId ?? null,
|
||||
);
|
||||
await logActivity(db, {
|
||||
companyId: company.id,
|
||||
actorType: "user",
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import type { PermissionKey, PrincipalType } from "@paperclipai/shared";
|
||||
import { conflict } from "../errors.js";
|
||||
import { authorizationService, type AuthorizationActor, type AuthorizationResource } from "./authorization.js";
|
||||
import { ensureHumanRoleDefaultGrants } from "./principal-access-compatibility.js";
|
||||
|
||||
type MembershipRow = typeof companyMemberships.$inferSelect;
|
||||
type GrantInput = {
|
||||
@@ -622,10 +623,30 @@ export function accessService(db: Db) {
|
||||
membership.membershipRole,
|
||||
"active",
|
||||
);
|
||||
await ensureHumanRoleDefaultGrants(db, {
|
||||
companyId: targetCompanyId,
|
||||
principalId: membership.principalId,
|
||||
membershipRole: membership.membershipRole,
|
||||
grantedByUserId: null,
|
||||
});
|
||||
}
|
||||
return sourceMemberships;
|
||||
}
|
||||
|
||||
async function ensureRoleDefaultGrants(
|
||||
companyId: string,
|
||||
principalId: string,
|
||||
membershipRole: string | null | undefined,
|
||||
grantedByUserId: string | null,
|
||||
) {
|
||||
return ensureHumanRoleDefaultGrants(db, {
|
||||
companyId,
|
||||
principalId,
|
||||
membershipRole,
|
||||
grantedByUserId,
|
||||
});
|
||||
}
|
||||
|
||||
async function listPrincipalGrants(
|
||||
companyId: string,
|
||||
principalType: PrincipalType,
|
||||
@@ -783,6 +804,7 @@ export function accessService(db: Db) {
|
||||
listMembers,
|
||||
listActiveUserMemberships,
|
||||
copyActiveUserMemberships,
|
||||
ensureRoleDefaultGrants,
|
||||
archiveMember,
|
||||
setMemberPermissions,
|
||||
updateMemberAndPermissions,
|
||||
|
||||
@@ -4118,7 +4118,14 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
|
||||
if (mode === "agent_safe" && options?.sourceCompanyId) {
|
||||
await access.copyActiveUserMemberships(options.sourceCompanyId, created.id);
|
||||
} else {
|
||||
await access.ensureMembership(created.id, "user", actorUserId ?? "board", "owner", "active");
|
||||
const ownerPrincipalId = actorUserId ?? "board";
|
||||
await access.ensureMembership(created.id, "user", ownerPrincipalId, "owner", "active");
|
||||
await access.ensureRoleDefaultGrants(
|
||||
created.id,
|
||||
ownerPrincipalId,
|
||||
"owner",
|
||||
actorUserId ?? null,
|
||||
);
|
||||
}
|
||||
targetCompany = created;
|
||||
companyAction = "created";
|
||||
|
||||
@@ -44,6 +44,12 @@ export { sidebarBadgeService } from "./sidebar-badges.js";
|
||||
export { sidebarPreferenceService } from "./sidebar-preferences.js";
|
||||
export { inboxDismissalService } from "./inbox-dismissals.js";
|
||||
export { accessService } from "./access.js";
|
||||
export {
|
||||
backfillPrincipalAccessCompatibility,
|
||||
ensureHumanRoleDefaultGrants,
|
||||
insertMissingPrincipalGrants,
|
||||
type PrincipalAccessCompatibilityBackfillStats,
|
||||
} from "./principal-access-compatibility.js";
|
||||
export { authorizationService } from "./authorization.js";
|
||||
export type {
|
||||
AuthorizationAction,
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import { and, eq, notInArray } from "drizzle-orm";
|
||||
import type { Db } from "@paperclipai/db";
|
||||
import { agents, companyMemberships, principalPermissionGrants } from "@paperclipai/db";
|
||||
import type { PermissionKey, PrincipalType } from "@paperclipai/shared";
|
||||
import { grantsForHumanRole, normalizeHumanRole } from "./company-member-roles.js";
|
||||
|
||||
type GrantInput = {
|
||||
permissionKey: PermissionKey;
|
||||
scope?: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
export type PrincipalAccessCompatibilityBackfillStats = {
|
||||
agentMembershipsInserted: number;
|
||||
humanGrantsInserted: number;
|
||||
};
|
||||
|
||||
export async function insertMissingPrincipalGrants(
|
||||
db: Db,
|
||||
input: {
|
||||
companyId: string;
|
||||
principalType: PrincipalType;
|
||||
principalId: string;
|
||||
grants: GrantInput[];
|
||||
grantedByUserId: string | null;
|
||||
},
|
||||
): Promise<number> {
|
||||
if (input.grants.length === 0) return 0;
|
||||
|
||||
const now = new Date();
|
||||
const inserted = await db
|
||||
.insert(principalPermissionGrants)
|
||||
.values(
|
||||
input.grants.map((grant) => ({
|
||||
companyId: input.companyId,
|
||||
principalType: input.principalType,
|
||||
principalId: input.principalId,
|
||||
permissionKey: grant.permissionKey,
|
||||
scope: grant.scope ?? null,
|
||||
grantedByUserId: input.grantedByUserId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})),
|
||||
)
|
||||
.onConflictDoNothing({
|
||||
target: [
|
||||
principalPermissionGrants.companyId,
|
||||
principalPermissionGrants.principalType,
|
||||
principalPermissionGrants.principalId,
|
||||
principalPermissionGrants.permissionKey,
|
||||
],
|
||||
})
|
||||
.returning({ id: principalPermissionGrants.id });
|
||||
|
||||
return inserted.length;
|
||||
}
|
||||
|
||||
export async function ensureHumanRoleDefaultGrants(
|
||||
db: Db,
|
||||
input: {
|
||||
companyId: string;
|
||||
principalId: string;
|
||||
membershipRole: string | null | undefined;
|
||||
grantedByUserId: string | null;
|
||||
},
|
||||
): Promise<number> {
|
||||
const role = normalizeHumanRole(input.membershipRole, "operator");
|
||||
return insertMissingPrincipalGrants(db, {
|
||||
companyId: input.companyId,
|
||||
principalType: "user",
|
||||
principalId: input.principalId,
|
||||
grants: grantsForHumanRole(role),
|
||||
grantedByUserId: input.grantedByUserId,
|
||||
});
|
||||
}
|
||||
|
||||
export async function backfillPrincipalAccessCompatibility(
|
||||
db: Db,
|
||||
): Promise<PrincipalAccessCompatibilityBackfillStats> {
|
||||
const now = new Date();
|
||||
const nonTerminalAgents = await db
|
||||
.select({
|
||||
companyId: agents.companyId,
|
||||
principalId: agents.id,
|
||||
})
|
||||
.from(agents)
|
||||
.where(notInArray(agents.status, ["pending_approval", "terminated"]));
|
||||
|
||||
const agentMembershipsInserted = nonTerminalAgents.length > 0
|
||||
? await db
|
||||
.insert(companyMemberships)
|
||||
.values(
|
||||
nonTerminalAgents.map((agent) => ({
|
||||
companyId: agent.companyId,
|
||||
principalType: "agent",
|
||||
principalId: agent.principalId,
|
||||
status: "active",
|
||||
membershipRole: "member",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})),
|
||||
)
|
||||
.onConflictDoNothing({
|
||||
target: [
|
||||
companyMemberships.companyId,
|
||||
companyMemberships.principalType,
|
||||
companyMemberships.principalId,
|
||||
],
|
||||
})
|
||||
.returning({ id: companyMemberships.id })
|
||||
.then((rows) => rows.length)
|
||||
: 0;
|
||||
|
||||
const activeHumanMemberships = await db
|
||||
.select({
|
||||
companyId: companyMemberships.companyId,
|
||||
principalId: companyMemberships.principalId,
|
||||
membershipRole: companyMemberships.membershipRole,
|
||||
})
|
||||
.from(companyMemberships)
|
||||
.where(
|
||||
and(
|
||||
eq(companyMemberships.principalType, "user"),
|
||||
eq(companyMemberships.status, "active"),
|
||||
),
|
||||
);
|
||||
|
||||
let humanGrantsInserted = 0;
|
||||
for (const membership of activeHumanMemberships) {
|
||||
humanGrantsInserted += await ensureHumanRoleDefaultGrants(db, {
|
||||
companyId: membership.companyId,
|
||||
principalId: membership.principalId,
|
||||
membershipRole: membership.membershipRole,
|
||||
grantedByUserId: null,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
agentMembershipsInserted,
|
||||
humanGrantsInserted,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user