Skip to content

Commit da0cc09

Browse files
committed
Update database management
1 parent 4ddb102 commit da0cc09

11 files changed

Lines changed: 190 additions & 131 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openworkers-api",
3-
"version": "1.0.4",
3+
"version": "1.0.5",
44
"license": "MIT",
55
"module": "src/index.ts",
66
"type": "module",

scripts/generate-types.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,7 @@ import {
3636
WorkerLanguageSchema
3737
} from '../src/types/schemas/worker.schema';
3838

39-
import {
40-
DatabaseSchema,
41-
DatabaseCreateInputSchema,
42-
DatabaseRulesSchema,
43-
SqlOperationSchema
44-
} from '../src/types/schemas/database.schema';
39+
import { DatabaseSchema, DatabaseCreateInputSchema } from '../src/types/schemas/database.schema';
4540

4641
// Schema definitions to generate
4742
const schemas = [
@@ -83,9 +78,7 @@ const schemas = [
8378

8479
// Database
8580
{ schema: DatabaseSchema, name: 'Database' },
86-
{ schema: DatabaseCreateInputSchema, name: 'DatabaseCreateInput' },
87-
{ schema: DatabaseRulesSchema, name: 'DatabaseRules' },
88-
{ schema: SqlOperationSchema, name: 'SqlOperation' }
81+
{ schema: DatabaseCreateInputSchema, name: 'DatabaseCreateInput' }
8982
];
9083

9184
async function generateTypes() {

src/config/index.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@ const ConfigSchema = z.object({
3535
// Postgate (SQL proxy)
3636
postgate: z.object({
3737
url: z.string().url().default('http://localhost:6080'),
38-
// Admin database (tenant management functions) - mode schema on public
39-
adminDatabaseId: uuidLike.default('00000000-0000-0000-0000-000000000000'),
40-
// OpenWorkers database (API data) - mode dedicated
41-
openworkersDatabaseId: uuidLike,
42-
jwtSecret: z.string().min(32, 'POSTGATE_JWT_SECRET must be at least 32 characters')
38+
// Admin token (pg_xxx format) - for managing databases and creating tokens
39+
adminToken: z.string().regex(/^pg_[a-f0-9]{64}$/, 'POSTGATE_ADMIN_TOKEN must be a valid pg_xxx token'),
40+
// OpenWorkers token (pg_xxx format) - for openworkers API database access
41+
openworkersToken: z.string().regex(/^pg_[a-f0-9]{64}$/, 'POSTGATE_OPENWORKERS_TOKEN must be a valid pg_xxx token')
4342
})
4443
});
4544

@@ -68,9 +67,8 @@ function loadConfig(): Config {
6867
},
6968
postgate: {
7069
url: process.env.POSTGATE_URL,
71-
adminDatabaseId: process.env.POSTGATE_ADMIN_DATABASE_ID,
72-
openworkersDatabaseId: process.env.POSTGATE_OPENWORKERS_DATABASE_ID,
73-
jwtSecret: process.env.POSTGATE_JWT_SECRET
70+
adminToken: process.env.POSTGATE_ADMIN_TOKEN,
71+
openworkersToken: process.env.POSTGATE_OPENWORKERS_TOKEN
7472
}
7573
};
7674

@@ -102,8 +100,6 @@ function loadConfig(): Config {
102100
}
103101
}
104102

105-
console.log('Loading configuration...', process.env.POSTGATE_OPENWORKERS_DATABASE_ID);
106-
107103
// Export singleton config instance
108104
export const config = loadConfig();
109105

src/routes/databases.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,27 @@ databases.delete('/:id', async (c) => {
7979
}
8080
});
8181

82+
// POST /databases/:id/token - Regenerate token for database
83+
databases.post('/:id/token', async (c) => {
84+
const userId = c.get('userId');
85+
const id = c.req.param('id');
86+
87+
try {
88+
const result = await databasesService.regenerateToken(userId, id);
89+
90+
if (!result) {
91+
return c.json({ error: 'Database not found' }, 404);
92+
}
93+
94+
return c.json({
95+
token: result.token,
96+
tokenId: result.tokenId,
97+
message: 'Store this token securely. It will not be shown again.'
98+
});
99+
} catch (error) {
100+
console.error('Failed to regenerate token:', error);
101+
return c.json({ error: 'Failed to regenerate token' }, 500);
102+
}
103+
});
104+
82105
export default databases;

src/services/databases.ts

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createAdminSqlClient } from './db/client';
2+
import { postgateAdminClient, TENANT_PERMISSIONS } from './postgate';
23
import * as db from './db/databases';
34
import type { IDatabase, IDatabaseCreateInput } from '../types';
45

@@ -7,35 +8,23 @@ const adminSql = createAdminSqlClient();
78

89
interface PostgateDatabaseRow {
910
id: string;
10-
name: string;
11-
schema_name: string | null;
1211
}
1312

1413
export class DatabasesService {
1514
/**
1615
* Create a new tenant database
1716
* 1. Create entry in postgate_databases (via admin sql)
1817
* 2. Create entry in openworkers databases table
18+
* Token is created later via POST /databases/:id/token
1919
*/
2020
async create(userId: string, input: IDatabaseCreateInput): Promise<IDatabase> {
21-
const { name, allowed_operations, max_rows, timeout_seconds } = input;
21+
const { name, desc, max_rows } = input;
2222

23-
// Generate schema name
24-
const schemaName = `tenant_${userId.substring(0, 8)}_${name}`;
25-
26-
// Build rules JSON
27-
const rules = {
28-
allowed_operations: allowed_operations || ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
29-
max_rows: max_rows || 1000,
30-
timeout_seconds: timeout_seconds || 30
31-
};
32-
33-
// Create in postgate
23+
// Create tenant database using PL/pgSQL function (creates schema + entry)
24+
// Pass random name to postgate - user's display name stays in openworkers only
3425
const postgateResult = await adminSql<PostgateDatabaseRow>(
35-
`INSERT INTO postgate_databases (name, backend_type, schema_name, rules)
36-
VALUES ($1, 'schema', $2, $3::jsonb)
37-
RETURNING id, name, schema_name`,
38-
[name, schemaName, JSON.stringify(rules)]
26+
`SELECT id FROM create_tenant_database($1, $2::integer)`,
27+
[crypto.randomUUID(), max_rows || 1000]
3928
);
4029

4130
if (postgateResult.length === 0) {
@@ -44,14 +33,13 @@ export class DatabasesService {
4433

4534
const postgateDb = postgateResult[0]!;
4635

47-
// Create in openworkers
48-
const owDb = await db.createDatabase(userId, name, postgateDb.id, postgateDb.schema_name);
36+
// Create in openworkers (no token yet)
37+
const owDb = await db.createDatabase(userId, name, postgateDb.id, desc);
4938

5039
return {
5140
id: owDb.id,
5241
name: owDb.name,
53-
desc: null,
54-
schemaName: owDb.schemaName ?? undefined,
42+
desc: owDb.desc,
5543
createdAt: owDb.createdAt,
5644
updatedAt: owDb.updatedAt
5745
};
@@ -66,8 +54,7 @@ export class DatabasesService {
6654
return rows.map((row) => ({
6755
id: row.id,
6856
name: row.name,
69-
desc: null,
70-
schemaName: row.schemaName ?? undefined,
57+
desc: row.desc,
7158
createdAt: row.createdAt,
7259
updatedAt: row.updatedAt
7360
}));
@@ -86,8 +73,7 @@ export class DatabasesService {
8673
return {
8774
id: row.id,
8875
name: row.name,
89-
desc: null,
90-
schemaName: row.schemaName ?? undefined,
76+
desc: row.desc,
9177
createdAt: row.createdAt,
9278
updatedAt: row.updatedAt
9379
};
@@ -106,17 +92,49 @@ export class DatabasesService {
10692
return false;
10793
}
10894

109-
// Delete from postgate
110-
await adminSql(
111-
`DELETE FROM postgate_databases WHERE id = $1::uuid`,
112-
[owDb.postgateId]
113-
);
95+
// Delete from postgate using PL/pgSQL function (drops schema + deletes entry)
96+
await adminSql(`SELECT delete_tenant_database($1::uuid)`, [owDb.postgateId]);
11497

11598
// Delete from openworkers
11699
const deleted = await db.deleteDatabase(userId, id);
117100

118101
return deleted > 0;
119102
}
103+
104+
/**
105+
* Regenerate token for a database
106+
* 1. Delete existing token (if any)
107+
* 2. Create new token via postgate API
108+
* 3. Update token_id in openworkers
109+
* Returns the new token (shown only once)
110+
*/
111+
async regenerateToken(userId: string, id: string): Promise<{ token: string; tokenId: string } | null> {
112+
// Get openworkers entry
113+
const owDb = await db.findDatabaseById(userId, id);
114+
if (!owDb) {
115+
return null;
116+
}
117+
118+
// Delete existing token if there is one
119+
if (owDb.tokenId) {
120+
try {
121+
await postgateAdminClient.deleteToken(owDb.tokenId);
122+
} catch {
123+
// Token might already be deleted, continue
124+
}
125+
}
126+
127+
// Create new token with tenant permissions (DML + DDL)
128+
const tokenResponse = await postgateAdminClient.createToken(owDb.postgateId, 'default', TENANT_PERMISSIONS);
129+
130+
// Update token_id in openworkers
131+
await db.updateTokenId(userId, id, tokenResponse.id);
132+
133+
return {
134+
token: tokenResponse.token,
135+
tokenId: tokenResponse.id
136+
};
137+
}
120138
}
121139

122140
export const databasesService = new DatabasesService();

src/services/db/databases.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { sql } from './client';
33
interface DatabaseRow {
44
id: string;
55
name: string;
6+
desc: string | null;
67
userId: string;
78
postgateId: string;
8-
schemaName: string | null;
9+
tokenId: string | null;
910
createdAt: Date;
1011
updatedAt: Date;
1112
}
@@ -15,9 +16,10 @@ export async function findAllDatabases(userId: string): Promise<DatabaseRow[]> {
1516
`SELECT
1617
id,
1718
name,
19+
"desc",
1820
user_id as "userId",
1921
postgate_id as "postgateId",
20-
schema_name as "schemaName",
22+
token_id as "tokenId",
2123
created_at as "createdAt",
2224
updated_at as "updatedAt"
2325
FROM databases
@@ -32,9 +34,10 @@ export async function findDatabaseById(userId: string, id: string): Promise<Data
3234
`SELECT
3335
id,
3436
name,
37+
"desc",
3538
user_id as "userId",
3639
postgate_id as "postgateId",
37-
schema_name as "schemaName",
40+
token_id as "tokenId",
3841
created_at as "createdAt",
3942
updated_at as "updatedAt"
4043
FROM databases
@@ -48,20 +51,21 @@ export async function createDatabase(
4851
userId: string,
4952
name: string,
5053
postgateId: string,
51-
schemaName: string | null
54+
desc?: string | null
5255
): Promise<DatabaseRow> {
5356
const rows = await sql<DatabaseRow>(
54-
`INSERT INTO databases (name, user_id, postgate_id, schema_name)
55-
VALUES ($1, $2::uuid, $3::uuid, $4)
57+
`INSERT INTO databases (name, "desc", user_id, postgate_id)
58+
VALUES ($1, $2, $3::uuid, $4::uuid)
5659
RETURNING
5760
id,
5861
name,
62+
"desc",
5963
user_id as "userId",
6064
postgate_id as "postgateId",
61-
schema_name as "schemaName",
65+
token_id as "tokenId",
6266
created_at as "createdAt",
6367
updated_at as "updatedAt"`,
64-
[name, userId, postgateId, schemaName]
68+
[name, desc ?? null, userId, postgateId]
6569
);
6670
return rows[0]!;
6771
}
@@ -75,3 +79,14 @@ export async function deleteDatabase(userId: string, id: string): Promise<number
7579
);
7680
return result.length;
7781
}
82+
83+
export async function updateTokenId(userId: string, id: string, tokenId: string): Promise<boolean> {
84+
const result = await sql<{ id: string }>(
85+
`UPDATE databases
86+
SET token_id = $3::uuid, updated_at = NOW()
87+
WHERE id = $1::uuid AND user_id = $2::uuid
88+
RETURNING id`,
89+
[id, userId, tokenId]
90+
);
91+
return result.length > 0;
92+
}

src/services/db/sql-client.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ function isNamedParams(params: unknown[] | NamedParams): params is NamedParams {
4949
}
5050

5151
/**
52-
* Create a Postgate-backed SQL client
52+
* Create a Postgate-backed SQL client from a token
5353
*/
54-
function createPostgateClient(baseUrl: string, jwtSecret: string, databaseId: string): PostgateSqlClient {
55-
const client = new PostgateClient(baseUrl, jwtSecret);
54+
function createPostgateClientFromToken(baseUrl: string, token: string): PostgateSqlClient {
55+
const client = new PostgateClient(baseUrl, token);
5656

5757
return async function sql<T = Record<string, unknown>>(
5858
query: string,
@@ -71,7 +71,7 @@ function createPostgateClient(baseUrl: string, jwtSecret: string, databaseId: st
7171
}
7272
}
7373

74-
const result = await client.query<T>(databaseId, finalQuery, finalParams);
74+
const result = await client.query<T>(finalQuery, finalParams);
7575

7676
// Return array-like result with count property
7777
const rows = result.rows as SqlResult<T>;
@@ -82,18 +82,19 @@ function createPostgateClient(baseUrl: string, jwtSecret: string, databaseId: st
8282

8383
/**
8484
* Create a SQL client for a specific database via postgate
85-
* @param databaseId - The database UUID to connect to
85+
* @param token - The pg_xxx token for authentication
8686
*/
87-
export function createSqlClient(databaseId: string): PostgateSqlClient {
88-
return createPostgateClient(postgateConfig.url, postgateConfig.jwtSecret, databaseId);
87+
export function createSqlClient(token: string): PostgateSqlClient {
88+
return createPostgateClientFromToken(postgateConfig.url, token);
8989
}
9090

9191
/**
9292
* Create a SQL client for the admin database (tenant management)
93+
* Uses the admin token from config
9394
*/
9495
export function createAdminSqlClient(): PostgateSqlClient {
95-
return createPostgateClient(postgateConfig.url, postgateConfig.jwtSecret, postgateConfig.adminDatabaseId);
96+
return createPostgateClientFromToken(postgateConfig.url, postgateConfig.adminToken);
9697
}
9798

98-
// Default export: openworkers database client (dedicated mode)
99-
export const sql = createPostgateClient(postgateConfig.url, postgateConfig.jwtSecret, postgateConfig.openworkersDatabaseId);
99+
// Default export: openworkers database client (uses openworkers token)
100+
export const sql = createPostgateClientFromToken(postgateConfig.url, postgateConfig.openworkersToken);

0 commit comments

Comments
 (0)