insforge 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +58 -0
- package/.env.example +49 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
- package/.github/copilot-instructions.md +147 -0
- package/.github/workflows/build-image.yml +65 -0
- package/.github/workflows/ci-premerge-check.yml +24 -0
- package/.github/workflows/deploy-aws.yml +130 -0
- package/.github/workflows/lint-and-format.yml +33 -0
- package/.prettierignore +65 -0
- package/.prettierrc +9 -0
- package/CHANGELOG.md +3 -0
- package/CONTRIBUTING.md +126 -0
- package/Dockerfile +27 -0
- package/GITHUB_OAUTH_SETUP.md +49 -0
- package/GOOGLE_OAUTH_SETUP.md +148 -0
- package/LICENSE +201 -0
- package/README.md +134 -0
- package/assets/Dark.svg +23 -0
- package/assets/archDiagram.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/assets/signin.png +0 -0
- package/assets/userflow.png +0 -0
- package/backend/migrations/000_create-base-tables.sql +142 -0
- package/backend/migrations/001_create-helper-functions.sql +41 -0
- package/backend/migrations/002_rename-auth-tables.sql +30 -0
- package/backend/migrations/003_create-users-table.sql +56 -0
- package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
- package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
- package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
- package/backend/migrations/007_drop-metadata-table.sql +2 -0
- package/backend/migrations/008_add-system-tables.sql +77 -0
- package/backend/migrations/009_add-function-secrets.sql +24 -0
- package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
- package/backend/migrations/011_refactor-secrets-table.sql +15 -0
- package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
- package/backend/package.json +75 -0
- package/backend/src/api/middleware/auth.ts +240 -0
- package/backend/src/api/middleware/error.ts +231 -0
- package/backend/src/api/middleware/upload.ts +59 -0
- package/backend/src/api/routes/agent.ts +29 -0
- package/backend/src/api/routes/ai.ts +472 -0
- package/backend/src/api/routes/auth.oauth.ts +482 -0
- package/backend/src/api/routes/auth.ts +386 -0
- package/backend/src/api/routes/database.advance.ts +275 -0
- package/backend/src/api/routes/database.records.ts +246 -0
- package/backend/src/api/routes/database.tables.ts +161 -0
- package/backend/src/api/routes/docs.ts +66 -0
- package/backend/src/api/routes/functions.ts +183 -0
- package/backend/src/api/routes/logs.ts +150 -0
- package/backend/src/api/routes/metadata.ts +160 -0
- package/backend/src/api/routes/openapi.ts +82 -0
- package/backend/src/api/routes/secrets.ts +199 -0
- package/backend/src/api/routes/storage.ts +547 -0
- package/backend/src/api/routes/usage.ts +96 -0
- package/backend/src/core/ai/chat.ts +207 -0
- package/backend/src/core/ai/client.ts +242 -0
- package/backend/src/core/ai/config.ts +187 -0
- package/backend/src/core/ai/image.ts +156 -0
- package/backend/src/core/ai/model.ts +117 -0
- package/backend/src/core/ai/usage.ts +290 -0
- package/backend/src/core/auth/auth.ts +781 -0
- package/backend/src/core/auth/oauth.ts +398 -0
- package/backend/src/core/database/advance.ts +1074 -0
- package/backend/src/core/database/manager.ts +178 -0
- package/backend/src/core/database/table.ts +772 -0
- package/backend/src/core/documentation/agent.ts +689 -0
- package/backend/src/core/documentation/openapi.ts +856 -0
- package/backend/src/core/functions/functions.ts +310 -0
- package/backend/src/core/logs/analytics.ts +76 -0
- package/backend/src/core/logs/audit.ts +255 -0
- package/backend/src/core/logs/providers/base.provider.ts +83 -0
- package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
- package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
- package/backend/src/core/secrets/encryption.ts +58 -0
- package/backend/src/core/secrets/secrets.ts +410 -0
- package/backend/src/core/socket/socket.ts +388 -0
- package/backend/src/core/socket/types.ts +79 -0
- package/backend/src/core/storage/storage.ts +923 -0
- package/backend/src/server.ts +288 -0
- package/backend/src/types/ai.ts +46 -0
- package/backend/src/types/auth.ts +90 -0
- package/backend/src/types/database.ts +136 -0
- package/backend/src/types/error-constants.ts +86 -0
- package/backend/src/types/logs.ts +47 -0
- package/backend/src/types/profile.ts +55 -0
- package/backend/src/types/storage.ts +23 -0
- package/backend/src/utils/cloud-token.ts +39 -0
- package/backend/src/utils/constants.ts +1 -0
- package/backend/src/utils/environment.ts +35 -0
- package/backend/src/utils/helpers.ts +49 -0
- package/backend/src/utils/logger.ts +13 -0
- package/backend/src/utils/response.ts +62 -0
- package/backend/src/utils/seed.ts +205 -0
- package/backend/src/utils/sql-parser.ts +63 -0
- package/backend/src/utils/uuid.ts +9 -0
- package/backend/src/utils/validations.ts +129 -0
- package/backend/tests/README.md +134 -0
- package/backend/tests/cleanup-all-test-data.sh +231 -0
- package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
- package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
- package/backend/tests/local/test-auth-router.sh +144 -0
- package/backend/tests/local/test-database-router.sh +222 -0
- package/backend/tests/local/test-e2e.sh +241 -0
- package/backend/tests/local/test-fk-errors.sh +97 -0
- package/backend/tests/local/test-id-field.sh +201 -0
- package/backend/tests/local/test-public-bucket.sh +265 -0
- package/backend/tests/local/test-secrets.sh +248 -0
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
- package/backend/tests/local/test-traditional-rest.sh +209 -0
- package/backend/tests/manual/README.md +51 -0
- package/backend/tests/manual/create-large-table-simple.sql +11 -0
- package/backend/tests/manual/seed-large-table.sql +101 -0
- package/backend/tests/manual/setup-large-table-extras.sql +34 -0
- package/backend/tests/manual/test-better-auth.sh +303 -0
- package/backend/tests/manual/test-bulk-upsert.sh +410 -0
- package/backend/tests/manual/test-database-advance.sh +297 -0
- package/backend/tests/manual/test-postgrest-stability.sh +192 -0
- package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
- package/backend/tests/manual/test-universal-storage.sh +264 -0
- package/backend/tests/manual/test-users.sql +18 -0
- package/backend/tests/run-all-tests.sh +140 -0
- package/backend/tests/setup.ts +22 -0
- package/backend/tests/test-config.sh +303 -0
- package/backend/tsconfig.json +23 -0
- package/backend/tsup.config.ts +18 -0
- package/backend/vitest.config.ts +22 -0
- package/docker-compose.prod.yml +145 -0
- package/docker-compose.yml +167 -0
- package/docker-init/db/db-init.sql +125 -0
- package/docker-init/db/jwt.sql +5 -0
- package/docker-init/db/logs.sql +9 -0
- package/docker-init/db/postgresql.conf +17 -0
- package/docs/deprecated/insforge-auth-api.md +215 -0
- package/docs/deprecated/insforge-auth-sdk.md +100 -0
- package/docs/deprecated/insforge-db-api.md +359 -0
- package/docs/deprecated/insforge-db-sdk.md +140 -0
- package/docs/deprecated/insforge-debug-sdk.md +157 -0
- package/docs/deprecated/insforge-debug.md +65 -0
- package/docs/deprecated/insforge-instructions.md +124 -0
- package/docs/deprecated/insforge-project.md +118 -0
- package/docs/deprecated/insforge-storage-api.md +279 -0
- package/docs/deprecated/insforge-storage-sdk.md +159 -0
- package/docs/insforge-instructions-sdk.md +407 -0
- package/eslint.config.js +317 -0
- package/examples/oauth/frontend-oauth-example.html +251 -0
- package/examples/response-examples.md +444 -0
- package/frontend/README.md +112 -0
- package/frontend/components.json +17 -0
- package/frontend/index.html +13 -0
- package/frontend/package.json +63 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/App.tsx +106 -0
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
- package/frontend/src/assets/icons/checked.svg +3 -0
- package/frontend/src/assets/icons/error.svg +3 -0
- package/frontend/src/assets/icons/pencil.svg +4 -0
- package/frontend/src/assets/icons/refresh.svg +4 -0
- package/frontend/src/assets/icons/step_active.svg +3 -0
- package/frontend/src/assets/icons/step_inactive.svg +11 -0
- package/frontend/src/assets/icons/warning.svg +3 -0
- package/frontend/src/assets/logos/amazon.svg +1 -0
- package/frontend/src/assets/logos/claude_code.svg +3 -0
- package/frontend/src/assets/logos/cline.svg +6 -0
- package/frontend/src/assets/logos/cursor.svg +20 -0
- package/frontend/src/assets/logos/discord.svg +9 -0
- package/frontend/src/assets/logos/gemini.svg +19 -0
- package/frontend/src/assets/logos/github.svg +5 -0
- package/frontend/src/assets/logos/google.svg +13 -0
- package/frontend/src/assets/logos/grok.svg +10 -0
- package/frontend/src/assets/logos/insforge_dark.svg +15 -0
- package/frontend/src/assets/logos/insforge_light.svg +15 -0
- package/frontend/src/assets/logos/openai.svg +10 -0
- package/frontend/src/assets/logos/roo_code.svg +9 -0
- package/frontend/src/assets/logos/trae.svg +3 -0
- package/frontend/src/assets/logos/windsurf.svg +10 -0
- package/frontend/src/components/ButtonWithLoading.tsx +27 -0
- package/frontend/src/components/Checkbox.tsx +61 -0
- package/frontend/src/components/CodeBlock.tsx +32 -0
- package/frontend/src/components/ConfirmDialog.tsx +96 -0
- package/frontend/src/components/CopyButton.tsx +69 -0
- package/frontend/src/components/DeleteActionButton.tsx +42 -0
- package/frontend/src/components/EmptyState.tsx +41 -0
- package/frontend/src/components/ErrorState.tsx +35 -0
- package/frontend/src/components/FeatureSidebar.tsx +126 -0
- package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
- package/frontend/src/components/JsonHighlight.tsx +61 -0
- package/frontend/src/components/LoadingState.tsx +16 -0
- package/frontend/src/components/PaginationControls.tsx +54 -0
- package/frontend/src/components/PromptDialog.tsx +68 -0
- package/frontend/src/components/SearchInput.tsx +90 -0
- package/frontend/src/components/SelectionClearButton.tsx +26 -0
- package/frontend/src/components/Stepper.tsx +139 -0
- package/frontend/src/components/ThemeToggle.tsx +58 -0
- package/frontend/src/components/TypeBadge.tsx +20 -0
- package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
- package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
- package/frontend/src/components/datagrid/IdCell.tsx +44 -0
- package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
- package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
- package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
- package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
- package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
- package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
- package/frontend/src/components/datagrid/index.tsx +20 -0
- package/frontend/src/components/index.ts +39 -0
- package/frontend/src/components/layout/AppHeader.tsx +146 -0
- package/frontend/src/components/layout/AppSidebar.tsx +190 -0
- package/frontend/src/components/layout/CloudLayout.tsx +95 -0
- package/frontend/src/components/layout/Layout.tsx +43 -0
- package/frontend/src/components/radix/Alert.tsx +45 -0
- package/frontend/src/components/radix/AlertDialog.tsx +115 -0
- package/frontend/src/components/radix/Avatar.tsx +45 -0
- package/frontend/src/components/radix/Badge.tsx +33 -0
- package/frontend/src/components/radix/Button.tsx +50 -0
- package/frontend/src/components/radix/Card.tsx +58 -0
- package/frontend/src/components/radix/Dialog.tsx +98 -0
- package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
- package/frontend/src/components/radix/Form.tsx +167 -0
- package/frontend/src/components/radix/Input.tsx +22 -0
- package/frontend/src/components/radix/Label.tsx +19 -0
- package/frontend/src/components/radix/Popover.tsx +29 -0
- package/frontend/src/components/radix/ScrollArea.tsx +44 -0
- package/frontend/src/components/radix/Select.tsx +151 -0
- package/frontend/src/components/radix/Separator.tsx +26 -0
- package/frontend/src/components/radix/Sheet.tsx +119 -0
- package/frontend/src/components/radix/Skeleton.tsx +7 -0
- package/frontend/src/components/radix/Switch.tsx +29 -0
- package/frontend/src/components/radix/Tabs.tsx +50 -0
- package/frontend/src/components/radix/Textarea.tsx +21 -0
- package/frontend/src/components/radix/Tooltip.tsx +28 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
- package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
- package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
- package/frontend/src/features/ai/helpers.ts +155 -0
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
- package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
- package/frontend/src/features/ai/page/AIPage.tsx +178 -0
- package/frontend/src/features/ai/services/ai.service.ts +148 -0
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
- package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
- package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
- package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
- package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
- package/frontend/src/features/auth/index.ts +9 -0
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
- package/frontend/src/features/auth/services/auth.service.ts +112 -0
- package/frontend/src/features/auth/services/oauth.service.ts +49 -0
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
- package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
- package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
- package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
- package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
- package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
- package/frontend/src/features/database/components/TableForm.tsx +656 -0
- package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
- package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
- package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
- package/frontend/src/features/database/constants.ts +26 -0
- package/frontend/src/features/database/helpers.ts +125 -0
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
- package/frontend/src/features/database/index.ts +12 -0
- package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
- package/frontend/src/features/database/schema.ts +25 -0
- package/frontend/src/features/database/services/database.service.ts +216 -0
- package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
- package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
- package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
- package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
- package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
- package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
- package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
- package/frontend/src/features/functions/services/functions.service.ts +48 -0
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
- package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
- package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
- package/frontend/src/features/login/page/LoginPage.tsx +174 -0
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
- package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
- package/frontend/src/features/logs/index.ts +5 -0
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
- package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
- package/frontend/src/features/logs/services/log.service.ts +171 -0
- package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
- package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
- package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
- package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
- package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
- package/frontend/src/features/onboard/index.ts +3 -0
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
- package/frontend/src/features/onboard/types.ts +8 -0
- package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
- package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
- package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
- package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
- package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
- package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
- package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
- package/frontend/src/features/storage/index.ts +3 -0
- package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
- package/frontend/src/features/storage/services/storage.service.ts +144 -0
- package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
- package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
- package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
- package/frontend/src/features/visualizer/components/index.ts +5 -0
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
- package/frontend/src/index.css +248 -0
- package/frontend/src/lib/api/client.ts +163 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
- package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
- package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
- package/frontend/src/lib/hooks/useAuth.ts +4 -0
- package/frontend/src/lib/hooks/useConfirm.ts +55 -0
- package/frontend/src/lib/hooks/useInterval.ts +27 -0
- package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
- package/frontend/src/lib/hooks/usePagination.ts +27 -0
- package/frontend/src/lib/hooks/useTimeout.ts +27 -0
- package/frontend/src/lib/hooks/useToast.tsx +229 -0
- package/frontend/src/lib/utils/constants.ts +38 -0
- package/frontend/src/lib/utils/utils.ts +165 -0
- package/frontend/src/lib/utils/validation-schemas.ts +126 -0
- package/frontend/src/main.tsx +16 -0
- package/frontend/src/rdg.css +194 -0
- package/frontend/src/vite-env.d.ts +12 -0
- package/frontend/tailwind.config.js +97 -0
- package/frontend/tsconfig.json +26 -0
- package/frontend/tsconfig.node.json +10 -0
- package/frontend/vite.config.ts +37 -0
- package/frontend/vitest.config.ts +36 -0
- package/functions/deno.json +25 -0
- package/functions/server.ts +290 -0
- package/functions/worker-template.js +126 -0
- package/openapi/ai.yaml +689 -0
- package/openapi/auth.yaml +563 -0
- package/openapi/functions.yaml +476 -0
- package/openapi/health.yaml +30 -0
- package/openapi/logs.yaml +224 -0
- package/openapi/metadata.yaml +178 -0
- package/openapi/records.yaml +382 -0
- package/openapi/secrets.yaml +371 -0
- package/openapi/storage.yaml +876 -0
- package/openapi/tables.yaml +464 -0
- package/package.json +88 -0
- package/shared-schemas/package.json +31 -0
- package/shared-schemas/src/ai-api.schema.ts +167 -0
- package/shared-schemas/src/ai.schema.ts +54 -0
- package/shared-schemas/src/auth-api.schema.ts +193 -0
- package/shared-schemas/src/auth.schema.ts +94 -0
- package/shared-schemas/src/database-api.schema.ts +259 -0
- package/shared-schemas/src/database.schema.ts +69 -0
- package/shared-schemas/src/functions-api.schema.ts +25 -0
- package/shared-schemas/src/functions.schema.ts +16 -0
- package/shared-schemas/src/index.ts +13 -0
- package/shared-schemas/src/logs-api.schema.ts +49 -0
- package/shared-schemas/src/logs.schema.ts +14 -0
- package/shared-schemas/src/metadata.schema.ts +56 -0
- package/shared-schemas/src/storage-api.schema.ts +65 -0
- package/shared-schemas/src/storage.schema.ts +19 -0
- package/shared-schemas/tsconfig.json +21 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { useForm, useFieldArray } from 'react-hook-form';
|
|
3
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
4
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
5
|
+
import { AlertCircle, Plus, X, Link, MoveRight } from 'lucide-react';
|
|
6
|
+
import { Button } from '@/components/radix/Button';
|
|
7
|
+
import { Input } from '@/components/radix/Input';
|
|
8
|
+
import { Alert, AlertDescription } from '@/components/radix/Alert';
|
|
9
|
+
import { databaseService } from '@/features/database/services/database.service';
|
|
10
|
+
import {
|
|
11
|
+
TableFormColumnSchema,
|
|
12
|
+
TableFormForeignKeySchema,
|
|
13
|
+
tableFormSchema,
|
|
14
|
+
TableFormSchema,
|
|
15
|
+
} from '@/features/database/schema';
|
|
16
|
+
import { useToast } from '@/lib/hooks/useToast';
|
|
17
|
+
import { TableFormColumn } from './TableFormColumn';
|
|
18
|
+
import { ForeignKeyPopover } from './ForeignKeyPopover';
|
|
19
|
+
import { ColumnType, TableSchema, UpdateTableSchemaRequest } from '@insforge/shared-schemas';
|
|
20
|
+
import { SYSTEM_FIELDS } from '../helpers';
|
|
21
|
+
|
|
22
|
+
const newColumn: TableFormColumnSchema = {
|
|
23
|
+
columnName: '',
|
|
24
|
+
type: ColumnType.STRING,
|
|
25
|
+
isNullable: true,
|
|
26
|
+
isUnique: false,
|
|
27
|
+
defaultValue: '',
|
|
28
|
+
isSystemColumn: false,
|
|
29
|
+
isNewColumn: true,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
interface TableFormProps {
|
|
33
|
+
open: boolean;
|
|
34
|
+
onOpenChange: (open: boolean) => void;
|
|
35
|
+
onSuccess?: (newTable?: string) => void;
|
|
36
|
+
mode?: 'create' | 'edit';
|
|
37
|
+
editTable?: TableSchema;
|
|
38
|
+
setFormIsDirty: (dirty: boolean) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function TableForm({
|
|
42
|
+
open,
|
|
43
|
+
onOpenChange,
|
|
44
|
+
onSuccess,
|
|
45
|
+
mode = 'create',
|
|
46
|
+
editTable,
|
|
47
|
+
setFormIsDirty,
|
|
48
|
+
}: TableFormProps) {
|
|
49
|
+
const [error, setError] = useState<string | null>(null);
|
|
50
|
+
const [showForeignKeyDialog, setShowForeignKeyDialog] = useState(false);
|
|
51
|
+
const [editingForeignKey, setEditingForeignKey] = useState<string>();
|
|
52
|
+
const [foreignKeys, setForeignKeys] = useState<TableFormForeignKeySchema[]>([]);
|
|
53
|
+
const queryClient = useQueryClient();
|
|
54
|
+
const { showToast } = useToast();
|
|
55
|
+
|
|
56
|
+
const form = useForm({
|
|
57
|
+
resolver: zodResolver(tableFormSchema),
|
|
58
|
+
defaultValues: {
|
|
59
|
+
tableName: '',
|
|
60
|
+
columns:
|
|
61
|
+
mode === 'create'
|
|
62
|
+
? [
|
|
63
|
+
{
|
|
64
|
+
columnName: 'id',
|
|
65
|
+
type: ColumnType.UUID,
|
|
66
|
+
defaultValue: 'gen_random_uuid()',
|
|
67
|
+
isPrimaryKey: true,
|
|
68
|
+
isNullable: false,
|
|
69
|
+
isUnique: true,
|
|
70
|
+
isSystemColumn: true,
|
|
71
|
+
isNewColumn: false,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
columnName: 'created_at',
|
|
75
|
+
type: ColumnType.DATETIME,
|
|
76
|
+
defaultValue: 'CURRENT_TIMESTAMP',
|
|
77
|
+
isNullable: true,
|
|
78
|
+
isUnique: false,
|
|
79
|
+
isSystemColumn: true,
|
|
80
|
+
isNewColumn: false,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
columnName: 'updated_at',
|
|
84
|
+
type: ColumnType.DATETIME,
|
|
85
|
+
defaultValue: 'CURRENT_TIMESTAMP',
|
|
86
|
+
isNullable: true,
|
|
87
|
+
isUnique: false,
|
|
88
|
+
isSystemColumn: true,
|
|
89
|
+
isNewColumn: false,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
...newColumn,
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
: [{ ...newColumn }],
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Reset form when switching between modes or when editTable changes
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
// Clear error when effect runs
|
|
102
|
+
setError(null);
|
|
103
|
+
|
|
104
|
+
if (open && mode === 'edit' && editTable) {
|
|
105
|
+
form.reset({
|
|
106
|
+
tableName: editTable.tableName,
|
|
107
|
+
columns: editTable.columns.map((col) => ({
|
|
108
|
+
columnName: col.columnName,
|
|
109
|
+
type: col.type,
|
|
110
|
+
isPrimaryKey: col.isPrimaryKey,
|
|
111
|
+
isNullable: col.isNullable,
|
|
112
|
+
isUnique: col.isUnique || false,
|
|
113
|
+
defaultValue: col.defaultValue || '',
|
|
114
|
+
originalName: col.columnName, // Track original name for rename detection
|
|
115
|
+
isSystemColumn: SYSTEM_FIELDS.includes(col.columnName),
|
|
116
|
+
isNewColumn: false,
|
|
117
|
+
})),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Set foreign keys from editTable
|
|
121
|
+
const existingForeignKeys = editTable.columns
|
|
122
|
+
.filter((col) => !SYSTEM_FIELDS.includes(col.columnName) && col.foreignKey)
|
|
123
|
+
.map((col) => ({
|
|
124
|
+
columnName: col.columnName,
|
|
125
|
+
referenceTable: col.foreignKey?.referenceTable ?? '',
|
|
126
|
+
referenceColumn: col.foreignKey?.referenceColumn ?? '',
|
|
127
|
+
onDelete: col.foreignKey?.onDelete || 'NO ACTION',
|
|
128
|
+
onUpdate: col.foreignKey?.onUpdate || 'NO ACTION',
|
|
129
|
+
}));
|
|
130
|
+
setForeignKeys(existingForeignKeys);
|
|
131
|
+
} else {
|
|
132
|
+
form.reset({
|
|
133
|
+
tableName: '',
|
|
134
|
+
columns: [
|
|
135
|
+
{
|
|
136
|
+
columnName: 'id',
|
|
137
|
+
type: ColumnType.UUID,
|
|
138
|
+
defaultValue: 'gen_random_uuid()',
|
|
139
|
+
isPrimaryKey: true,
|
|
140
|
+
isNullable: false,
|
|
141
|
+
isUnique: true,
|
|
142
|
+
isSystemColumn: true,
|
|
143
|
+
isNewColumn: false,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
columnName: 'created_at',
|
|
147
|
+
type: ColumnType.DATETIME,
|
|
148
|
+
defaultValue: 'CURRENT_TIMESTAMP',
|
|
149
|
+
isNullable: true,
|
|
150
|
+
isUnique: false,
|
|
151
|
+
isSystemColumn: true,
|
|
152
|
+
isNewColumn: false,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
columnName: 'updated_at',
|
|
156
|
+
type: ColumnType.DATETIME,
|
|
157
|
+
defaultValue: 'CURRENT_TIMESTAMP',
|
|
158
|
+
isNullable: true,
|
|
159
|
+
isUnique: false,
|
|
160
|
+
isSystemColumn: true,
|
|
161
|
+
isNewColumn: false,
|
|
162
|
+
},
|
|
163
|
+
{ ...newColumn },
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
setForeignKeys([]);
|
|
167
|
+
}
|
|
168
|
+
}, [mode, editTable, form, open]);
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
setFormIsDirty(form.formState.isDirty);
|
|
172
|
+
}, [form.formState.isDirty, setFormIsDirty]);
|
|
173
|
+
|
|
174
|
+
const { fields, append, remove } = useFieldArray({
|
|
175
|
+
control: form.control,
|
|
176
|
+
name: 'columns',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const sortedFields = useMemo(() => {
|
|
180
|
+
return [...fields].sort((a, b) => {
|
|
181
|
+
// System fields come first
|
|
182
|
+
if (a.isSystemColumn && !b.isSystemColumn) {
|
|
183
|
+
return -1;
|
|
184
|
+
}
|
|
185
|
+
if (!a.isSystemColumn && b.isSystemColumn) {
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Within system fields, maintain the order: id, createdAt, updated_at
|
|
190
|
+
if (a.isSystemColumn && b.isSystemColumn) {
|
|
191
|
+
return SYSTEM_FIELDS.indexOf(a.columnName) - SYSTEM_FIELDS.indexOf(b.columnName);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Keep original order for non-system fields
|
|
195
|
+
return 0;
|
|
196
|
+
});
|
|
197
|
+
}, [fields]);
|
|
198
|
+
|
|
199
|
+
const createTableMutation = useMutation({
|
|
200
|
+
mutationFn: (data: TableFormSchema) => {
|
|
201
|
+
const columns = data.columns.map((col) => {
|
|
202
|
+
// Find foreign key for this field if it exists
|
|
203
|
+
const foreignKey = foreignKeys.find((fk) => fk.columnName === col.columnName);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
columnName: col.columnName,
|
|
207
|
+
type: col.type,
|
|
208
|
+
isNullable: col.isNullable,
|
|
209
|
+
isUnique: col.isUnique,
|
|
210
|
+
defaultValue: col.defaultValue,
|
|
211
|
+
// Embed foreign key information directly in the column
|
|
212
|
+
...(foreignKey && {
|
|
213
|
+
foreignKey: {
|
|
214
|
+
referenceTable: foreignKey.referenceTable,
|
|
215
|
+
referenceColumn: foreignKey.referenceColumn,
|
|
216
|
+
onDelete: foreignKey.onDelete,
|
|
217
|
+
onUpdate: foreignKey.onUpdate,
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return databaseService.createTable(data.tableName, columns);
|
|
224
|
+
},
|
|
225
|
+
onSuccess: (data) => {
|
|
226
|
+
void queryClient.invalidateQueries({ queryKey: ['database-metadata'] });
|
|
227
|
+
void queryClient.invalidateQueries({ queryKey: ['tables'] });
|
|
228
|
+
void queryClient.invalidateQueries({ queryKey: ['metadata'] });
|
|
229
|
+
|
|
230
|
+
showToast('Table created successfully!', 'success');
|
|
231
|
+
|
|
232
|
+
form.reset();
|
|
233
|
+
setError(null);
|
|
234
|
+
setForeignKeys([]);
|
|
235
|
+
onSuccess?.(data.tableName);
|
|
236
|
+
},
|
|
237
|
+
onError: (err) => {
|
|
238
|
+
const errorMessage = err.message || 'Failed to create table';
|
|
239
|
+
setError(errorMessage);
|
|
240
|
+
showToast('Failed to create table', 'error');
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const updateTableMutation = useMutation({
|
|
245
|
+
mutationFn: (data: TableFormSchema) => {
|
|
246
|
+
if (!editTable) {
|
|
247
|
+
return Promise.resolve();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Compare fields to determine what operations to perform
|
|
251
|
+
const addColumns: UpdateTableSchemaRequest['addColumns'] = [];
|
|
252
|
+
const dropColumns: UpdateTableSchemaRequest['dropColumns'] = [];
|
|
253
|
+
const updateColumns: UpdateTableSchemaRequest['updateColumns'] = [];
|
|
254
|
+
const addForeignKeys: UpdateTableSchemaRequest['addForeignKeys'] = [];
|
|
255
|
+
const dropForeignKeys: UpdateTableSchemaRequest['dropForeignKeys'] = [];
|
|
256
|
+
|
|
257
|
+
// Filter out system columns from existing fields for comparison
|
|
258
|
+
const existingUserColumns = editTable.columns.filter(
|
|
259
|
+
(col) => !SYSTEM_FIELDS.includes(col.columnName)
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Track which original columns we've seen
|
|
263
|
+
const processedOriginalColumns = new Set<string>();
|
|
264
|
+
|
|
265
|
+
// Process each field
|
|
266
|
+
data.columns.forEach((col) => {
|
|
267
|
+
if (col.originalName) {
|
|
268
|
+
// This field existed before
|
|
269
|
+
processedOriginalColumns.add(col.originalName);
|
|
270
|
+
const newDefaultValue = col.defaultValue || undefined;
|
|
271
|
+
const originalDefaultValue = editTable.columns.find(
|
|
272
|
+
(_col) => _col.columnName === col.originalName
|
|
273
|
+
)?.defaultValue;
|
|
274
|
+
|
|
275
|
+
// Check if it was renamed
|
|
276
|
+
if (col.originalName !== col.columnName) {
|
|
277
|
+
updateColumns.push({
|
|
278
|
+
columnName: col.originalName,
|
|
279
|
+
defaultValue:
|
|
280
|
+
newDefaultValue !== originalDefaultValue ? (newDefaultValue ?? '') : undefined,
|
|
281
|
+
newColumnName: col.columnName,
|
|
282
|
+
});
|
|
283
|
+
} else if (newDefaultValue !== originalDefaultValue) {
|
|
284
|
+
updateColumns.push({
|
|
285
|
+
columnName: col.columnName,
|
|
286
|
+
defaultValue: newDefaultValue ?? '',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
// This is a new field (added via Add Field button)
|
|
291
|
+
const { ...fieldData } = col;
|
|
292
|
+
addColumns.push({
|
|
293
|
+
...fieldData,
|
|
294
|
+
defaultValue: fieldData.defaultValue || undefined,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Find dropped columns
|
|
300
|
+
existingUserColumns.forEach((col) => {
|
|
301
|
+
if (!processedOriginalColumns.has(col.columnName)) {
|
|
302
|
+
dropColumns.push(col.columnName);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Handle foreign keys
|
|
307
|
+
// Get existing foreign keys from editTable
|
|
308
|
+
const existingForeignKeys = existingUserColumns
|
|
309
|
+
.filter((col) => col.foreignKey)
|
|
310
|
+
.map((col) => ({
|
|
311
|
+
columnName: col.columnName,
|
|
312
|
+
...col.foreignKey,
|
|
313
|
+
}));
|
|
314
|
+
|
|
315
|
+
// Compare with new foreign keys
|
|
316
|
+
foreignKeys.forEach((fk) => {
|
|
317
|
+
const existingFK = existingForeignKeys.find((efk) => efk.columnName === fk.columnName);
|
|
318
|
+
|
|
319
|
+
if (!existingFK) {
|
|
320
|
+
addForeignKeys.push({
|
|
321
|
+
columnName: fk.columnName,
|
|
322
|
+
foreignKey: {
|
|
323
|
+
referenceTable: fk.referenceTable,
|
|
324
|
+
referenceColumn: fk.referenceColumn,
|
|
325
|
+
onDelete: fk.onDelete,
|
|
326
|
+
onUpdate: fk.onUpdate,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Check for dropped foreign keys
|
|
333
|
+
existingForeignKeys.forEach((efk) => {
|
|
334
|
+
const stillExists = foreignKeys.find((fk) => fk.columnName === efk.columnName);
|
|
335
|
+
if (!stillExists) {
|
|
336
|
+
// This foreign key was removed
|
|
337
|
+
dropForeignKeys.push(efk.columnName);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const operations: UpdateTableSchemaRequest = {
|
|
342
|
+
addColumns,
|
|
343
|
+
dropColumns,
|
|
344
|
+
updateColumns,
|
|
345
|
+
addForeignKeys,
|
|
346
|
+
dropForeignKeys,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
if (data.tableName !== editTable.tableName) {
|
|
350
|
+
operations.renameTable = { newTableName: data.tableName };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return databaseService.updateTableSchema(editTable.tableName, operations);
|
|
354
|
+
},
|
|
355
|
+
onSuccess: (_, data) => {
|
|
356
|
+
void queryClient.invalidateQueries({ queryKey: ['database-metadata'] });
|
|
357
|
+
void queryClient.invalidateQueries({ queryKey: ['tables'] });
|
|
358
|
+
void queryClient.invalidateQueries({ queryKey: ['metadata'] });
|
|
359
|
+
|
|
360
|
+
// Invalidate all table data queries for this table (with all parameter combinations)
|
|
361
|
+
void queryClient.invalidateQueries({ queryKey: ['table', editTable?.tableName] });
|
|
362
|
+
|
|
363
|
+
// Invalidate the separate table schema query used by AddRecordSheet
|
|
364
|
+
void queryClient.invalidateQueries({ queryKey: ['table-schema', editTable?.tableName] });
|
|
365
|
+
|
|
366
|
+
showToast(`Table "${data.tableName}" updated successfully!`, 'success');
|
|
367
|
+
|
|
368
|
+
form.reset();
|
|
369
|
+
setError(null);
|
|
370
|
+
setForeignKeys([]);
|
|
371
|
+
onSuccess?.();
|
|
372
|
+
},
|
|
373
|
+
onError: (err) => {
|
|
374
|
+
// Invalidate queries to ensure we have fresh data after failed request
|
|
375
|
+
void queryClient.invalidateQueries({ queryKey: ['table', editTable?.tableName] });
|
|
376
|
+
void queryClient.invalidateQueries({ queryKey: ['table-schema', editTable?.tableName] });
|
|
377
|
+
|
|
378
|
+
const errorMessage = err.message || 'Failed to update table';
|
|
379
|
+
setError(errorMessage);
|
|
380
|
+
showToast('Failed to update table', 'error');
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const handleSubmit = form.handleSubmit((data) => {
|
|
385
|
+
if (mode === 'edit') {
|
|
386
|
+
updateTableMutation.mutate(data);
|
|
387
|
+
} else {
|
|
388
|
+
createTableMutation.mutate(data);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const addField = () => {
|
|
393
|
+
append({ ...newColumn });
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const handleAddForeignKey = (fk: TableFormForeignKeySchema) => {
|
|
397
|
+
if (editingForeignKey) {
|
|
398
|
+
// Update existing foreign key
|
|
399
|
+
setForeignKeys(
|
|
400
|
+
foreignKeys.map((existingFk) =>
|
|
401
|
+
existingFk.columnName === editingForeignKey ? { ...fk } : existingFk
|
|
402
|
+
)
|
|
403
|
+
);
|
|
404
|
+
setEditingForeignKey(undefined);
|
|
405
|
+
} else {
|
|
406
|
+
// Add new foreign key
|
|
407
|
+
setForeignKeys([
|
|
408
|
+
...foreignKeys,
|
|
409
|
+
{
|
|
410
|
+
...fk,
|
|
411
|
+
},
|
|
412
|
+
]);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const handleRemoveForeignKey = (columnName?: string) => {
|
|
417
|
+
setForeignKeys(foreignKeys.filter((fk) => fk.columnName !== columnName));
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
if (!open) {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return (
|
|
425
|
+
<div className="flex flex-col h-full">
|
|
426
|
+
{/* Content area with slate background */}
|
|
427
|
+
<div className="flex-1 bg-slate-100 dark:bg-neutral-900 flex flex-col items-center overflow-auto">
|
|
428
|
+
<div className="flex flex-col gap-6 w-full max-w-[1080px] px-6 py-6">
|
|
429
|
+
{/* Title Bar */}
|
|
430
|
+
<div className="flex items-center justify-between">
|
|
431
|
+
<h1 className="text-xl font-semibold text-black dark:text-neutral-50">
|
|
432
|
+
{mode === 'edit' ? 'Edit Table' : 'Create New Table'}
|
|
433
|
+
</h1>
|
|
434
|
+
<button
|
|
435
|
+
type="button"
|
|
436
|
+
onClick={() => onOpenChange(false)}
|
|
437
|
+
className="flex items-center justify-center w-12 h-12 bg-white dark:bg-neutral-600 rounded-full border border-zinc-200 shadow-sm hover:bg-gray-50 transition-colors dark:border-transparent dark:hover:bg-neutral-700"
|
|
438
|
+
>
|
|
439
|
+
<X className="w-5 h-5 text-zinc-500 dark:text-zinc-300" />
|
|
440
|
+
</button>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<form onSubmit={() => void handleSubmit()} className="flex flex-col gap-6">
|
|
444
|
+
{/* Table Name */}
|
|
445
|
+
<div className="bg-white rounded-xl border border-zinc-200 p-6 dark:bg-neutral-800 dark:border-transparent">
|
|
446
|
+
<div className="flex flex-row gap-10 items-center">
|
|
447
|
+
<label className="whitespace-nowrap text-sm font-normal text-zinc-950 dark:text-neutral-50">
|
|
448
|
+
Table Name
|
|
449
|
+
</label>
|
|
450
|
+
<Input
|
|
451
|
+
{...form.register('tableName')}
|
|
452
|
+
placeholder="e.g., products, orders, customers"
|
|
453
|
+
className="h-10 rounded-md border-zinc-200 shadow-sm placeholder:text-zinc-500 dark:text-white dark:bg-neutral-900 dark:border-neutral-700 dark:placeholder:text-neutral-400"
|
|
454
|
+
/>
|
|
455
|
+
{form.formState.errors.tableName && (
|
|
456
|
+
<p className="text-sm text-destructive dark:text-red-400">
|
|
457
|
+
{form.formState.errors.tableName.message}
|
|
458
|
+
</p>
|
|
459
|
+
)}
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
{/* Columns Section */}
|
|
464
|
+
<div className="bg-white rounded-xl border border-zinc-200 overflow-hidden dark:bg-neutral-800 dark:border-transparent pb-3">
|
|
465
|
+
{/* Columns Header */}
|
|
466
|
+
<div className="p-6 bg-white dark:bg-neutral-800">
|
|
467
|
+
<h2 className="text-base font-medium text-black dark:text-neutral-50">Columns</h2>
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
{/* Columns Table */}
|
|
471
|
+
<div className="px-3 overflow-x-auto">
|
|
472
|
+
{/* Table Headers */}
|
|
473
|
+
<div className="flex items-center gap-6 px-4 py-2 bg-slate-50 rounded-t text-sm font-medium text-zinc-950 dark:bg-neutral-700 dark:text-white">
|
|
474
|
+
<div className="flex-1 min-w-[175px]">Name</div>
|
|
475
|
+
<div className="flex-1 min-w-[175px]">Type</div>
|
|
476
|
+
<div className="flex-1 min-w-[175px]">Default Value</div>
|
|
477
|
+
<div className="w-18 2xl:w-25 text-center flex-shrink-0">Nullable</div>
|
|
478
|
+
<div className="w-18 2xl:w-25 text-center flex-shrink-0">Unique</div>
|
|
479
|
+
<div className="w-5 flex-shrink-0" />
|
|
480
|
+
</div>
|
|
481
|
+
|
|
482
|
+
{/* Columns */}
|
|
483
|
+
{sortedFields.map((field) => {
|
|
484
|
+
const originalIndex = fields.findIndex((f) => f.id === field.id);
|
|
485
|
+
return (
|
|
486
|
+
<TableFormColumn
|
|
487
|
+
key={field.id}
|
|
488
|
+
column={field}
|
|
489
|
+
index={originalIndex}
|
|
490
|
+
control={form.control}
|
|
491
|
+
onRemove={() => remove(originalIndex)}
|
|
492
|
+
isSystemColumn={field.isSystemColumn}
|
|
493
|
+
isNewColumn={field.isNewColumn}
|
|
494
|
+
/>
|
|
495
|
+
);
|
|
496
|
+
})}
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
{/* Add Column Button */}
|
|
500
|
+
<div className="border-t border-zinc-200 dark:border-neutral-700 flex justify-center py-3">
|
|
501
|
+
<Button
|
|
502
|
+
type="button"
|
|
503
|
+
onClick={addField}
|
|
504
|
+
variant="outline"
|
|
505
|
+
className="w-50 h-9 px-2 gap-2 text-sm font-medium text-zinc-700 hover:text-zinc-950 dark:bg-neutral-600 dark:hover:bg-neutral-700 dark:text-white dark:hover:text-white dark:border-transparent"
|
|
506
|
+
>
|
|
507
|
+
<Plus className="w-5 h-5" />
|
|
508
|
+
Add Column
|
|
509
|
+
</Button>
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
|
|
513
|
+
{/* Foreign Keys Section */}
|
|
514
|
+
<div className="bg-white pb-3 rounded-xl border border-zinc-200 dark:bg-neutral-800 dark:border-transparent">
|
|
515
|
+
<div className="p-6">
|
|
516
|
+
<h2 className="text-base font-semibold text-black dark:text-white">Foreign Keys</h2>
|
|
517
|
+
<p className="text-sm text-zinc-500 dark:text-neutral-400">
|
|
518
|
+
Create a relationship between this table and another table
|
|
519
|
+
</p>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
{/* Existing foreign keys */}
|
|
523
|
+
{foreignKeys.length > 0 && (
|
|
524
|
+
<div className="px-6 pb-6 space-y-3">
|
|
525
|
+
{foreignKeys.map((fk) => (
|
|
526
|
+
<div
|
|
527
|
+
key={fk.columnName}
|
|
528
|
+
className="group flex items-center gap-6 2xl:gap-8 pl-4 pr-2 py-2 rounded-lg border border-zinc-200 bg-white hover:bg-zinc-100 transition-colors duration-150 dark:bg-neutral-700 dark:border-transparent dark:hover:bg-neutral-600"
|
|
529
|
+
>
|
|
530
|
+
<div className="flex items-center gap-2 flex-1 min-w-[188px] overflow-hidden">
|
|
531
|
+
<Link className="flex-shrink-0 w-5 h-5 text-zinc-500 dark:text-neutral-400" />
|
|
532
|
+
<span className="font-medium text-sm text-zinc-950 dark:text-white truncate">
|
|
533
|
+
{fk.columnName}
|
|
534
|
+
</span>
|
|
535
|
+
<MoveRight className="flex-shrink-0 w-5 h-5 text-zinc-950 dark:text-neutral-400" />
|
|
536
|
+
<span className="font-medium text-sm text-zinc-950 dark:text-white flex-1 truncate">
|
|
537
|
+
{fk.referenceTable}.{fk.referenceColumn}
|
|
538
|
+
</span>
|
|
539
|
+
</div>
|
|
540
|
+
<div className="flex items-center gap-2 w-45">
|
|
541
|
+
<span className="font-medium text-sm text-zinc-950 dark:text-white whitespace-nowrap">
|
|
542
|
+
On Update:
|
|
543
|
+
</span>
|
|
544
|
+
<span className="text-sm font-medium text-zinc-500 dark:text-neutral-400">
|
|
545
|
+
{fk.onUpdate}
|
|
546
|
+
</span>
|
|
547
|
+
</div>
|
|
548
|
+
<div className="flex items-center gap-2 w-45">
|
|
549
|
+
<span className="font-medium text-sm text-zinc-950 dark:text-white whitespace-nowrap">
|
|
550
|
+
On Delete:
|
|
551
|
+
</span>
|
|
552
|
+
<span className="text-sm font-medium text-zinc-500 dark:text-neutral-400">
|
|
553
|
+
{fk.onDelete}
|
|
554
|
+
</span>
|
|
555
|
+
</div>
|
|
556
|
+
<div className="flex items-center gap-3">
|
|
557
|
+
{/* <Button
|
|
558
|
+
type="button"
|
|
559
|
+
variant="ghost"
|
|
560
|
+
size="sm"
|
|
561
|
+
className="h-10 px-3 gap-1.5 text-zinc-950 hover:bg-zinc-200 transition-colors rounded-md"
|
|
562
|
+
onClick={() => {
|
|
563
|
+
setEditingForeignKey(fk.columnName);
|
|
564
|
+
setShowForeignKeyDialog(true);
|
|
565
|
+
}}
|
|
566
|
+
>
|
|
567
|
+
<Pencil className="w-4 h-4" />
|
|
568
|
+
<span className="font-medium text-sm">Edit</span>
|
|
569
|
+
</Button> */}
|
|
570
|
+
<Button
|
|
571
|
+
type="button"
|
|
572
|
+
variant="ghost"
|
|
573
|
+
size="sm"
|
|
574
|
+
onClick={() => handleRemoveForeignKey(fk.columnName)}
|
|
575
|
+
className="h-10 px-3 gap-1.5 text-zinc-950 hover:bg-zinc-200 transition-colors rounded-md dark:bg-neutral-700 dark:text-white dark:group-hover:bg-neutral-600 dark:hover:bg-neutral-500"
|
|
576
|
+
>
|
|
577
|
+
<X className="w-4 h-4" />
|
|
578
|
+
<span className="font-medium text-sm">Remove</span>
|
|
579
|
+
</Button>
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
))}
|
|
583
|
+
</div>
|
|
584
|
+
)}
|
|
585
|
+
|
|
586
|
+
{/* Add Foreign Key Button */}
|
|
587
|
+
<div className="flex justify-center py-3 border-t border-zinc-200 dark:border-neutral-700">
|
|
588
|
+
<Button
|
|
589
|
+
type="button"
|
|
590
|
+
variant="outline"
|
|
591
|
+
className="w-50 h-9 p-2 gap-2 text-sm font-medium text-zinc-950 bg-white border-zinc-200 shadow-sm hover:bg-zinc-50 dark:bg-neutral-600 dark:text-white dark:border-transparent dark:hover:bg-neutral-700"
|
|
592
|
+
onClick={() => setShowForeignKeyDialog(true)}
|
|
593
|
+
>
|
|
594
|
+
<Link className="w-5 h-5" />
|
|
595
|
+
Add Foreign Keys
|
|
596
|
+
</Button>
|
|
597
|
+
</div>
|
|
598
|
+
|
|
599
|
+
{/* Foreign Key Popover */}
|
|
600
|
+
<ForeignKeyPopover
|
|
601
|
+
form={form}
|
|
602
|
+
mode={mode}
|
|
603
|
+
editTableName={editTable?.tableName}
|
|
604
|
+
open={showForeignKeyDialog}
|
|
605
|
+
onOpenChange={(open) => {
|
|
606
|
+
setShowForeignKeyDialog(open);
|
|
607
|
+
if (!open) {
|
|
608
|
+
setEditingForeignKey(undefined);
|
|
609
|
+
}
|
|
610
|
+
}}
|
|
611
|
+
onAddForeignKey={handleAddForeignKey}
|
|
612
|
+
initialValue={
|
|
613
|
+
editingForeignKey
|
|
614
|
+
? foreignKeys.find((fk) => fk.columnName === editingForeignKey)
|
|
615
|
+
: undefined
|
|
616
|
+
}
|
|
617
|
+
/>
|
|
618
|
+
</div>
|
|
619
|
+
|
|
620
|
+
{error && (
|
|
621
|
+
<Alert variant="destructive" className="dark:bg-neutral-800 dark:text-zinc-300">
|
|
622
|
+
<AlertCircle className="h-4 w-4" />
|
|
623
|
+
<AlertDescription>{error}</AlertDescription>
|
|
624
|
+
</Alert>
|
|
625
|
+
)}
|
|
626
|
+
</form>
|
|
627
|
+
</div>
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
{/* Footer */}
|
|
631
|
+
<div className="bg-slate-50 border-t border-zinc-200 px-6 py-3 pb-6 dark:bg-neutral-800 dark:border-neutral-700">
|
|
632
|
+
<div className="flex justify-end gap-3 max-w-[1080px] mx-auto px-6">
|
|
633
|
+
<Button
|
|
634
|
+
type="button"
|
|
635
|
+
variant="outline"
|
|
636
|
+
onClick={() => onOpenChange(false)}
|
|
637
|
+
className="h-10 px-4 text-sm font-medium border-zinc-200 shadow-sm dark:bg-neutral-800 dark:text-zinc-300 dark:border-neutral-700 dark:hover:bg-neutral-700"
|
|
638
|
+
>
|
|
639
|
+
Cancel
|
|
640
|
+
</Button>
|
|
641
|
+
<Button
|
|
642
|
+
onClick={() => void handleSubmit()}
|
|
643
|
+
disabled={
|
|
644
|
+
!form.formState.isValid ||
|
|
645
|
+
createTableMutation.isPending ||
|
|
646
|
+
updateTableMutation.isPending
|
|
647
|
+
}
|
|
648
|
+
className="h-10 px-4 text-sm font-medium bg-zinc-950 text-neutral-50 shadow-sm disabled:opacity-40 dark:bg-emerald-300 dark:text-zinc-950 dark:hover:bg-emerald-400"
|
|
649
|
+
>
|
|
650
|
+
{mode === 'edit' ? 'Update Table' : 'Save Table'}
|
|
651
|
+
</Button>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
);
|
|
656
|
+
}
|