Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/app/languine.lock
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,21 @@ files:
user_menu.settings: f4f70727dc34561dfde1a3c529b6205c
user_menu.teams: 1fe1b6cf4f52930c301b03e5a69c42c2
frameworks.title: 04fb5df59766a9852e5dd2d0430ca27e
frameworks.overview.error: 4b391acbc8dd7f27c6b1a3a7424ca32b
frameworks.overview.loading: 5419fcb0c4ef464156ab6a3ddb84a1b2
frameworks.overview.empty.title: 52dfa6681993843928351cd9c5276ce0
frameworks.overview.empty.description: 12c6ccdede82f3bac2b486157ab5c98b
frameworks.overview.progress.title: 9c53d5a3379ee2c9bc7c1c55f54724b9
frameworks.overview.progress.empty.title: d58046b18909d6337ef6fa050b44f690
frameworks.overview.progress.empty.description: 6d27f139ab529d4ef3311f719a5e7c83
frameworks.overview.progress.empty.action: 1eb71b6f13a7af1f1c88fded44717525
frameworks.overview.grid.welcome.title: 54efd9605180a9a74e6d1a53529f858e
frameworks.overview.grid.welcome.description: 6d8ed4e08e874457c002a9db2075c813
frameworks.overview.grid.welcome.action: e0c4332e8c13be976552a059f106354f
frameworks.overview.grid.title: 55ffb038f3a6c5b740ec2b5ec6dd1733
frameworks.overview.grid.version: 34b6cd75171affba6957e308dcbd92be
frameworks.overview.grid.actions.clear: dc30bc0c7914db5918da4263fce93ad2
frameworks.overview.grid.actions.confirm: f1e36ba5764b8ddcf6064d977f0482bb
frameworks.controls.title: 24b6472fcfa59cddd3f7bbc326582b5f
frameworks.controls.description: de7fdb6cf858368e9e60bb991149b912
frameworks.controls.table.status: ec53a8c4f07baed5d8825072c89799be
Expand Down
4 changes: 2 additions & 2 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@ai-sdk/google": "^1.1.8",
"@ai-sdk/openai": "^1.1.9",
"@browserbasehq/sdk": "^2.2.0",
"@bubba/notifications": "workspace:*",
"@date-fns/tz": "^1.2.0",
"@hookform/resolvers": "^3.10.0",
"@nangohq/frontend": "^0.48.4",
Expand Down Expand Up @@ -87,7 +88,6 @@
},
"devDependencies": {
"@bubba/db": "workspace:*",
"@bubba/notifications": "workspace:*",
"@trigger.dev/build": "3.3.13",
"@types/node": "^22.13.0",
"@types/react": "^19.0.8",
Expand All @@ -96,4 +96,4 @@
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3"
}
}
}
9 changes: 6 additions & 3 deletions apps/app/src/actions/framework/get-framework-categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const getFrameworkCategoriesAction = authActionClient
artifacts: true,
},
},
requirements: true,
},
},
},
Expand All @@ -60,12 +61,14 @@ export const getFrameworkCategoriesAction = authActionClient
name: control.name,
code: control.code,
description: control.description,
categoryId: control.categoryId,
requiredArtifactTypes: control.requiredArtifactTypes,
domain: control.domain,
frameworkCategoryId: control.frameworkCategoryId,
status: control.organizationControls[0]?.status || "not_started",
artifacts: control.organizationControls[0]?.artifacts || [],
requiredArtifactTypes:
control.requirements?.map((req) => req.type) || [],
})),
}),
})
);

return {
Expand Down
62 changes: 62 additions & 0 deletions apps/app/src/actions/framework/get-frameworks-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use server";

import { db } from "@bubba/db";
import { authActionClient } from "../safe-action";
import type {
Framework,
OrganizationControl,
OrganizationFramework,
} from "@bubba/db";
import { z } from "zod";
import type { ActionData } from "../types";

type FrameworkWithControls = OrganizationFramework & {
organizationControl: OrganizationControl[];
framework: Framework;
};

export interface FrameworksResponse {
frameworks: FrameworkWithControls[];
availableFrameworks: Framework[];
}

export const getFrameworksAction = authActionClient
.schema(z.void())
.metadata({
name: "getFrameworks",
track: {
event: "get-frameworks",
channel: "server",
},
})
.action(async ({ ctx }): Promise<ActionData<FrameworksResponse>> => {
const { user } = ctx;

if (!user.organizationId) {
return {
error: "Not authorized - no organization found",
};
}

try {
const [frameworks, availableFrameworks] = await Promise.all([
db.organizationFramework.findMany({
where: { organizationId: user.organizationId },
include: {
organizationControl: true,
framework: true,
},
}),
db.framework.findMany(),
]);

return {
data: { frameworks, availableFrameworks },
};
} catch (error) {
console.error("Error fetching frameworks:", error);
return {
error: "Failed to fetch frameworks",
};
}
});
143 changes: 143 additions & 0 deletions apps/app/src/actions/framework/select-frameworks-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"use server";

import { db, type Policy, type User } from "@bubba/db";
import { authActionClient } from "../safe-action";
import { z } from "zod";
import type { ActionData } from "../types";

const selectFrameworksSchema = z.object({
frameworkIds: z.array(z.string()),
});

export const selectFrameworksAction = authActionClient
.schema(selectFrameworksSchema)
.metadata({
name: "select-frameworks",
track: {
event: "select-frameworks",
channel: "server",
},
})
.action(async ({ parsedInput, ctx }): Promise<ActionData<true>> => {
const { frameworkIds } = parsedInput;
const { user } = ctx;

if (!user.organizationId) {
return {
error: "Not authorized - no organization found",
};
}

try {
await Promise.all([
frameworkIds.map((frameworkId) =>
createOrganizationFramework(user as User, frameworkId)
),
createOrganizationPolicy(user as User, frameworkIds),
]);
Comment on lines +32 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix concurrency bug in Promise.all usage.

You're passing an array of promises as the first element rather than spreading them. Currently:

await Promise.all([
  frameworkIds.map((frameworkId) => createOrganizationFramework(user, frameworkId)),
  createOrganizationPolicy(user, frameworkIds),
]);

This means the first element is just an array, not a promise for each framework. Consider spreading or concatenating the mapped promises. For example:

- await Promise.all([
-   frameworkIds.map((frameworkId) =>
-     createOrganizationFramework(user, frameworkId)
-   ),
-   createOrganizationPolicy(user, frameworkIds),
- ]);
+ await Promise.all(
+   frameworkIds.map((frameworkId) =>
+     createOrganizationFramework(user, frameworkId)
+   ).concat(
+     createOrganizationPolicy(user, frameworkIds)
+   )
+ );


return {
data: true,
};
} catch (error) {
console.error("Error selecting frameworks:", error);
return {
error: "Failed to select frameworks",
};
}
});

const createOrganizationFramework = async (user: User, frameworkId: string) => {
if (!user.organizationId) {
throw new Error("Not authorized - no organization found");
}

// Connect the framework to the organization.
const organizationFramework = await db.organizationFramework.create({
data: {
organizationId: user.organizationId,
frameworkId,
status: "not_started",
},
select: {
id: true,
},
});

// For each framework we need to get the categories and controls.
const framework = await db.framework.findUnique({
where: { id: frameworkId },
});

if (!framework) {
throw new Error("Framework not found");
}

const frameworkCategories = await db.frameworkCategory.findMany({
where: { frameworkId },
select: {
id: true,
},
});

// For each category we need to get the controls.
const frameworkControls = await db.control.findMany({
where: {
frameworkCategoryId: {
in: frameworkCategories.map((category) => category.id),
},
},
select: {
id: true,
},
});

if (!user.organizationId) {
throw new Error("Organization ID is required");
}

await db.organizationControl.createMany({
data: frameworkControls.map((control) => ({
organizationFrameworkId: organizationFramework.id,
controlId: control.id,
organizationId: user.organizationId!,
status: "not_started",
})),
});
};

const createOrganizationPolicy = async (user: User, frameworkIds: string[]) => {
if (!user.organizationId) {
throw new Error("Not authorized - no organization found");
}

const policies = await db.policy.findMany();
const policiesForFrameworks: Policy[] = [];

for (const policy of policies) {
const usedBy = policy.usedBy;
if (!usedBy) {
continue;
}

const usedByFrameworkIds = Object.keys(usedBy);

if (
usedByFrameworkIds.some((frameworkId) =>
frameworkIds.includes(frameworkId)
)
) {
policiesForFrameworks.push(policy);
}
}

const organizationPolicies = await db.organizationPolicy.createMany({
data: policiesForFrameworks.map((policy) => ({
organizationId: user.organizationId!,
policyId: policy.id,
status: "draft",
})),
});

return organizationPolicies;
};
21 changes: 8 additions & 13 deletions apps/app/src/actions/organization/create-organization-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { tasks } from "@trigger.dev/sdk/v3";
import { revalidateTag } from "next/cache";
import { authActionClient } from "../safe-action";
import { organizationSchema } from "../schema";
import { soc2Seed } from "../soc2-seed";

export const createOrganizationAction = authActionClient
.schema(organizationSchema)
Expand Down Expand Up @@ -96,18 +95,14 @@ export const createOrganizationAction = authActionClient
});
});

await soc2Seed({
organizationId: organization.id,
});

await tasks.trigger<typeof createDefaultPoliciesTask>(
"create-default-policies",
{
ownerId: userId,
organizationId: organization.id,
organizationName: name,
},
);
// await tasks.trigger<typeof createDefaultPoliciesTask>(
// "create-default-policies",
// {
// ownerId: userId,
// organizationId: organization.id,
// organizationName: name,
// }
// );

revalidateTag(`user_${userId}`);
revalidateTag(`organization_${organizationId}`);
Expand Down
Loading