insforge 1.3.0 → 1.4.8
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/CHANGELOG.md +2 -0
- package/auth/package.json +5 -3
- package/auth/src/lib/broadcastService.ts +115 -117
- package/auth/src/lib/insforge.ts +8 -0
- package/auth/src/main.tsx +2 -4
- package/auth/src/pages/SignInPage.tsx +60 -60
- package/auth/src/pages/SignUpPage.tsx +60 -60
- package/auth/src/pages/VerifyEmailPage.tsx +18 -0
- package/auth/tsconfig.json +2 -1
- package/backend/package.json +10 -6
- package/backend/src/api/middlewares/rate-limiters.ts +127 -127
- package/backend/src/api/routes/ai/index.routes.ts +475 -468
- package/backend/src/api/routes/auth/index.routes.ts +85 -32
- package/backend/src/api/routes/auth/oauth.routes.ts +11 -6
- package/backend/src/api/routes/database/index.routes.ts +2 -0
- package/backend/src/api/routes/database/records.routes.ts +39 -175
- package/backend/src/api/routes/database/rpc.routes.ts +69 -0
- package/backend/src/api/routes/deployments/index.routes.ts +192 -0
- package/backend/src/api/routes/docs/index.routes.ts +3 -2
- package/backend/src/api/routes/email/index.routes.ts +35 -35
- package/backend/src/api/routes/functions/index.routes.ts +3 -3
- package/backend/src/api/routes/metadata/index.routes.ts +26 -0
- package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
- package/backend/src/infra/database/database.manager.ts +0 -10
- package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
- package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
- package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
- package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
- package/backend/src/infra/security/token.manager.ts +1 -4
- package/backend/src/providers/ai/openrouter.provider.ts +12 -3
- package/backend/src/providers/database/base.provider.ts +39 -0
- package/backend/src/providers/database/cloud.provider.ts +159 -0
- package/backend/src/providers/deployments/vercel.provider.ts +516 -0
- package/backend/src/server.ts +19 -7
- package/backend/src/services/ai/ai-config.service.ts +6 -6
- package/backend/src/services/ai/ai-model.service.ts +60 -60
- package/backend/src/services/ai/ai-usage.service.ts +7 -7
- package/backend/src/services/ai/chat-completion.service.ts +415 -220
- package/backend/src/services/ai/helpers.ts +64 -64
- package/backend/src/services/ai/index.ts +13 -13
- package/backend/src/services/auth/auth-config.service.ts +4 -4
- package/backend/src/services/auth/auth-otp.service.ts +6 -6
- package/backend/src/services/auth/auth.service.ts +134 -74
- package/backend/src/services/auth/index.ts +4 -4
- package/backend/src/services/auth/oauth-config.service.ts +12 -12
- package/backend/src/services/database/database-advance.service.ts +19 -55
- package/backend/src/services/database/database-table.service.ts +38 -85
- package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
- package/backend/src/services/deployments/deployment.service.ts +693 -0
- package/backend/src/services/functions/function.service.ts +61 -41
- package/backend/src/services/logs/audit.service.ts +10 -10
- package/backend/src/services/secrets/secret.service.ts +101 -27
- package/backend/src/services/storage/storage.service.ts +30 -30
- package/backend/src/services/usage/usage.service.ts +6 -6
- package/backend/src/types/ai.ts +8 -0
- package/backend/src/types/auth.ts +5 -1
- package/backend/src/types/database.ts +2 -0
- package/backend/src/types/deployments.ts +33 -0
- package/backend/src/types/storage.ts +1 -1
- package/backend/src/types/webhooks.ts +45 -0
- package/backend/src/utils/cookies.ts +34 -35
- package/backend/src/utils/environment.ts +0 -14
- package/backend/src/utils/s3-config-loader.ts +64 -64
- package/backend/src/utils/seed.ts +334 -301
- package/backend/src/utils/sql-parser.ts +126 -0
- package/backend/src/utils/utils.ts +114 -114
- package/backend/src/utils/validations.ts +10 -10
- package/backend/tests/local/test-rpc.sh +141 -0
- package/backend/tests/local/test-secrets.sh +1 -1
- package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
- package/backend/tests/manual/test-rawsql-modes.sh +24 -24
- package/backend/tests/unit/database-advance.test.ts +326 -0
- package/backend/tests/unit/helpers.test.ts +2 -2
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +13 -10
- package/docker-compose.prod.yml +1 -1
- package/docker-compose.yml +1 -1
- package/docs/agent-docs/deployment.md +79 -0
- package/docs/changelog.mdx +165 -72
- package/docs/core-concepts/ai/architecture.mdx +1 -23
- package/docs/core-concepts/ai/sdk.mdx +26 -1
- package/docs/core-concepts/authentication/architecture.mdx +6 -8
- package/docs/core-concepts/authentication/sdk.mdx +387 -91
- package/docs/core-concepts/authentication/ui-components/customization.mdx +460 -256
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +50 -24
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +18 -19
- package/docs/core-concepts/authentication/ui-components/react.mdx +26 -19
- package/docs/core-concepts/database/architecture.mdx +58 -21
- package/docs/core-concepts/database/pgvector.mdx +138 -0
- package/docs/core-concepts/database/sdk.mdx +17 -17
- package/docs/core-concepts/deployments/architecture.mdx +152 -0
- package/docs/core-concepts/email/architecture.mdx +4 -2
- package/docs/core-concepts/functions/architecture.mdx +1 -1
- package/docs/core-concepts/functions/sdk.mdx +0 -1
- package/docs/core-concepts/realtime/architecture.mdx +1 -1
- package/docs/core-concepts/storage/architecture.mdx +1 -1
- package/docs/core-concepts/storage/sdk.mdx +25 -25
- package/docs/docs.json +14 -6
- package/docs/favicon.png +0 -0
- package/docs/favicon.svg +3 -18
- package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
- package/docs/images/changelog/dec-2025/moreModels.png +0 -0
- package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
- package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
- package/docs/images/changelog/dec-2025/realtime2.png +0 -0
- package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
- package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
- package/docs/images/mcp-setup/claude-code-connect.png +0 -0
- package/docs/images/mcp-setup/cline-1.png +0 -0
- package/docs/images/mcp-setup/cline-2.png +0 -0
- package/docs/images/mcp-setup/cline-3.png +0 -0
- package/docs/images/mcp-setup/connect-project.png +0 -0
- package/docs/images/mcp-setup/copilot-1.png +0 -0
- package/docs/images/mcp-setup/copilot-2.png +0 -0
- package/docs/images/mcp-setup/copilot-3.png +0 -0
- package/docs/images/mcp-setup/mcp-json-1.png +0 -0
- package/docs/images/mcp-setup/mcp-json-2.png +0 -0
- package/docs/images/mcp-setup/qoder-1.png +0 -0
- package/docs/images/mcp-setup/qoder-2.png +0 -0
- package/docs/images/mcp-setup/roocode-1.png +0 -0
- package/docs/images/mcp-setup/roocode-2.png +0 -0
- package/docs/images/mcp-setup/trae-1.png +0 -0
- package/docs/images/mcp-setup/trae-2.png +0 -0
- package/docs/images/mcp-setup/trae-3.png +0 -0
- package/docs/images/mcp-setup/trae-4.png +0 -0
- package/docs/images/mcp-setup/trae-5.png +0 -0
- package/docs/images/mcp-setup/windsurf-1.png +0 -0
- package/docs/images/mcp-setup/windsurf-2.png +0 -0
- package/docs/insforge-instructions-sdk.md +7 -3
- package/docs/introduction.mdx +9 -8
- package/docs/mcp-setup.mdx +332 -0
- package/docs/oauth-server.mdx +563 -0
- package/docs/partnership.mdx +79 -10
- package/docs/quickstart.mdx +1 -1
- package/docs/vscode-extension.mdx +74 -0
- package/eslint.config.js +1 -0
- package/examples/response-examples.md +1 -1
- package/frontend/package.json +1 -1
- package/frontend/src/App.tsx +8 -3
- package/frontend/src/assets/logos/antigravity.svg +1 -0
- package/frontend/src/assets/logos/copilot.svg +10 -0
- package/frontend/src/assets/logos/deepseek.svg +139 -0
- package/frontend/src/assets/logos/kiro.svg +9 -0
- package/frontend/src/assets/logos/qoder.svg +4 -0
- package/frontend/src/assets/logos/qwen.svg +15 -0
- package/frontend/src/components/CodeBlock.tsx +2 -2
- package/frontend/src/components/ConnectCTA.tsx +3 -2
- package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
- package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
- package/frontend/src/components/datagrid/index.ts +1 -1
- package/frontend/src/components/index.ts +0 -1
- package/frontend/src/components/layout/AppHeader.tsx +4 -27
- package/frontend/src/components/layout/AppSidebar.tsx +85 -100
- package/frontend/src/components/layout/Layout.tsx +34 -32
- package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
- package/frontend/src/components/radix/Select.tsx +151 -151
- package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
- package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
- package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
- package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
- package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
- package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
- package/frontend/src/features/ai/components/index.ts +6 -6
- package/frontend/src/features/ai/helpers.ts +147 -141
- package/frontend/src/features/ai/pages/AIPage.tsx +166 -166
- package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +55 -31
- package/frontend/src/features/auth/components/index.ts +5 -5
- package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -275
- package/frontend/src/features/dashboard/pages/DashboardPage.tsx +1 -1
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
- package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
- package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
- package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
- package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
- package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
- package/frontend/src/features/database/constants.ts +16 -28
- package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
- package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
- package/frontend/src/features/database/hooks/useTables.ts +5 -7
- package/frontend/src/features/database/pages/FunctionsPage.tsx +0 -5
- package/frontend/src/features/database/pages/IndexesPage.tsx +0 -5
- package/frontend/src/features/database/pages/PoliciesPage.tsx +0 -5
- package/frontend/src/features/database/pages/SQLEditorPage.tsx +2 -2
- package/frontend/src/features/database/pages/TriggersPage.tsx +0 -5
- package/frontend/src/features/database/services/advance.service.ts +1 -15
- package/frontend/src/features/database/services/record.service.ts +4 -20
- package/frontend/src/features/database/services/table.service.ts +1 -4
- package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
- package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
- package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
- package/frontend/src/features/database/templates/notion-clone.ts +8 -8
- package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
- package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
- package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
- package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
- package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
- package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
- package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
- package/frontend/src/features/functions/components/index.ts +5 -5
- package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
- package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
- package/frontend/src/features/functions/pages/SecretsPage.tsx +118 -118
- package/frontend/src/features/functions/services/function.service.ts +8 -25
- package/frontend/src/features/functions/services/secret.service.ts +23 -41
- package/frontend/src/features/login/pages/CloudLoginPage.tsx +125 -118
- package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
- package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
- package/frontend/src/features/logs/components/index.ts +1 -0
- package/frontend/src/features/logs/pages/LogsPage.tsx +36 -6
- package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
- package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
- package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
- package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
- package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
- package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
- package/frontend/src/features/onboard/components/index.ts +9 -4
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
- package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
- package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
- package/frontend/src/features/onboard/index.ts +17 -13
- package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
- package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +21 -8
- package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +10 -1
- package/frontend/src/index.css +249 -249
- package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
- package/frontend/src/lib/hooks/useMetadata.ts +45 -1
- package/frontend/src/lib/hooks/useModal.tsx +2 -0
- package/frontend/src/lib/routing/AppRoutes.tsx +103 -99
- package/frontend/src/lib/services/metadata.service.ts +20 -3
- package/frontend/src/lib/utils/menuItems.ts +223 -207
- package/frontend/src/lib/utils/utils.ts +196 -196
- package/functions/server.ts +315 -315
- package/functions/worker-template.js +1 -1
- package/openapi/ai.yaml +115 -5
- package/openapi/auth.yaml +97 -17
- package/openapi/logs.yaml +0 -2
- package/openapi/metadata.yaml +0 -2
- package/openapi/records.yaml +21 -21
- package/openapi/tables.yaml +1 -2
- package/package.json +1 -1
- package/shared-schemas/package.json +1 -1
- package/shared-schemas/src/ai-api.schema.ts +251 -143
- package/shared-schemas/src/ai.schema.ts +63 -63
- package/shared-schemas/src/auth-api.schema.ts +34 -6
- package/shared-schemas/src/auth.schema.ts +17 -10
- package/shared-schemas/src/cloud-events.schema.ts +26 -0
- package/shared-schemas/src/deployments-api.schema.ts +55 -0
- package/shared-schemas/src/deployments.schema.ts +30 -0
- package/shared-schemas/src/docs.schema.ts +8 -2
- package/shared-schemas/src/email-api.schema.ts +30 -30
- package/shared-schemas/src/functions-api.schema.ts +13 -4
- package/shared-schemas/src/functions.schema.ts +1 -1
- package/shared-schemas/src/index.ts +22 -18
- package/shared-schemas/src/metadata.schema.ts +30 -4
- package/shared-schemas/src/secrets-api.schema.ts +44 -0
- package/shared-schemas/src/secrets.schema.ts +15 -0
- package/zeabur/README.md +13 -0
- package/zeabur/template.yml +20 -51
- package/backend/src/types/profile.ts +0 -55
- package/frontend/src/components/ProjectInfoModal.tsx +0 -128
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
2
2
|
import {
|
|
3
3
|
EdgeFunctionMetadataSchema,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
UploadFunctionRequest,
|
|
5
|
+
UpdateFunctionRequest,
|
|
6
|
+
FunctionSchema,
|
|
7
|
+
ListFunctionsResponse,
|
|
6
8
|
} from '@insforge/shared-schemas';
|
|
7
9
|
import logger from '@/utils/logger.js';
|
|
8
10
|
import { DatabaseError, Pool } from 'pg';
|
|
@@ -10,13 +12,6 @@ import fetch from 'node-fetch';
|
|
|
10
12
|
import { AppError } from '@/api/middlewares/error.js';
|
|
11
13
|
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
12
14
|
|
|
13
|
-
export interface FunctionWithRuntime {
|
|
14
|
-
functions: Record<string, unknown>[];
|
|
15
|
-
runtime: {
|
|
16
|
-
status: 'running' | 'unavailable';
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
15
|
export class FunctionService {
|
|
21
16
|
private static instance: FunctionService;
|
|
22
17
|
private pool: Pool | null = null;
|
|
@@ -41,13 +36,19 @@ export class FunctionService {
|
|
|
41
36
|
/**
|
|
42
37
|
* List all functions with runtime health check
|
|
43
38
|
*/
|
|
44
|
-
async listFunctions(): Promise<
|
|
39
|
+
async listFunctions(): Promise<ListFunctionsResponse> {
|
|
45
40
|
try {
|
|
46
41
|
const result = await this.getPool().query(
|
|
47
42
|
`SELECT
|
|
48
|
-
id,
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
id,
|
|
44
|
+
slug,
|
|
45
|
+
name,
|
|
46
|
+
description,
|
|
47
|
+
status,
|
|
48
|
+
created_at as "createdAt",
|
|
49
|
+
updated_at as "updatedAt",
|
|
50
|
+
deployed_at as "deployedAt"
|
|
51
|
+
FROM functions.definitions
|
|
51
52
|
ORDER BY created_at DESC`
|
|
52
53
|
);
|
|
53
54
|
|
|
@@ -86,13 +87,20 @@ export class FunctionService {
|
|
|
86
87
|
/**
|
|
87
88
|
* Get a specific function by slug
|
|
88
89
|
*/
|
|
89
|
-
async getFunction(slug: string): Promise<
|
|
90
|
+
async getFunction(slug: string): Promise<FunctionSchema | undefined> {
|
|
90
91
|
try {
|
|
91
92
|
const result = await this.getPool().query(
|
|
92
93
|
`SELECT
|
|
93
|
-
id,
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
id,
|
|
95
|
+
slug,
|
|
96
|
+
name,
|
|
97
|
+
description,
|
|
98
|
+
code,
|
|
99
|
+
status,
|
|
100
|
+
created_at as "createdAt",
|
|
101
|
+
updated_at as "updatedAt",
|
|
102
|
+
deployed_at as "deployedAt"
|
|
103
|
+
FROM functions.definitions
|
|
96
104
|
WHERE slug = $1`,
|
|
97
105
|
[slug]
|
|
98
106
|
);
|
|
@@ -111,7 +119,7 @@ export class FunctionService {
|
|
|
111
119
|
/**
|
|
112
120
|
* Create a new function
|
|
113
121
|
*/
|
|
114
|
-
async createFunction(data:
|
|
122
|
+
async createFunction(data: UploadFunctionRequest): Promise<FunctionSchema> {
|
|
115
123
|
const client = await this.getPool().connect();
|
|
116
124
|
try {
|
|
117
125
|
const { name, code, description, status } = data;
|
|
@@ -125,22 +133,23 @@ export class FunctionService {
|
|
|
125
133
|
|
|
126
134
|
// Insert function
|
|
127
135
|
await client.query(
|
|
128
|
-
`INSERT INTO
|
|
136
|
+
`INSERT INTO functions.definitions (id, slug, name, description, code, status)
|
|
129
137
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
130
138
|
[id, slug, name, description || null, code, status]
|
|
131
139
|
);
|
|
132
140
|
|
|
133
141
|
// If status is active, update deployed_at
|
|
134
142
|
if (status === 'active') {
|
|
135
|
-
await client.query(
|
|
136
|
-
id
|
|
137
|
-
|
|
143
|
+
await client.query(
|
|
144
|
+
`UPDATE functions.definitions SET deployed_at = CURRENT_TIMESTAMP WHERE id = $1`,
|
|
145
|
+
[id]
|
|
146
|
+
);
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
// Fetch the created function
|
|
141
150
|
const result = await client.query(
|
|
142
|
-
`SELECT id, slug, name, description, status, created_at
|
|
143
|
-
FROM
|
|
151
|
+
`SELECT id, slug, name, description, status, created_at as "createdAt"
|
|
152
|
+
FROM functions.definitions WHERE id = $1`,
|
|
144
153
|
[id]
|
|
145
154
|
);
|
|
146
155
|
|
|
@@ -176,14 +185,15 @@ export class FunctionService {
|
|
|
176
185
|
*/
|
|
177
186
|
async updateFunction(
|
|
178
187
|
slug: string,
|
|
179
|
-
updates:
|
|
180
|
-
): Promise<
|
|
188
|
+
updates: UpdateFunctionRequest
|
|
189
|
+
): Promise<FunctionSchema | null> {
|
|
181
190
|
const client = await this.getPool().connect();
|
|
182
191
|
try {
|
|
183
192
|
// Check if function exists
|
|
184
|
-
const existingResult = await client.query(
|
|
185
|
-
slug,
|
|
186
|
-
|
|
193
|
+
const existingResult = await client.query(
|
|
194
|
+
'SELECT id FROM functions.definitions WHERE slug = $1',
|
|
195
|
+
[slug]
|
|
196
|
+
);
|
|
187
197
|
if (existingResult.rows.length === 0) {
|
|
188
198
|
return null;
|
|
189
199
|
}
|
|
@@ -195,22 +205,28 @@ export class FunctionService {
|
|
|
195
205
|
|
|
196
206
|
// Update fields
|
|
197
207
|
if (updates.name !== undefined) {
|
|
198
|
-
await client.query('UPDATE
|
|
208
|
+
await client.query('UPDATE functions.definitions SET name = $1 WHERE slug = $2', [
|
|
209
|
+
updates.name,
|
|
210
|
+
slug,
|
|
211
|
+
]);
|
|
199
212
|
}
|
|
200
213
|
|
|
201
214
|
if (updates.description !== undefined) {
|
|
202
|
-
await client.query('UPDATE
|
|
215
|
+
await client.query('UPDATE functions.definitions SET description = $1 WHERE slug = $2', [
|
|
203
216
|
updates.description,
|
|
204
217
|
slug,
|
|
205
218
|
]);
|
|
206
219
|
}
|
|
207
220
|
|
|
208
221
|
if (updates.code !== undefined) {
|
|
209
|
-
await client.query('UPDATE
|
|
222
|
+
await client.query('UPDATE functions.definitions SET code = $1 WHERE slug = $2', [
|
|
223
|
+
updates.code,
|
|
224
|
+
slug,
|
|
225
|
+
]);
|
|
210
226
|
}
|
|
211
227
|
|
|
212
228
|
if (updates.status !== undefined) {
|
|
213
|
-
await client.query('UPDATE
|
|
229
|
+
await client.query('UPDATE functions.definitions SET status = $1 WHERE slug = $2', [
|
|
214
230
|
updates.status,
|
|
215
231
|
slug,
|
|
216
232
|
]);
|
|
@@ -218,21 +234,22 @@ export class FunctionService {
|
|
|
218
234
|
// Update deployed_at if status changes to active
|
|
219
235
|
if (updates.status === 'active') {
|
|
220
236
|
await client.query(
|
|
221
|
-
'UPDATE
|
|
237
|
+
'UPDATE functions.definitions SET deployed_at = CURRENT_TIMESTAMP WHERE slug = $1',
|
|
222
238
|
[slug]
|
|
223
239
|
);
|
|
224
240
|
}
|
|
225
241
|
}
|
|
226
242
|
|
|
227
243
|
// Update updated_at
|
|
228
|
-
await client.query(
|
|
229
|
-
slug,
|
|
230
|
-
|
|
244
|
+
await client.query(
|
|
245
|
+
'UPDATE functions.definitions SET updated_at = CURRENT_TIMESTAMP WHERE slug = $1',
|
|
246
|
+
[slug]
|
|
247
|
+
);
|
|
231
248
|
|
|
232
249
|
// Fetch updated function
|
|
233
250
|
const result = await client.query(
|
|
234
|
-
`SELECT id, slug, name, description, status, updated_at
|
|
235
|
-
FROM
|
|
251
|
+
`SELECT id, slug, name, description, status, updated_at as "updatedAt", deployed_at as "deployedAt"
|
|
252
|
+
FROM functions.definitions WHERE slug = $1`,
|
|
236
253
|
[slug]
|
|
237
254
|
);
|
|
238
255
|
|
|
@@ -254,7 +271,10 @@ export class FunctionService {
|
|
|
254
271
|
*/
|
|
255
272
|
async deleteFunction(slug: string): Promise<boolean> {
|
|
256
273
|
try {
|
|
257
|
-
const result = await this.getPool().query(
|
|
274
|
+
const result = await this.getPool().query(
|
|
275
|
+
'DELETE FROM functions.definitions WHERE slug = $1',
|
|
276
|
+
[slug]
|
|
277
|
+
);
|
|
258
278
|
|
|
259
279
|
if (result.rowCount === 0) {
|
|
260
280
|
return false;
|
|
@@ -278,7 +298,7 @@ export class FunctionService {
|
|
|
278
298
|
try {
|
|
279
299
|
const result = await this.getPool().query(
|
|
280
300
|
`SELECT slug, name, description, status
|
|
281
|
-
FROM
|
|
301
|
+
FROM functions.definitions
|
|
282
302
|
ORDER BY created_at DESC`
|
|
283
303
|
);
|
|
284
304
|
|
|
@@ -35,7 +35,7 @@ export class AuditService {
|
|
|
35
35
|
try {
|
|
36
36
|
const pool = this.getPool();
|
|
37
37
|
const result = await pool.query(
|
|
38
|
-
`INSERT INTO
|
|
38
|
+
`INSERT INTO system.audit_logs (actor, action, module, details, ip_address)
|
|
39
39
|
VALUES ($1, $2, $3, $4, $5)
|
|
40
40
|
RETURNING *`,
|
|
41
41
|
[
|
|
@@ -109,12 +109,12 @@ export class AuditService {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Get total count first
|
|
112
|
-
const countSql = `SELECT COUNT(*) as count FROM
|
|
112
|
+
const countSql = `SELECT COUNT(*) as count FROM system.audit_logs ${whereClause}`;
|
|
113
113
|
const countResult = await pool.query(countSql, params);
|
|
114
114
|
const total = parseInt(countResult.rows[0].count, 10);
|
|
115
115
|
|
|
116
116
|
// Get paginated records
|
|
117
|
-
let dataSql = `SELECT * FROM
|
|
117
|
+
let dataSql = `SELECT * FROM system.audit_logs ${whereClause} ORDER BY created_at DESC`;
|
|
118
118
|
const dataParams = [...params];
|
|
119
119
|
|
|
120
120
|
if (query.limit) {
|
|
@@ -154,7 +154,7 @@ export class AuditService {
|
|
|
154
154
|
async getById(id: string): Promise<AuditLogSchema | null> {
|
|
155
155
|
try {
|
|
156
156
|
const pool = this.getPool();
|
|
157
|
-
const result = await pool.query('SELECT * FROM
|
|
157
|
+
const result = await pool.query('SELECT * FROM system.audit_logs WHERE id = $1', [id]);
|
|
158
158
|
|
|
159
159
|
const row = result.rows[0];
|
|
160
160
|
|
|
@@ -186,30 +186,30 @@ export class AuditService {
|
|
|
186
186
|
startDate.setDate(startDate.getDate() - days);
|
|
187
187
|
|
|
188
188
|
const totalLogsResult = await pool.query(
|
|
189
|
-
'SELECT COUNT(*) as count FROM
|
|
189
|
+
'SELECT COUNT(*) as count FROM system.audit_logs WHERE created_at >= $1',
|
|
190
190
|
[startDate.toISOString()]
|
|
191
191
|
);
|
|
192
192
|
|
|
193
193
|
const uniqueActorsResult = await pool.query(
|
|
194
|
-
'SELECT COUNT(DISTINCT actor) as count FROM
|
|
194
|
+
'SELECT COUNT(DISTINCT actor) as count FROM system.audit_logs WHERE created_at >= $1',
|
|
195
195
|
[startDate.toISOString()]
|
|
196
196
|
);
|
|
197
197
|
|
|
198
198
|
const uniqueModulesResult = await pool.query(
|
|
199
|
-
'SELECT COUNT(DISTINCT module) as count FROM
|
|
199
|
+
'SELECT COUNT(DISTINCT module) as count FROM system.audit_logs WHERE created_at >= $1',
|
|
200
200
|
[startDate.toISOString()]
|
|
201
201
|
);
|
|
202
202
|
|
|
203
203
|
const actionsByModuleResult = await pool.query(
|
|
204
204
|
`SELECT module, COUNT(*) as count
|
|
205
|
-
FROM
|
|
205
|
+
FROM system.audit_logs
|
|
206
206
|
WHERE created_at >= $1
|
|
207
207
|
GROUP BY module`,
|
|
208
208
|
[startDate.toISOString()]
|
|
209
209
|
);
|
|
210
210
|
|
|
211
211
|
const recentActivityResult = await pool.query(
|
|
212
|
-
`SELECT * FROM
|
|
212
|
+
`SELECT * FROM system.audit_logs
|
|
213
213
|
WHERE created_at >= $1
|
|
214
214
|
ORDER BY created_at DESC
|
|
215
215
|
LIMIT 10`,
|
|
@@ -253,7 +253,7 @@ export class AuditService {
|
|
|
253
253
|
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
|
|
254
254
|
|
|
255
255
|
const result = await pool.query(
|
|
256
|
-
'DELETE FROM
|
|
256
|
+
'DELETE FROM system.audit_logs WHERE created_at < $1 RETURNING id',
|
|
257
257
|
[cutoffDate.toISOString()]
|
|
258
258
|
);
|
|
259
259
|
|
|
@@ -3,21 +3,9 @@ import crypto from 'crypto';
|
|
|
3
3
|
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
4
4
|
import logger from '@/utils/logger.js';
|
|
5
5
|
import { EncryptionManager } from '@/infra/security/encryption.manager.js';
|
|
6
|
+
import { SecretSchema, CreateSecretRequest } from '@insforge/shared-schemas';
|
|
6
7
|
|
|
7
|
-
export interface
|
|
8
|
-
id: string;
|
|
9
|
-
key: string;
|
|
10
|
-
isActive: boolean;
|
|
11
|
-
isReserved: boolean;
|
|
12
|
-
lastUsedAt: Date | null;
|
|
13
|
-
expiresAt: Date | null;
|
|
14
|
-
createdAt: Date;
|
|
15
|
-
updatedAt: Date;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface CreateSecretInput {
|
|
19
|
-
key: string;
|
|
20
|
-
value: string;
|
|
8
|
+
export interface CreateSecretInput extends CreateSecretRequest {
|
|
21
9
|
isReserved?: boolean;
|
|
22
10
|
expiresAt?: Date;
|
|
23
11
|
}
|
|
@@ -59,7 +47,7 @@ export class SecretService {
|
|
|
59
47
|
const encryptedValue = EncryptionManager.encrypt(input.value);
|
|
60
48
|
|
|
61
49
|
const result = await this.getPool().query(
|
|
62
|
-
`INSERT INTO
|
|
50
|
+
`INSERT INTO system.secrets (key, value_ciphertext, is_reserved, expires_at)
|
|
63
51
|
VALUES ($1, $2, $3, $4)
|
|
64
52
|
RETURNING id`,
|
|
65
53
|
[input.key, encryptedValue, input.isReserved || false, input.expiresAt || null]
|
|
@@ -79,7 +67,7 @@ export class SecretService {
|
|
|
79
67
|
async getSecretById(id: string): Promise<string | null> {
|
|
80
68
|
try {
|
|
81
69
|
const result = await this.getPool().query(
|
|
82
|
-
`UPDATE
|
|
70
|
+
`UPDATE system.secrets
|
|
83
71
|
SET last_used_at = NOW()
|
|
84
72
|
WHERE id = $1 AND is_active = true
|
|
85
73
|
AND (expires_at IS NULL OR expires_at > NOW())
|
|
@@ -106,7 +94,7 @@ export class SecretService {
|
|
|
106
94
|
async getSecretByKey(key: string): Promise<string | null> {
|
|
107
95
|
try {
|
|
108
96
|
const result = await this.getPool().query(
|
|
109
|
-
`UPDATE
|
|
97
|
+
`UPDATE system.secrets
|
|
110
98
|
SET last_used_at = NOW()
|
|
111
99
|
WHERE key = $1 AND is_active = true
|
|
112
100
|
AND (expires_at IS NULL OR expires_at > NOW())
|
|
@@ -142,7 +130,7 @@ export class SecretService {
|
|
|
142
130
|
expires_at as "expiresAt",
|
|
143
131
|
created_at as "createdAt",
|
|
144
132
|
updated_at as "updatedAt"
|
|
145
|
-
FROM
|
|
133
|
+
FROM system.secrets
|
|
146
134
|
ORDER BY created_at DESC`
|
|
147
135
|
);
|
|
148
136
|
|
|
@@ -183,10 +171,14 @@ export class SecretService {
|
|
|
183
171
|
values.push(input.expiresAt);
|
|
184
172
|
}
|
|
185
173
|
|
|
174
|
+
if (updates.length === 0) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
186
178
|
values.push(id);
|
|
187
179
|
|
|
188
180
|
const result = await this.getPool().query(
|
|
189
|
-
`UPDATE
|
|
181
|
+
`UPDATE system.secrets
|
|
190
182
|
SET ${updates.join(', ')}
|
|
191
183
|
WHERE id = $${paramCount}`,
|
|
192
184
|
values
|
|
@@ -203,6 +195,81 @@ export class SecretService {
|
|
|
203
195
|
}
|
|
204
196
|
}
|
|
205
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Update a secret by key
|
|
200
|
+
*/
|
|
201
|
+
async updateSecretByKey(key: string, input: UpdateSecretInput): Promise<boolean> {
|
|
202
|
+
try {
|
|
203
|
+
const updates: string[] = [];
|
|
204
|
+
const values: (string | boolean | Date | null)[] = [];
|
|
205
|
+
let paramCount = 1;
|
|
206
|
+
|
|
207
|
+
if (input.value !== undefined) {
|
|
208
|
+
const encryptedValue = EncryptionManager.encrypt(input.value);
|
|
209
|
+
updates.push(`value_ciphertext = $${paramCount++}`);
|
|
210
|
+
values.push(encryptedValue);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (input.isActive !== undefined) {
|
|
214
|
+
updates.push(`is_active = $${paramCount++}`);
|
|
215
|
+
values.push(input.isActive);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (input.isReserved !== undefined) {
|
|
219
|
+
updates.push(`is_reserved = $${paramCount++}`);
|
|
220
|
+
values.push(input.isReserved);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (input.expiresAt !== undefined) {
|
|
224
|
+
updates.push(`expires_at = $${paramCount++}`);
|
|
225
|
+
values.push(input.expiresAt);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (updates.length === 0) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
values.push(key);
|
|
233
|
+
|
|
234
|
+
const result = await this.getPool().query(
|
|
235
|
+
`UPDATE system.secrets
|
|
236
|
+
SET ${updates.join(', ')}
|
|
237
|
+
WHERE key = $${paramCount}`,
|
|
238
|
+
values
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const success = (result.rowCount ?? 0) > 0;
|
|
242
|
+
if (success) {
|
|
243
|
+
logger.info('Secret updated by key', { key });
|
|
244
|
+
}
|
|
245
|
+
return success;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
logger.error('Failed to update secret by key', { error, key });
|
|
248
|
+
throw new Error('Failed to update secret');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Delete a secret by key
|
|
254
|
+
*/
|
|
255
|
+
async deleteSecretByKey(key: string): Promise<boolean> {
|
|
256
|
+
try {
|
|
257
|
+
const result = await this.getPool().query(
|
|
258
|
+
'DELETE FROM system.secrets WHERE key = $1 AND is_reserved = false',
|
|
259
|
+
[key]
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const success = (result.rowCount ?? 0) > 0;
|
|
263
|
+
if (success) {
|
|
264
|
+
logger.info('Secret deleted by key', { key });
|
|
265
|
+
}
|
|
266
|
+
return success;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.error('Failed to delete secret by key', { error, key });
|
|
269
|
+
throw new Error('Failed to delete secret');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
206
273
|
/**
|
|
207
274
|
* Check if a secret value matches the stored value
|
|
208
275
|
*/
|
|
@@ -210,7 +277,7 @@ export class SecretService {
|
|
|
210
277
|
try {
|
|
211
278
|
// Optimized: Single query that retrieves and updates in one operation
|
|
212
279
|
const result = await this.getPool().query(
|
|
213
|
-
`UPDATE
|
|
280
|
+
`UPDATE system.secrets
|
|
214
281
|
SET last_used_at = NOW()
|
|
215
282
|
WHERE key = $1
|
|
216
283
|
AND is_active = true
|
|
@@ -225,7 +292,12 @@ export class SecretService {
|
|
|
225
292
|
}
|
|
226
293
|
|
|
227
294
|
const decryptedValue = EncryptionManager.decrypt(result.rows[0].value_ciphertext);
|
|
228
|
-
|
|
295
|
+
// Use constant-time comparison to prevent timing attacks
|
|
296
|
+
const decryptedBuffer = Buffer.from(decryptedValue);
|
|
297
|
+
const valueBuffer = Buffer.from(value);
|
|
298
|
+
const matches =
|
|
299
|
+
decryptedBuffer.length === valueBuffer.length &&
|
|
300
|
+
crypto.timingSafeEqual(decryptedBuffer, valueBuffer);
|
|
229
301
|
|
|
230
302
|
if (matches) {
|
|
231
303
|
logger.info('Secret check successful', { key });
|
|
@@ -247,7 +319,7 @@ export class SecretService {
|
|
|
247
319
|
try {
|
|
248
320
|
// Optimized: Single query with WHERE clause to prevent deleting reserved secrets
|
|
249
321
|
const result = await this.getPool().query(
|
|
250
|
-
'DELETE FROM
|
|
322
|
+
'DELETE FROM system.secrets WHERE id = $1 AND is_reserved = false',
|
|
251
323
|
[id]
|
|
252
324
|
);
|
|
253
325
|
|
|
@@ -257,7 +329,7 @@ export class SecretService {
|
|
|
257
329
|
} else {
|
|
258
330
|
// Check if it exists but is reserved
|
|
259
331
|
const checkResult = await this.getPool().query(
|
|
260
|
-
'SELECT is_reserved FROM
|
|
332
|
+
'SELECT is_reserved FROM system.secrets WHERE id = $1',
|
|
261
333
|
[id]
|
|
262
334
|
);
|
|
263
335
|
if (checkResult.rows.length && checkResult.rows[0].is_reserved) {
|
|
@@ -279,7 +351,9 @@ export class SecretService {
|
|
|
279
351
|
try {
|
|
280
352
|
await client.query('BEGIN');
|
|
281
353
|
|
|
282
|
-
const oldSecretResult = await client.query(`SELECT key FROM
|
|
354
|
+
const oldSecretResult = await client.query(`SELECT key FROM system.secrets WHERE id = $1`, [
|
|
355
|
+
id,
|
|
356
|
+
]);
|
|
283
357
|
|
|
284
358
|
if (!oldSecretResult.rows.length) {
|
|
285
359
|
throw new Error('Secret not found');
|
|
@@ -288,7 +362,7 @@ export class SecretService {
|
|
|
288
362
|
const secretKey = oldSecretResult.rows[0].key;
|
|
289
363
|
|
|
290
364
|
await client.query(
|
|
291
|
-
`UPDATE
|
|
365
|
+
`UPDATE system.secrets
|
|
292
366
|
SET is_active = false,
|
|
293
367
|
expires_at = NOW() + INTERVAL '24 hours'
|
|
294
368
|
WHERE id = $1`,
|
|
@@ -297,7 +371,7 @@ export class SecretService {
|
|
|
297
371
|
|
|
298
372
|
const encryptedValue = EncryptionManager.encrypt(newValue);
|
|
299
373
|
const newSecretResult = await client.query(
|
|
300
|
-
`INSERT INTO
|
|
374
|
+
`INSERT INTO system.secrets (key, value_ciphertext)
|
|
301
375
|
VALUES ($1, $2)
|
|
302
376
|
RETURNING id`,
|
|
303
377
|
[secretKey, encryptedValue]
|
|
@@ -327,7 +401,7 @@ export class SecretService {
|
|
|
327
401
|
async cleanupExpiredSecrets(): Promise<number> {
|
|
328
402
|
try {
|
|
329
403
|
const result = await this.getPool().query(
|
|
330
|
-
`DELETE FROM
|
|
404
|
+
`DELETE FROM system.secrets
|
|
331
405
|
WHERE expires_at IS NOT NULL
|
|
332
406
|
AND expires_at < NOW()
|
|
333
407
|
RETURNING id`
|