WIP Serverless with infra & drizzle start files.
This commit is contained in:
31
serverless/src/db/schema/index.ts
Normal file
31
serverless/src/db/schema/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { AnyPgColumn, boolean, pgTable, text, timestamp, uuid, index } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const shops = pgTable('shops', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
esApiKey: text('es_api_key').notNull().unique(),
|
||||
active: boolean('active').notNull().default(true),
|
||||
});
|
||||
|
||||
export const jobs = pgTable(
|
||||
'jobs',
|
||||
{
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
shopId: uuid('shopId')
|
||||
.references((): AnyPgColumn => shops.id)
|
||||
.notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
clm_no: text('clm_no'),
|
||||
ciecaid: text('ciecaid'),
|
||||
},
|
||||
(table) => [index('clm_no_idx').on(table.clm_no)]
|
||||
);
|
||||
|
||||
export const joblines = pgTable('joblines', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
jobId: uuid('jobId')
|
||||
.references((): AnyPgColumn => jobs.id)
|
||||
.notNull(),
|
||||
line_desc: text('line_desc'),
|
||||
});
|
||||
22
serverless/src/handlers/dbMigrate.ts
Normal file
22
serverless/src/handlers/dbMigrate.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { APIGatewayProxyResult } from 'aws-lambda';
|
||||
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
|
||||
import { getDb } from '../lib/db';
|
||||
|
||||
export const handler = async (): Promise<APIGatewayProxyResult> => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
await migrate(db, { migrationsFolder: 'drizzle' });
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ success: true, message: 'Migrations applied.' }),
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ success: false, error: errorMessage }),
|
||||
};
|
||||
}
|
||||
};
|
||||
23
serverless/src/handlers/dbPing.ts
Normal file
23
serverless/src/handlers/dbPing.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { APIGatewayProxyResult } from 'aws-lambda';
|
||||
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
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`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ success: true, result }),
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ success: false, error: errorMessage }),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,2 +1,75 @@
|
||||
// Placeholder for database utilities
|
||||
// Export database connection and helper functions here
|
||||
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
|
||||
import { drizzle, NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
import * as schema from '../db/schema';
|
||||
|
||||
type DbSecret = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
let cachedSecret: DbSecret | undefined;
|
||||
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;
|
||||
}
|
||||
|
||||
async function getDbSecret(): Promise<DbSecret> {
|
||||
if (cachedSecret) return cachedSecret;
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export async function getPool(): Promise<Pool> {
|
||||
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();
|
||||
|
||||
cachedPool = new Pool({
|
||||
host,
|
||||
port,
|
||||
database,
|
||||
user,
|
||||
password,
|
||||
max: 5,
|
||||
idleTimeoutMillis: 30_000,
|
||||
connectionTimeoutMillis: 10_000,
|
||||
});
|
||||
|
||||
return cachedPool;
|
||||
}
|
||||
|
||||
export async function getDb(): Promise<NodePgDatabase<typeof schema>> {
|
||||
if (cachedDb) return cachedDb;
|
||||
const pool = await getPool();
|
||||
cachedDb = drizzle(pool, { schema });
|
||||
return cachedDb;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user