feat(db): add rent_payment_destinations column and multi-schema support

- Add migration 0020 for rent_payment_destinations jsonb column
- Add DB_SCHEMA env var support for multi-schema deployments
- Create custom migrate.ts script with proper search_path handling
- Update drizzle.config.ts and client.ts to use DB_SCHEMA
- Add db_schema variable to Terraform with dev=test/prod=public defaults
- Update CD workflow to set DB_SCHEMA based on branch
This commit is contained in:
2026-03-15 20:25:31 +04:00
parent f4fe4470f7
commit 0747973c8f
12 changed files with 3522 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ PORT=3000
# Database # Database
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:54322/postgres DATABASE_URL=postgres://postgres:postgres@127.0.0.1:54322/postgres
DB_SCHEMA=public
# Telegram # Telegram
TELEGRAM_BOT_TOKEN=your-telegram-bot-token TELEGRAM_BOT_TOKEN=your-telegram-bot-token

View File

@@ -100,6 +100,7 @@ jobs:
- name: Run database migrations - name: Run database migrations
env: env:
DATABASE_URL: ${{ secrets.DATABASE_URL }} DATABASE_URL: ${{ secrets.DATABASE_URL }}
DB_SCHEMA: ${{ github.ref == 'refs/heads/main' && 'public' || 'test' }}
run: bun run db:migrate run: bun run db:migrate
- name: Setup gcloud - name: Setup gcloud

View File

@@ -96,7 +96,8 @@ module "bot_api_service" {
env = merge( env = merge(
{ {
NODE_ENV = var.environment NODE_ENV = var.environment
DB_SCHEMA = var.db_schema
}, },
var.bot_purchase_parser_model == null ? {} : { var.bot_purchase_parser_model == null ? {} : {
PURCHASE_PARSER_MODEL = var.bot_purchase_parser_model PURCHASE_PARSER_MODEL = var.bot_purchase_parser_model

View File

@@ -259,3 +259,9 @@ variable "github_deploy_service_account_id" {
type = string type = string
default = "github-deployer" default = "github-deployer"
} }
variable "db_schema" {
description = "Database schema name for the application"
type = string
default = "public"
}

View File

@@ -18,12 +18,12 @@
"format:check": "bunx oxfmt --check .", "format:check": "bunx oxfmt --check .",
"db:generate": "bunx drizzle-kit generate --config packages/db/drizzle.config.ts", "db:generate": "bunx drizzle-kit generate --config packages/db/drizzle.config.ts",
"db:check": "bunx drizzle-kit check --config packages/db/drizzle.config.ts", "db:check": "bunx drizzle-kit check --config packages/db/drizzle.config.ts",
"db:migrate": "bunx drizzle-kit migrate --config packages/db/drizzle.config.ts", "db:migrate": "bun run packages/db/src/migrate.ts",
"db:migrations:check": "bun run scripts/check-migration-hygiene.ts", "db:migrations:check": "bun run scripts/check-migration-hygiene.ts",
"db:migrations:manifest": "bun run scripts/update-migration-checksums.ts", "db:migrations:manifest": "bun run scripts/update-migration-checksums.ts",
"db:push": "bunx drizzle-kit push --config packages/db/drizzle.config.ts", "db:push": "bunx drizzle-kit push --config packages/db/drizzle.config.ts",
"db:studio": "bunx drizzle-kit studio --config packages/db/drizzle.config.ts", "db:studio": "bunx drizzle-kit studio --config packages/db/drizzle.config.ts",
"db:seed": "set -a; [ -f .env ] && . ./.env; set +a; bun run --filter @household/db seed", "db:seed": "set -a; [ -f .env ] && . ./.env; set +a; DB_SCHEMA=${DB_SCHEMA:-public} bun run --filter @household/db seed",
"review:coderabbit": "coderabbit --prompt-only --base main || ~/.local/bin/coderabbit --prompt-only --base main", "review:coderabbit": "coderabbit --prompt-only --base main || ~/.local/bin/coderabbit --prompt-only --base main",
"infra:fmt": "terraform -chdir=infra/terraform fmt -recursive", "infra:fmt": "terraform -chdir=infra/terraform fmt -recursive",
"infra:fmt:check": "terraform -chdir=infra/terraform fmt -check -recursive", "infra:fmt:check": "terraform -chdir=infra/terraform fmt -check -recursive",

View File

@@ -1,14 +1,14 @@
import { defineConfig } from 'drizzle-kit' import { defineConfig } from 'drizzle-kit'
const dbCredentials = process.env.DATABASE_URL
? {
url: process.env.DATABASE_URL
}
: undefined
export default defineConfig({ export default defineConfig({
dialect: 'postgresql', dialect: 'postgresql',
schema: './packages/db/src/schema.ts', schema: './packages/db/src/schema.ts',
out: './packages/db/drizzle', out: './packages/db/drizzle',
dbCredentials dbCredentials: {
url: process.env.DATABASE_URL!
},
migrations: {
schema: process.env.DB_SCHEMA || 'public',
table: '__drizzle_migrations'
}
}) })

View File

@@ -0,0 +1 @@
ALTER TABLE "household_billing_settings" ADD COLUMN "rent_payment_destinations" jsonb;

File diff suppressed because it is too large Load Diff

View File

@@ -141,6 +141,13 @@
"when": 1773327708167, "when": 1773327708167,
"tag": "0019_faithful_madame_masque", "tag": "0019_faithful_madame_masque",
"breakpoints": true "breakpoints": true
},
{
"idx": 20,
"version": "7",
"when": 1773590603863,
"tag": "0020_natural_mauler",
"breakpoints": true
} }
] ]
} }

View File

@@ -7,9 +7,19 @@ export interface DbClientOptions {
} }
export function createDbClient(databaseUrl: string, options: DbClientOptions = {}) { export function createDbClient(databaseUrl: string, options: DbClientOptions = {}) {
const dbSchema = process.env.DB_SCHEMA || 'public'
const queryClient = postgres(databaseUrl, { const queryClient = postgres(databaseUrl, {
max: options.max ?? 5, max: options.max ?? 5,
prepare: options.prepare ?? false prepare: options.prepare ?? false,
onnotice: () => {},
connection: {
search_path: dbSchema
},
transform: {
...postgres.camel,
undefined: null
}
}) })
const db = drizzle(queryClient) const db = drizzle(queryClient)

View File

@@ -0,0 +1,35 @@
import postgres from 'postgres'
import { drizzle } from 'drizzle-orm/postgres-js'
import { migrate } from 'drizzle-orm/postgres-js/migrator'
import path from 'path'
const databaseUrl = process.env.DATABASE_URL
if (!databaseUrl) {
throw new Error('DATABASE_URL is not set')
}
const dbSchema = process.env.DB_SCHEMA || 'public'
console.log(`Running migrations for schema: ${dbSchema}...`)
const migrationClient = postgres(databaseUrl, {
max: 1,
onnotice: () => {}
})
// Explicitly set search_path to the target schema
// This ensures that 'CREATE TABLE "x"' goes into the right schema
await migrationClient.unsafe(`SET search_path TO ${dbSchema}`)
const db = drizzle(migrationClient)
// This runs migrations from the 'drizzle' folder
await migrate(db, {
migrationsFolder: path.resolve(__dirname, '../drizzle'),
migrationsSchema: dbSchema,
migrationsTable: '__drizzle_migrations'
})
console.log('Migrations applied successfully!')
await migrationClient.end()
process.exit(0)

View File

@@ -3,9 +3,8 @@ import { $ } from 'bun'
const PROJECT_ID = 'gen-lang-client-0200379851' const PROJECT_ID = 'gen-lang-client-0200379851'
async function secretExists(name: string): Promise<boolean> { async function secretExists(name: string): Promise<boolean> {
const result = const result = await $`gcloud secrets describe ${name} --project=${PROJECT_ID}`.quiet().nothrow()
(await $`gcloud secrets describe ${name} --project=${PROJECT_ID}`.quiet().exitCode) === 0 return result.exitCode === 0
return result
} }
async function createSecret(name: string, value: string) { async function createSecret(name: string, value: string) {