From d5f943be578355853f0d1d238e1c4533eb4468f0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:09:17 -0500 Subject: [PATCH 01/26] [dev] [Marfuen] mariano/integrations-architecture (#1852) * feat: working version of integrations framework * feat: make integrations more robust * feat(google-workspace): add integration manifest and checks for Google Workspace * feat(integration-platform): add custom settings to OAuth app and platform credential models * feat(integration-platform): add Google Workspace sync functionality and update integration manifest * feat(integration-platform): add scheduled task for syncing employees from integrations * feat(integration-platform): add AWS SDK clients and update integration manifest * refactor(integration-platform): integrate AWS Security Hub and update cloud tests * feat(integration-platform): add Rippling integration for employee sync and update manifest * feat(integration-platform): implement employee sync provider management and update related APIs * feat(integration-platform): add employee access review check for Google Workspace * feat(gcp): add GCP integration with IAM access and monitoring checks * feat(gcp): add GCP integration with IAM access and monitoring checks * feat: working version of integrations framework * feat: make integrations more robust * feat(google-workspace): add integration manifest and checks for Google Workspace * feat(integration-platform): add custom settings to OAuth app and platform credential models * feat(integration-platform): add Google Workspace sync functionality and update integration manifest * feat(integration-platform): add scheduled task for syncing employees from integrations * feat(integration-platform): add AWS SDK clients and update integration manifest * refactor(integration-platform): integrate AWS Security Hub and update cloud tests * feat(integration-platform): add Rippling integration for employee sync and update manifest * feat(integration-platform): implement employee sync provider management and update related APIs * feat(integration-platform): add employee access review check for Google Workspace * chore: remove deprecated vector tasks and update openapi.json * feat(integration-platform): add Azure integration with credential management and checks * feat(integrations): unify integrations list and add task card component * feat(integration-platform): add auto-check runner service and related tasks * feat(integration-platform): implement GCP OAuth integration with credential management and checks * feat(integrations): add combobox support for credential fields and update AWS region type * refactor(db): fix integrations order * feat(integration-platform): add validation for API key and basic auth credentials in checks * refactor(api): add throttling and security headers with helmet integration * chore(cloud-security): add cloud security module with scanning capabilities * chore(dependencies): update next and react versions to 16.0.8 and 19.2.1 * refactor(proxy): add proxy function with request matcher configuration * chore(dependencies): update react and next versions to 19.1.1 and 16.0.8 * refactor(people): allow selectedProvider to be undefined and simplify date handling * chore(dependencies): update @ai-sdk/openai and ai package versions * refactor(onboarding): remove redundant mode parameter from generateObject calls * refactor(api): update content security policy for improved security --------- Co-authored-by: Mariano Fuentes --- .cursor/rules/after-changes.mdc | 38 + .cursor/rules/better-auth.mdc | 28 - .cursor/rules/code-standards.mdc | 186 + .cursor/rules/componentization.mdc | 439 + .../create-dialog-on-framework-editor.mdc | 34 - .cursor/rules/cursor-usage.mdc | 266 + .cursor/rules/data-fetching-and-writing.mdc | 17 - .cursor/rules/data-fetching.mdc | 255 + .cursor/rules/design-system.mdc | 251 +- .cursor/rules/file-structure.mdc | 121 +- .cursor/rules/forms.mdc | 423 + .cursor/rules/imports.mdc | 6 - .cursor/rules/monorepo.mdc | 263 + .cursor/rules/package-manager.mdc | 66 +- .cursor/rules/prisma.mdc | 143 +- .cursor/rules/prompt-engineering.mdc | 184 + .cursor/rules/react-code.mdc | 202 +- .cursor/rules/server-actions.mdc | 16 - .cursor/rules/trigger.advanced-tasks.mdc | 155 +- .cursor/rules/typescript-rules.mdc | 180 +- apps/.cursor/rules/trigger.basic.mdc | 190 - apps/api/Dockerfile | 1 + apps/api/package.json | 3 + apps/api/packages/docs/openapi.json | 11109 ++++++++++++++++ apps/api/prisma/client.d.ts | 2 + apps/api/prisma/client.js | 9 + apps/api/prisma/client.js.map | 1 + apps/api/prisma/index.d.ts | 2 + apps/api/prisma/index.js | 21 + apps/api/prisma/index.js.map | 1 + apps/api/src/app.module.ts | 20 +- apps/api/src/app/s3.ts | 3 +- apps/api/src/auth/platform-admin.guard.ts | 153 + .../cloud-security.controller.ts | 56 + .../cloud-security/cloud-security.module.ts | 20 + .../cloud-security/cloud-security.service.ts | 264 + .../providers/aws-security.service.ts | 194 + .../providers/azure-security.service.ts | 274 + .../providers/gcp-security.service.ts | 148 + .../common/filters/cors-exception.filter.ts | 55 + apps/api/src/config/load-env.ts | 1 - .../src/device-agent/device-agent.service.ts | 10 +- .../admin-integrations.controller.ts | 197 + .../controllers/checks.controller.ts | 297 + .../controllers/connections.controller.ts | 688 + .../controllers/oauth-apps.controller.ts | 180 + .../controllers/oauth.controller.ts | 494 + .../controllers/sync.controller.ts | 907 ++ .../task-integrations.controller.ts | 554 + .../controllers/variables.controller.ts | 351 + .../controllers/webhook.controller.ts | 210 + .../integration-platform.module.ts | 57 + .../repositories/check-run.repository.ts | 206 + .../repositories/connection.repository.ts | 158 + .../repositories/credential.repository.ts | 98 + .../repositories/oauth-app.repository.ts | 148 + .../repositories/oauth-state.repository.ts | 70 + .../platform-credential.repository.ts | 105 + .../repositories/provider.repository.ts | 88 + .../services/auto-check-runner.service.ts | 135 + .../services/connection.service.ts | 151 + .../services/credential-vault.service.ts | 398 + .../services/oauth-credentials.service.ts | 298 + .../knowledge-base.controller.ts | 4 +- .../knowledge-base/knowledge-base.service.ts | 31 +- .../api/src/knowledge-base/utils/constants.ts | 1 - .../src/knowledge-base/utils/s3-operations.ts | 1 - apps/api/src/main.ts | 103 +- .../questionnaire/questionnaire.controller.ts | 13 +- .../questionnaire/questionnaire.service.ts | 94 +- apps/api/src/questionnaire/utils/constants.ts | 1 - .../questionnaire/utils/content-extractor.ts | 209 +- .../questionnaire/utils/export-generator.ts | 1 - .../questionnaire/utils/question-parser.ts | 33 +- .../utils/questionnaire-storage.ts | 7 +- apps/api/src/soa/soa.service.ts | 36 +- apps/api/src/soa/utils/constants.ts | 6 +- .../api/src/soa/utils/soa-answer-generator.ts | 23 +- apps/api/src/soa/utils/soa-answer-parser.ts | 9 +- apps/api/src/soa/utils/soa-storage.ts | 5 +- .../run-connection-checks.ts | 263 + .../run-integration-checks-schedule.ts | 140 + .../run-task-integration-checks.ts | 299 + .../sync-employees-schedule.ts | 274 + .../questionnaire}/answer-question-helpers.ts | 36 +- .../questionnaire}/answer-question.ts | 0 .../questionnaire}/parse-questionnaire.ts | 40 +- .../delete-all-manual-answers-orchestrator.ts | 0 .../delete-knowledge-base-document.ts | 0 .../vector-store}/delete-manual-answer.ts | 0 .../helpers/extract-content-from-file.ts | 2 +- .../process-knowledge-base-document.ts | 0 ...s-knowledge-base-documents-orchestrator.ts | 0 .../trust-portal/trust-access.controller.ts | 9 +- .../src/trust-portal/trust-access.service.ts | 4 +- apps/api/src/utils/sse-utils.ts | 1 - .../lib/core/find-existing-embeddings.ts | 30 +- .../src/vector-store/lib/core/find-similar.ts | 19 +- .../vector-store/lib/core/query-helpers.ts | 3 +- apps/api/src/vector-store/lib/index.ts | 5 +- .../src/vector-store/lib/sync/sync-context.ts | 11 +- .../lib/sync/sync-knowledge-base.ts | 24 +- .../lib/sync/sync-organization.ts | 196 +- .../vector-store/lib/sync/sync-policies.ts | 15 +- .../src/vector-store/lib/sync/sync-utils.ts | 12 +- apps/api/src/vector-store/logger.ts | 24 +- apps/api/trigger.config.ts | 4 +- apps/app/package.json | 12 +- apps/app/scripts/backfill-training-videos.ts | 4 +- .../actions/organization/accept-invitation.ts | 2 +- .../actions/organization/invite-employee.ts | 2 +- .../src/actions/organization/invite-member.ts | 2 +- .../actions/organization/remove-employee.ts | 4 +- ...pdate-organization-advanced-mode-action.ts | 2 +- .../update-organization-name-action.ts | 2 +- .../update-organization-website-action.ts | 2 +- .../accept-requested-policy-changes.ts | 4 +- .../src/actions/policies/archive-policy.ts | 2 +- .../src/actions/policies/create-new-policy.ts | 2 +- .../app/src/actions/policies/delete-policy.ts | 2 +- .../policies/deny-requested-policy-changes.ts | 2 +- apps/app/src/actions/policies/publish-all.ts | 9 +- .../actions/policies/update-policy-action.ts | 2 +- .../policies/update-policy-form-action.ts | 2 +- apps/app/src/actions/research-vendor.ts | 2 +- .../src/actions/risk/create-risk-action.ts | 2 +- .../actions/risk/task/revalidate-upload.ts | 2 +- .../actions/risk/task/update-task-action.ts | 2 +- .../risk/update-inherent-risk-action.ts | 2 +- .../risk/update-residual-risk-action.ts | 2 +- .../risk/update-residual-risk-enum-action.ts | 2 +- .../src/actions/risk/update-risk-action.ts | 2 +- .../[orgId]/cloud-tests/actions/run-tests.ts | 2 +- .../components/CloudSettingsModal.tsx | 189 +- .../cloud-tests/components/EmptyState.tsx | 21 +- .../cloud-tests/components/FindingsTable.tsx | 9 +- .../cloud-tests/components/ResultsView.tsx | 122 +- .../cloud-tests/components/TestsLayout.tsx | 271 +- .../app/(app)/[orgId]/cloud-tests/page.tsx | 274 +- .../[controlId]/actions/delete-control.ts | 2 +- .../actions/delete-framework.ts | 2 +- .../components/IntegrationsGrid.tsx | 488 - .../components/PlatformIntegrations.tsx | 827 ++ .../integrations/components/TaskCard.tsx | 117 + .../app/(app)/[orgId]/integrations/page.tsx | 11 +- .../integrations/platform-test/page.tsx | 1444 ++ .../people/all/actions/removeMember.ts | 2 +- .../people/all/actions/revokeInvitation.ts | 2 +- .../people/all/actions/updateMemberRole.ts | 2 +- .../people/all/components/MemberRow.tsx | 21 +- .../people/all/components/TeamMembers.tsx | 16 +- .../all/components/TeamMembersClient.tsx | 159 +- .../(app)/[orgId]/people/all/data/queries.ts | 55 + .../people/all/hooks/useEmployeeSync.ts | 160 + .../[policyId]/actions/regenerate-policy.ts | 2 +- .../all/actions/regenerate-full-policies.ts | 2 +- .../hooks/useQuestionnaireParse.ts | 22 +- .../hooks/useDocumentProcessing.ts | 10 +- .../actions/regenerate-risk-mitigation.ts | 4 +- .../tasks/[taskId]/actions/delete-task.ts | 2 +- .../tasks/[taskId]/components/SingleTask.tsx | 16 +- .../[taskId]/components/TaskAutomations.tsx | 538 +- .../components/TaskIntegrationChecks.tsx | 1219 ++ .../hooks/use-task-integration-checks.ts | 67 + .../actions/check-dns-record.ts | 2 +- .../portal-settings/actions/custom-domain.ts | 2 +- .../actions/trust-portal-switch.ts | 2 +- .../actions/update-trust-portal-frameworks.ts | 2 +- .../actions/regenerate-vendor-mitigation.ts | 4 +- .../actions/task/create-task-action.ts | 2 +- .../actions/task/revalidate-upload.ts | 2 +- .../actions/task/update-task-action.ts | 2 +- .../actions/update-vendor-action.ts | 2 +- .../src/app/(app)/admin/integrations/page.tsx | 400 + apps/app/src/app/(app)/admin/layout.tsx | 53 + apps/app/src/app/(app)/admin/page.tsx | 7 + .../onboarding/actions/complete-onboarding.ts | 4 +- .../setup/actions/create-organization.ts | 4 +- .../go/[id]/components/onboarding-status.tsx | 2 +- .../src/app/api/cloud-tests/findings/route.ts | 163 +- .../app/api/cloud-tests/providers/route.ts | 89 +- .../integrations/ConnectIntegrationDialog.tsx | 445 + .../integrations/ManageIntegrationDialog.tsx | 781 ++ .../app/src/hooks/use-integration-platform.ts | 434 + apps/app/src/lib/server-api-client.ts | 82 + .../src/lib/vector/sync/sync-organization.ts | 87 +- apps/app/src/test-utils/mocks/auth.ts | 1 + apps/app/src/{jobs => trigger}/lib/prompts.ts | 0 .../app/src/{jobs => trigger}/lib/research.ts | 0 .../tasks/auditor/generate-auditor-content.ts | 2 +- .../device/create-fleet-label-for-all-orgs.ts | 0 .../device/create-fleet-label-for-org.ts | 0 .../tasks/email/new-policy-email.ts | 0 .../tasks/email/publish-all-policies-email.ts | 0 .../tasks/email/weekly-task-digest-email.ts | 0 .../tasks/integration/integration-results.ts | 0 .../tasks/integration/integration-schedule.ts | 0 .../integration/run-integration-tests.ts | 0 .../backfill-executive-context-all-orgs.ts | 0 .../backfill-executive-context-single-org.ts | 0 .../backfill-training-videos-for-all-orgs.ts | 0 .../backfill-training-videos-for-org.ts | 0 .../onboarding/generate-full-policies.ts | 0 .../onboarding/generate-risk-mitigation.ts | 0 .../onboarding/generate-vendor-mitigation.ts | 0 .../onboard-organization-helpers.ts | 2 - .../tasks/onboarding/onboard-organization.ts | 0 .../onboarding/prompts/risk-mitigation.ts | 0 .../prompts/vendor-risk-assessment.ts | 0 .../onboarding/update-policies-helpers.ts | 3 - .../tasks/onboarding/update-policy.ts | 0 .../tasks/scrape/research.ts | 2 +- .../tasks/task/policy-schedule.ts | 0 .../tasks/task/task-schedule.ts | 0 .../tasks/task/weekly-task-reminder.ts | 0 apps/app/tsconfig.json | 16 +- apps/portal/package.json | 6 +- apps/portal/src/{middleware.ts => proxy.ts} | 2 +- apps/portal/tsconfig.json | 2 +- bun.lock | 160 +- packages/db/prisma.config.ts | 4 + .../migration.sql | 2 + .../migration.sql | 190 + .../migration.sql | 26 + .../migration.sql | 24 + .../migration.sql | 2 + .../migration.sql | 70 + .../migration.sql | 2 + .../migration.sql | 2 + packages/db/prisma/schema.prisma | 1 - packages/db/prisma/schema/auth.prisma | 1 + .../prisma/schema/integration-platform.prisma | 415 + packages/db/prisma/schema/organization.prisma | 8 + packages/db/prisma/schema/task.prisma | 3 +- packages/db/prisma/seed/seed.ts | 4 +- packages/db/src/client.ts | 6 +- packages/docs/docs.json | 27 + packages/docs/integrations/authentication.mdx | 641 + packages/docs/integrations/checks.mdx | 794 ++ packages/docs/integrations/contributing.mdx | 236 + packages/docs/integrations/index.mdx | 113 + packages/docs/integrations/oauth-setup.mdx | 351 + packages/docs/integrations/variables.mdx | 531 + .../integrations/writing-integrations.mdx | 336 + packages/docs/openapi.json | 1010 ++ packages/integration-platform/README.md | 773 ++ packages/integration-platform/SELF_HOSTING.md | 240 + packages/integration-platform/package.json | 56 + .../scripts/generate-task-types.ts | 115 + .../integration-platform/src/api-types.ts | 181 + packages/integration-platform/src/index.ts | 114 + .../_template/checks/example-check.ts | 56 + .../src/manifests/_template/checks/index.ts | 8 + .../src/manifests/_template/index.ts | 67 + .../src/manifests/_template/types.ts | 20 + .../src/manifests/_template/variables.ts | 43 + .../src/manifests/aws/credentials.ts | 214 + .../src/manifests/aws/helpers/aws-client.ts | 275 + .../src/manifests/aws/helpers/index.ts | 26 + .../src/manifests/aws/index.ts | 28 + .../src/manifests/aws/types.ts | 92 + .../src/manifests/azure/credentials.ts | 108 + .../manifests/azure/helpers/azure-client.ts | 162 + .../src/manifests/azure/helpers/index.ts | 9 + .../src/manifests/azure/index.ts | 27 + .../src/manifests/azure/types.ts | 142 + .../src/manifests/gcp/helpers/gcp-client.ts | 247 + .../src/manifests/gcp/helpers/index.ts | 13 + .../src/manifests/gcp/index.ts | 65 + .../src/manifests/gcp/types.ts | 103 + .../github/checks/branch-protection.ts | 228 + .../src/manifests/github/checks/dependabot.ts | 74 + .../src/manifests/github/checks/index.ts | 8 + .../github/checks/sanitized-inputs.ts | 193 + .../src/manifests/github/index.ts | 64 + .../src/manifests/github/types.ts | 77 + .../src/manifests/github/variables.ts | 49 + .../checks/employee-access.ts | 193 + .../google-workspace/checks/index.ts | 2 + .../checks/two-factor-auth.ts | 118 + .../src/manifests/google-workspace/index.ts | 51 + .../src/manifests/google-workspace/types.ts | 81 + .../manifests/google-workspace/variables.ts | 48 + .../src/manifests/rippling/index.ts | 59 + .../src/manifests/rippling/types.ts | 31 + .../src/manifests/vercel/checks/index.ts | 1 + .../vercel/checks/monitoring-alerting.ts | 157 + .../src/manifests/vercel/index.ts | 59 + .../src/manifests/vercel/types.ts | 123 + .../src/registry/index.ts | 190 + .../src/runtime/check-context.ts | 507 + .../src/runtime/check-runner.ts | 127 + .../integration-platform/src/runtime/index.ts | 9 + .../integration-platform/src/task-mappings.ts | 630 + packages/integration-platform/src/types.ts | 790 ++ .../src/validators/manifest-validator.ts | 156 + packages/integration-platform/tsconfig.json | 20 + packages/ui/src/components/badge.tsx | 3 +- packages/ui/src/globals.css | 4 + 299 files changed, 42204 insertions(+), 2337 deletions(-) create mode 100644 .cursor/rules/after-changes.mdc delete mode 100644 .cursor/rules/better-auth.mdc create mode 100644 .cursor/rules/code-standards.mdc create mode 100644 .cursor/rules/componentization.mdc delete mode 100644 .cursor/rules/create-dialog-on-framework-editor.mdc create mode 100644 .cursor/rules/cursor-usage.mdc delete mode 100644 .cursor/rules/data-fetching-and-writing.mdc create mode 100644 .cursor/rules/data-fetching.mdc create mode 100644 .cursor/rules/forms.mdc delete mode 100644 .cursor/rules/imports.mdc create mode 100644 .cursor/rules/monorepo.mdc create mode 100644 .cursor/rules/prompt-engineering.mdc delete mode 100644 .cursor/rules/server-actions.mdc delete mode 100644 apps/.cursor/rules/trigger.basic.mdc create mode 100644 apps/api/packages/docs/openapi.json create mode 100644 apps/api/prisma/client.d.ts create mode 100644 apps/api/prisma/client.js create mode 100644 apps/api/prisma/client.js.map create mode 100644 apps/api/prisma/index.d.ts create mode 100644 apps/api/prisma/index.js create mode 100644 apps/api/prisma/index.js.map create mode 100644 apps/api/src/auth/platform-admin.guard.ts create mode 100644 apps/api/src/cloud-security/cloud-security.controller.ts create mode 100644 apps/api/src/cloud-security/cloud-security.module.ts create mode 100644 apps/api/src/cloud-security/cloud-security.service.ts create mode 100644 apps/api/src/cloud-security/providers/aws-security.service.ts create mode 100644 apps/api/src/cloud-security/providers/azure-security.service.ts create mode 100644 apps/api/src/cloud-security/providers/gcp-security.service.ts create mode 100644 apps/api/src/common/filters/cors-exception.filter.ts create mode 100644 apps/api/src/integration-platform/controllers/admin-integrations.controller.ts create mode 100644 apps/api/src/integration-platform/controllers/checks.controller.ts create mode 100644 apps/api/src/integration-platform/controllers/connections.controller.ts create mode 100644 apps/api/src/integration-platform/controllers/oauth-apps.controller.ts create mode 100644 apps/api/src/integration-platform/controllers/oauth.controller.ts create mode 100644 apps/api/src/integration-platform/controllers/sync.controller.ts create mode 100644 apps/api/src/integration-platform/controllers/task-integrations.controller.ts create mode 100644 apps/api/src/integration-platform/controllers/variables.controller.ts create mode 100644 apps/api/src/integration-platform/controllers/webhook.controller.ts create mode 100644 apps/api/src/integration-platform/integration-platform.module.ts create mode 100644 apps/api/src/integration-platform/repositories/check-run.repository.ts create mode 100644 apps/api/src/integration-platform/repositories/connection.repository.ts create mode 100644 apps/api/src/integration-platform/repositories/credential.repository.ts create mode 100644 apps/api/src/integration-platform/repositories/oauth-app.repository.ts create mode 100644 apps/api/src/integration-platform/repositories/oauth-state.repository.ts create mode 100644 apps/api/src/integration-platform/repositories/platform-credential.repository.ts create mode 100644 apps/api/src/integration-platform/repositories/provider.repository.ts create mode 100644 apps/api/src/integration-platform/services/auto-check-runner.service.ts create mode 100644 apps/api/src/integration-platform/services/connection.service.ts create mode 100644 apps/api/src/integration-platform/services/credential-vault.service.ts create mode 100644 apps/api/src/integration-platform/services/oauth-credentials.service.ts create mode 100644 apps/api/src/trigger/integration-platform/run-connection-checks.ts create mode 100644 apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts create mode 100644 apps/api/src/trigger/integration-platform/run-task-integration-checks.ts create mode 100644 apps/api/src/trigger/integration-platform/sync-employees-schedule.ts rename apps/api/src/{questionnaire/vendors => trigger/questionnaire}/answer-question-helpers.ts (93%) rename apps/api/src/{questionnaire/vendors => trigger/questionnaire}/answer-question.ts (100%) rename apps/api/src/{questionnaire/vendors => trigger/questionnaire}/parse-questionnaire.ts (92%) rename apps/api/src/{vector-store/jobs => trigger/vector-store}/delete-all-manual-answers-orchestrator.ts (100%) rename apps/api/src/{vector-store/jobs => trigger/vector-store}/delete-knowledge-base-document.ts (100%) rename apps/api/src/{vector-store/jobs => trigger/vector-store}/delete-manual-answer.ts (100%) rename apps/api/src/{vector-store/jobs => trigger/vector-store}/helpers/extract-content-from-file.ts (99%) rename apps/api/src/{vector-store/jobs => trigger/vector-store}/process-knowledge-base-document.ts (100%) rename apps/api/src/{vector-store/jobs => trigger/vector-store}/process-knowledge-base-documents-orchestrator.ts (100%) delete mode 100644 apps/app/src/app/(app)/[orgId]/integrations/components/IntegrationsGrid.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/integrations/components/TaskCard.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/integrations/platform-test/page.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/people/all/data/queries.ts create mode 100644 apps/app/src/app/(app)/[orgId]/people/all/hooks/useEmployeeSync.ts create mode 100644 apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/TaskIntegrationChecks.tsx create mode 100644 apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/use-task-integration-checks.ts create mode 100644 apps/app/src/app/(app)/admin/integrations/page.tsx create mode 100644 apps/app/src/app/(app)/admin/layout.tsx create mode 100644 apps/app/src/app/(app)/admin/page.tsx create mode 100644 apps/app/src/components/integrations/ConnectIntegrationDialog.tsx create mode 100644 apps/app/src/components/integrations/ManageIntegrationDialog.tsx create mode 100644 apps/app/src/hooks/use-integration-platform.ts create mode 100644 apps/app/src/lib/server-api-client.ts rename apps/app/src/{jobs => trigger}/lib/prompts.ts (100%) rename apps/app/src/{jobs => trigger}/lib/research.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/auditor/generate-auditor-content.ts (99%) rename apps/app/src/{jobs => trigger}/tasks/device/create-fleet-label-for-all-orgs.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/device/create-fleet-label-for-org.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/email/new-policy-email.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/email/publish-all-policies-email.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/email/weekly-task-digest-email.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/integration/integration-results.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/integration/integration-schedule.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/integration/run-integration-tests.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/backfill-executive-context-all-orgs.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/backfill-executive-context-single-org.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/backfill-training-videos-for-all-orgs.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/backfill-training-videos-for-org.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/generate-full-policies.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/generate-risk-mitigation.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/generate-vendor-mitigation.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/onboard-organization-helpers.ts (99%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/onboard-organization.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/prompts/risk-mitigation.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/prompts/vendor-risk-assessment.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/update-policies-helpers.ts (99%) rename apps/app/src/{jobs => trigger}/tasks/onboarding/update-policy.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/scrape/research.ts (98%) rename apps/app/src/{jobs => trigger}/tasks/task/policy-schedule.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/task/task-schedule.ts (100%) rename apps/app/src/{jobs => trigger}/tasks/task/weekly-task-reminder.ts (100%) rename apps/portal/src/{middleware.ts => proxy.ts} (78%) create mode 100644 packages/db/prisma/migrations/20251203212921_add_employee_sync_provider/migration.sql create mode 100644 packages/db/prisma/migrations/20251206100000_add_integrations_tables/migration.sql create mode 100644 packages/db/prisma/migrations/20251206101000_add_oauth_table/migration.sql create mode 100644 packages/db/prisma/migrations/20251206102000_add_platform_credentials/migration.sql create mode 100644 packages/db/prisma/migrations/20251206103000_add_variables_to_providers/migration.sql create mode 100644 packages/db/prisma/migrations/20251206104000_add_runs_to_integrations/migration.sql create mode 100644 packages/db/prisma/migrations/20251206105000_add_custom_settings_to_oauth_app/migration.sql create mode 100644 packages/db/prisma/migrations/20251206106000_add_custom_settings_to_platform_credential/migration.sql create mode 100644 packages/db/prisma/schema/integration-platform.prisma create mode 100644 packages/docs/integrations/authentication.mdx create mode 100644 packages/docs/integrations/checks.mdx create mode 100644 packages/docs/integrations/contributing.mdx create mode 100644 packages/docs/integrations/index.mdx create mode 100644 packages/docs/integrations/oauth-setup.mdx create mode 100644 packages/docs/integrations/variables.mdx create mode 100644 packages/docs/integrations/writing-integrations.mdx create mode 100644 packages/integration-platform/README.md create mode 100644 packages/integration-platform/SELF_HOSTING.md create mode 100644 packages/integration-platform/package.json create mode 100644 packages/integration-platform/scripts/generate-task-types.ts create mode 100644 packages/integration-platform/src/api-types.ts create mode 100644 packages/integration-platform/src/index.ts create mode 100644 packages/integration-platform/src/manifests/_template/checks/example-check.ts create mode 100644 packages/integration-platform/src/manifests/_template/checks/index.ts create mode 100644 packages/integration-platform/src/manifests/_template/index.ts create mode 100644 packages/integration-platform/src/manifests/_template/types.ts create mode 100644 packages/integration-platform/src/manifests/_template/variables.ts create mode 100644 packages/integration-platform/src/manifests/aws/credentials.ts create mode 100644 packages/integration-platform/src/manifests/aws/helpers/aws-client.ts create mode 100644 packages/integration-platform/src/manifests/aws/helpers/index.ts create mode 100644 packages/integration-platform/src/manifests/aws/index.ts create mode 100644 packages/integration-platform/src/manifests/aws/types.ts create mode 100644 packages/integration-platform/src/manifests/azure/credentials.ts create mode 100644 packages/integration-platform/src/manifests/azure/helpers/azure-client.ts create mode 100644 packages/integration-platform/src/manifests/azure/helpers/index.ts create mode 100644 packages/integration-platform/src/manifests/azure/index.ts create mode 100644 packages/integration-platform/src/manifests/azure/types.ts create mode 100644 packages/integration-platform/src/manifests/gcp/helpers/gcp-client.ts create mode 100644 packages/integration-platform/src/manifests/gcp/helpers/index.ts create mode 100644 packages/integration-platform/src/manifests/gcp/index.ts create mode 100644 packages/integration-platform/src/manifests/gcp/types.ts create mode 100644 packages/integration-platform/src/manifests/github/checks/branch-protection.ts create mode 100644 packages/integration-platform/src/manifests/github/checks/dependabot.ts create mode 100644 packages/integration-platform/src/manifests/github/checks/index.ts create mode 100644 packages/integration-platform/src/manifests/github/checks/sanitized-inputs.ts create mode 100644 packages/integration-platform/src/manifests/github/index.ts create mode 100644 packages/integration-platform/src/manifests/github/types.ts create mode 100644 packages/integration-platform/src/manifests/github/variables.ts create mode 100644 packages/integration-platform/src/manifests/google-workspace/checks/employee-access.ts create mode 100644 packages/integration-platform/src/manifests/google-workspace/checks/index.ts create mode 100644 packages/integration-platform/src/manifests/google-workspace/checks/two-factor-auth.ts create mode 100644 packages/integration-platform/src/manifests/google-workspace/index.ts create mode 100644 packages/integration-platform/src/manifests/google-workspace/types.ts create mode 100644 packages/integration-platform/src/manifests/google-workspace/variables.ts create mode 100644 packages/integration-platform/src/manifests/rippling/index.ts create mode 100644 packages/integration-platform/src/manifests/rippling/types.ts create mode 100644 packages/integration-platform/src/manifests/vercel/checks/index.ts create mode 100644 packages/integration-platform/src/manifests/vercel/checks/monitoring-alerting.ts create mode 100644 packages/integration-platform/src/manifests/vercel/index.ts create mode 100644 packages/integration-platform/src/manifests/vercel/types.ts create mode 100644 packages/integration-platform/src/registry/index.ts create mode 100644 packages/integration-platform/src/runtime/check-context.ts create mode 100644 packages/integration-platform/src/runtime/check-runner.ts create mode 100644 packages/integration-platform/src/runtime/index.ts create mode 100644 packages/integration-platform/src/task-mappings.ts create mode 100644 packages/integration-platform/src/types.ts create mode 100644 packages/integration-platform/src/validators/manifest-validator.ts create mode 100644 packages/integration-platform/tsconfig.json diff --git a/.cursor/rules/after-changes.mdc b/.cursor/rules/after-changes.mdc new file mode 100644 index 000000000..81a245000 --- /dev/null +++ b/.cursor/rules/after-changes.mdc @@ -0,0 +1,38 @@ +--- +description: Always run typecheck and lint after making code changes +globs: **/*.{ts,tsx} +alwaysApply: true +--- + +# After Code Changes + +After making any code changes, **always run these checks**: + +```bash +# TypeScript type checking +bun run typecheck + +# ESLint linting +bun run lint +``` + +## Fix Errors Before Committing + +If either check fails: + +1. Fix all TypeScript errors first (they break the build) +2. Fix ESLint errors/warnings +3. Re-run checks until both pass +4. Only then commit or deploy + +## Common TypeScript Fixes + +- **Property does not exist**: Check interface/type definitions, ensure correct property names +- **Type mismatch**: Verify the expected type vs actual type being passed +- **Empty interface extends**: Use `type X = SomeType` instead of `interface X extends SomeType {}` + +## Common ESLint Fixes + +- **Unused variables**: Remove or prefix with `_` +- **Any type**: Add proper typing +- **Empty object type**: Use `type` instead of `interface` for type aliases diff --git a/.cursor/rules/better-auth.mdc b/.cursor/rules/better-auth.mdc deleted file mode 100644 index 4986ca7a2..000000000 --- a/.cursor/rules/better-auth.mdc +++ /dev/null @@ -1,28 +0,0 @@ ---- -description: When dealing with auth or authClient related code. -globs: -alwaysApply: false ---- -Only use auth.ts and auth.api methods serverside. To use them you have to pass Next.js headers as follows: -auth.api.hasPermission({ - headers: await headers(), - body: { - permissions: { - project: ["create"] // This must match the structure in your access control - } - } -}); - - - - Only use auth-client.ts and authClient on clienside. You do not need to pass headers, it's already contextually aware. - - const canCreateProject = await authClient.organization.hasPermission({ - permissions: { - project: ["create"] - } -}) - - -For the full list of methods/supported actions reference: -https://www.better-auth.com/docs/plugins/organization \ No newline at end of file diff --git a/.cursor/rules/code-standards.mdc b/.cursor/rules/code-standards.mdc new file mode 100644 index 000000000..da46b64d1 --- /dev/null +++ b/.cursor/rules/code-standards.mdc @@ -0,0 +1,186 @@ +--- +description: General code quality standards and file size limits +globs: **/*.{ts,tsx} +alwaysApply: true +--- + +# Code Standards + +## File Size Limits + +**Files must not exceed 300 lines.** When a file approaches this limit, split it. + +### ✅ How to Split Large Files + +``` +# Before: One 400-line file +components/TaskList.tsx (400 lines) + +# After: Multiple focused files +components/ +├── TaskList.tsx # Main component (~100 lines) +├── TaskListItem.tsx # Individual item (~80 lines) +├── TaskListFilters.tsx # Filter controls (~60 lines) +└── TaskListEmpty.tsx # Empty state (~40 lines) +``` + +### Splitting Strategies + +| File Type | Split By | +| --------- | -------------------------------------------- | +| Component | Extract sub-components, hooks, utils | +| Hook | Extract helper functions, split by concern | +| Utils | Group by domain (dates, strings, validation) | +| Types | Split by entity (Task, User, Organization) | + +### ❌ Warning Signs + +```tsx +// ❌ File is too long +// TaskList.tsx - 450 lines with inline helpers, multiple components + +// ❌ Multiple components in one file +export function TaskList() { ... } +export function TaskCard() { ... } // Should be separate file +export function TaskBadge() { ... } // Should be separate file +``` + +## Code Quality + +### ✅ Always Do This + +```tsx +// Early returns for readability +function processTask(task: Task | null) { + if (!task) return null; + if (task.deleted) return null; + + return ; +} + +// Descriptive names +const handleTaskComplete = (taskId: string) => { ... }; +const isTaskOverdue = (task: Task) => task.dueDate < new Date(); + +// Const arrow functions with types +const formatDate = (date: Date): string => { + return date.toLocaleDateString(); +}; +``` + +### ❌ Never Do This + +```tsx +// No early returns - deeply nested +function processTask(task) { + if (task) { + if (!task.deleted) { + return ; + } + } + return null; +} + +// Vague names +const handleClick = () => { ... }; // Click on what? +const data = fetchStuff(); // What data? + +// Function keyword when const works +function formatDate(date) { ... } +``` + +## Function Parameters + +**Use named parameters (object destructuring) for functions with 2+ parameters.** + +### ✅ Always Do This + +```tsx +// Named parameters - clear at call site +const createTask = ({ title, assigneeId, dueDate }: CreateTaskParams) => { ... }; +createTask({ title: 'Review PR', assigneeId: user.id, dueDate: tomorrow }); + +// Hook with options object +const useTasks = ({ organizationId, initialData }: UseTasksOptions) => { ... }; +const { tasks } = useTasks({ organizationId: orgId, initialData: serverTasks }); + +// Component props (always named) +function TaskCard({ task, onComplete, showDetails }: TaskCardProps) { ... } + +``` + +### ❌ Never Do This + +```tsx +// Positional parameters - unclear at call site +const createTask = (title: string, assigneeId: string, dueDate: Date) => { ... }; +createTask('Review PR', user.id, tomorrow); // What's the 2nd param? + +// Multiple positional args are confusing +const formatRange = (start: Date, end: Date, format: string, timezone: string) => { ... }; +formatRange(startDate, endDate, 'MM/dd', 'UTC'); // Hard to read + +// Boolean positional params are the worst +fetchTasks(orgId, true, false, true); // What do these booleans mean? +``` + +### Exception: Single Parameter + +```tsx +// Single param is fine as positional +const getTask = (taskId: string) => { ... }; +const formatDate = (date: Date) => { ... }; +const isOverdue = (task: Task) => { ... }; +``` + +## Accessibility + +### ✅ Always Include + +```tsx +// Interactive elements need keyboard support +
e.key === 'Enter' && handleClick()} + aria-label="Delete task" +> + +
+ +// Form inputs need labels + + + +// Images need alt text +{`${user.name}'s +``` + +## DRY Principle + +### ✅ Extract Repeated Logic + +```tsx +// Before: Duplicated validation +if (email && email.includes('@') && email.length > 5) { ... } +if (email && email.includes('@') && email.length > 5) { ... } + +// After: Extracted helper +const isValidEmail = (email: string) => + email?.includes('@') && email.length > 5; + +if (isValidEmail(email)) { ... } +``` + +## Checklist + +Before committing: + +- [ ] No file exceeds 300 lines +- [ ] Uses early returns for conditionals +- [ ] Variable/function names are descriptive +- [ ] Functions with 2+ params use named parameters +- [ ] Interactive elements have keyboard support +- [ ] No duplicated logic (DRY) +- [ ] Const arrow functions with types diff --git a/.cursor/rules/componentization.mdc b/.cursor/rules/componentization.mdc new file mode 100644 index 000000000..23fa0af67 --- /dev/null +++ b/.cursor/rules/componentization.mdc @@ -0,0 +1,439 @@ +--- +description: Componentization philosophy - reuse over repetition with TailwindCSS and shadcn/ui +globs: **/*.{ts,tsx} +alwaysApply: true +--- + +# Componentization Philosophy + +## Core Principles + +### 1. Componentize Everything + +**Reuse over repetition.** + +- If a pattern appears 2+ times, it MUST be a component +- No copy-paste UI patterns +- Every visual element should trace back to a reusable component +- Manual implementations are technical debt + +### 2. Hard Component Enforcement + +**Components are immutable contracts.** + +```tsx +// ❌ NEVER - Arbitrary className overrides on design system components + +Content +Status + +// ✅ ALWAYS - Use component variants and props + +Content +Status +``` + +**Rules:** + +- Use Tailwind utility classes in custom components, NOT on design system components +- Use shadcn/ui variant props (`variant`, `size`) - never override with className +- Compose from primitives (flex containers, semantic HTML) +- Use `cn()` utility for conditional class merging + +### 3. Extension Strategy + +**When you need different styling, extend the component properly.** + +#### Option A: Add a Variant (using shadcn patterns) + +```tsx +// In components/ui/badge.tsx - extend variants +import { cva, type VariantProps } from "class-variance-authority"; + +const badgeVariants = cva( + "inline-flex items-center rounded-md px-2 py-1 text-xs font-medium", + { + variants: { + variant: { + default: "bg-primary/10 text-primary", + secondary: "bg-secondary text-secondary-foreground", + destructive: "bg-destructive/10 text-destructive", + outline: "border border-input bg-transparent", + // ADD NEW VARIANTS HERE + counter: "bg-muted text-muted-foreground tabular-nums font-mono", + technical: "bg-teal-500/10 text-teal-600 dark:text-teal-400 font-mono", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +// Usage +03 +SYS.ID +``` + +#### Option B: Add Props for Flexibility + +```tsx +// In components/ui/text.tsx - semantic text component +import { cn } from "@/lib/utils"; + +interface TextProps extends React.HTMLAttributes { + size?: "xs" | "sm" | "base" | "lg"; + variant?: "default" | "muted" | "primary" | "destructive"; + mono?: boolean; +} + +const sizeClasses = { + xs: "text-xs", + sm: "text-sm", + base: "text-base", + lg: "text-lg", +}; + +const variantClasses = { + default: "text-foreground", + muted: "text-muted-foreground", + primary: "text-primary", + destructive: "text-destructive", +}; + +export const Text = ({ + size = "base", + variant = "default", + mono, + className, + ...props +}: TextProps) => ( +

+); + +// Usage +Technical Label +``` + +#### Option C: Create a New Component + +```tsx +// components/ui/metric-number.tsx +import { cn } from "@/lib/utils"; + +interface MetricNumberProps { + value: number; + size?: "2xl" | "3xl" | "4xl"; + variant?: "default" | "destructive" | "success"; +} + +const sizeClasses = { + "2xl": "text-2xl", + "3xl": "text-3xl", + "4xl": "text-4xl", +}; + +const variantClasses = { + default: "text-foreground", + destructive: "text-red-600 dark:text-red-400", + success: "text-green-600 dark:text-green-400", +}; + +export const MetricNumber = ({ + value, + size = "4xl", + variant = "default" +}: MetricNumberProps) => ( + + {String(value).padStart(2, "0")} + +); + +// Usage + +``` + +### 4. Reusability Through Composition + +**Build complex UIs from simple, focused components.** + +```tsx +// ❌ BAD: Monolithic custom implementation with raw divs and classes +

+
+
+
+ + TITLE + + META +
+
+ 42 +
+
+ SYS.ID +
+
+
+ +// ✅ GOOD: Composed from reusable components + + + +``` + +## Component Development Workflow + +### When You Need Custom Styling + +1. **Check existing variants** + - Review shadcn component variants (`variant`, `size`) + - Check if existing props achieve what you need + +2. **If existing variants aren't enough, extend the component** + + ```tsx + // Add new variant to the cva definition + const buttonVariants = cva("...", { + variants: { + variant: { + // existing... + newVariant: "bg-teal-500 text-white hover:bg-teal-600", // ADD HERE + }, + }, + }); + ``` + +3. **If it's a new pattern, CREATE A COMPONENT** + + ```tsx + // components/ui/your-new-component.tsx + import { cn } from "@/lib/utils"; + + interface YourNewComponentProps { + children: React.ReactNode; + variant?: "default" | "muted"; + } + + export const YourNewComponent = ({ + children, + variant = "default" + }: YourNewComponentProps) => ( +
+ {children} +
+ ); + ``` + +4. **NEVER bypass the design system** + - Don't add random className overrides + - It breaks consistency + - It creates technical debt + +## Pattern Recognition + +### If You See This Pattern 2+ Times → Componentize + +```tsx +// Repeated list item layout +
+
+ {title} + {status} +
+ +
+→ Create ListItem component + +// Repeated counter pattern +
+ LABEL + {count} +
+→ Create CounterBadge component + +// Repeated status dot +
+→ Create StatusDot component + +// Repeated large numbers + + {String(value).padStart(2, "0")} + +→ Create MetricNumber component +``` + +## Testing Component Compliance + +### Before Committing, Check: + +- [ ] Uses shadcn/ui base components without className overrides +- [ ] Semantic props exposed (`variant`, `size`) +- [ ] No arbitrary style overrides on design system components +- [ ] Repeated patterns extracted to components +- [ ] Works in light and dark mode (uses semantic tokens) +- [ ] Uses Tailwind responsive prefixes (`sm:`, `md:`, `lg:`) +- [ ] Proper TypeScript types with inference where possible + +## Anti-Patterns to Avoid + +### ❌ Never Do This + +```tsx +// 1. Raw inline styles +
Content
+ +// 2. Overriding design system components with className + + +// 3. Hardcoded colors instead of semantic tokens +
+ +// 4. Mixing approaches + + +// 5. Copy-paste UI patterns +// If you copy UI code, STOP and componentize it first + +// 6. Non-responsive hardcoded values +
+``` + +### ✅ Always Do This + +```tsx +// 1. Use component variants + +Status +Content -- **Moderate Text Sizes**: Avoid overly large text - prefer `text-base`, `text-sm`, `text-xs` over `text-xl+` -- **Consistent Hierarchy**: Use `font-medium`, `font-semibold` sparingly, prefer `font-normal` with size differentiation -- **Tabular Numbers**: Use `tabular-nums` class for numeric data to ensure proper alignment +// Use semantic color tokens +
+
+
+ +// Dark mode with explicit variants +
+``` + +### ❌ Never Do This + +```tsx +// Overriding shadcn components with className + +Content + +// Hardcoded colors +
+
+
+``` ## Layout & Spacing -- **Flexbox-First**: ALWAYS prefer flexbox with `gap` over hardcoded margins (`mt-`, `mb-`, `ml-`, `mr-`) -- **Use Gaps, Not Margins**: Use `gap-2`, `gap-4`, `space-y-4` for spacing between elements -- **Consistent Spacing**: Use standard Tailwind spacing scale (`space-y-4`, `gap-6`, etc.) -- **Card-Based Layouts**: Prefer Card components for content organization -- **Minimal Padding**: Use conservative padding - `p-3`, `p-4` rather than larger values -- **Clean Separators**: Use subtle borders (`border-t`, `border-muted`) instead of heavy dividers -- **NEVER Hardcode Margins**: Avoid `mt-4`, `mb-2`, `ml-3` unless absolutely necessary for exceptions - -## Color & Visual Elements - -- **Status Colors**: - - Green for completed/success states - - Blue for in-progress/info states - - Yellow for warnings - - Red for errors/destructive actions -- **Subtle Indicators**: Use small colored dots (`w-2 h-2 rounded-full`) instead of large icons for status -- **Minimal Shadows**: Prefer `hover:shadow-sm` over heavy shadow effects -- **Progress Bars**: Keep thin (`h-1`, `h-2`) for minimal visual weight +### ✅ Always Do This -## Interactive Elements +```tsx +// Flexbox with gap +
+
+
-- **Subtle Hover States**: Use gentle transitions (`transition-shadow`, `hover:shadow-sm`) -- **Consistent Button Sizing**: Prefer `size="sm"` for most buttons, `size="icon"` for icon-only -- **Badge Usage**: Keep badges minimal with essential info only (percentages, short status) +// Responsive prefixes +
+
+``` -## Data Display +### ❌ Never Do This -- **Shared Design Language**: Ensure related components (cards, overviews, details) use consistent patterns -- **Minimal Stats**: Present data cleanly without excessive decoration -- **Contextual Icons**: Use small, relevant icons (`h-3 w-3`, `h-4 w-4`) sparingly for context +```tsx +// Hardcoded margins +
+ + + +// ❌ Heavy shadows and borders + +``` + +## Icons + +```tsx +// ✅ Small, contextual icons + + + +// ❌ Oversized icons + +``` + +## Exceptions + +The ONLY time you can pass className to a shadcn component: + +1. **Width utilities**: `w-full`, `max-w-md`, `flex-1` +2. **Responsive display**: `hidden`, `md:block`, `lg:flex` +3. **Grid/flex positioning**: `col-span-2`, `justify-self-end` -## Data Display +```tsx +// ✅ Acceptable className usage + +Spanning Card +Desktop Only +``` -- **Shared Design Language**: Ensure related components (cards, overviews, details) use consistent patterns -- **Minimal Stats**: Present data cleanly without excessive decoration -- **Contextual Icons**: Use small, relevant icons (`h-3 w-3`, `h-4 w-4`) sparingly for context +## Checklist -## Anti-Patterns to Avoid +Before committing UI code: -- Large text sizes (`text-2xl+` except for main headings) -- Heavy shadows or borders -- Excessive use of colored backgrounds -- Redundant badges or status indicators -- Complex custom styling overrides -- Non-semantic color usage (hardcoded hex values) -- Cluttered layouts with too many visual elements +- [ ] Uses shadcn component variants (not className overrides) +- [ ] Uses semantic color tokens (`bg-background`, `text-foreground`) +- [ ] Works in both light and dark mode +- [ ] Uses `gap` instead of margins for spacing +- [ ] Text sizes are moderate (`text-sm`, `text-base`) +- [ ] Icons are small (`h-4 w-4` or smaller) +- [ ] No hardcoded colors or arbitrary values +- [ ] Responsive with Tailwind prefixes (`md:`, `lg:`) diff --git a/.cursor/rules/file-structure.mdc b/.cursor/rules/file-structure.mdc index 2945a163b..d3d166e2c 100644 --- a/.cursor/rules/file-structure.mdc +++ b/.cursor/rules/file-structure.mdc @@ -1,8 +1,121 @@ --- -description: -globs: +description: File structure and colocalization guidelines for Next.js app router +globs: **/*.{ts,tsx} alwaysApply: true --- -Always opt for colocallizing code, where components, actions, etc.. are encapsulated at a route/page level in their own directory. If you encounter examples of non-colocalized code, try to colocalize it. -The only exception to this rule is code that will be reused across many different components/pages. \ No newline at end of file +# File Structure + +## Core Principle + +**Colocate code at the route/page level.** Components, hooks, actions, and queries should live next to the page that uses them. + +## Directory Structure + +### ✅ Good: Colocated Structure + +``` +app/(app)/[orgId]/tasks/ +├── page.tsx # Server component +├── components/ +│ ├── TaskList.tsx # Client component +│ ├── TaskCard.tsx +│ └── TaskFilters.tsx +├── hooks/ +│ └── useTasks.ts # SWR hook with mutations +├── data/ +│ └── queries.ts # Server-side queries +└── actions/ + └── task-actions.ts # If needed +``` + +### ❌ Bad: Scattered Structure + +``` +app/(app)/[orgId]/tasks/page.tsx +components/tasks/TaskList.tsx # ❌ Far from page +components/tasks/TaskCard.tsx # ❌ Hard to find +hooks/useTasks.ts # ❌ Not colocated +lib/queries/task-queries.ts # ❌ Disconnected +``` + +## When to Colocate vs Share + +### ✅ Colocate When + +- Component is only used by one page +- Hook fetches data specific to one route +- Query is only called from one page + +``` +app/(app)/[orgId]/vendors/ +├── components/ +│ └── VendorTable.tsx # Only used on vendors page +├── hooks/ +│ └── useVendors.ts # Only used here +└── page.tsx +``` + +### ✅ Share When + +- Component is used across 3+ pages +- Utility is genuinely generic +- Hook is reused across multiple routes + +``` +# Shared components live in: +src/components/ui/ # shadcn primitives +src/components/shared/ # cross-page components + +# Shared hooks live in: +src/hooks/ # e.g., useApiSWR, useDebounce +``` + +## Rules + +1. **Default to colocating** - Start by putting files next to the page +2. **Move to shared only when reused** - Don't pre-optimize +3. **Remove unused imports** - Always clean up after refactoring +4. **One component per file** - Name file same as component + +## Examples + +### ✅ Creating a New Feature + +```tsx +// app/(app)/[orgId]/risks/page.tsx +import { RiskTable } from './components/RiskTable'; +import { getRisks } from './data/queries'; + +export default async function RisksPage({ params }) { + const { orgId } = await params; + const risks = await getRisks(orgId); + return ; +} +``` + +```tsx +// app/(app)/[orgId]/risks/components/RiskTable.tsx +'use client'; +import { useRisks } from '../hooks/useRisks'; +// ... component implementation +``` + +### ❌ Anti-Pattern: Global Everything + +```tsx +// Don't do this: +import { RiskTable } from '@/components/risks/RiskTable'; +import { useRisks } from '@/hooks/useRisks'; +import { getRisks } from '@/lib/queries/risks'; +``` + +## Checklist + +Before committing: + +- [ ] New components live next to their page +- [ ] Hooks are colocated with the page that uses them +- [ ] Shared code is genuinely used by 3+ pages +- [ ] No unused imports remain +- [ ] File names match component/hook names diff --git a/.cursor/rules/forms.mdc b/.cursor/rules/forms.mdc new file mode 100644 index 000000000..0e99b10dd --- /dev/null +++ b/.cursor/rules/forms.mdc @@ -0,0 +1,423 @@ +--- +description: Form handling with React Hook Form, Zod validation, and shadcn/ui +globs: **/*.{ts,tsx} +alwaysApply: true +--- + +# Forms: React Hook Form + Zod + +## Core Principle + +**All forms MUST use React Hook Form with Zod validation. No exceptions.** + +## Required Stack + +- `react-hook-form` - Form state management +- `zod` - Schema validation +- `@hookform/resolvers` - Zod resolver for RHF + +## Basic Pattern + +```tsx +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; + +// 1. Define schema with Zod +const formSchema = z.object({ + email: z.string().email('Invalid email address'), + password: z.string().min(8, 'Password must be at least 8 characters'), +}); + +// 2. Infer TypeScript type from schema +type FormData = z.infer; + +// 3. Use in component +function MyForm() { + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + email: '', + password: '', + }, + }); + + const onSubmit = async (data: FormData) => { + // data is fully typed and validated + await submitToApi(data); + }; + + return ( +
+
+ + + {errors.email && ( +

{errors.email.message}

+ )} +
+ + +
+ ); +} +``` + +## Schema Best Practices + +### String Validation + +```tsx +const schema = z.object({ + // Required string with min length + name: z.string().min(1, 'Name is required'), + + // Email validation + email: z.string().email('Invalid email'), + + // URL validation + website: z.string().url('Invalid URL'), + + // Optional string + bio: z.string().optional(), + + // String with regex + slug: z.string().regex(/^[a-z0-9-]+$/, 'Only lowercase letters, numbers, and hyphens'), + + // Trimmed string (removes whitespace) + title: z.string().trim().min(1, 'Title is required'), +}); +``` + +### Number Validation + +```tsx +const schema = z.object({ + // Integer with range + age: z.number().int().min(0).max(150), + + // Positive number + price: z.number().positive('Must be positive'), + + // Coerce string to number (for inputs) + quantity: z.coerce.number().int().min(1), +}); +``` + +### Array & Object Validation + +```tsx +const schema = z.object({ + // Array of strings + tags: z.array(z.string()).min(1, 'At least one tag required'), + + // Array of objects + items: z.array( + z.object({ + name: z.string(), + quantity: z.number(), + }) + ), + + // Nested object + address: z.object({ + street: z.string(), + city: z.string(), + zip: z.string(), + }), +}); +``` + +### Conditional Validation + +```tsx +const schema = z + .object({ + hasAccount: z.boolean(), + accountId: z.string().optional(), + }) + .refine((data) => !data.hasAccount || data.accountId, { + message: 'Account ID required when has account', + path: ['accountId'], + }); +``` + +## shadcn/ui Form Components + +### Basic Input + +```tsx +
+ + + {errors.name && ( +

{errors.name.message}

+ )} +
+``` + +### With Controller (for complex components) + +```tsx +import { Controller } from 'react-hook-form'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; + + ( + + )} +/> +``` + +### Textarea + +```tsx +import { Textarea } from '@/components/ui/textarea'; + +
+ +