From 520c99a7c48b73215f097f565cb2af610ad57226 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:25:09 -0400 Subject: [PATCH 1/5] COMP-255 [API] - Add following endpoints for Organization (#1444) * add PATCH and DELETE endpoints for organization * move ApiResponses out to owen files to make organization.controller file clean * refactoring --------- Co-authored-by: chasprowebdev Co-authored-by: Mariano Fuentes --- .../dto/update-organization.dto.ts | 11 ++ .../organization/organization.controller.ts | 124 +++++++++-------- .../src/organization/organization.service.ts | 93 +++++++++++++ .../schemas/delete-organization.responses.ts | 70 ++++++++++ .../schemas/get-organization.responses.ts | 96 +++++++++++++ .../schemas/organization-api-bodies.ts | 56 ++++++++ .../schemas/organization-operations.ts | 19 +++ .../schemas/update-organization.responses.ts | 126 ++++++++++++++++++ 8 files changed, 536 insertions(+), 59 deletions(-) create mode 100644 apps/api/src/organization/dto/update-organization.dto.ts create mode 100644 apps/api/src/organization/schemas/delete-organization.responses.ts create mode 100644 apps/api/src/organization/schemas/get-organization.responses.ts create mode 100644 apps/api/src/organization/schemas/organization-api-bodies.ts create mode 100644 apps/api/src/organization/schemas/organization-operations.ts create mode 100644 apps/api/src/organization/schemas/update-organization.responses.ts diff --git a/apps/api/src/organization/dto/update-organization.dto.ts b/apps/api/src/organization/dto/update-organization.dto.ts new file mode 100644 index 000000000..5cbf2935b --- /dev/null +++ b/apps/api/src/organization/dto/update-organization.dto.ts @@ -0,0 +1,11 @@ +export interface UpdateOrganizationDto { + name?: string; + slug?: string; + logo?: string; + metadata?: string; + website?: string; + onboardingCompleted?: boolean; + hasAccess?: boolean; + fleetDmLabelId?: number; + isFleetSetupCompleted?: boolean; +} diff --git a/apps/api/src/organization/organization.controller.ts b/apps/api/src/organization/organization.controller.ts index 83a9fd371..41ff3b711 100644 --- a/apps/api/src/organization/organization.controller.ts +++ b/apps/api/src/organization/organization.controller.ts @@ -1,5 +1,6 @@ -import { Controller, Get, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Patch, UseGuards } from '@nestjs/common'; import { + ApiBody, ApiHeader, ApiOperation, ApiResponse, @@ -13,7 +14,13 @@ import { } from '../auth/auth-context.decorator'; import { HybridAuthGuard } from '../auth/hybrid-auth.guard'; import type { AuthContext as AuthContextType } from '../auth/types'; +import type { UpdateOrganizationDto } from './dto/update-organization.dto'; import { OrganizationService } from './organization.service'; +import { GET_ORGANIZATION_RESPONSES } from './schemas/get-organization.responses'; +import { UPDATE_ORGANIZATION_RESPONSES } from './schemas/update-organization.responses'; +import { DELETE_ORGANIZATION_RESPONSES } from './schemas/delete-organization.responses'; +import { UPDATE_ORGANIZATION_BODY } from './schemas/organization-api-bodies'; +import { ORGANIZATION_OPERATIONS } from './schemas/organization-operations'; @ApiTags('Organization') @Controller({ path: 'organization', version: '1' }) @@ -29,64 +36,9 @@ export class OrganizationController { constructor(private readonly organizationService: OrganizationService) {} @Get() - @ApiOperation({ - summary: 'Get organization information', - description: - 'Returns detailed information about the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).', - }) - @ApiResponse({ - status: 200, - description: 'Organization information retrieved successfully', - schema: { - type: 'object', - properties: { - id: { - type: 'string', - description: 'The organization ID', - example: 'org_abc123def456', - }, - name: { - type: 'string', - description: 'Organization name', - example: 'Acme Corporation', - }, - slug: { - type: 'string', - description: 'Organization slug', - example: 'acme-corp', - }, - createdAt: { - type: 'string', - format: 'date-time', - description: 'When the organization was created', - }, - authType: { - type: 'string', - enum: ['api-key', 'session'], - description: 'How the request was authenticated', - }, - }, - }, - }) - @ApiResponse({ - status: 401, - description: - 'Unauthorized - Invalid authentication or insufficient permissions', - schema: { - type: 'object', - properties: { - message: { - type: 'string', - examples: [ - 'Invalid or expired API key', - 'Invalid or expired session', - 'User does not have access to organization', - 'Organization context required', - ], - }, - }, - }, - }) + @ApiOperation(ORGANIZATION_OPERATIONS.getOrganization) + @ApiResponse(GET_ORGANIZATION_RESPONSES[200]) + @ApiResponse(GET_ORGANIZATION_RESPONSES[401]) async getOrganization( @OrganizationId() organizationId: string, @AuthContext() authContext: AuthContextType, @@ -106,4 +58,58 @@ export class OrganizationController { }), }; } + + @Patch() + @ApiOperation(ORGANIZATION_OPERATIONS.updateOrganization) + @ApiBody(UPDATE_ORGANIZATION_BODY) + @ApiResponse(UPDATE_ORGANIZATION_RESPONSES[200]) + @ApiResponse(UPDATE_ORGANIZATION_RESPONSES[400]) + @ApiResponse(UPDATE_ORGANIZATION_RESPONSES[401]) + @ApiResponse(UPDATE_ORGANIZATION_RESPONSES[404]) + async updateOrganization( + @OrganizationId() organizationId: string, + @AuthContext() authContext: AuthContextType, + @Body() updateData: UpdateOrganizationDto, + ) { + const updatedOrg = await this.organizationService.updateById( + organizationId, + updateData, + ); + + return { + ...updatedOrg, + authType: authContext.authType, + // Include user context for session auth (helpful for debugging) + ...(authContext.userId && { + authenticatedUser: { + id: authContext.userId, + email: authContext.userEmail, + }, + }), + }; + } + + @Delete() + @ApiOperation(ORGANIZATION_OPERATIONS.deleteOrganization) + @ApiResponse(DELETE_ORGANIZATION_RESPONSES[200]) + @ApiResponse(DELETE_ORGANIZATION_RESPONSES[401]) + @ApiResponse(DELETE_ORGANIZATION_RESPONSES[404]) + async deleteOrganization( + @OrganizationId() organizationId: string, + @AuthContext() authContext: AuthContextType, + ) { + const result = await this.organizationService.deleteById(organizationId); + + return { + ...result, + authType: authContext.authType, + // Include user context for session auth (helpful for debugging) + ...(authContext.userId && { + authenticatedUser: { + id: authContext.userId, + email: authContext.userEmail, + }, + }), + }; + } } diff --git a/apps/api/src/organization/organization.service.ts b/apps/api/src/organization/organization.service.ts index c1a6f7788..89bd4436d 100644 --- a/apps/api/src/organization/organization.service.ts +++ b/apps/api/src/organization/organization.service.ts @@ -1,5 +1,6 @@ import { Injectable, NotFoundException, Logger } from '@nestjs/common'; import { db } from '@trycompai/db'; +import type { UpdateOrganizationDto } from './dto/update-organization.dto'; @Injectable() export class OrganizationService { @@ -13,6 +14,13 @@ export class OrganizationService { id: true, name: true, slug: true, + logo: true, + metadata: true, + website: true, + onboardingCompleted: true, + hasAccess: true, + fleetDmLabelId: true, + isFleetSetupCompleted: true, createdAt: true, }, }); @@ -31,4 +39,89 @@ export class OrganizationService { throw error; } } + + async updateById(id: string, updateData: UpdateOrganizationDto) { + try { + // First check if the organization exists + const existingOrganization = await db.organization.findUnique({ + where: { id }, + select: { + id: true, + name: true, + slug: true, + logo: true, + metadata: true, + website: true, + onboardingCompleted: true, + hasAccess: true, + fleetDmLabelId: true, + isFleetSetupCompleted: true, + createdAt: true, + }, + }); + + if (!existingOrganization) { + throw new NotFoundException(`Organization with ID ${id} not found`); + } + + // Update the organization with only provided fields + const updatedOrganization = await db.organization.update({ + where: { id }, + data: updateData, + select: { + id: true, + name: true, + slug: true, + logo: true, + metadata: true, + website: true, + onboardingCompleted: true, + hasAccess: true, + fleetDmLabelId: true, + isFleetSetupCompleted: true, + createdAt: true, + }, + }); + + this.logger.log(`Updated organization: ${updatedOrganization.name} (${id})`); + return updatedOrganization; + } catch (error) { + if (error instanceof NotFoundException) { + throw error; + } + this.logger.error(`Failed to update organization ${id}:`, error); + throw error; + } + } + + async deleteById(id: string) { + try { + // First check if the organization exists + const organization = await db.organization.findUnique({ + where: { id }, + select: { + id: true, + name: true, + }, + }); + + if (!organization) { + throw new NotFoundException(`Organization with ID ${id} not found`); + } + + // Delete the organization + await db.organization.delete({ + where: { id }, + }); + + this.logger.log(`Deleted organization: ${organization.name} (${id})`); + return { success: true, deletedOrganization: organization }; + } catch (error) { + if (error instanceof NotFoundException) { + throw error; + } + this.logger.error(`Failed to delete organization ${id}:`, error); + throw error; + } + } } diff --git a/apps/api/src/organization/schemas/delete-organization.responses.ts b/apps/api/src/organization/schemas/delete-organization.responses.ts new file mode 100644 index 000000000..8712db454 --- /dev/null +++ b/apps/api/src/organization/schemas/delete-organization.responses.ts @@ -0,0 +1,70 @@ +import { ApiResponseOptions } from '@nestjs/swagger'; + +export const DELETE_ORGANIZATION_RESPONSES: Record = { + 200: { + status: 200, + description: 'Organization deleted successfully', + schema: { + type: 'object', + properties: { + success: { + type: 'boolean', + description: 'Indicates successful deletion', + example: true, + }, + deletedOrganization: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'The deleted organization ID', + example: 'org_abc123def456', + }, + name: { + type: 'string', + description: 'The deleted organization name', + example: 'Acme Corporation', + }, + }, + }, + authType: { + type: 'string', + enum: ['api-key', 'session'], + description: 'How the request was authenticated', + }, + }, + }, + }, + 401: { + status: 401, + description: + 'Unauthorized - Invalid authentication or insufficient permissions', + schema: { + type: 'object', + properties: { + message: { + type: 'string', + examples: [ + 'Invalid or expired API key', + 'Invalid or expired session', + 'User does not have access to organization', + 'Organization context required', + ], + }, + }, + }, + }, + 404: { + status: 404, + description: 'Organization not found', + schema: { + type: 'object', + properties: { + message: { + type: 'string', + example: 'Organization with ID org_abc123def456 not found', + }, + }, + }, + }, +}; diff --git a/apps/api/src/organization/schemas/get-organization.responses.ts b/apps/api/src/organization/schemas/get-organization.responses.ts new file mode 100644 index 000000000..d482556b2 --- /dev/null +++ b/apps/api/src/organization/schemas/get-organization.responses.ts @@ -0,0 +1,96 @@ +import { ApiResponseOptions } from '@nestjs/swagger'; + +export const GET_ORGANIZATION_RESPONSES: Record = { + 200: { + status: 200, + description: 'Organization information retrieved successfully', + schema: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'The organization ID', + example: 'org_abc123def456', + }, + name: { + type: 'string', + description: 'Organization name', + example: 'Acme Corporation', + }, + slug: { + type: 'string', + description: 'Organization slug', + example: 'acme-corp', + }, + logo: { + type: 'string', + nullable: true, + description: 'Organization logo URL', + example: 'https://example.com/logo.png', + }, + metadata: { + type: 'string', + nullable: true, + description: 'Additional metadata in JSON format', + example: '{"theme": "dark", "preferences": {}}', + }, + website: { + type: 'string', + nullable: true, + description: 'Organization website URL', + example: 'https://acme-corp.com', + }, + onboardingCompleted: { + type: 'boolean', + description: 'Whether onboarding is completed', + example: true, + }, + hasAccess: { + type: 'boolean', + description: 'Whether organization has access to the platform', + example: true, + }, + fleetDmLabelId: { + type: 'integer', + nullable: true, + description: 'FleetDM label ID for device management', + example: 123, + }, + isFleetSetupCompleted: { + type: 'boolean', + description: 'Whether FleetDM setup is completed', + example: false, + }, + createdAt: { + type: 'string', + format: 'date-time', + description: 'When the organization was created', + }, + authType: { + type: 'string', + enum: ['api-key', 'session'], + description: 'How the request was authenticated', + }, + }, + }, + }, + 401: { + status: 401, + description: + 'Unauthorized - Invalid authentication or insufficient permissions', + schema: { + type: 'object', + properties: { + message: { + type: 'string', + examples: [ + 'Invalid or expired API key', + 'Invalid or expired session', + 'User does not have access to organization', + 'Organization context required', + ], + }, + }, + }, + }, +}; diff --git a/apps/api/src/organization/schemas/organization-api-bodies.ts b/apps/api/src/organization/schemas/organization-api-bodies.ts new file mode 100644 index 000000000..87d7d133c --- /dev/null +++ b/apps/api/src/organization/schemas/organization-api-bodies.ts @@ -0,0 +1,56 @@ +import type { ApiBodyOptions } from '@nestjs/swagger'; + +export const UPDATE_ORGANIZATION_BODY: ApiBodyOptions = { + description: 'Organization update data', + schema: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'Organization name', + example: 'New Acme Corporation', + }, + slug: { + type: 'string', + description: 'Organization slug', + example: 'new-acme-corp', + }, + logo: { + type: 'string', + description: 'Organization logo URL', + example: 'https://example.com/logo.png', + }, + metadata: { + type: 'string', + description: 'Additional metadata in JSON format', + example: '{"theme": "dark", "preferences": {}}', + }, + website: { + type: 'string', + description: 'Organization website URL', + example: 'https://acme-corp.com', + }, + onboardingCompleted: { + type: 'boolean', + description: 'Whether onboarding is completed', + example: true, + }, + hasAccess: { + type: 'boolean', + description: 'Whether organization has access to the platform', + example: true, + }, + fleetDmLabelId: { + type: 'integer', + description: 'FleetDM label ID for device management', + example: 123, + }, + isFleetSetupCompleted: { + type: 'boolean', + description: 'Whether FleetDM setup is completed', + example: false, + }, + }, + additionalProperties: false, + }, +}; diff --git a/apps/api/src/organization/schemas/organization-operations.ts b/apps/api/src/organization/schemas/organization-operations.ts new file mode 100644 index 000000000..bc8cc7be8 --- /dev/null +++ b/apps/api/src/organization/schemas/organization-operations.ts @@ -0,0 +1,19 @@ +import type { ApiOperationOptions } from '@nestjs/swagger'; + +export const ORGANIZATION_OPERATIONS: Record = { + getOrganization: { + summary: 'Get organization information', + description: + 'Returns detailed information about the authenticated organization. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).', + }, + updateOrganization: { + summary: 'Update organization', + description: + 'Partially updates the authenticated organization. Only provided fields will be updated. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).', + }, + deleteOrganization: { + summary: 'Delete organization', + description: + 'Permanently deletes the authenticated organization. This action cannot be undone. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).', + }, +}; diff --git a/apps/api/src/organization/schemas/update-organization.responses.ts b/apps/api/src/organization/schemas/update-organization.responses.ts new file mode 100644 index 000000000..118d4afbf --- /dev/null +++ b/apps/api/src/organization/schemas/update-organization.responses.ts @@ -0,0 +1,126 @@ +import { ApiResponseOptions } from '@nestjs/swagger'; + +export const UPDATE_ORGANIZATION_RESPONSES: Record = { + 200: { + status: 200, + description: 'Organization updated successfully', + schema: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'The organization ID', + example: 'org_abc123def456', + }, + name: { + type: 'string', + description: 'Organization name', + example: 'New Acme Corporation', + }, + slug: { + type: 'string', + description: 'Organization slug', + example: 'new-acme-corp', + }, + logo: { + type: 'string', + nullable: true, + description: 'Organization logo URL', + example: 'https://example.com/logo.png', + }, + metadata: { + type: 'string', + nullable: true, + description: 'Additional metadata in JSON format', + example: '{"theme": "dark", "preferences": {}}', + }, + website: { + type: 'string', + nullable: true, + description: 'Organization website URL', + example: 'https://acme-corp.com', + }, + onboardingCompleted: { + type: 'boolean', + description: 'Whether onboarding is completed', + example: true, + }, + hasAccess: { + type: 'boolean', + description: 'Whether organization has access to the platform', + example: true, + }, + fleetDmLabelId: { + type: 'integer', + nullable: true, + description: 'FleetDM label ID for device management', + example: 123, + }, + isFleetSetupCompleted: { + type: 'boolean', + description: 'Whether FleetDM setup is completed', + example: false, + }, + createdAt: { + type: 'string', + format: 'date-time', + description: 'When the organization was created', + }, + authType: { + type: 'string', + enum: ['api-key', 'session'], + description: 'How the request was authenticated', + }, + }, + }, + }, + 400: { + status: 400, + description: 'Bad Request - Invalid update data', + schema: { + type: 'object', + properties: { + message: { + type: 'string', + examples: [ + 'Validation failed', + 'Invalid slug format', + 'Organization name already exists', + ], + }, + }, + }, + }, + 401: { + status: 401, + description: + 'Unauthorized - Invalid authentication or insufficient permissions', + schema: { + type: 'object', + properties: { + message: { + type: 'string', + examples: [ + 'Invalid or expired API key', + 'Invalid or expired session', + 'User does not have access to organization', + 'Organization context required', + ], + }, + }, + }, + }, + 404: { + status: 404, + description: 'Organization not found', + schema: { + type: 'object', + properties: { + message: { + type: 'string', + example: 'Organization with ID org_abc123def456 not found', + }, + }, + }, + }, +}; From b6fd933306e01e323bc27c84b7757dd18af90bd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:37:05 -0400 Subject: [PATCH 2/5] chore(deps): bump @radix-ui/react-toast from 1.2.14 to 1.2.15 (#1454) Bumps [@radix-ui/react-toast](https://github.com/radix-ui/primitives) from 1.2.14 to 1.2.15. - [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md) - [Commits](https://github.com/radix-ui/primitives/commits) --- updated-dependencies: - dependency-name: "@radix-ui/react-toast" dependency-version: 1.2.15 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 7d9c837da..bc8efd200 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -30,7 +30,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "1.2.5", "@radix-ui/react-tabs": "1.1.12", - "@radix-ui/react-toast": "1.2.14", + "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.9", "@radix-ui/react-toggle-group": "1.1.10", "@radix-ui/react-tooltip": "1.2.7", From 94c382ca1a2905891a9a1875d44ac504ac248a01 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:05:44 -0400 Subject: [PATCH 3/5] refactor: Update PdfViewer styles for improved UI consistency (#1478) - Changed border and background colors in the PdfViewer component to use a primary color scheme. - Updated loading and file icons to reflect the new color theme. - Minor adjustments to text prompts for clarity during file upload interactions. These changes enhance the visual consistency of the PDF upload interface. Co-authored-by: Mariano Fuentes --- .../policies/[policyId]/components/PdfViewer.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PdfViewer.tsx b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PdfViewer.tsx index 36105e878..8ee67c6de 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PdfViewer.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/[policyId]/components/PdfViewer.tsx @@ -158,8 +158,8 @@ export function PdfViewer({ policyId, pdfUrl, isPendingApproval }: PdfViewerProp className={cn( 'cursor-pointer rounded-md border-2 border-dashed p-4 text-center transition-colors', isDragActive - ? 'border-emerald-400 bg-emerald-50 dark:border-emerald-500 dark:bg-emerald-950/20' - : 'border-emerald-200 hover:border-emerald-300 dark:border-emerald-800 dark:hover:border-emerald-700', + ? 'border-primary bg-primary/10 dark:border-primary dark:bg-primary/10' + : 'border-primary/30 hover:border-primary/50 dark:border-primary/30 dark:hover:border-primary/50', isUploading && 'pointer-events-none opacity-60', )} > @@ -191,16 +191,16 @@ export function PdfViewer({ policyId, pdfUrl, isPendingApproval }: PdfViewerProp className={cn( 'flex cursor-pointer flex-col items-center justify-center space-y-4 rounded-md border-2 border-dashed p-12 text-center transition-colors', isDragActive - ? 'border-emerald-400 bg-emerald-50 dark:border-emerald-500 dark:bg-emerald-950/20' - : 'border-emerald-200 hover:border-emerald-300 dark:border-emerald-800 dark:hover:border-emerald-700', + ? 'border-primary bg-primary/10 dark:border-primary dark:bg-primary/10' + : 'border-primary/30 hover:border-primary/50 dark:border-primary/30 dark:hover:border-primary/50', isUploading && 'pointer-events-none opacity-60', )} > {isUploading ? ( - + ) : ( - + )}

{isUploading @@ -214,7 +214,7 @@ export function PdfViewer({ policyId, pdfUrl, isPendingApproval }: PdfViewerProp ? 'Please wait while we upload your PDF.' : isDragActive ? 'Release to upload your PDF.' - : 'Drag and drop a PDF here or click to browse (up to 100MB).'} + : 'Drag and drop a PDF here or click to browse (up to 100MB)'}

)} From a45a4a387532b8741870d914be0ea5a8c78ea3d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:44:55 -0400 Subject: [PATCH 4/5] chore(deps): bump posthog-node from 4.18.0 to 5.8.2 (#1472) Bumps [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) from 4.18.0 to 5.8.2. - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.8.2/packages/node) --- updated-dependencies: - dependency-name: posthog-node dependency-version: 5.8.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mariano Fuentes --- apps/app/package.json | 2 +- packages/analytics/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/package.json b/apps/app/package.json index a4083b23d..4d424e57d 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -68,7 +68,7 @@ "nuqs": "^2.4.3", "playwright-core": "^1.52.0", "posthog-js": "^1.236.6", - "posthog-node": "^4.14.0", + "posthog-node": "^5.8.2", "puppeteer-core": "^24.7.2", "react": "^19.1.1", "react-dom": "^19.1.0", diff --git a/packages/analytics/package.json b/packages/analytics/package.json index 8e565176e..22949a102 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "dependencies": { "posthog-js": "^1.236.6", - "posthog-node": "^4.14.0" + "posthog-node": "^5.8.2" }, "devDependencies": { "tsup": "^8.5.0", From 41a8edc9c0aa9ec77430861cc937888f76cf0a7e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:15:40 -0400 Subject: [PATCH 5/5] [dev] [Marfuen] mariano/fraud-risk (#1473) * Update zod dependency to version 3.25.76 across multiple files - Changed zod version from 4.0.14 to 3.25.76 in package.json and bun.lock. - Updated zod version in apps/app/package.json from 4.0.17 to 3.25.76. - Refactored onboarding helpers to utilize new zod schema validation methods. These changes ensure compatibility with the updated zod version and improve the overall stability of the application. * fix: Update error handling in schema validation to use 'required_error' for consistency - Changed error messages in various schemas to use 'required_error' instead of 'error' for better clarity and consistency across the application. - Updated schemas include createRiskSchema, updateRiskSchema, createPolicySchema, createVendorTaskSchema, and others. These changes enhance the user experience by providing clearer validation messages. --------- Co-authored-by: Mariano Fuentes --- apps/app/package.json | 2 +- apps/app/src/actions/schema.ts | 24 +-- .../vendors/[vendorId]/actions/schema.ts | 4 +- .../onboard-organization-helpers.ts | 161 ++++++++++++++---- bun.lock | 26 +-- package.json | 8 +- 6 files changed, 156 insertions(+), 69 deletions(-) diff --git a/apps/app/package.json b/apps/app/package.json index 4d424e57d..553263619 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -91,7 +91,7 @@ "use-long-press": "^3.3.0", "xml2js": "^0.6.2", "zaraz-ts": "^1.2.0", - "zod": "^4.0.17", + "zod": "^3.25.76", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/apps/app/src/actions/schema.ts b/apps/app/src/actions/schema.ts index d92cad218..8a95aa782 100644 --- a/apps/app/src/actions/schema.ts +++ b/apps/app/src/actions/schema.ts @@ -65,7 +65,7 @@ export const organizationWebsiteSchema = z.object({ export const createRiskSchema = z.object({ title: z .string({ - error: 'Risk name is required', + required_error: 'Risk name is required', }) .min(1, { message: 'Risk name should be at least 1 character', @@ -75,7 +75,7 @@ export const createRiskSchema = z.object({ }), description: z .string({ - error: 'Risk description is required', + required_error: 'Risk description is required', }) .min(1, { message: 'Risk description should be at least 1 character', @@ -84,10 +84,10 @@ export const createRiskSchema = z.object({ message: 'Risk description should be at most 255 characters', }), category: z.nativeEnum(RiskCategory, { - error: 'Risk category is required', + required_error: 'Risk category is required', }), department: z.nativeEnum(Departments, { - error: 'Risk department is required', + required_error: 'Risk department is required', }), assigneeId: z.string().optional().nullable(), }); @@ -103,14 +103,14 @@ export const updateRiskSchema = z.object({ message: 'Risk description is required', }), category: z.nativeEnum(RiskCategory, { - error: 'Risk category is required', + required_error: 'Risk category is required', }), department: z.nativeEnum(Departments, { - error: 'Risk department is required', + required_error: 'Risk department is required', }), assigneeId: z.string().optional().nullable(), status: z.nativeEnum(RiskStatus, { - error: 'Risk status is required', + required_error: 'Risk status is required', }), }); @@ -162,7 +162,7 @@ export const updateTaskSchema = z.object({ description: z.string().optional(), dueDate: z.date().optional(), status: z.nativeEnum(TaskStatus, { - error: 'Task status is required', + required_error: 'Task status is required', }), assigneeId: z.string().optional().nullable(), }); @@ -251,8 +251,10 @@ export const updateResidualRiskEnumSchema = z.object({ // Policies export const createPolicySchema = z.object({ - title: z.string({ error: 'Title is required' }).min(1, 'Title is required'), - description: z.string({ error: 'Description is required' }).min(1, 'Description is required'), + title: z.string({ required_error: 'Title is required' }).min(1, 'Title is required'), + description: z + .string({ required_error: 'Description is required' }) + .min(1, 'Description is required'), frameworkIds: z.array(z.string()).optional(), controlIds: z.array(z.string()).optional(), entityId: z.string().optional(), @@ -279,7 +281,7 @@ export const createEmployeeSchema = z.object({ name: z.string().min(1, 'Name is required'), email: z.string().email('Invalid email address'), department: z.nativeEnum(Departments, { - error: 'Department is required', + required_error: 'Department is required', }), externalEmployeeId: z.string().optional(), isActive: z.boolean().default(true), diff --git a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/actions/schema.ts b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/actions/schema.ts index 6cb96fa10..fa93708db 100644 --- a/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/actions/schema.ts +++ b/apps/app/src/app/(app)/[orgId]/vendors/[vendorId]/actions/schema.ts @@ -24,7 +24,7 @@ export const createVendorTaskSchema = z.object({ message: 'Description is required', }), dueDate: z.date({ - error: 'Due date is required', + required_error: 'Due date is required', }), assigneeId: z.string().nullable(), }); @@ -79,7 +79,7 @@ export const updateVendorTaskSchema = z.object({ }), dueDate: z.date().optional(), status: z.nativeEnum(TaskStatus, { - error: 'Task status is required', + required_error: 'Task status is required', }), assigneeId: z.string().nullable(), }); diff --git a/apps/app/src/jobs/tasks/onboarding/onboard-organization-helpers.ts b/apps/app/src/jobs/tasks/onboarding/onboard-organization-helpers.ts index f09d05bfd..c0ae68613 100644 --- a/apps/app/src/jobs/tasks/onboarding/onboard-organization-helpers.ts +++ b/apps/app/src/jobs/tasks/onboarding/onboard-organization-helpers.ts @@ -8,13 +8,13 @@ import { Likelihood, Risk, RiskCategory, + RiskStatus, RiskTreatmentType, VendorCategory, } from '@db'; import { logger, tasks } from '@trigger.dev/sdk'; -import { generateObject, generateText } from 'ai'; +import { generateObject, generateText, jsonSchema } from 'ai'; import axios from 'axios'; -import z from 'zod'; import type { researchVendor } from '../scrape/research'; import { RISK_MITIGATION_PROMPT } from './prompts/risk-mitigation'; import { VENDOR_RISK_ASSESSMENT_PROMPT } from './prompts/vendor-risk-assessment'; @@ -53,6 +53,58 @@ export type RiskData = { department: Departments; }; +// Baseline risks that must always exist for every organization regardless of frameworks +const BASELINE_RISKS: Array<{ + title: string; + description: string; + category: RiskCategory; + department: Departments; + status: RiskStatus; +}> = [ + { + title: 'Intentional Fraud and Misuse', + description: + 'Intentional misrepresentation or deception by an internal actor (employee, contractor) or by the organization as a whole, for the purpose of achieving an unauthorized or improper gain.', + category: RiskCategory.governance, + department: Departments.gov, + status: RiskStatus.closed, + }, +]; + +/** + * Ensures baseline risks are present for the organization. + * Creates them if missing. Returns the list of risks that were created. + */ +export async function ensureBaselineRisks(organizationId: string): Promise { + const created: Risk[] = []; + + for (const base of BASELINE_RISKS) { + const existing = await db.risk.findFirst({ + where: { + organizationId, + title: base.title, + }, + }); + + if (!existing) { + const risk = await db.risk.create({ + data: { + title: base.title, + description: base.description, + category: base.category, + department: base.department, + status: base.status, + organizationId, + }, + }); + created.push(risk); + logger.info(`Created baseline risk: ${risk.id} (${risk.title})`); + } + } + + return created; +} + /** * Revalidates the organization path for cache busting */ @@ -114,28 +166,47 @@ export async function getOrganizationContext(organizationId: string) { export async function extractVendorsFromContext( questionsAndAnswers: ContextItem[], ): Promise { - const result = await generateObject({ + const { object } = await generateObject({ model: openai('gpt-4.1-mini'), - schema: z.object({ - vendors: z.array( - z.object({ - vendor_name: z.string(), - vendor_website: z.string(), - vendor_description: z.string(), - category: z.enum(Object.values(VendorCategory) as [string, ...string[]]), - inherent_probability: z.enum(Object.values(Likelihood) as [string, ...string[]]), - inherent_impact: z.enum(Object.values(Impact) as [string, ...string[]]), - residual_probability: z.enum(Object.values(Likelihood) as [string, ...string[]]), - residual_impact: z.enum(Object.values(Impact) as [string, ...string[]]), - }), - ), + mode: 'json', + schema: jsonSchema({ + type: 'object', + properties: { + vendors: { + type: 'array', + items: { + type: 'object', + properties: { + vendor_name: { type: 'string' }, + vendor_website: { type: 'string' }, + vendor_description: { type: 'string' }, + category: { type: 'string', enum: Object.values(VendorCategory) }, + inherent_probability: { type: 'string', enum: Object.values(Likelihood) }, + inherent_impact: { type: 'string', enum: Object.values(Impact) }, + residual_probability: { type: 'string', enum: Object.values(Likelihood) }, + residual_impact: { type: 'string', enum: Object.values(Impact) }, + }, + required: [ + 'vendor_name', + 'vendor_website', + 'vendor_description', + 'category', + 'inherent_probability', + 'inherent_impact', + 'residual_probability', + 'residual_impact', + ], + }, + }, + }, + required: ['vendors'], }), system: 'Extract vendor names from the following questions and answers. Return their name (grammar-correct), website, description, category, inherent probability, inherent impact, residual probability, and residual impact.', prompt: questionsAndAnswers.map((q) => `${q.question}\n${q.answer}`).join('\n'), }); - return result.object.vendors as VendorData[]; + return (object as { vendors: VendorData[] }).vendors; } /** @@ -335,23 +406,40 @@ export async function extractRisksFromContext( organizationName: string, existingRisks: { title: string }[], ): Promise { - const result = await generateObject({ + const { object } = await generateObject({ model: openai('gpt-4.1-mini'), - schema: z.object({ - risks: z.array( - z.object({ - risk_name: z.string(), - risk_description: z.string(), - risk_treatment_strategy: z.enum( - Object.values(RiskTreatmentType) as [string, ...string[]], - ), - risk_treatment_strategy_description: z.string(), - risk_residual_probability: z.enum(Object.values(Likelihood) as [string, ...string[]]), - risk_residual_impact: z.enum(Object.values(Impact) as [string, ...string[]]), - category: z.enum(Object.values(RiskCategory) as [string, ...string[]]), - department: z.enum(Object.values(Departments) as [string, ...string[]]), - }), - ), + mode: 'json', + schema: jsonSchema({ + type: 'object', + properties: { + risks: { + type: 'array', + items: { + type: 'object', + properties: { + risk_name: { type: 'string' }, + risk_description: { type: 'string' }, + risk_treatment_strategy: { type: 'string', enum: Object.values(RiskTreatmentType) }, + risk_treatment_strategy_description: { type: 'string' }, + risk_residual_probability: { type: 'string', enum: Object.values(Likelihood) }, + risk_residual_impact: { type: 'string', enum: Object.values(Impact) }, + category: { type: 'string', enum: Object.values(RiskCategory) }, + department: { type: 'string', enum: Object.values(Departments) }, + }, + required: [ + 'risk_name', + 'risk_description', + 'risk_treatment_strategy', + 'risk_treatment_strategy_description', + 'risk_residual_probability', + 'risk_residual_impact', + 'category', + 'department', + ], + }, + }, + }, + required: ['risks'], }), system: `Create a list of 8-12 risks that are relevant to the organization. Use action-oriented language, assume reviewers understand basic termilology - skip definitions. Your mandate is to propose risks that satisfy both ISO 27001:2022 clause 6.1 (risk management) and SOC 2 trust services criteria CC3 and CC4. @@ -367,7 +455,7 @@ export async function extractRisksFromContext( `, }); - return result.object.risks as RiskData[]; + return (object as { risks: RiskData[] }).risks; } /** @@ -489,7 +577,10 @@ export async function createRisks( organizationId: string, organizationName: string, ): Promise { - // Get existing risks to avoid duplicates + // Ensure baseline risks exist first so the AI doesn't recreate them + await ensureBaselineRisks(organizationId); + + // Get existing risks to avoid duplicates (includes baseline) const existingRisks = await getExistingRisks(organizationId); // Extract risks using AI diff --git a/bun.lock b/bun.lock index 3745b4faf..4bcb2159b 100644 --- a/bun.lock +++ b/bun.lock @@ -3,6 +3,9 @@ "workspaces": { "": { "name": "comp", + "dependencies": { + "zod": "^3.25.76", + }, "devDependencies": { "@azure/core-http": "^3.0.5", "@azure/core-rest-pipeline": "^1.21.0", @@ -51,7 +54,6 @@ "turbo": "^2.5.4", "typescript": "^5.8.3", "use-debounce": "^10.0.4", - "zod": "^4.0.14", }, }, "apps/api": { @@ -196,7 +198,7 @@ "use-long-press": "^3.3.0", "xml2js": "^0.6.2", "zaraz-ts": "^1.2.0", - "zod": "^4.0.17", + "zod": "^3.25.76", "zustand": "^5.0.3", }, "devDependencies": { @@ -4943,7 +4945,7 @@ "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], - "zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "zod-error": ["zod-error@1.5.0", "", { "dependencies": { "zod": "^3.20.2" } }, "sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ=="], @@ -5021,6 +5023,8 @@ "@commitlint/types/chalk": ["chalk@5.6.0", "", {}, "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ=="], + "@comp/api/zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], + "@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=="], @@ -5029,8 +5033,6 @@ "@discordjs/ws/@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@dub/better-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@dub/embed-react/vite": ["vite@5.2.9", "", { "dependencies": { "esbuild": "^0.20.1", "postcss": "^8.4.38", "rollup": "^4.13.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -5081,8 +5083,6 @@ "@jest/transform/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - "@mendable/firecrawl-js/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@nangohq/types/type-fest": ["type-fest@4.32.0", "", {}, "sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw=="], "@nestjs/cli/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], @@ -5185,17 +5185,15 @@ "@trigger.dev/core/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - "@trigger.dev/core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@trigger.dev/sdk/chalk": ["chalk@5.6.0", "", {}, "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ=="], "@trigger.dev/sdk/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "@trycompai/db/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "@trycompai/integrations/zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], - "@vercel/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -5229,8 +5227,6 @@ "chalk-template/chalk": ["chalk@5.6.0", "", {}, "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ=="], - "chromium-bidi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "cli-highlight/parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="], "cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], @@ -5257,8 +5253,6 @@ "dotenv-expand/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], - "dub/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "ecdsa-sig-formatter/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], @@ -5991,8 +5985,6 @@ "yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], - "zod-error/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@angular-devkit/core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@angular-devkit/schematics/ora/cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], diff --git a/package.json b/package.json index 4ed8f37af..a1c44fc40 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,7 @@ "tsup": "^8.5.0", "turbo": "^2.5.4", "typescript": "^5.8.3", - "use-debounce": "^10.0.4", - "zod": "^4.0.14" + "use-debounce": "^10.0.4" }, "engines": { "node": ">=18" @@ -92,5 +91,8 @@ "packages/tsconfig", "packages/ui", "packages/utils" - ] + ], + "dependencies": { + "zod": "^3.25.76" + } } \ No newline at end of file