-
Notifications
You must be signed in to change notification settings - Fork 321
Mariano/stuff #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mariano/stuff #55
Changes from all commits
810217b
61a3e84
8f936ce
770b268
c2870bb
0d7f855
acb7df8
09b3d2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| "use server"; | ||
|
|
||
| import { db, OrganizationControl, Control } from "@bubba/db"; | ||
| import { authActionClient } from "@/actions/safe-action"; | ||
| import { z } from "zod"; | ||
|
|
||
| export interface OrganizationControlResponse { | ||
| organizationControl: OrganizationControl & { | ||
| control: Control; | ||
| }; | ||
| } | ||
|
|
||
| export const getOrganizationControl = authActionClient | ||
| .schema(z.object({ controlId: z.string() })) | ||
| .metadata({ | ||
| name: "getOrganizationControl", | ||
| track: { | ||
| event: "get-organization-control", | ||
| channel: "server", | ||
| }, | ||
| }) | ||
| .action(async ({ ctx, parsedInput }) => { | ||
| const { user } = ctx; | ||
| const { controlId } = parsedInput; | ||
|
|
||
| if (!user.organizationId) { | ||
| return { | ||
| error: "Not authorized - no organization found", | ||
| }; | ||
| } | ||
|
|
||
| try { | ||
| const organizationControl = await db.organizationControl.findUnique({ | ||
| where: { | ||
| organizationId: user.organizationId, | ||
| id: controlId, | ||
| }, | ||
| include: { | ||
| control: true, | ||
| }, | ||
| }); | ||
|
|
||
| if (!organizationControl) { | ||
| return { | ||
| error: "Organization control not found", | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| data: { | ||
| organizationControl, | ||
| }, | ||
| }; | ||
| } catch (error) { | ||
| console.error("Error fetching organization control:", error); | ||
| return { | ||
| error: "Failed to fetch organization control", | ||
| }; | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||
| "use client"; | ||||||||||||||||
|
|
||||||||||||||||
| import { | ||||||||||||||||
| DisplayFrameworkStatus, | ||||||||||||||||
| StatusType, | ||||||||||||||||
| } from "@/components/frameworks/framework-status"; | ||||||||||||||||
| import { useOrganizationControl } from "../hooks/useOrganizationControl"; | ||||||||||||||||
| import { Card } from "@bubba/ui/card"; | ||||||||||||||||
| import { Label } from "@bubba/ui/label"; | ||||||||||||||||
|
|
||||||||||||||||
| interface SingleControlProps { | ||||||||||||||||
| controlId: string; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| export const SingleControl = ({ controlId }: SingleControlProps) => { | ||||||||||||||||
| const { data: control } = useOrganizationControl(controlId); | ||||||||||||||||
| if (!control) return null; | ||||||||||||||||
|
Comment on lines
+16
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider handling loading and error states. The component currently returns null when data is not available, but it should handle loading and error states explicitly. - const { data: control } = useOrganizationControl(controlId);
- if (!control) return null;
+ const { data: control, isLoading, error } = useOrganizationControl(controlId);
+
+ if (isLoading) return <div>Loading...</div>;
+ if (error) return <div>Error: {error.message}</div>;
+ if (!control) return <div>No control found</div>;📝 Committable suggestion
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| return ( | ||||||||||||||||
| <div className="max-w-[1200px] mx-auto py-8 gap-4 flex flex-col"> | ||||||||||||||||
| <div className="flex flex-row justify-between items-center"> | ||||||||||||||||
| <h1 className="text-3xl font-bold">{control?.control.name}</h1> | ||||||||||||||||
| <DisplayFrameworkStatus | ||||||||||||||||
| status={control?.status.toLowerCase() as StatusType} | ||||||||||||||||
| /> | ||||||||||||||||
| </div> | ||||||||||||||||
| <div className="grid grid-cols-2 gap-4"> | ||||||||||||||||
| <Card className="flex flex-col gap-2 p-4 px-8 min-h-[200px]"> | ||||||||||||||||
| <div> | ||||||||||||||||
| <Label className="text-lg">Description</Label> | ||||||||||||||||
| <p className="text-sm">{control?.control.description}</p> | ||||||||||||||||
| </div> | ||||||||||||||||
| </Card> | ||||||||||||||||
| <Card className="gap-2 p-4 px-8 grid grid-cols-2"> | ||||||||||||||||
| <div> | ||||||||||||||||
| <Label className="text-lg">Code</Label> | ||||||||||||||||
| <h1 className="text-sm">{control?.control.code}</h1> | ||||||||||||||||
| </div> | ||||||||||||||||
| <div> | ||||||||||||||||
| <Label className="text-lg">Domain</Label> | ||||||||||||||||
| <p className="text-sm">{control?.control.domain}</p> | ||||||||||||||||
| </div> | ||||||||||||||||
| </Card> | ||||||||||||||||
| </div> | ||||||||||||||||
| </div> | ||||||||||||||||
| ); | ||||||||||||||||
| }; | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| "use client"; | ||
|
|
||
| import { OrganizationControl } from "@bubba/db"; | ||
| import useSWR from "swr"; | ||
| import { | ||
| getOrganizationControl, | ||
| OrganizationControlResponse, | ||
| } from "../Actions/getOrganizationControl"; | ||
|
|
||
| async function fetchOrganizationControl( | ||
| controlId: string | ||
| ): Promise<OrganizationControlResponse> { | ||
| const result = await getOrganizationControl({ controlId }); | ||
|
|
||
| if (!result) { | ||
| throw new Error("Failed to fetch control"); | ||
| } | ||
|
|
||
| const data = result.data?.data; | ||
| if (!data) { | ||
| throw new Error("Invalid response from server"); | ||
| } | ||
|
|
||
| return data; | ||
| } | ||
|
|
||
| export function useOrganizationControl(controlId: string) { | ||
| const { data, error, isLoading, mutate } = | ||
| useSWR<OrganizationControlResponse>( | ||
| ["organization-control", controlId], | ||
| () => fetchOrganizationControl(controlId), | ||
| { | ||
| revalidateOnFocus: false, | ||
| revalidateOnReconnect: false, | ||
| } | ||
| ); | ||
|
|
||
| return { | ||
| data: data?.organizationControl, | ||
| isLoading, | ||
| error, | ||
| mutate, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,11 @@ | ||||||||||||||
| import { SingleControl } from "./Components/SingleControl"; | ||||||||||||||
|
|
||||||||||||||
| interface PageProps { | ||||||||||||||
| params: Promise<{ id: string }>; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+3
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the type of The Apply this diff to fix the type: - params: Promise<{ id: string }>;
+ params: { id: string };📝 Committable suggestion
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| export default async function SingleControlPage({ params }: PageProps) { | ||||||||||||||
| const { id } = await params; | ||||||||||||||
|
|
||||||||||||||
| return <SingleControl controlId={id} />; | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default function ControlsPage() { | ||
| return <div>Controls</div>; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||||||||||
| "use server"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import { db } from "@bubba/db"; | ||||||||||||||||||||||
| import { authActionClient } from "@/actions/safe-action"; | ||||||||||||||||||||||
| import type { | ||||||||||||||||||||||
| Framework, | ||||||||||||||||||||||
| OrganizationControl, | ||||||||||||||||||||||
| OrganizationFramework, | ||||||||||||||||||||||
| OrganizationCategory, | ||||||||||||||||||||||
| Control, | ||||||||||||||||||||||
| } from "@bubba/db"; | ||||||||||||||||||||||
| import { z } from "zod"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export type OrganizationCategoryWithControls = OrganizationCategory & { | ||||||||||||||||||||||
| organizationControl: (OrganizationControl & { | ||||||||||||||||||||||
| control: Control; | ||||||||||||||||||||||
| })[]; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export interface OrganizationCategoriesResponse { | ||||||||||||||||||||||
| organizationCategories: OrganizationCategoryWithControls[]; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export const getOrganizationCategories = authActionClient | ||||||||||||||||||||||
| .schema(z.object({ frameworkId: z.string() })) | ||||||||||||||||||||||
| .metadata({ | ||||||||||||||||||||||
| name: "getOrganizationCategories", | ||||||||||||||||||||||
| track: { | ||||||||||||||||||||||
| event: "get-organization-categories", | ||||||||||||||||||||||
| channel: "server", | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
| .action(async ({ ctx, parsedInput }) => { | ||||||||||||||||||||||
| const { user } = ctx; | ||||||||||||||||||||||
| const { frameworkId } = parsedInput; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!user.organizationId) { | ||||||||||||||||||||||
| return { | ||||||||||||||||||||||
| error: "Not authorized - no organization found", | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| const organizationCategories = await db.organizationCategory.findMany({ | ||||||||||||||||||||||
| where: { | ||||||||||||||||||||||
| organizationId: user.organizationId, | ||||||||||||||||||||||
| frameworkId, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| include: { | ||||||||||||||||||||||
| organizationControl: { | ||||||||||||||||||||||
| include: { | ||||||||||||||||||||||
| control: true, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!organizationCategories) { | ||||||||||||||||||||||
| return { | ||||||||||||||||||||||
| error: "Organization categories not found", | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+58
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix incorrect empty check for findMany result. The - if (!organizationCategories) {
+ if (organizationCategories.length === 0) {
return {
error: "Organization categories not found",
};
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return { | ||||||||||||||||||||||
| data: organizationCategories, | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||
| console.error("Error fetching organization categories:", error); | ||||||||||||||||||||||
| return { | ||||||||||||||||||||||
| error: "Failed to fetch organization categories", | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Wrap operations in a transaction for atomicity.
The sequential operations (creating categories, frameworks, and policies) should be wrapped in a transaction to ensure atomicity. If any operation fails, all changes should be rolled back.
try { + return await db.$transaction(async (tx) => { // First create categories - await createOrganizationCategories(user as User, frameworkIds); + await createOrganizationCategories(user as User, frameworkIds, tx); // Then create frameworks and controls await Promise.all( frameworkIds.map((frameworkId) => - createOrganizationFramework(user as User, frameworkId) + createOrganizationFramework(user as User, frameworkId, tx) ) ); // Finally create policies - await createOrganizationPolicy(user as User, frameworkIds); + await createOrganizationPolicy(user as User, frameworkIds, tx); - return { - data: true, - }; + return { data: true }; + }); } catch (error) {📝 Committable suggestion