diff --git a/apps/api/package.json b/apps/api/package.json index 1c16e53d3..c7630a538 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -13,6 +13,7 @@ "@nestjs/swagger": "^11.2.0", "@trycompai/db": "^1.3.4", "archiver": "^7.0.1", + "axios": "^1.12.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "jose": "^6.0.12", diff --git a/apps/app/public/badges/pci-dss.svg b/apps/app/public/badges/pci-dss.svg new file mode 100644 index 000000000..ec647137b --- /dev/null +++ b/apps/app/public/badges/pci-dss.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/app/src/actions/organization/update-organization-advanced-mode-action.ts b/apps/app/src/actions/organization/update-organization-advanced-mode-action.ts new file mode 100644 index 000000000..0044f1a30 --- /dev/null +++ b/apps/app/src/actions/organization/update-organization-advanced-mode-action.ts @@ -0,0 +1,48 @@ +'use server'; + +import { db } from '@db'; +import { revalidatePath, revalidateTag } from 'next/cache'; +import { headers } from 'next/headers'; +import { authActionClient } from '../safe-action'; +import { organizationAdvancedModeSchema } from '../schema'; + +export const updateOrganizationAdvancedModeAction = authActionClient + .inputSchema(organizationAdvancedModeSchema) + .metadata({ + name: 'update-organization-advanced-mode', + track: { + event: 'update-organization-advanced-mode', + channel: 'server', + }, + }) + .action(async ({ parsedInput, ctx }) => { + const { advancedModeEnabled } = parsedInput; + const { activeOrganizationId } = ctx.session; + + if (!activeOrganizationId) { + throw new Error('No active organization'); + } + + try { + await db.$transaction(async () => { + await db.organization.update({ + where: { id: activeOrganizationId }, + data: { advancedModeEnabled }, + }); + }); + + const headersList = await headers(); + let path = headersList.get('x-pathname') || headersList.get('referer') || ''; + path = path.replace(/\/[a-z]{2}\//, '/'); + + revalidatePath(path); + revalidateTag(`organization_${activeOrganizationId}`); + + return { + success: true, + }; + } catch (error) { + console.error(error); + throw new Error('Failed to update advanced mode setting'); + } + }); diff --git a/apps/app/src/actions/schema.ts b/apps/app/src/actions/schema.ts index 8a95aa782..6dc15fe11 100644 --- a/apps/app/src/actions/schema.ts +++ b/apps/app/src/actions/schema.ts @@ -61,6 +61,10 @@ export const organizationWebsiteSchema = z.object({ .max(255, 'Website cannot exceed 255 characters'), }); +export const organizationAdvancedModeSchema = z.object({ + advancedModeEnabled: z.boolean(), +}); + // Risks export const createRiskSchema = z.object({ title: z diff --git a/apps/app/src/app/(app)/[orgId]/frameworks/components/FrameworksOverview.tsx b/apps/app/src/app/(app)/[orgId]/frameworks/components/FrameworksOverview.tsx index 184deb60c..a17329ff9 100644 --- a/apps/app/src/app/(app)/[orgId]/frameworks/components/FrameworksOverview.tsx +++ b/apps/app/src/app/(app)/[orgId]/frameworks/components/FrameworksOverview.tsx @@ -36,6 +36,10 @@ export function mapFrameworkToBadge(framework: FrameworkInstanceWithControls) { return '/badges/gdpr.svg'; } + if (framework.framework.name === 'PCI DSS') { + return '/badges/pci-dss.svg'; + } + return null; } diff --git a/apps/app/src/app/(app)/[orgId]/settings/page.tsx b/apps/app/src/app/(app)/[orgId]/settings/page.tsx index 8b3a39311..4e4a00f92 100644 --- a/apps/app/src/app/(app)/[orgId]/settings/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/settings/page.tsx @@ -1,4 +1,5 @@ import { DeleteOrganization } from '@/components/forms/organization/delete-organization'; +import { UpdateOrganizationAdvancedMode } from '@/components/forms/organization/update-organization-advanced-mode'; import { UpdateOrganizationName } from '@/components/forms/organization/update-organization-name'; import { UpdateOrganizationWebsite } from '@/components/forms/organization/update-organization-website'; import { auth } from '@/utils/auth'; @@ -14,6 +15,9 @@ export default async function OrganizationSettings() {
+
); @@ -40,6 +44,7 @@ const organizationDetails = cache(async () => { name: true, id: true, website: true, + advancedModeEnabled: true, }, }); diff --git a/apps/app/src/app/(app)/[orgId]/settings/trust-portal/actions/update-trust-portal-frameworks.ts b/apps/app/src/app/(app)/[orgId]/settings/trust-portal/actions/update-trust-portal-frameworks.ts index 55934b4b6..192624f84 100644 --- a/apps/app/src/app/(app)/[orgId]/settings/trust-portal/actions/update-trust-portal-frameworks.ts +++ b/apps/app/src/app/(app)/[orgId]/settings/trust-portal/actions/update-trust-portal-frameworks.ts @@ -7,26 +7,34 @@ import { headers } from 'next/headers'; interface UpdateTrustPortalFrameworksParams { orgId: string; - soc2?: boolean; + soc2type1?: boolean; + soc2type2?: boolean; iso27001?: boolean; gdpr?: boolean; hipaa?: boolean; - soc2Status?: 'started' | 'in_progress' | 'compliant'; + pcidss?: boolean; + soc2type1Status?: 'started' | 'in_progress' | 'compliant'; + soc2type2Status?: 'started' | 'in_progress' | 'compliant'; iso27001Status?: 'started' | 'in_progress' | 'compliant'; gdprStatus?: 'started' | 'in_progress' | 'compliant'; hipaaStatus?: 'started' | 'in_progress' | 'compliant'; + pcidssStatus?: 'started' | 'in_progress' | 'compliant'; } export async function updateTrustPortalFrameworks({ orgId, - soc2, + soc2type1, + soc2type2, iso27001, gdpr, hipaa, - soc2Status, + pcidss, + soc2type1Status, + soc2type2Status, iso27001Status, gdprStatus, hipaaStatus, + pcidssStatus, }: UpdateTrustPortalFrameworksParams) { const session = await auth.api.getSession({ headers: await headers(), @@ -51,14 +59,20 @@ export async function updateTrustPortalFrameworks({ organizationId: orgId, }, data: { - soc2: soc2 ?? trustPortal.soc2, + soc2: soc2type2 ?? trustPortal.soc2, + soc2type1: soc2type1 ?? trustPortal.soc2type1, + soc2type2: soc2type2 ?? trustPortal.soc2type2, iso27001: iso27001 ?? trustPortal.iso27001, gdpr: gdpr ?? trustPortal.gdpr, hipaa: hipaa ?? trustPortal.hipaa, - soc2_status: soc2Status ?? trustPortal.soc2_status, + pci_dss: pcidss ?? trustPortal.pci_dss, + soc2_status: soc2type2Status ?? trustPortal.soc2_status, + soc2type1_status: soc2type1Status ?? trustPortal.soc2type1_status, + soc2type2_status: soc2type2Status ?? trustPortal.soc2type2_status, iso27001_status: iso27001Status ?? trustPortal.iso27001_status, gdpr_status: gdprStatus ?? trustPortal.gdpr_status, hipaa_status: hipaaStatus ?? trustPortal.hipaa_status, + pci_dss_status: pcidssStatus ?? trustPortal.pci_dss_status, }, }); diff --git a/apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/TrustPortalSwitch.tsx b/apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/TrustPortalSwitch.tsx index af6ca0fcb..7b49ef5d7 100644 --- a/apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/TrustPortalSwitch.tsx +++ b/apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/TrustPortalSwitch.tsx @@ -17,20 +17,24 @@ import { z } from 'zod'; import { isFriendlyAvailable } from '../actions/is-friendly-available'; import { trustPortalSwitchAction } from '../actions/trust-portal-switch'; import { updateTrustPortalFrameworks } from '../actions/update-trust-portal-frameworks'; -import { GDPR, HIPAA, ISO27001, SOC2 } from './logos'; +import { GDPR, HIPAA, ISO27001, SOC2, SOC2Type1, SOC2Type2, PCIDSS } from './logos'; const trustPortalSwitchSchema = z.object({ enabled: z.boolean(), contactEmail: z.string().email().or(z.literal('')).optional(), friendlyUrl: z.string().optional(), - soc2: z.boolean(), + soc2type1: z.boolean(), + soc2type2: z.boolean(), iso27001: z.boolean(), gdpr: z.boolean(), hipaa: z.boolean(), - soc2Status: z.enum(['started', 'in_progress', 'compliant']), + pcidss: z.boolean(), + soc2type1Status: z.enum(['started', 'in_progress', 'compliant']), + soc2type2Status: z.enum(['started', 'in_progress', 'compliant']), iso27001Status: z.enum(['started', 'in_progress', 'compliant']), gdprStatus: z.enum(['started', 'in_progress', 'compliant']), hipaaStatus: z.enum(['started', 'in_progress', 'compliant']), + pcidssStatus: z.enum(['started', 'in_progress', 'compliant']), }); export function TrustPortalSwitch({ @@ -40,14 +44,18 @@ export function TrustPortalSwitch({ domain, contactEmail, orgId, - soc2, + soc2type1, + soc2type2, iso27001, gdpr, hipaa, - soc2Status, + pcidss, + soc2type1Status, + soc2type2Status, iso27001Status, gdprStatus, hipaaStatus, + pcidssStatus, friendlyUrl, }: { enabled: boolean; @@ -56,14 +64,18 @@ export function TrustPortalSwitch({ domain: string; contactEmail: string | null; orgId: string; - soc2: boolean; + soc2type1: boolean; + soc2type2: boolean; iso27001: boolean; gdpr: boolean; hipaa: boolean; - soc2Status: 'started' | 'in_progress' | 'compliant'; + pcidss: boolean; + soc2type1Status: 'started' | 'in_progress' | 'compliant'; + soc2type2Status: 'started' | 'in_progress' | 'compliant'; iso27001Status: 'started' | 'in_progress' | 'compliant'; gdprStatus: 'started' | 'in_progress' | 'compliant'; hipaaStatus: 'started' | 'in_progress' | 'compliant'; + pcidssStatus: 'started' | 'in_progress' | 'compliant'; friendlyUrl: string | null; }) { const trustPortalSwitch = useAction(trustPortalSwitchAction, { @@ -82,14 +94,18 @@ export function TrustPortalSwitch({ defaultValues: { enabled: enabled, contactEmail: contactEmail ?? undefined, - soc2: soc2 ?? false, + soc2type1: soc2type1 ?? false, + soc2type2: soc2type2 ?? false, iso27001: iso27001 ?? false, gdpr: gdpr ?? false, hipaa: hipaa ?? false, - soc2Status: soc2Status ?? 'started', + pcidss: pcidss ?? false, + soc2type1Status: soc2type1Status ?? 'started', + soc2type2Status: soc2type2Status ?? 'started', iso27001Status: iso27001Status ?? 'started', gdprStatus: gdprStatus ?? 'started', hipaaStatus: hipaaStatus ?? 'started', + pcidssStatus: pcidssStatus ?? 'started', friendlyUrl: friendlyUrl ?? undefined, }, }); @@ -314,35 +330,6 @@ export function TrustPortalSwitch({ Share the frameworks your organization is compliant with or working towards.

- {/* SOC 2 */} - { - try { - await updateTrustPortalFrameworks({ - orgId, - soc2Status: value as 'started' | 'in_progress' | 'compliant', - }); - toast.success('SOC 2 status updated'); - } catch (error) { - toast.error('Failed to update SOC 2 status'); - } - }} - onToggle={async (checked) => { - try { - await updateTrustPortalFrameworks({ - orgId, - soc2: checked, - }); - toast.success('SOC 2 status updated'); - } catch (error) { - toast.error('Failed to update SOC 2 status'); - } - }} - /> {/* ISO 27001 */} + {/* SOC 2 TYPE I*/} + { + try { + await updateTrustPortalFrameworks({ + orgId, + soc2type1Status: value as 'started' | 'in_progress' | 'compliant', + }); + toast.success('SOC 2 TYPE I status updated'); + } catch (error) { + toast.error('Failed to update SOC 2 TYPE I status'); + } + }} + onToggle={async (checked) => { + try { + await updateTrustPortalFrameworks({ + orgId, + soc2type1: checked, + }); + toast.success('SOC 2 TYPE I status updated'); + } catch (error) { + toast.error('Failed to update SOC 2 TYPE I status'); + } + }} + /> + {/* SOC 2 TYPE II*/} + { + try { + await updateTrustPortalFrameworks({ + orgId, + soc2type2Status: value as 'started' | 'in_progress' | 'compliant', + }); + toast.success('SOC 2 TYPE II status updated'); + } catch (error) { + toast.error('Failed to update SOC 2 TYPE II status'); + } + }} + onToggle={async (checked) => { + try { + await updateTrustPortalFrameworks({ + orgId, + soc2type2: checked, + }); + toast.success('SOC 2 TYPE II status updated'); + } catch (error) { + toast.error('Failed to update SOC 2 TYPE II status'); + } + }} + /> + {/* PCI DSS */} + { + try { + await updateTrustPortalFrameworks({ + orgId, + pcidssStatus: value as 'started' | 'in_progress' | 'compliant', + }); + toast.success('PCI DSS status updated'); + } catch (error) { + toast.error('Failed to update PCI DSS status'); + } + }} + onToggle={async (checked) => { + try { + await updateTrustPortalFrameworks({ + orgId, + pcidss: checked, + }); + toast.success('PCI DSS status updated'); + } catch (error) { + toast.error('Failed to update PCI DSS status'); + } + }} + />
@@ -458,11 +532,7 @@ function ComplianceFramework({ onToggle: (checked: boolean) => Promise; }) { const logo = - title === 'SOC 2' ? ( -
- -
- ) : title === 'ISO 27001' ? ( + title === 'ISO 27001' ? (
@@ -474,6 +544,18 @@ function ComplianceFramework({
+ ) : title === 'SOC 2 TYPE I' ? ( +
+ +
+ ) : title === 'SOC 2 TYPE II' ? ( +
+ +
+ ) : title === 'PCI DSS' ? ( +
+ +
) : null; return ( diff --git a/apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/logos.tsx b/apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/logos.tsx index 0a3a6fb2e..d353f1282 100644 --- a/apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/logos.tsx +++ b/apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/logos.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; export const SOC2 = (props: React.SVGProps) => ( - + ) => ( d="M32.5114 2.35261L31.1211 2.24988V1.71094H35.8267V5.48351H34.5884C34.5884 5.66315 34.4364 5.57277 34.4364 4.06374C34.4364 2.55471 33.2544 2.35261 32.5114 2.35261Z" fill="#16171B" stroke="#16171B" - stroke-width="0.427782" + strokeWidth="0.427782" /> @@ -90,13 +90,13 @@ export const ISO27001 = (props: React.SVGProps) => ( d="M32.3776 2.35261L30.9873 2.24988V1.71094H35.6929V5.48351H34.4546C34.4546 5.66315 34.3026 5.57277 34.3026 4.06374C34.3026 2.55471 33.1206 2.35261 32.3776 2.35261Z" fill="#16171B" stroke="#16171B" - stroke-width="0.427782" + strokeWidth="0.427782" /> ) => ( d="M32.3776 2.35261L30.9873 2.24988V1.71094H35.6929V5.48351H34.4546C34.4546 5.66315 34.3026 5.57277 34.3026 4.06374C34.3026 2.55471 33.1206 2.35261 32.3776 2.35261Z" fill="#16171B" stroke="#16171B" - stroke-width="0.427782" + strokeWidth="0.427782" /> ) => ( d="M32.3776 2.35261L30.9873 2.24988V1.71094H35.6929V5.48351H34.4546C34.4546 5.66315 34.3026 5.57277 34.3026 4.06374C34.3026 2.55471 33.1206 2.35261 32.3776 2.35261Z" fill="#16171B" stroke="#16171B" - stroke-width="0.427782" + strokeWidth="0.427782" /> ) => ( /> ); + +export const SOC2Type1 = (props: React.SVGProps) => ( + + + + + + + + + + + + + + + + + + + + +); + +export const SOC2Type2 = (props: React.SVGProps) => ( + + + + + + + + + + + + + + + + + + + + +); + +export const PCIDSS = (props: React.SVGProps) => ( + + + + + + + + + + + + + + +); diff --git a/apps/app/src/app/(app)/[orgId]/settings/trust-portal/page.tsx b/apps/app/src/app/(app)/[orgId]/settings/trust-portal/page.tsx index 1105435ec..919ebecc7 100644 --- a/apps/app/src/app/(app)/[orgId]/settings/trust-portal/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/settings/trust-portal/page.tsx @@ -23,14 +23,18 @@ export default async function TrustPortalSettings({ domainVerified={trustPortal?.domainVerified ?? false} contactEmail={trustPortal?.contactEmail ?? null} orgId={orgId} - soc2={trustPortal?.soc2 ?? false} + soc2type1={trustPortal?.soc2type1 ?? false} + soc2type2={trustPortal?.soc2type2 ?? false} iso27001={trustPortal?.iso27001 ?? false} gdpr={trustPortal?.gdpr ?? false} hipaa={trustPortal?.hipaa ?? false} - soc2Status={trustPortal?.soc2Status ?? 'started'} + pcidss={trustPortal?.pcidss ?? false} + soc2type1Status={trustPortal?.soc2type1Status ?? 'started'} + soc2type2Status={trustPortal?.soc2type2Status ?? 'started'} iso27001Status={trustPortal?.iso27001Status ?? 'started'} gdprStatus={trustPortal?.gdprStatus ?? 'started'} hipaaStatus={trustPortal?.hipaaStatus ?? 'started'} + pcidssStatus={trustPortal?.pcidssStatus ?? 'started'} friendlyUrl={trustPortal?.friendlyUrl ?? null} /> { domain: trustPortal?.domain, domainVerified: trustPortal?.domainVerified, contactEmail: trustPortal?.contactEmail ?? '', - soc2: trustPortal?.soc2, + soc2type1: trustPortal?.soc2type1, + soc2type2: trustPortal?.soc2type2 || trustPortal?.soc2, iso27001: trustPortal?.iso27001, gdpr: trustPortal?.gdpr, hipaa: trustPortal?.hipaa, - soc2Status: trustPortal?.soc2_status, + pcidss: trustPortal?.pci_dss, + soc2type1Status: trustPortal?.soc2type1_status, + soc2type2Status: !trustPortal?.soc2type2 && trustPortal?.soc2 ? trustPortal?.soc2_status : trustPortal?.soc2type2_status, iso27001Status: trustPortal?.iso27001_status, gdprStatus: trustPortal?.gdpr_status, hipaaStatus: trustPortal?.hipaa_status, + pcidssStatus: trustPortal?.pci_dss_status, isVercelDomain: trustPortal?.isVercelDomain, vercelVerification: trustPortal?.vercelVerification, friendlyUrl: trustPortal?.friendlyUrl, diff --git a/apps/app/src/app/(app)/upgrade/[orgId]/components/booking-step.tsx b/apps/app/src/app/(app)/upgrade/[orgId]/components/booking-step.tsx index 176963467..30ce09888 100644 --- a/apps/app/src/app/(app)/upgrade/[orgId]/components/booking-step.tsx +++ b/apps/app/src/app/(app)/upgrade/[orgId]/components/booking-step.tsx @@ -1,9 +1,12 @@ 'use client'; +import { useState } from 'react'; +import { ArrowRight, Check, Copy } from 'lucide-react'; +import { toast } from 'sonner'; +import Link from 'next/link'; import { Button } from '@comp/ui/button'; import { Card } from '@comp/ui/card'; -import { ArrowRight } from 'lucide-react'; -import Link from 'next/link'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@comp/ui/tooltip'; export function BookingStep({ email, @@ -20,6 +23,8 @@ export function BookingStep({ complianceFrameworks: string[]; hasAccess: boolean; }) { + const [isCopied, setIsCopied] = useState(false); + const title = !hasAccess ? `Let's get ${company} approved` : 'Talk to us to upgrade'; const description = !hasAccess @@ -28,6 +33,23 @@ export function BookingStep({ const cta = !hasAccess ? 'Book Your Demo' : 'Book a Call'; + const handleCopyOrgId = async () => { + if (isCopied) return; + + try { + await navigator.clipboard.writeText(orgId); + setIsCopied(true); + toast.success('Org ID copied to clipboard'); + + // Reset after 3 seconds + setTimeout(() => { + setIsCopied(false); + }, 3000); + } catch (error) { + toast.error('Failed to copy Org ID'); + } + }; + return (
@@ -38,6 +60,36 @@ export function BookingStep({

{description}

+ {/* Org ID Display with Copy Button */} +
+ + Org ID: {orgId} + + + + + + + +

{isCopied ? "Copied!" : "Copy Org ID"}

+
+
+
+
+ {/* CTA Button */}
{ + toast.success('Advanced mode setting updated'); + }, + onError: () => { + toast.error('Error updating advanced mode setting'); + }, + }); + + const form = useForm>({ + resolver: zodResolver(organizationAdvancedModeSchema), + defaultValues: { + advancedModeEnabled, + }, + }); + + const onSubmit = (data: z.infer) => { + updateAdvancedMode.execute(data); + }; + + return ( +
+ + + + Advanced Mode + +
+ Enable advanced mode to access additional features like the Controls page. This + setting is designed for users who need access to more detailed compliance management + tools. +
+
+
+ + ( + +
+
Advanced Mode
+
+ Show advanced features and pages +
+
+ + { + field.onChange(checked); + // Auto-submit when switch is toggled + form.handleSubmit(onSubmit)(); + }} + /> + + +
+ )} + /> +
+ +
+ Changes are saved automatically when toggled. +
+ {updateAdvancedMode.status === 'executing' && ( +
+ + Saving... +
+ )} +
+
+
+ + ); +} diff --git a/apps/app/src/components/main-menu.tsx b/apps/app/src/components/main-menu.tsx index 4be652028..4a4070eff 100644 --- a/apps/app/src/components/main-menu.tsx +++ b/apps/app/src/components/main-menu.tsx @@ -44,7 +44,12 @@ interface ItemProps { itemRef: (el: HTMLDivElement | null) => void; } -export function MainMenu({ organizationId, isCollapsed = false, onItemClick }: Props) { +export function MainMenu({ + organizationId, + organization, + isCollapsed = false, + onItemClick, +}: Props) { const pathname = usePathname(); const [activeStyle, setActiveStyle] = useState({ top: '0px', height: '0px' }); const itemRefs = useRef<(HTMLDivElement | null)[]>([]); @@ -65,6 +70,7 @@ export function MainMenu({ organizationId, isCollapsed = false, onItemClick }: P disabled: false, icon: ShieldEllipsis, protected: false, + hidden: !organization?.advancedModeEnabled, }, { id: 'policies', @@ -314,6 +320,7 @@ const Item = ({ type Props = { organizationId?: string; + organization?: { advancedModeEnabled?: boolean } | null; isCollapsed?: boolean; onItemClick?: () => void; }; diff --git a/apps/app/src/components/mobile-menu.tsx b/apps/app/src/components/mobile-menu.tsx index 673c7e5a3..2eeeef227 100644 --- a/apps/app/src/components/mobile-menu.tsx +++ b/apps/app/src/components/mobile-menu.tsx @@ -45,7 +45,11 @@ export function MobileMenu({ organizationId, organizations }: MobileMenuProps) { organization={currentOrganization} isCollapsed={false} /> - +
diff --git a/apps/app/src/components/sidebar.tsx b/apps/app/src/components/sidebar.tsx index 761a58ff7..d0fca391e 100644 --- a/apps/app/src/components/sidebar.tsx +++ b/apps/app/src/components/sidebar.tsx @@ -30,7 +30,11 @@ export async function Sidebar({ organization={organization} isCollapsed={isCollapsed} /> - +
diff --git a/bun.lock b/bun.lock index 10200f0e0..23e443eb7 100644 --- a/bun.lock +++ b/bun.lock @@ -69,6 +69,7 @@ "@nestjs/swagger": "^11.2.0", "@trycompai/db": "^1.3.4", "archiver": "^7.0.1", + "axios": "^1.12.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "jose": "^6.0.12", @@ -2297,7 +2298,7 @@ "axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="], - "axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + "axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -5065,6 +5066,8 @@ "@comp/api/zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], + "@comp/app/axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], @@ -5123,6 +5126,10 @@ "@jest/transform/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "@mendable/firecrawl-js/axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + + "@nangohq/types/axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + "@nangohq/types/type-fest": ["type-fest@4.32.0", "", {}, "sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw=="], "@nestjs/cli/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], @@ -5235,6 +5242,8 @@ "@slack/bolt/@types/express": ["@types/express@4.17.23", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ=="], + "@slack/bolt/axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + "@slack/bolt/express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="], "@slack/oauth/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="], @@ -5247,6 +5256,8 @@ "@slack/socket-mode/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "@slack/web-api/axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + "@smithy/core/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "@smithy/middleware-retry/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], @@ -6237,12 +6248,16 @@ "@slack/bolt/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + "@slack/oauth/@slack/web-api/axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + "@slack/oauth/@slack/web-api/eventemitter3": ["eventemitter3@3.1.2", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="], "@slack/oauth/@slack/web-api/form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="], "@slack/oauth/@slack/web-api/is-stream": ["is-stream@1.1.0", "", {}, "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="], + "@slack/socket-mode/@slack/web-api/axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="], + "@slack/socket-mode/@slack/web-api/eventemitter3": ["eventemitter3@3.1.2", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="], "@slack/socket-mode/@slack/web-api/form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="], @@ -6571,10 +6586,14 @@ "@slack/bolt/express/type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "@slack/oauth/@slack/web-api/axios/form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], + "@slack/oauth/@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "@slack/oauth/@slack/web-api/form-data/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "@slack/socket-mode/@slack/web-api/axios/form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], + "@slack/socket-mode/@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "@slack/socket-mode/@slack/web-api/form-data/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], @@ -6655,8 +6674,12 @@ "@slack/bolt/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "@slack/oauth/@slack/web-api/axios/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "@slack/oauth/@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "@slack/socket-mode/@slack/web-api/axios/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "@slack/socket-mode/@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "cli-highlight/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6673,6 +6696,10 @@ "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit/p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "@slack/oauth/@slack/web-api/axios/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "@slack/socket-mode/@slack/web-api/axios/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "pkg-dir/find-up/locate-path/p-locate/p-limit/p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], } } diff --git a/packages/db/package.json b/packages/db/package.json index 9ed8a996f..da4a62d1d 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,7 +1,7 @@ { "name": "@trycompai/db", "description": "Database package with Prisma client and schema for Comp AI", - "version": "1.3.4", + "version": "1.3.5", "dependencies": { "@prisma/client": "^6.13.0", "dotenv": "^16.4.5" diff --git a/packages/db/prisma/migrations/20250910222020_add_soc2_type_I_II_columns/migration.sql b/packages/db/prisma/migrations/20250910222020_add_soc2_type_I_II_columns/migration.sql new file mode 100644 index 000000000..05eac663f --- /dev/null +++ b/packages/db/prisma/migrations/20250910222020_add_soc2_type_I_II_columns/migration.sql @@ -0,0 +1,11 @@ +-- AlterTable +ALTER TABLE "Trust" ADD COLUMN "soc2typei" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "Trust" ADD COLUMN "soc2typei_status" "FrameworkStatus" NOT NULL DEFAULT 'started'; + +-- AlterTable +ALTER TABLE "Trust" ADD COLUMN "soc2typeii" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "Trust" ADD COLUMN "soc2typeii_status" "FrameworkStatus" NOT NULL DEFAULT 'started'; diff --git a/packages/db/prisma/migrations/20250910233442_add_pci_dss_columns/migration.sql b/packages/db/prisma/migrations/20250910233442_add_pci_dss_columns/migration.sql new file mode 100644 index 000000000..22bedf470 --- /dev/null +++ b/packages/db/prisma/migrations/20250910233442_add_pci_dss_columns/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Trust" ADD COLUMN "pci_dss" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "Trust" ADD COLUMN "pci_dss_status" "FrameworkStatus" NOT NULL DEFAULT 'started'; diff --git a/packages/db/prisma/migrations/20250911095434_rename_soc2_type_columns/migration.sql b/packages/db/prisma/migrations/20250911095434_rename_soc2_type_columns/migration.sql new file mode 100644 index 000000000..3b1acde0c --- /dev/null +++ b/packages/db/prisma/migrations/20250911095434_rename_soc2_type_columns/migration.sql @@ -0,0 +1,13 @@ +-- Rename SOC2 Type I/II columns to use numbers instead of Roman numerals + +-- Rename soc2typei to soc2type1 +ALTER TABLE "Trust" RENAME COLUMN "soc2typei" TO "soc2type1"; + +-- Rename soc2typeii to soc2type2 +ALTER TABLE "Trust" RENAME COLUMN "soc2typeii" TO "soc2type2"; + +-- Rename soc2typei_status to soc2type1_status +ALTER TABLE "Trust" RENAME COLUMN "soc2typei_status" TO "soc2type1_status"; + +-- Rename soc2typeii_status to soc2type2_status +ALTER TABLE "Trust" RENAME COLUMN "soc2typeii_status" TO "soc2type2_status"; diff --git a/packages/db/prisma/migrations/20250911162601_add_advanced_mode_to_organization/migration.sql b/packages/db/prisma/migrations/20250911162601_add_advanced_mode_to_organization/migration.sql new file mode 100644 index 000000000..0bfcceb8d --- /dev/null +++ b/packages/db/prisma/migrations/20250911162601_add_advanced_mode_to_organization/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "public"."Organization" ADD COLUMN "advancedModeEnabled" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/db/prisma/schema/organization.prisma b/packages/db/prisma/schema/organization.prisma index 99b63d01a..211f6d1d3 100644 --- a/packages/db/prisma/schema/organization.prisma +++ b/packages/db/prisma/schema/organization.prisma @@ -9,6 +9,7 @@ model Organization { website String? onboardingCompleted Boolean @default(false) hasAccess Boolean @default(false) + advancedModeEnabled Boolean @default(false) // FleetDM fleetDmLabelId Int? diff --git a/packages/db/prisma/schema/trust.prisma b/packages/db/prisma/schema/trust.prisma index 150f23c1b..ae4273cf6 100644 --- a/packages/db/prisma/schema/trust.prisma +++ b/packages/db/prisma/schema/trust.prisma @@ -12,14 +12,20 @@ model Trust { email String? privacyPolicy String? soc2 Boolean @default(false) + soc2type1 Boolean @default(false) + soc2type2 Boolean @default(false) iso27001 Boolean @default(false) gdpr Boolean @default(false) hipaa Boolean @default(false) + pci_dss Boolean @default(false) - soc2_status FrameworkStatus @default(started) - iso27001_status FrameworkStatus @default(started) - gdpr_status FrameworkStatus @default(started) - hipaa_status FrameworkStatus @default(started) + soc2_status FrameworkStatus @default(started) + soc2type1_status FrameworkStatus @default(started) + soc2type2_status FrameworkStatus @default(started) + iso27001_status FrameworkStatus @default(started) + gdpr_status FrameworkStatus @default(started) + hipaa_status FrameworkStatus @default(started) + pci_dss_status FrameworkStatus @default(started) @@id([status, organizationId]) @@unique([organizationId])