Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening
Batch commit of the pending working tree on security/audit-fixes-2026-07. Major areas: - Outbound webhooks / Zapier: schema + signed delivery with retries, public v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain. - Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when Spaces is unconfigured instead of silently using ephemeral disk. - Integrations & features (concurrent work): accounting (QuickBooks/Xero), e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding, expanded legal pages. - DB migrations 0006–0009. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
969d5d4c8a
commit
c9968531e4
@@ -0,0 +1,68 @@
|
||||
// One-off production migration runner (DigitalOcean Managed Postgres).
|
||||
//
|
||||
// Applies any pending Drizzle migrations in lib/db/migrations to the production
|
||||
// database, using the doadmin connection. Drizzle tracks what's already applied
|
||||
// in the __drizzle_migrations table, so this only runs the missing migrations
|
||||
// and is safe to re-run.
|
||||
//
|
||||
// The connection string is read from PROD_DATABASE_URL so the credential never
|
||||
// has to be typed on the command line. Set it in .env.local (git-ignored):
|
||||
//
|
||||
// PROD_DATABASE_URL=postgresql://doadmin:<pw>@<cluster>...:25060/<app-db>
|
||||
//
|
||||
// Then run: node scripts/migrate-prod.mjs
|
||||
//
|
||||
// TLS: verified against ca-certificate.crt when present, otherwise encrypted
|
||||
// but unverified. Uses the direct port (25060), NOT the 25061 pooler — the
|
||||
// migrator needs a real session (transactions + advisory locks).
|
||||
import { readFileSync } from "node:fs"
|
||||
import { config } from "dotenv"
|
||||
import { drizzle } from "drizzle-orm/node-postgres"
|
||||
import { migrate } from "drizzle-orm/node-postgres/migrator"
|
||||
import pg from "pg"
|
||||
|
||||
config({ path: ".env.local", quiet: true })
|
||||
|
||||
const url = process.env.PROD_DATABASE_URL
|
||||
if (!url) {
|
||||
console.error(
|
||||
"[migrate-prod] PROD_DATABASE_URL is not set. Add the doadmin connection " +
|
||||
"string (port 25060, app database) to .env.local and re-run."
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (url.includes(":25061")) {
|
||||
console.error(
|
||||
"[migrate-prod] Refusing to run migrations through the 25061 connection " +
|
||||
"pooler. Use the direct port 25060."
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let ssl
|
||||
try {
|
||||
const ca = readFileSync("ca-certificate.crt", "utf8")
|
||||
ssl = { rejectUnauthorized: true, ca }
|
||||
console.log("[migrate-prod] TLS: verifying against ca-certificate.crt")
|
||||
} catch {
|
||||
ssl = { rejectUnauthorized: false }
|
||||
console.log("[migrate-prod] TLS: encrypted (certificate not verified)")
|
||||
}
|
||||
|
||||
const { Pool } = pg
|
||||
const pool = new Pool({ connectionString: url, ssl })
|
||||
const db = drizzle(pool)
|
||||
|
||||
try {
|
||||
const host = new URL(url).host
|
||||
console.log(`[migrate-prod] Connecting to ${host} ...`)
|
||||
await migrate(db, { migrationsFolder: "./lib/db/migrations" })
|
||||
console.log("[migrate-prod] Migrations applied successfully.")
|
||||
await pool.end()
|
||||
process.exit(0)
|
||||
} catch (err) {
|
||||
console.error(`[migrate-prod] Failed: ${err?.message ?? err}`)
|
||||
await pool.end().catch(() => {})
|
||||
process.exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user