Separate infrastructure to separate yaml and base schema.
This commit is contained in:
@@ -7,17 +7,88 @@ import { getDb } from '../lib/db';
|
||||
export const handler = async (): Promise<APIGatewayProxyResult> => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const result = await db.execute(sql`select 1 as ok`);
|
||||
const ping = await db.execute(sql`select 1 as ok`);
|
||||
|
||||
const schemaRows = await db.execute(sql`
|
||||
select
|
||||
table_schema,
|
||||
table_name,
|
||||
column_name,
|
||||
data_type,
|
||||
is_nullable,
|
||||
ordinal_position
|
||||
from information_schema.columns
|
||||
where table_schema not in ('pg_catalog', 'information_schema')
|
||||
order by table_schema, table_name, ordinal_position
|
||||
`);
|
||||
|
||||
type ColumnInfo = {
|
||||
name: string;
|
||||
type: string;
|
||||
nullable: boolean;
|
||||
position: number;
|
||||
};
|
||||
|
||||
type TableInfo = {
|
||||
schema: string;
|
||||
name: string;
|
||||
columns: ColumnInfo[];
|
||||
};
|
||||
|
||||
const tableMap = new Map<string, TableInfo>();
|
||||
const rows = (schemaRows as unknown as { rows?: unknown[] })?.rows ?? [];
|
||||
for (const row of rows as Array<Record<string, unknown>>) {
|
||||
const tableSchema = String(row.table_schema ?? '');
|
||||
const tableName = String(row.table_name ?? '');
|
||||
if (!tableSchema || !tableName) continue;
|
||||
|
||||
const key = `${tableSchema}.${tableName}`;
|
||||
const existing = tableMap.get(key);
|
||||
const tableInfo: TableInfo = existing ?? {
|
||||
schema: tableSchema,
|
||||
name: tableName,
|
||||
columns: [],
|
||||
};
|
||||
|
||||
tableInfo.columns.push({
|
||||
name: String(row.column_name ?? ''),
|
||||
type: String(row.data_type ?? ''),
|
||||
nullable: String(row.is_nullable ?? '') === 'YES',
|
||||
position: Number(row.ordinal_position ?? 0),
|
||||
});
|
||||
|
||||
if (!existing) tableMap.set(key, tableInfo);
|
||||
}
|
||||
|
||||
const tables = [...tableMap.values()];
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ success: true, result }),
|
||||
body: JSON.stringify({ success: true, ping, schema: { tables } }),
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
const maybeAny = error as unknown as { cause?: unknown; code?: unknown };
|
||||
const causeMessage =
|
||||
maybeAny?.cause instanceof Error
|
||||
? maybeAny.cause.message
|
||||
: typeof maybeAny?.cause === 'string'
|
||||
? maybeAny.cause
|
||||
: undefined;
|
||||
const causeCode =
|
||||
typeof (maybeAny?.cause as { code?: unknown } | undefined)?.code === 'string'
|
||||
? (maybeAny.cause as { code?: string }).code
|
||||
: typeof maybeAny?.code === 'string'
|
||||
? maybeAny.code
|
||||
: undefined;
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ success: false, error: errorMessage }),
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
cause: causeMessage,
|
||||
code: causeCode,
|
||||
}),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Pool } from 'pg';
|
||||
import * as schema from '../db/schema';
|
||||
|
||||
type DbSecret = {
|
||||
username: string;
|
||||
password: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
let cachedSecret: DbSecret | undefined;
|
||||
@@ -14,62 +14,65 @@ let cachedPool: Pool | undefined;
|
||||
let cachedDb: NodePgDatabase<typeof schema> | undefined;
|
||||
|
||||
function requireEnv(name: string): string {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`Missing required env var: ${name}`);
|
||||
}
|
||||
return value;
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`Missing required env var: ${name}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async function getDbSecret(): Promise<DbSecret> {
|
||||
if (cachedSecret) return cachedSecret;
|
||||
if (cachedSecret) return cachedSecret;
|
||||
|
||||
const secretArn = requireEnv('DB_SECRET_ARN');
|
||||
const client = new SecretsManagerClient({});
|
||||
const result = await client.send(
|
||||
new GetSecretValueCommand({
|
||||
SecretId: secretArn,
|
||||
}),
|
||||
);
|
||||
const secretArn = requireEnv('DB_SECRET_ARN');
|
||||
const client = new SecretsManagerClient({});
|
||||
const result = await client.send(
|
||||
new GetSecretValueCommand({
|
||||
SecretId: secretArn,
|
||||
})
|
||||
);
|
||||
|
||||
if (!result.SecretString) {
|
||||
throw new Error('SecretString was empty for DB_SECRET_ARN');
|
||||
}
|
||||
if (!result.SecretString) {
|
||||
throw new Error('SecretString was empty for DB_SECRET_ARN');
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(result.SecretString) as Partial<DbSecret>;
|
||||
if (!parsed.username || !parsed.password) {
|
||||
throw new Error('DB secret missing username/password');
|
||||
}
|
||||
const parsed = JSON.parse(result.SecretString) as Partial<DbSecret>;
|
||||
if (!parsed.username || !parsed.password) {
|
||||
throw new Error('DB secret missing username/password');
|
||||
}
|
||||
|
||||
cachedSecret = { username: parsed.username, password: parsed.password };
|
||||
return cachedSecret;
|
||||
cachedSecret = { username: parsed.username, password: parsed.password };
|
||||
return cachedSecret;
|
||||
}
|
||||
|
||||
export async function getPool(): Promise<Pool> {
|
||||
if (cachedPool) return cachedPool;
|
||||
if (cachedPool) return cachedPool;
|
||||
|
||||
const host = requireEnv('DB_HOST');
|
||||
const port = Number.parseInt(requireEnv('DB_PORT'), 10);
|
||||
const database = requireEnv('DB_NAME');
|
||||
const { username: user, password } = await getDbSecret();
|
||||
const host = requireEnv('DB_HOST');
|
||||
const port = Number.parseInt(requireEnv('DB_PORT'), 10);
|
||||
const database = requireEnv('DB_NAME');
|
||||
const { username: user, password } = await getDbSecret();
|
||||
|
||||
cachedPool = new Pool({
|
||||
host,
|
||||
port,
|
||||
database,
|
||||
user,
|
||||
password,
|
||||
max: 5,
|
||||
idleTimeoutMillis: 30_000,
|
||||
connectionTimeoutMillis: 10_000,
|
||||
});
|
||||
cachedPool = new Pool({
|
||||
host,
|
||||
port,
|
||||
database,
|
||||
user,
|
||||
password,
|
||||
ssl: {
|
||||
rejectUnauthorized: true,
|
||||
},
|
||||
max: 5,
|
||||
idleTimeoutMillis: 30_000,
|
||||
connectionTimeoutMillis: 10_000,
|
||||
});
|
||||
|
||||
return cachedPool;
|
||||
return cachedPool;
|
||||
}
|
||||
|
||||
export async function getDb(): Promise<NodePgDatabase<typeof schema>> {
|
||||
if (cachedDb) return cachedDb;
|
||||
const pool = await getPool();
|
||||
cachedDb = drizzle(pool, { schema });
|
||||
return cachedDb;
|
||||
if (cachedDb) return cachedDb;
|
||||
const pool = await getPool();
|
||||
cachedDb = drizzle(pool, { schema });
|
||||
return cachedDb;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user