# yaml-language-server: $schema=https://schema.zeabur.app/template.json apiVersion: zeabur.com/v1 kind: Template metadata: name: InsForge spec: description: InsForge is the Agent-Native Supabase Alternative, enabling AI agents to build and manage full-stack applications autonomously. coverImage: https://cdn.zeabur.com/insforge.png icon: https://avatars.githubusercontent.com/u/198419463?s=96&v=4 variables: - key: PUBLIC_DOMAIN type: DOMAIN name: InsForge Domain description: The domain for accessing your InsForge application. - key: ROOT_ADMIN_USERNAME type: STRING name: Root Admin Username description: Username for the root admin. - key: ROOT_ADMIN_PASSWORD type: STRING name: Root Admin Password description: Password for the root admin. Must be at least 8 characters. - key: OPENROUTER_API_KEY type: STRING name: OpenRouter API Key description: API key for OpenRouter LLM services (optional). - key: STRIPE_TEST_SECRET_KEY type: STRING name: Stripe Test Secret Key description: Stripe test mode secret key for payment catalog sync (optional). - key: STRIPE_LIVE_SECRET_KEY type: STRING name: Stripe Live Secret Key description: Stripe live mode secret key for payment catalog sync (optional). tags: - Development - Database - API - Platform readme: |- # InsForge InsForge is the Agent-Native Supabase Alternative, enabling AI agents to build and manage full-stack applications autonomously. ## Features - **Authentication System** - User management and secure authentication - **Database Storage** - Flexible PostgreSQL database with automatic schema management - **File Management** - Secure file upload and storage capabilities - **PostgREST API** - Automatic REST API generation from database schema - **Serverless Functions** - Edge functions for custom business logic - **AI Agent Integration** - Connect AI agents like Claude or GPT to manage your backend ## Quick Start This template provides **one-click deployment** of the complete InsForge platform: 1. **Deploy** - Click deploy and bind a domain 2. **Login** - Use your admin credentials to access the dashboard 3. **Connect AI Agent** - Link your AI agent (Claude, GPT, etc.) through the dashboard 4. **Build Apps** - Use natural language prompts to create applications: - "Build a todo app with user authentication" - "Create an Instagram clone with image upload" - "Build a blog with comments and likes" ## Use Cases - **AI-Generated Frontends** - Rapidly create backends for AI-generated frontend projects - **Agent-Driven Development** - Let AI agents manage your entire backend infrastructure - **Rapid Prototyping** - Build full-stack applications using natural language ## Community - [Discord](https://discord.gg/MPxwj5xVvW) - Join our community - [GitHub](https://github.com/InsForge/InsForge) - Contribute to the project - Email: info@insforge.dev services: - name: postgres icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/postgresql.svg template: PREBUILT_V2 spec: source: image: ghcr.io/insforge/postgres:v15.13.4 command: - docker-entrypoint.sh - postgres - -c - config_file=/etc/postgresql/postgresql.conf ports: - id: database port: 5432 type: TCP volumes: - id: data dir: /var/lib/postgresql/data instructions: - title: Connection String content: postgres://postgres:${POSTGRES_PASSWORD}@${PORT_FORWARDED_HOSTNAME}:${DATABASE_PORT_FORWARDED_PORT}/insforge - title: PostgreSQL Connect Command content: psql "postgres://postgres:${POSTGRES_PASSWORD}@${PORT_FORWARDED_HOSTNAME}:${DATABASE_PORT_FORWARDED_PORT}/insforge" - title: PostgreSQL username content: postgres - title: PostgresSQL password content: ${POSTGRES_PASSWORD} - title: PostgresSQL database content: insforge - title: PostgreSQL host content: ${PORT_FORWARDED_HOSTNAME} - title: PostgreSQL port content: ${DATABASE_PORT_FORWARDED_PORT} env: PGDATA: default: /var/lib/postgresql/data/pgdata POSTGRES_CONNECTION_STRING: default: postgres://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/insforge expose: true POSTGRES_DATABASE: default: insforge expose: true POSTGRES_DB: default: insforge POSTGRES_HOST: default: ${CONTAINER_HOSTNAME} expose: true POSTGRES_PASSWORD: default: ${PASSWORD} expose: true readonly: true POSTGRES_PORT: default: ${DATABASE_PORT} expose: true POSTGRES_URI: default: ${POSTGRES_CONNECTION_STRING} expose: true POSTGRES_USER: default: postgres POSTGRES_USERNAME: default: postgres expose: true JWT_SECRET: default: ${PASSWORD} expose: true readonly: true JWT_EXP: default: "3600" configs: - path: /etc/postgresql/postgresql.conf template: | # PostgreSQL configuration for InsForge # Enable logical replication for Logflare # Listen on all interfaces to allow container connections listen_addresses = '*' # Set WAL level to logical for Logflare replication wal_level = logical # Set max replication slots (needed for logical replication) max_replication_slots = 10 # Set max WAL senders max_wal_senders = 10 shared_preload_libraries = 'pg_cron,http,pgcrypto,insforge_pg_utils' insforge.policy_grant_role = 'project_admin' insforge.policy_grant_tables = 'storage.objects,realtime.channels,realtime.messages,payments.stripe_checkout_sessions,payments.stripe_customer_portal_sessions,payments.razorpay_orders,payments.razorpay_subscriptions' insforge.internal_schemas = 'ai,auth,compute,deployments,email,functions,memory,payments,realtime,schedules,storage,system' cron.database_name = 'insforge' - path: /docker-entrypoint-initdb.d/01-init.sql template: | -- init.sql -- Create role for anonymous user CREATE ROLE anon NOLOGIN; -- Create role for authenticator CREATE ROLE authenticated NOLOGIN; -- Create project admin role for admin users CREATE ROLE project_admin NOLOGIN; GRANT USAGE ON SCHEMA public TO anon; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO anon; GRANT USAGE ON SCHEMA public TO authenticated; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated; GRANT USAGE ON SCHEMA public TO project_admin; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO project_admin; -- Grant permissions to roles GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO anon, authenticated, project_admin; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO anon, authenticated, project_admin; -- Create function to automatically create RLS policies for new tables CREATE OR REPLACE FUNCTION public.create_default_policies() RETURNS event_trigger AS $$ DECLARE obj record; table_schema text; table_name text; has_rls boolean; BEGIN FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'CREATE TABLE' LOOP -- Extract schema and table name from object_identity -- Handle quoted identifiers by removing quotes SELECT INTO table_schema, table_name split_part(obj.object_identity, '.', 1), trim(both '"' from split_part(obj.object_identity, '.', 2)); -- Check if RLS is enabled on the table SELECT INTO has_rls rowsecurity FROM pg_tables WHERE schemaname = table_schema AND tablename = table_name; -- Only create policies if RLS is enabled IF has_rls THEN -- Create policy for project_admin role only -- Users must define their own policies for anon and authenticated roles EXECUTE format('CREATE POLICY "project_admin_policy" ON %s FOR ALL TO project_admin USING (true) WITH CHECK (true)', obj.object_identity); END IF; END LOOP; END; $$ LANGUAGE plpgsql; -- Create event trigger to run the function when new tables are created CREATE EVENT TRIGGER create_policies_on_table_create ON ddl_command_end WHEN TAG IN ('CREATE TABLE') EXECUTE FUNCTION public.create_default_policies(); -- Create function to handle RLS enablement CREATE OR REPLACE FUNCTION public.create_policies_after_rls() RETURNS event_trigger AS $$ DECLARE obj record; table_schema text; table_name text; BEGIN FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'ALTER TABLE' LOOP -- Extract schema and table name -- Handle quoted identifiers by removing quotes SELECT INTO table_schema, table_name split_part(obj.object_identity, '.', 1), trim(both '"' from split_part(obj.object_identity, '.', 2)); -- Check if table has RLS enabled and no policies yet IF EXISTS ( SELECT 1 FROM pg_tables WHERE schemaname = table_schema AND tablename = table_name AND rowsecurity = true ) AND NOT EXISTS ( SELECT 1 FROM pg_policies WHERE schemaname = table_schema AND tablename = table_name ) THEN -- Create policy for project_admin role only -- Users must define their own policies for anon and authenticated roles EXECUTE format('CREATE POLICY "project_admin_policy" ON %s FOR ALL TO project_admin USING (true) WITH CHECK (true)', obj.object_identity); END IF; END LOOP; END; $$ LANGUAGE plpgsql; -- Create event trigger for ALTER TABLE commands CREATE EVENT TRIGGER create_policies_on_rls_enable ON ddl_command_end WHEN TAG IN ('ALTER TABLE') EXECUTE FUNCTION public.create_policies_after_rls(); - path: /docker-entrypoint-initdb.d/02-jwt.sql template: | \set jwt_secret `echo "$JWT_SECRET"` \set jwt_exp `echo "$JWT_EXP"` ALTER DATABASE insforge SET "app.settings.jwt_secret" TO :'jwt_secret'; ALTER DATABASE insforge SET "app.settings.jwt_exp" TO :'jwt_exp'; healthCheck: type: TCP port: database - name: postgrest icon: https://avatars.githubusercontent.com/u/15115011?s=96&v=4 dependencies: - postgres template: PREBUILT_V2 spec: source: image: postgrest/postgrest:v12.2.12 ports: - id: api port: 3000 type: HTTP env: PGHOST: default: postgres PGPORT: default: "5432" PGDATABASE: default: insforge PGUSER: default: postgres PGPASSWORD: default: ${POSTGRES_PASSWORD} expose: true PGRST_OPENAPI_SERVER_PROXY_URI: default: http://localhost:3000 PGRST_DB_SCHEMA: default: public PGRST_DB_ANON_ROLE: default: anon PGRST_JWT_SECRET: default: ${JWT_SECRET} expose: true readonly: true PGRST_DB_CHANNEL_ENABLED: default: "true" PGRST_DB_CHANNEL: default: pgrst healthCheck: type: HTTP port: api http: path: / - name: deno icon: https://avatars.githubusercontent.com/u/42048915?s=96&v=4 dependencies: - postgres - postgrest template: PREBUILT_V2 spec: source: image: denoland/deno:alpine-2.0.6 command: - sh - -c - | cd /app && echo 'Downloading Deno dependencies...' && deno cache functions/server.ts && echo 'Starting Deno server on port 7133...' && deno run --allow-net --allow-env --allow-read=./functions/worker-template.js --watch functions/server.ts ports: - id: runtime port: 7133 type: HTTP volumes: - id: cache dir: /deno-dir env: PORT: default: "7133" DENO_ENV: default: development DENO_DIR: default: /deno-dir POSTGRES_HOST: default: postgres POSTGRES_PORT: default: "5432" POSTGRES_DB: default: insforge POSTGRES_USER: default: postgres POSTGRES_PASSWORD: default: ${PGPASSWORD} POSTGREST_BASE_URL: default: http://postgrest:3000 WORKER_TIMEOUT_MS: default: "60000" ENCRYPTION_KEY: default: ${PASSWORD} JWT_SECRET: default: ${PGRST_JWT_SECRET} configs: - path: /app/functions/server.ts template: | import { Client } from 'https://deno.land/x/postgres@v0.17.0/mod.ts'; import { join, dirname, fromFileUrl } from 'https://deno.land/std@0.224.0/path/mod.ts'; /* eslint-disable no-console */ const port = parseInt(Deno.env.get('PORT') ?? '7133'); console.log(`Deno serverless runtime running on port ${port}`); // Configuration const WORKER_TIMEOUT_MS = parseInt(Deno.env.get('WORKER_TIMEOUT_MS') ?? '60000'); // Worker template code - loaded on first use let workerTemplateCode: string | null = null; async function getWorkerTemplateCode(): Promise { if (!workerTemplateCode) { const currentDir = dirname(fromFileUrl(import.meta.url)); workerTemplateCode = await Deno.readTextFile(join(currentDir, 'worker-template.js')); } return workerTemplateCode; } // Decrypt function for Deno (compatible with Node.js encryption) async function decryptSecret(ciphertext: string, key: string): Promise { try { const parts = ciphertext.split(':'); if (parts.length !== 3) { throw new Error('Invalid ciphertext format'); } // Get the encryption key by hashing the JWT secret const keyData = new TextEncoder().encode(key); const hashBuffer = await crypto.subtle.digest('SHA-256', keyData); const cryptoKey = await crypto.subtle.importKey('raw', hashBuffer, { name: 'AES-GCM' }, false, [ 'decrypt', ]); // Extract IV, auth tag, and encrypted data const iv = Uint8Array.from(parts[0].match(/.{2}/g)!.map((byte) => parseInt(byte, 16))); const authTag = Uint8Array.from(parts[1].match(/.{2}/g)!.map((byte) => parseInt(byte, 16))); const encrypted = Uint8Array.from(parts[2].match(/.{2}/g)!.map((byte) => parseInt(byte, 16))); // Combine encrypted data and auth tag (GCM expects them together) const cipherData = new Uint8Array(encrypted.length + authTag.length); cipherData.set(encrypted); cipherData.set(authTag, encrypted.length); // Decrypt const decryptedBuffer = await crypto.subtle.decrypt( { name: 'AES-GCM', iv }, cryptoKey, cipherData ); return new TextDecoder().decode(decryptedBuffer); } catch (error) { console.error('Failed to decrypt secret:', error); throw error; } } // Database connection const dbConfig = { user: Deno.env.get('POSTGRES_USER') || 'postgres', password: Deno.env.get('POSTGRES_PASSWORD') || 'postgres', database: Deno.env.get('POSTGRES_DB') || 'insforge', hostname: Deno.env.get('POSTGRES_HOST') || 'postgres', port: parseInt(Deno.env.get('POSTGRES_PORT') || '5432', 10), }; // Get function code from database async function getFunctionCode(slug: string): Promise { const client = new Client(dbConfig); try { await client.connect(); const result = await client.queryObject<{ code: string }>` SELECT code FROM functions.definitions WHERE slug = ${slug} AND status = 'active' `; if (!result.rows.length) { return null; } return result.rows[0].code; } catch (error) { console.error(`Error fetching function ${slug}:`, error); return null; } finally { await client.end(); } } // Get all secrets from main secrets table and decrypt them async function getFunctionSecrets(): Promise> { const client = new Client(dbConfig); try { await client.connect(); // Get the encryption key from environment const encryptionKey = Deno.env.get('ENCRYPTION_KEY') || Deno.env.get('JWT_SECRET'); if (!encryptionKey) { console.error('No encryption key available for decrypting secrets'); return {}; } // Fetch all active secrets from system.secrets table const result = await client.queryObject<{ key: string; value_ciphertext: string; }>` SELECT key, value_ciphertext FROM system.secrets WHERE is_active = true AND (expires_at IS NULL OR expires_at > NOW()) `; const secrets: Record = {}; // Decrypt each secret for (const row of result.rows) { try { secrets[row.key] = await decryptSecret(row.value_ciphertext, encryptionKey); } catch (error) { console.error(`Failed to decrypt secret ${row.key}:`, error); // Skip this secret if decryption fails } } return secrets; } catch (error) { console.error('Error fetching secrets:', error); return {}; } finally { await client.end(); } } // Execute function in isolated worker async function executeInWorker(code: string, request: Request): Promise { // Get worker template const template = await getWorkerTemplateCode(); // Fetch all function secrets const secrets = await getFunctionSecrets(); // Create blob for worker const workerBlob = new Blob([template], { type: 'application/javascript' }); const workerUrl = URL.createObjectURL(workerBlob); return new Promise(async (resolve) => { const worker = new Worker(workerUrl, { type: 'module' }); // Set timeout for worker execution const timeout = setTimeout(() => { worker.terminate(); URL.revokeObjectURL(workerUrl); resolve( new Response(JSON.stringify({ error: 'Function timeout' }), { status: 504, headers: { 'Content-Type': 'application/json' }, }) ); }, WORKER_TIMEOUT_MS); // Handle worker response worker.onmessage = (e) => { clearTimeout(timeout); worker.terminate(); URL.revokeObjectURL(workerUrl); if (e.data.success) { const { response } = e.data; // The worker now properly sends null for bodyless responses resolve( new Response(response.body, { status: response.status, statusText: response.statusText, headers: response.headers, }) ); } else { resolve( new Response(JSON.stringify({ error: e.data.error }), { status: e.data.status || 500, headers: { 'Content-Type': 'application/json' }, }) ); } }; // Handle worker errors worker.onerror = (error) => { clearTimeout(timeout); worker.terminate(); URL.revokeObjectURL(workerUrl); console.error('Worker error:', error); resolve( new Response(JSON.stringify({ error: 'Worker execution error' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }) ); }; // Prepare request data const body = request.body ? await request.text() : null; const requestData = { url: request.url, method: request.method, headers: Object.fromEntries(request.headers), body, }; // Send message with code, request data, and secrets worker.postMessage({ code, requestData, secrets }); }); } Deno.serve({ port }, async (req: Request) => { const url = new URL(req.url); const pathname = url.pathname; // Health check if (pathname === '/health') { return new Response( JSON.stringify({ status: 'ok', runtime: 'deno', version: Deno.version.deno, typescript: Deno.version.typescript, v8: Deno.version.v8, }), { headers: { 'Content-Type': 'application/json' }, } ); } // Function execution - match ONLY exact slug, no subpaths const slugMatch = pathname.match(/^\/([a-zA-Z0-9_-]+)$/); if (slugMatch) { const slug = slugMatch[1]; const startTime = Date.now(); // Get function code from database const code = await getFunctionCode(slug); if (!code) { return new Response(JSON.stringify({ error: 'Function not found or not active' }), { status: 404, headers: { 'Content-Type': 'application/json' }, }); } // Execute in worker with original request try { const response = await executeInWorker(code, req); const duration = Date.now() - startTime; // Log completed invocations only console.log( JSON.stringify({ timestamp: new Date().toISOString(), level: 'info', slug, method: req.method, status: response.status, duration: `${duration}ms`, }) ); return response; } catch (error) { const duration = Date.now() - startTime; console.error( JSON.stringify({ timestamp: new Date().toISOString(), level: 'error', slug, error: error instanceof Error ? error.message : String(error), duration: `${duration}ms`, }) ); return new Response(JSON.stringify({ error: 'Function execution failed' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } } // Runtime info if (pathname === '/info') { return new Response( JSON.stringify({ runtime: 'deno', version: Deno.version, env: Deno.env.get('DENO_ENV') || 'production', database: { host: dbConfig.hostname, database: dbConfig.database, }, }), { headers: { 'Content-Type': 'application/json' }, } ); } // 404 return new Response('Not Found', { status: 404 }); }); - path: /app/functions/deno.json template: | { "compilerOptions": { "lib": ["deno.window", "deno.worker"], "strict": true }, "lint": { "files": { "include": ["./**/*.ts", "./**/*.js"] }, "rules": { "tags": ["recommended"] } }, "fmt": { "files": { "include": ["./**/*.ts", "./**/*.js"] }, "options": { "useTabs": false, "lineWidth": 100, "indentWidth": 2, "singleQuote": true } } } - path: /app/functions/worker-template.js template: | /** * Worker Template for Serverless Functions * * This code runs inside a Web Worker environment created by Deno. * Each worker is created fresh for a single request, executes once, and terminates. */ /* eslint-env worker */ /* global self, Request, Deno */ // Import SDK at worker level - this will be available to all functions import { createClient } from 'npm:@insforge/sdk'; // Import base64 utilities for encoding/decoding import { encodeBase64, decodeBase64 } from 'https://deno.land/std@0.224.0/encoding/base64.ts'; // Handle the single message with code, request data, and secrets self.onmessage = async (e) => { const { code, requestData, secrets = {} } = e.data; try { /** * MOCK DENO OBJECT EXPLANATION: * * Why we need a mock Deno object: * - Edge functions run in isolated Web Workers (sandboxed environments) * - Web Workers don't have access to the real Deno global object for security * - We need to provide Deno.env functionality so functions can access secrets * * How it works: * 1. The main server (server.ts) fetches all active secrets from the system.secrets table * 2. Only active (is_active=true) and non-expired secrets are included * 3. Secrets are decrypted and passed to this worker via the 'secrets' object * 4. We create a mock Deno object that provides Deno.env.get() * 5. When user code calls Deno.env.get('MY_SECRET'), it reads from our secrets object * * This allows edge functions to use familiar Deno.env syntax while maintaining security * Secrets are managed via the /api/secrets endpoint */ const mockDeno = { // Mock the Deno.env API - only get() is needed for reading secrets env: { get: (key) => secrets[key] || undefined, }, }; /** * FUNCTION WRAPPING EXPLANATION: * * Here we create a wrapper function that will execute the user's code. * The user's function expects to have access to: * - module.exports (to export their function) * - createClient (the Insforge SDK) * - Deno (for Deno.env.get() etc.) * - encodeBase64, decodeBase64 (base64 encoding utilities) * * We inject our mockDeno as the 'Deno' parameter, so when the user's code * calls Deno.env.get('MY_SECRET'), it's actually calling mockDeno.env.get('MY_SECRET') */ const wrapper = new Function( 'exports', 'module', 'createClient', 'Deno', 'encodeBase64', 'decodeBase64', code ); const exports = {}; const module = { exports }; // Execute the wrapper, passing mockDeno as the Deno global and utility functions // This makes Deno.env.get(), encodeBase64(), and decodeBase64() available inside the user's function wrapper(exports, module, createClient, mockDeno, encodeBase64, decodeBase64); // Get the exported function const functionHandler = module.exports || exports.default || exports; if (typeof functionHandler !== 'function') { throw new Error( 'No function exported. Expected: module.exports = async function(req) { ... }' ); } // Create Request object from data const request = new Request(requestData.url, { method: requestData.method, headers: requestData.headers, body: requestData.body, }); // Execute the function const response = await functionHandler(request); // Serialize and send response // Properly handle responses with no body let body = null; // Only read body if response has content // Status codes 204, 205, and 304 should not have a body if (![204, 205, 304].includes(response.status)) { body = await response.text(); } const responseData = { status: response.status, statusText: response.statusText, headers: Object.fromEntries(response.headers), body: body, }; self.postMessage({ success: true, response: responseData }); } catch (error) { // Check if the error is actually a Response object (thrown by the function) if (error instanceof Response) { // Handle error responses the same way let body = null; if (![204, 205, 304].includes(error.status)) { body = await error.text(); } const responseData = { status: error.status, statusText: error.statusText, headers: Object.fromEntries(error.headers), body: body, }; self.postMessage({ success: true, response: responseData }); } else { // For actual errors, include status if available self.postMessage({ success: false, error: error.message || 'Unknown error', status: error.status || 500, }); } } }; healthCheck: type: HTTP port: runtime http: path: /health - name: insforge icon: https://avatars.githubusercontent.com/u/198419463?s=96&v=4 dependencies: - postgres - postgrest - deno template: PREBUILT_V2 spec: source: image: ghcr.io/insforge/insforge-oss:latest ports: - id: web port: 7130 type: HTTP - id: dev port: 7131 type: HTTP env: PORT: default: "7130" PROJECT_ROOT: default: /app API_BASE_URL: default: ${ZEABUR_WEB_URL} VITE_API_BASE_URL: default: ${ZEABUR_WEB_URL} JWT_SECRET: default: ${PGRST_JWT_SECRET} ROOT_ADMIN_USERNAME: default: ${ROOT_ADMIN_USERNAME} ROOT_ADMIN_PASSWORD: default: ${ROOT_ADMIN_PASSWORD} # PostgreSQL connection POSTGRES_HOST: default: postgres POSTGRES_PORT: default: "5432" POSTGRES_DB: default: insforge POSTGRES_USER: default: postgres POSTGRESDB_PASSWORD: default: ${PGPASSWORD} DATABASE_URL: default: postgres://postgres:${PGPASSWORD}@postgres:5432/insforge POSTGREST_BASE_URL: default: http://postgrest:3000 # Deno Runtime URL for serverless functions DENO_RUNTIME_URL: default: http://deno:7133 # OAuth Configuration (optional) GOOGLE_CLIENT_ID: default: "" GOOGLE_CLIENT_SECRET: default: "" GOOGLE_REDIRECT_URI: default: ${ZEABUR_WEB_URL}/api/auth/v1/callback GITHUB_CLIENT_ID: default: "" GITHUB_CLIENT_SECRET: default: "" GITHUB_REDIRECT_URI: default: ${ZEABUR_WEB_URL}/api/auth/v1/callback # Multi-tenant Cloud Configuration DEPLOYMENT_ID: default: ${ZEABUR_SERVICE_ID} PROJECT_ID: default: ${ZEABUR_PROJECT_ID} APP_KEY: default: "" ACCESS_API_KEY: default: "" ACCESS_ANON_KEY: default: "" OPENROUTER_API_KEY: default: ${OPENROUTER_API_KEY} STRIPE_TEST_SECRET_KEY: default: ${STRIPE_TEST_SECRET_KEY} STRIPE_LIVE_SECRET_KEY: default: ${STRIPE_LIVE_SECRET_KEY} healthCheck: type: HTTP port: web http: path: /api/health domainKey: PUBLIC_DOMAIN localization: zh-CN: description: InsForge 是 Agent-Native 的 Supabase 替代方案,让 AI 智能体能够自主构建和管理全栈应用程序。 readme: | # InsForge InsForge 是 Agent-Native 的 Supabase 替代方案,让 AI 智能体能够自主构建和管理全栈应用程序。 ## 功能特性 - **身份验证系统** - 用户管理和安全认证 - **数据库存储** - 灵活的 PostgreSQL 数据库,支持自动模式管理 - **文件管理** - 安全的文件上传和存储功能 - **PostgREST API** - 从数据库模式自动生成 REST API - **无服务器函数** - 用于自定义业务逻辑的边缘函数 - **AI 智能体集成** - 连接 Claude 或 GPT 等 AI 智能体来管理后端 ## 快速开始 此模板提供 InsForge 平台的**一键部署**: 1. **部署** - 点击部署并绑定域名 2. **登录** - 使用管理员凭据访问控制台 3. **连接 AI 智能体** - 通过控制台连接您的 AI 智能体(Claude、GPT 等) 4. **构建应用** - 使用自然语言提示创建应用程序: - "构建一个带用户认证的待办事项应用" - "创建一个带图片上传的 Instagram 克隆" - "构建一个带评论和点赞的博客" ## 使用场景 - **AI 生成的前端** - 为 AI 生成的前端项目快速创建后端 - **智能体驱动开发** - 让 AI 智能体管理您的整个后端基础设施 - **快速原型设计** - 使用自然语言构建全栈应用程序 zh-TW: description: InsForge 是 Agent-Native 的 Supabase 替代方案,讓 AI 智慧體能夠自主建構和管理全端應用程式。 readme: | # InsForge InsForge 是 Agent-Native 的 Supabase 替代方案,讓 AI 智慧體能夠自主建構和管理全端應用程式。 ## 功能特色 - **身份驗證系統** - 使用者管理和安全認證 - **資料庫儲存** - 靈活的 PostgreSQL 資料庫,支援自動模式管理 - **檔案管理** - 安全的檔案上傳和儲存功能 - **PostgREST API** - 從資料庫模式自動產生 REST API - **無伺服器函數** - 用於自訂業務邏輯的邊緣函數 - **AI 智慧體整合** - 連接 Claude 或 GPT 等 AI 智慧體來管理後端 ## 快速開始 此模版提供 InsForge 平台的**一鍵部署**: 1. **部署** - 點擊部署並綁定網域 2. **登入** - 使用管理員憑證存取控制台 3. **連接 AI 智慧體** - 透過控制台連接您的 AI 智慧體(Claude、GPT 等) 4. **建構應用** - 使用自然語言提示建立應用程式: - "建構一個帶使用者認證的待辦事項應用" - "建立一個帶圖片上傳的 Instagram 複製品" - "建構一個帶評論和按讚的部落格" ## 使用情境 - **AI 產生的前端** - 為 AI 產生的前端專案快速建立後端 - **智慧體驅動開發** - 讓 AI 智慧體管理您的整個後端基礎設施 - **快速原型設計** - 使用自然語言建構全端應用程式