@sudobility/shapeshyft_service 1.0.0
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/CLAUDE.md +35 -0
- package/README.md +17 -0
- package/dist/context.d.ts +62 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +55 -0
- package/dist/context.js.map +1 -0
- package/dist/contracts.d.ts +110 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +7 -0
- package/dist/contracts.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-key.d.ts +22 -0
- package/dist/lib/api-key.d.ts.map +1 -0
- package/dist/lib/api-key.js +87 -0
- package/dist/lib/api-key.js.map +1 -0
- package/dist/lib/encryption.d.ts +22 -0
- package/dist/lib/encryption.d.ts.map +1 -0
- package/dist/lib/encryption.js +41 -0
- package/dist/lib/encryption.js.map +1 -0
- package/dist/lib/entity-api-key.d.ts +21 -0
- package/dist/lib/entity-api-key.d.ts.map +1 -0
- package/dist/lib/entity-api-key.js +54 -0
- package/dist/lib/entity-api-key.js.map +1 -0
- package/dist/lib/entity-helpers.d.ts +90 -0
- package/dist/lib/entity-helpers.d.ts.map +1 -0
- package/dist/lib/entity-helpers.js +136 -0
- package/dist/lib/entity-helpers.js.map +1 -0
- package/dist/lib/money.d.ts +7 -0
- package/dist/lib/money.d.ts.map +1 -0
- package/dist/lib/money.js +9 -0
- package/dist/lib/money.js.map +1 -0
- package/dist/lib/public-project.d.ts +14 -0
- package/dist/lib/public-project.d.ts.map +1 -0
- package/dist/lib/public-project.js +17 -0
- package/dist/lib/public-project.js.map +1 -0
- package/dist/lib/user-api-key-cache.d.ts +35 -0
- package/dist/lib/user-api-key-cache.d.ts.map +1 -0
- package/dist/lib/user-api-key-cache.js +99 -0
- package/dist/lib/user-api-key-cache.js.map +1 -0
- package/dist/lib/user-api-key.d.ts +46 -0
- package/dist/lib/user-api-key.d.ts.map +1 -0
- package/dist/lib/user-api-key.js +102 -0
- package/dist/lib/user-api-key.js.map +1 -0
- package/dist/middleware/firebaseAuth.d.ts +34 -0
- package/dist/middleware/firebaseAuth.d.ts.map +1 -0
- package/dist/middleware/firebaseAuth.js +133 -0
- package/dist/middleware/firebaseAuth.js.map +1 -0
- package/dist/middleware/rateLimit.d.ts +29 -0
- package/dist/middleware/rateLimit.d.ts.map +1 -0
- package/dist/middleware/rateLimit.js +85 -0
- package/dist/middleware/rateLimit.js.map +1 -0
- package/dist/middleware/subscription.d.ts +8 -0
- package/dist/middleware/subscription.d.ts.map +1 -0
- package/dist/middleware/subscription.js +19 -0
- package/dist/middleware/subscription.js.map +1 -0
- package/dist/routes/ai.d.ts +10 -0
- package/dist/routes/ai.d.ts.map +1 -0
- package/dist/routes/ai.js +698 -0
- package/dist/routes/ai.js.map +1 -0
- package/dist/routes/analytics.d.ts +9 -0
- package/dist/routes/analytics.d.ts.map +1 -0
- package/dist/routes/analytics.js +192 -0
- package/dist/routes/analytics.js.map +1 -0
- package/dist/routes/endpoints.d.ts +10 -0
- package/dist/routes/endpoints.d.ts.map +1 -0
- package/dist/routes/endpoints.js +255 -0
- package/dist/routes/endpoints.js.map +1 -0
- package/dist/routes/entities.d.ts +15 -0
- package/dist/routes/entities.d.ts.map +1 -0
- package/dist/routes/entities.js +332 -0
- package/dist/routes/entities.js.map +1 -0
- package/dist/routes/entity-api-keys.d.ts +17 -0
- package/dist/routes/entity-api-keys.d.ts.map +1 -0
- package/dist/routes/entity-api-keys.js +142 -0
- package/dist/routes/entity-api-keys.js.map +1 -0
- package/dist/routes/invitations.d.ts +15 -0
- package/dist/routes/invitations.d.ts.map +1 -0
- package/dist/routes/invitations.js +58 -0
- package/dist/routes/invitations.js.map +1 -0
- package/dist/routes/projects.d.ts +9 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +241 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/providers.d.ts +9 -0
- package/dist/routes/providers.d.ts.map +1 -0
- package/dist/routes/providers.js +89 -0
- package/dist/routes/providers.js.map +1 -0
- package/dist/routes/ratelimits.d.ts +9 -0
- package/dist/routes/ratelimits.d.ts.map +1 -0
- package/dist/routes/ratelimits.js +171 -0
- package/dist/routes/ratelimits.js.map +1 -0
- package/dist/routes/settings.d.ts +9 -0
- package/dist/routes/settings.d.ts.map +1 -0
- package/dist/routes/settings.js +138 -0
- package/dist/routes/settings.js.map +1 -0
- package/dist/routes/storage.d.ts +9 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +180 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/user-api-keys.d.ts +14 -0
- package/dist/routes/user-api-keys.d.ts.map +1 -0
- package/dist/routes/user-api-keys.js +237 -0
- package/dist/routes/user-api-keys.js.map +1 -0
- package/dist/routes/users.d.ts +7 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +102 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/schema/init.d.ts +12 -0
- package/dist/schema/init.d.ts.map +1 -0
- package/dist/schema/init.js +404 -0
- package/dist/schema/init.js.map +1 -0
- package/dist/schema/tables.d.ts +2110 -0
- package/dist/schema/tables.d.ts.map +1 -0
- package/dist/schema/tables.js +251 -0
- package/dist/schema/tables.js.map +1 -0
- package/dist/schemas/index.d.ts +671 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +299 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/service.d.ts +41 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +70 -0
- package/dist/service.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview User routes
|
|
3
|
+
*/
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import { NONE_ENTITLEMENT, } from "@sudobility/types";
|
|
6
|
+
import { successResponse, errorResponse, } from "@sudobility/shapeshyft_engine/types";
|
|
7
|
+
export function createUsersRouter(ctx) {
|
|
8
|
+
const getUserInfo = (...args) => ctx.auth.getUserInfo(...args);
|
|
9
|
+
const { getSubscriptionHelper, getTestMode } = ctx.subscription;
|
|
10
|
+
const usersRouter = new Hono();
|
|
11
|
+
/**
|
|
12
|
+
* GET /users/:userId
|
|
13
|
+
*
|
|
14
|
+
* Get user information including siteAdmin status.
|
|
15
|
+
* Requires the Firebase token to match the requested userId.
|
|
16
|
+
* Returns 403 if token doesn't match or user not found.
|
|
17
|
+
*
|
|
18
|
+
* Note: This route is under adminRoutes which applies firebaseAuthMiddleware,
|
|
19
|
+
* so c.get("userId") is already available.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* GET /users/me
|
|
23
|
+
*
|
|
24
|
+
* Identify the authenticated caller. Works with either auth method, and is the
|
|
25
|
+
* only way an API-key client can learn its own Firebase UID — which the
|
|
26
|
+
* /users/:userId/* routes require. Registered before "/:userId" so the literal
|
|
27
|
+
* "me" is not captured as a user id.
|
|
28
|
+
*/
|
|
29
|
+
usersRouter.get("/me", async (c) => {
|
|
30
|
+
const userId = c.get("userId");
|
|
31
|
+
const userEmail = c.get("userEmail");
|
|
32
|
+
// The display name is a nicety fetched from Firebase. Identity is already
|
|
33
|
+
// established by the middleware, so a Firebase outage (or an API-key caller on
|
|
34
|
+
// a deployment without Firebase reachable) must not fail this route.
|
|
35
|
+
let displayName = null;
|
|
36
|
+
try {
|
|
37
|
+
const userInfo = await getUserInfo(userId);
|
|
38
|
+
displayName = userInfo?.displayName ?? null;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error("Could not load Firebase profile for /users/me:", error);
|
|
42
|
+
}
|
|
43
|
+
return c.json(successResponse({
|
|
44
|
+
firebase_uid: userId,
|
|
45
|
+
email: userEmail,
|
|
46
|
+
siteAdmin: c.get("siteAdmin") ?? false,
|
|
47
|
+
auth_method: c.get("authMethod") ?? "firebase",
|
|
48
|
+
display_name: displayName,
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
51
|
+
usersRouter.get("/:userId", async (c) => {
|
|
52
|
+
const requestedUserId = c.req.param("userId");
|
|
53
|
+
const tokenUserId = c.get("userId");
|
|
54
|
+
// Verify the token belongs to the requested user
|
|
55
|
+
if (requestedUserId !== tokenUserId) {
|
|
56
|
+
return c.json(errorResponse("Token does not match requested user"), 403);
|
|
57
|
+
}
|
|
58
|
+
const userInfo = await getUserInfo(requestedUserId);
|
|
59
|
+
if (!userInfo) {
|
|
60
|
+
return c.json(errorResponse("User not found"), 403);
|
|
61
|
+
}
|
|
62
|
+
return c.json(successResponse(userInfo));
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* GET /users/:userId/subscriptions
|
|
66
|
+
*
|
|
67
|
+
* Get user subscription status (requires Firebase auth).
|
|
68
|
+
*/
|
|
69
|
+
usersRouter.get("/:userId/subscriptions", async (c) => {
|
|
70
|
+
const requestedUserId = c.req.param("userId");
|
|
71
|
+
const tokenUserId = c.get("userId");
|
|
72
|
+
if (requestedUserId !== tokenUserId) {
|
|
73
|
+
return c.json(errorResponse("You can only access your own subscription"), 403);
|
|
74
|
+
}
|
|
75
|
+
const subHelper = getSubscriptionHelper();
|
|
76
|
+
if (!subHelper) {
|
|
77
|
+
return c.json(errorResponse("Subscription service not configured"), 500);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const testMode = getTestMode(c);
|
|
81
|
+
const subscriptionInfo = await subHelper.getSubscriptionInfo(requestedUserId, testMode);
|
|
82
|
+
const subscriptionResult = {
|
|
83
|
+
hasSubscription: subscriptionInfo.entitlements.length > 0 &&
|
|
84
|
+
!subscriptionInfo.entitlements.includes(NONE_ENTITLEMENT),
|
|
85
|
+
entitlements: subscriptionInfo.entitlements,
|
|
86
|
+
subscriptionStartedAt: subscriptionInfo.subscriptionStartedAt?.toISOString() ?? null,
|
|
87
|
+
platform: subscriptionInfo.platform,
|
|
88
|
+
productIdentifier: subscriptionInfo.productIdentifier,
|
|
89
|
+
expiresDate: subscriptionInfo.expiresDate?.toISOString() ?? null,
|
|
90
|
+
willRenew: subscriptionInfo.willRenew,
|
|
91
|
+
managementUrl: subscriptionInfo.managementUrl,
|
|
92
|
+
};
|
|
93
|
+
return c.json(successResponse(subscriptionResult));
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error("Error fetching subscription:", error);
|
|
97
|
+
return c.json(errorResponse("Failed to fetch subscription status"), 500);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return usersRouter;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=users.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Idempotent DDL for the shared tables
|
|
3
|
+
* @description Runs on every server boot. Every statement is IF NOT EXISTS or
|
|
4
|
+
* guarded, so re-running is safe. Identifiers cannot be bound as parameters,
|
|
5
|
+
* so schema and prefix are validated and interpolated into `client.unsafe`.
|
|
6
|
+
*/
|
|
7
|
+
import type { Sql } from "postgres";
|
|
8
|
+
export declare function initServiceTables(client: Sql, opts: {
|
|
9
|
+
schemaName: string;
|
|
10
|
+
indexPrefix: string;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/schema/init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAapC,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,GAAG,EACX,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAChD,OAAO,CAAC,IAAI,CAAC,CAgbf"}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Idempotent DDL for the shared tables
|
|
3
|
+
* @description Runs on every server boot. Every statement is IF NOT EXISTS or
|
|
4
|
+
* guarded, so re-running is safe. Identifiers cannot be bound as parameters,
|
|
5
|
+
* so schema and prefix are validated and interpolated into `client.unsafe`.
|
|
6
|
+
*/
|
|
7
|
+
import { initRateLimitTable } from "@sudobility/ratelimit_service";
|
|
8
|
+
import { runEntityMigration } from "@sudobility/entity_service";
|
|
9
|
+
const IDENTIFIER = /^[a-z_][a-z0-9_]*$/;
|
|
10
|
+
function identifier(value, what) {
|
|
11
|
+
if (!IDENTIFIER.test(value)) {
|
|
12
|
+
throw new Error(`Invalid ${what} "${value}": must match ${IDENTIFIER}`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
export async function initServiceTables(client, opts) {
|
|
17
|
+
const s = identifier(opts.schemaName, "schemaName");
|
|
18
|
+
const p = identifier(opts.indexPrefix, "indexPrefix");
|
|
19
|
+
// Create schema if it doesn't exist
|
|
20
|
+
await client.unsafe(`CREATE SCHEMA IF NOT EXISTS ${s}`);
|
|
21
|
+
// Create enums (if they don't exist)
|
|
22
|
+
await client.unsafe(`
|
|
23
|
+
DO $$ BEGIN
|
|
24
|
+
CREATE TYPE ${s}.llm_provider AS ENUM ('openai', 'anthropic', 'gemini', 'mistral', 'cohere', 'groq', 'xai', 'deepseek', 'perplexity', 'lm_studio');
|
|
25
|
+
EXCEPTION
|
|
26
|
+
WHEN duplicate_object THEN null;
|
|
27
|
+
END $$;
|
|
28
|
+
`);
|
|
29
|
+
// Add new enum values if they don't exist (migration for existing databases)
|
|
30
|
+
await client.unsafe(`
|
|
31
|
+
DO $$
|
|
32
|
+
BEGIN
|
|
33
|
+
-- Add mistral if not exists
|
|
34
|
+
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'mistral' AND enumtypid = '${s}.llm_provider'::regtype) THEN
|
|
35
|
+
ALTER TYPE ${s}.llm_provider ADD VALUE 'mistral';
|
|
36
|
+
END IF;
|
|
37
|
+
-- Add cohere if not exists
|
|
38
|
+
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'cohere' AND enumtypid = '${s}.llm_provider'::regtype) THEN
|
|
39
|
+
ALTER TYPE ${s}.llm_provider ADD VALUE 'cohere';
|
|
40
|
+
END IF;
|
|
41
|
+
-- Add groq if not exists
|
|
42
|
+
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'groq' AND enumtypid = '${s}.llm_provider'::regtype) THEN
|
|
43
|
+
ALTER TYPE ${s}.llm_provider ADD VALUE 'groq';
|
|
44
|
+
END IF;
|
|
45
|
+
-- Add xai if not exists
|
|
46
|
+
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'xai' AND enumtypid = '${s}.llm_provider'::regtype) THEN
|
|
47
|
+
ALTER TYPE ${s}.llm_provider ADD VALUE 'xai';
|
|
48
|
+
END IF;
|
|
49
|
+
-- Add deepseek if not exists
|
|
50
|
+
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'deepseek' AND enumtypid = '${s}.llm_provider'::regtype) THEN
|
|
51
|
+
ALTER TYPE ${s}.llm_provider ADD VALUE 'deepseek';
|
|
52
|
+
END IF;
|
|
53
|
+
-- Add perplexity if not exists
|
|
54
|
+
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'perplexity' AND enumtypid = '${s}.llm_provider'::regtype) THEN
|
|
55
|
+
ALTER TYPE ${s}.llm_provider ADD VALUE 'perplexity';
|
|
56
|
+
END IF;
|
|
57
|
+
-- Add lm_studio if not exists
|
|
58
|
+
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'lm_studio' AND enumtypid = '${s}.llm_provider'::regtype) THEN
|
|
59
|
+
ALTER TYPE ${s}.llm_provider ADD VALUE 'lm_studio';
|
|
60
|
+
END IF;
|
|
61
|
+
END $$;
|
|
62
|
+
`);
|
|
63
|
+
await client.unsafe(`
|
|
64
|
+
DO $$ BEGIN
|
|
65
|
+
CREATE TYPE ${s}.http_method AS ENUM ('GET', 'POST');
|
|
66
|
+
EXCEPTION
|
|
67
|
+
WHEN duplicate_object THEN null;
|
|
68
|
+
END $$;
|
|
69
|
+
`);
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Step 1: Create users and user_settings tables
|
|
72
|
+
// firebase_uid is now the primary key
|
|
73
|
+
// =============================================================================
|
|
74
|
+
await client.unsafe(`
|
|
75
|
+
CREATE TABLE IF NOT EXISTS ${s}.users (
|
|
76
|
+
firebase_uid VARCHAR(128) PRIMARY KEY,
|
|
77
|
+
email VARCHAR(255),
|
|
78
|
+
display_name VARCHAR(255),
|
|
79
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
80
|
+
updated_at TIMESTAMP DEFAULT NOW()
|
|
81
|
+
)
|
|
82
|
+
`);
|
|
83
|
+
await client.unsafe(`
|
|
84
|
+
CREATE TABLE IF NOT EXISTS ${s}.user_settings (
|
|
85
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
86
|
+
firebase_uid VARCHAR(128) NOT NULL UNIQUE REFERENCES ${s}.users(firebase_uid) ON DELETE CASCADE,
|
|
87
|
+
organization_name VARCHAR(255),
|
|
88
|
+
organization_path VARCHAR(255) NOT NULL UNIQUE,
|
|
89
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
90
|
+
updated_at TIMESTAMP DEFAULT NOW()
|
|
91
|
+
)
|
|
92
|
+
`);
|
|
93
|
+
// =============================================================================
|
|
94
|
+
// Step 2: Run entity migration (creates entities, entity_members tables)
|
|
95
|
+
// This must happen BEFORE tables that reference entities.id
|
|
96
|
+
// =============================================================================
|
|
97
|
+
await runEntityMigration({
|
|
98
|
+
client: client, // postgres type versions can differ under bun link
|
|
99
|
+
schemaName: s,
|
|
100
|
+
indexPrefix: p,
|
|
101
|
+
migrateProjects: false, // Tables are created fresh with entity_id
|
|
102
|
+
migrateUsers: false, // Personal entities created on-demand via EntityHelper
|
|
103
|
+
});
|
|
104
|
+
// =============================================================================
|
|
105
|
+
// Step 2b: Create user_api_keys table (references users.firebase_uid)
|
|
106
|
+
// Personal API keys used as an alternative to a Firebase ID token.
|
|
107
|
+
// =============================================================================
|
|
108
|
+
await client.unsafe(`
|
|
109
|
+
CREATE TABLE IF NOT EXISTS ${s}.user_api_keys (
|
|
110
|
+
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
111
|
+
firebase_uid VARCHAR(128) NOT NULL REFERENCES ${s}.users(firebase_uid) ON DELETE CASCADE,
|
|
112
|
+
key_name VARCHAR(255) NOT NULL,
|
|
113
|
+
key_hash VARCHAR(64) NOT NULL UNIQUE,
|
|
114
|
+
key_prefix VARCHAR(20) NOT NULL,
|
|
115
|
+
encrypted_key TEXT NOT NULL,
|
|
116
|
+
encryption_iv VARCHAR(32) NOT NULL,
|
|
117
|
+
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
118
|
+
last_used_at TIMESTAMP,
|
|
119
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
120
|
+
updated_at TIMESTAMP DEFAULT NOW()
|
|
121
|
+
)
|
|
122
|
+
`);
|
|
123
|
+
await client.unsafe(`
|
|
124
|
+
CREATE INDEX IF NOT EXISTS ${p}_user_api_keys_user_idx
|
|
125
|
+
ON ${s}.user_api_keys(firebase_uid)
|
|
126
|
+
`);
|
|
127
|
+
await client.unsafe(`
|
|
128
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ${p}_user_api_keys_hash_idx
|
|
129
|
+
ON ${s}.user_api_keys(key_hash)
|
|
130
|
+
`);
|
|
131
|
+
// =============================================================================
|
|
132
|
+
// Step 2c: Create entity_api_keys table (references entities.id)
|
|
133
|
+
// Entity-scoped keys used by CI, scripts, and MCP clients.
|
|
134
|
+
// =============================================================================
|
|
135
|
+
await client.unsafe(`
|
|
136
|
+
CREATE TABLE IF NOT EXISTS ${s}.entity_api_keys (
|
|
137
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
138
|
+
entity_id UUID NOT NULL REFERENCES ${s}.entities(id) ON DELETE CASCADE,
|
|
139
|
+
key_name VARCHAR(255) NOT NULL,
|
|
140
|
+
key_hash VARCHAR(64) NOT NULL UNIQUE,
|
|
141
|
+
key_prefix VARCHAR(20) NOT NULL,
|
|
142
|
+
created_by_user_id VARCHAR(128) NOT NULL,
|
|
143
|
+
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
144
|
+
last_used_at TIMESTAMPTZ,
|
|
145
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
146
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
147
|
+
)
|
|
148
|
+
`);
|
|
149
|
+
await client.unsafe(`
|
|
150
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ${p}_entity_api_keys_hash_idx
|
|
151
|
+
ON ${s}.entity_api_keys(key_hash)
|
|
152
|
+
`);
|
|
153
|
+
await client.unsafe(`
|
|
154
|
+
CREATE INDEX IF NOT EXISTS ${p}_entity_api_keys_entity_idx
|
|
155
|
+
ON ${s}.entity_api_keys(entity_id)
|
|
156
|
+
`);
|
|
157
|
+
await client.unsafe(`
|
|
158
|
+
CREATE INDEX IF NOT EXISTS ${p}_entity_api_keys_active_idx
|
|
159
|
+
ON ${s}.entity_api_keys(is_active)
|
|
160
|
+
`);
|
|
161
|
+
// =============================================================================
|
|
162
|
+
// Step 4: Create projects table (references entities.id)
|
|
163
|
+
// =============================================================================
|
|
164
|
+
await client.unsafe(`
|
|
165
|
+
CREATE TABLE IF NOT EXISTS ${s}.projects (
|
|
166
|
+
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
167
|
+
entity_id UUID NOT NULL REFERENCES ${s}.entities(id) ON DELETE CASCADE,
|
|
168
|
+
project_name VARCHAR(255) NOT NULL,
|
|
169
|
+
display_name VARCHAR(255) NOT NULL,
|
|
170
|
+
description TEXT,
|
|
171
|
+
is_active BOOLEAN DEFAULT true,
|
|
172
|
+
encrypted_api_key TEXT,
|
|
173
|
+
api_key_iv VARCHAR(32),
|
|
174
|
+
api_key_prefix VARCHAR(20),
|
|
175
|
+
api_key_created_at TIMESTAMP,
|
|
176
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
177
|
+
updated_at TIMESTAMP DEFAULT NOW()
|
|
178
|
+
)
|
|
179
|
+
`);
|
|
180
|
+
// Create unique index for project_name per entity
|
|
181
|
+
await client.unsafe(`
|
|
182
|
+
CREATE UNIQUE INDEX IF NOT EXISTS unique_project_per_entity
|
|
183
|
+
ON ${s}.projects(entity_id, project_name)
|
|
184
|
+
`);
|
|
185
|
+
// Create index for entity_id lookups
|
|
186
|
+
await client.unsafe(`
|
|
187
|
+
CREATE INDEX IF NOT EXISTS ${p}_projects_entity_idx
|
|
188
|
+
ON ${s}.projects(entity_id)
|
|
189
|
+
`);
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// Step 5: Create endpoints table (references projects.uuid)
|
|
192
|
+
// =============================================================================
|
|
193
|
+
await client.unsafe(`
|
|
194
|
+
CREATE TABLE IF NOT EXISTS ${s}.endpoints (
|
|
195
|
+
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
196
|
+
project_id UUID NOT NULL REFERENCES ${s}.projects(uuid) ON DELETE CASCADE,
|
|
197
|
+
endpoint_name VARCHAR(255) NOT NULL,
|
|
198
|
+
display_name VARCHAR(255) NOT NULL,
|
|
199
|
+
http_method ${s}.http_method NOT NULL DEFAULT 'POST',
|
|
200
|
+
llm_key_id UUID,
|
|
201
|
+
provider ${s}.llm_provider,
|
|
202
|
+
model VARCHAR(255),
|
|
203
|
+
input_schema JSONB,
|
|
204
|
+
output_schema JSONB,
|
|
205
|
+
instructions TEXT,
|
|
206
|
+
context TEXT,
|
|
207
|
+
is_active BOOLEAN DEFAULT true,
|
|
208
|
+
ip_allowlist JSONB,
|
|
209
|
+
call_count INTEGER NOT NULL DEFAULT 0,
|
|
210
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
211
|
+
updated_at TIMESTAMP DEFAULT NOW(),
|
|
212
|
+
UNIQUE(project_id, endpoint_name)
|
|
213
|
+
)
|
|
214
|
+
`);
|
|
215
|
+
// Add model column if it doesn't exist (migration for existing tables)
|
|
216
|
+
await client.unsafe(`
|
|
217
|
+
DO $$
|
|
218
|
+
BEGIN
|
|
219
|
+
IF NOT EXISTS (
|
|
220
|
+
SELECT 1 FROM information_schema.columns
|
|
221
|
+
WHERE table_schema = '${s}'
|
|
222
|
+
AND table_name = 'endpoints'
|
|
223
|
+
AND column_name = 'model'
|
|
224
|
+
) THEN
|
|
225
|
+
ALTER TABLE ${s}.endpoints ADD COLUMN model VARCHAR(255);
|
|
226
|
+
END IF;
|
|
227
|
+
END $$;
|
|
228
|
+
`);
|
|
229
|
+
// Add call_count column if it doesn't exist (migration for existing tables).
|
|
230
|
+
// Existing endpoints start at 0 rather than backfilling from usage_analytics:
|
|
231
|
+
// the counter is a forward-looking tally, and a backfill would silently
|
|
232
|
+
// disagree with any analytics rows that have since been pruned.
|
|
233
|
+
await client.unsafe(`
|
|
234
|
+
DO $$
|
|
235
|
+
BEGIN
|
|
236
|
+
IF NOT EXISTS (
|
|
237
|
+
SELECT 1 FROM information_schema.columns
|
|
238
|
+
WHERE table_schema = '${s}'
|
|
239
|
+
AND table_name = 'endpoints'
|
|
240
|
+
AND column_name = 'call_count'
|
|
241
|
+
) THEN
|
|
242
|
+
ALTER TABLE ${s}.endpoints
|
|
243
|
+
ADD COLUMN call_count INTEGER NOT NULL DEFAULT 0;
|
|
244
|
+
END IF;
|
|
245
|
+
END $$;
|
|
246
|
+
`);
|
|
247
|
+
// Create index for project_id lookups
|
|
248
|
+
await client.unsafe(`
|
|
249
|
+
CREATE INDEX IF NOT EXISTS ${p}_endpoints_project_idx
|
|
250
|
+
ON ${s}.endpoints(project_id)
|
|
251
|
+
`);
|
|
252
|
+
// =============================================================================
|
|
253
|
+
// Step 6: Create usage_analytics table (references endpoints.uuid)
|
|
254
|
+
// =============================================================================
|
|
255
|
+
await client.unsafe(`
|
|
256
|
+
CREATE TABLE IF NOT EXISTS ${s}.usage_analytics (
|
|
257
|
+
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
258
|
+
endpoint_id UUID NOT NULL REFERENCES ${s}.endpoints(uuid) ON DELETE CASCADE,
|
|
259
|
+
timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
260
|
+
success BOOLEAN NOT NULL,
|
|
261
|
+
error_message TEXT,
|
|
262
|
+
tokens_input INTEGER,
|
|
263
|
+
tokens_output INTEGER,
|
|
264
|
+
latency_ms INTEGER,
|
|
265
|
+
estimated_cost_cents INTEGER,
|
|
266
|
+
request_metadata JSONB
|
|
267
|
+
)
|
|
268
|
+
`);
|
|
269
|
+
// Create indexes for analytics queries
|
|
270
|
+
await client.unsafe(`
|
|
271
|
+
CREATE INDEX IF NOT EXISTS idx_usage_endpoint_timestamp
|
|
272
|
+
ON ${s}.usage_analytics(endpoint_id, timestamp DESC)
|
|
273
|
+
`);
|
|
274
|
+
// =============================================================================
|
|
275
|
+
// Step 7: Rate limit counters table
|
|
276
|
+
// =============================================================================
|
|
277
|
+
await initRateLimitTable(client, s, p);
|
|
278
|
+
// =============================================================================
|
|
279
|
+
// Step 8: Entity Storage Configs table (for generated media uploads)
|
|
280
|
+
// =============================================================================
|
|
281
|
+
await client.unsafe(`
|
|
282
|
+
CREATE TABLE IF NOT EXISTS ${s}.entity_storage_configs (
|
|
283
|
+
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
284
|
+
entity_id UUID NOT NULL UNIQUE REFERENCES ${s}.entities(id) ON DELETE CASCADE,
|
|
285
|
+
provider VARCHAR(20) NOT NULL,
|
|
286
|
+
bucket VARCHAR(255) NOT NULL,
|
|
287
|
+
path_prefix VARCHAR(500),
|
|
288
|
+
encrypted_credentials TEXT NOT NULL,
|
|
289
|
+
encryption_iv VARCHAR(32) NOT NULL,
|
|
290
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
291
|
+
updated_at TIMESTAMP DEFAULT NOW(),
|
|
292
|
+
created_by VARCHAR(128) NOT NULL
|
|
293
|
+
)
|
|
294
|
+
`);
|
|
295
|
+
// Create index for entity_id lookups
|
|
296
|
+
await client.unsafe(`
|
|
297
|
+
CREATE INDEX IF NOT EXISTS ${p}_entity_storage_configs_entity_idx
|
|
298
|
+
ON ${s}.entity_storage_configs(entity_id)
|
|
299
|
+
`);
|
|
300
|
+
// =============================================================================
|
|
301
|
+
// Step 9: Add multimodal columns to endpoints table (migration for existing DBs)
|
|
302
|
+
// =============================================================================
|
|
303
|
+
// Add expects_media_output column
|
|
304
|
+
await client.unsafe(`
|
|
305
|
+
DO $$
|
|
306
|
+
BEGIN
|
|
307
|
+
IF NOT EXISTS (
|
|
308
|
+
SELECT 1 FROM information_schema.columns
|
|
309
|
+
WHERE table_schema = '${s}'
|
|
310
|
+
AND table_name = 'endpoints'
|
|
311
|
+
AND column_name = 'expects_media_output'
|
|
312
|
+
) THEN
|
|
313
|
+
ALTER TABLE ${s}.endpoints ADD COLUMN expects_media_output JSONB;
|
|
314
|
+
END IF;
|
|
315
|
+
END $$;
|
|
316
|
+
`);
|
|
317
|
+
// Add output_media_format column
|
|
318
|
+
await client.unsafe(`
|
|
319
|
+
DO $$
|
|
320
|
+
BEGIN
|
|
321
|
+
IF NOT EXISTS (
|
|
322
|
+
SELECT 1 FROM information_schema.columns
|
|
323
|
+
WHERE table_schema = '${s}'
|
|
324
|
+
AND table_name = 'endpoints'
|
|
325
|
+
AND column_name = 'output_media_format'
|
|
326
|
+
) THEN
|
|
327
|
+
ALTER TABLE ${s}.endpoints ADD COLUMN output_media_format VARCHAR(20);
|
|
328
|
+
END IF;
|
|
329
|
+
END $$;
|
|
330
|
+
`);
|
|
331
|
+
// Add web_search column
|
|
332
|
+
await client.unsafe(`
|
|
333
|
+
DO $$
|
|
334
|
+
BEGIN
|
|
335
|
+
IF NOT EXISTS (
|
|
336
|
+
SELECT 1 FROM information_schema.columns
|
|
337
|
+
WHERE table_schema = '${s}'
|
|
338
|
+
AND table_name = 'endpoints'
|
|
339
|
+
AND column_name = 'web_search'
|
|
340
|
+
) THEN
|
|
341
|
+
ALTER TABLE ${s}.endpoints ADD COLUMN web_search BOOLEAN DEFAULT false;
|
|
342
|
+
END IF;
|
|
343
|
+
END $$;
|
|
344
|
+
`);
|
|
345
|
+
// Add temperature column
|
|
346
|
+
await client.unsafe(`
|
|
347
|
+
DO $$
|
|
348
|
+
BEGIN
|
|
349
|
+
IF NOT EXISTS (
|
|
350
|
+
SELECT 1 FROM information_schema.columns
|
|
351
|
+
WHERE table_schema = '${s}'
|
|
352
|
+
AND table_name = 'endpoints'
|
|
353
|
+
AND column_name = 'temperature'
|
|
354
|
+
) THEN
|
|
355
|
+
ALTER TABLE ${s}.endpoints ADD COLUMN temperature REAL;
|
|
356
|
+
END IF;
|
|
357
|
+
END $$;
|
|
358
|
+
`);
|
|
359
|
+
// Add transcription_extraction_model column
|
|
360
|
+
await client.unsafe(`
|
|
361
|
+
DO $$
|
|
362
|
+
BEGIN
|
|
363
|
+
IF NOT EXISTS (
|
|
364
|
+
SELECT 1 FROM information_schema.columns
|
|
365
|
+
WHERE table_schema = '${s}'
|
|
366
|
+
AND table_name = 'endpoints'
|
|
367
|
+
AND column_name = 'transcription_extraction_model'
|
|
368
|
+
) THEN
|
|
369
|
+
ALTER TABLE ${s}.endpoints ADD COLUMN transcription_extraction_model VARCHAR(255);
|
|
370
|
+
END IF;
|
|
371
|
+
END $$;
|
|
372
|
+
`);
|
|
373
|
+
// Add max_output_tokens column.
|
|
374
|
+
// Intentionally nullable with NO default and NO backfill: NULL means "no
|
|
375
|
+
// runaway protection", and existing endpoints must keep behaving exactly as
|
|
376
|
+
// they did. Adding a default here would silently cap live endpoints whose
|
|
377
|
+
// callers depend on longer answers.
|
|
378
|
+
await client.unsafe(`
|
|
379
|
+
DO $$
|
|
380
|
+
BEGIN
|
|
381
|
+
IF NOT EXISTS (
|
|
382
|
+
SELECT 1 FROM information_schema.columns
|
|
383
|
+
WHERE table_schema = '${s}'
|
|
384
|
+
AND table_name = 'endpoints'
|
|
385
|
+
AND column_name = 'max_output_tokens'
|
|
386
|
+
) THEN
|
|
387
|
+
ALTER TABLE ${s}.endpoints ADD COLUMN max_output_tokens INTEGER;
|
|
388
|
+
END IF;
|
|
389
|
+
END $$;
|
|
390
|
+
`);
|
|
391
|
+
// =============================================================================
|
|
392
|
+
// Step 10: Provider binding (service extraction)
|
|
393
|
+
// provider: which LLM the endpoint calls, written by bindEndpoint.
|
|
394
|
+
// llm_key_id: relaxed to nullable; products without per-entity keys leave it
|
|
395
|
+
// null. Apps that keep a key table add their own FK and backfill provider.
|
|
396
|
+
// =============================================================================
|
|
397
|
+
await client.unsafe(`
|
|
398
|
+
ALTER TABLE ${s}.endpoints ADD COLUMN IF NOT EXISTS provider ${s}.llm_provider
|
|
399
|
+
`);
|
|
400
|
+
await client.unsafe(`
|
|
401
|
+
ALTER TABLE ${s}.endpoints ALTER COLUMN llm_key_id DROP NOT NULL
|
|
402
|
+
`);
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/schema/init.ts"],"names":[],"mappings":"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"}
|