2 Commits
3 changed files with 113 additions and 0 deletions
+20
View File
@@ -57,3 +57,23 @@ this directory:
Deployed to Fly.io (`fly.toml`). After a domain changes, re-run Deployed to Fly.io (`fly.toml`). After a domain changes, re-run
`src/scripts/register-webhooks.ts` with production env vars to point Printful's `src/scripts/register-webhooks.ts` with production env vars to point Printful's
and Webflow's webhooks at the new domain — see issue #3 for the full checklist. and Webflow's webhooks at the new domain — see issue #3 for the full checklist.
### Running migrations/seed against production
The deployed image only contains the compiled binary (see `Dockerfile`) — no
source, no `bun_modules`, no migration files — so these can't be run from
`fly ssh console` on the API app itself. Instead, use `migrate.sh` at the repo
root, which tunnels to the Postgres app (`blade-and-brawn-db`, legacy/unmanaged
Fly Postgres) via `fly proxy`, fetches the production `DATABASE_URL` for you,
and runs the scripts against it:
```bash
./migrate.sh migrate # db:migrate:latest (default if no argument given)
./migrate.sh seed # db:seed
./migrate.sh both # both, in order
```
Both underlying scripts prompt for a `y/N` confirmation before touching the
database, and `db:seed` is idempotent (skips seeding if the default rows
already exist). If the tunneled connection fails on TLS, legacy Postgres
sometimes needs `?sslmode=disable` appended — edit `migrate.sh` if so.
@@ -0,0 +1,41 @@
import { Kysely, sql } from 'kysely'
import { addDefaultColumns } from '../db';
export async function up(db: Kysely<any>): Promise<void> {
// TABLE: ACCOUNTS
await db.schema.createTable("accounts")
.addColumn("id", "uuid", (cb) => cb.primaryKey().defaultTo(sql`gen_random_uuid()`))
.addColumn("created_at", "timestamptz", (cb) => cb
.notNull()
.defaultTo(sql`now()`)
)
.addColumn("discord_id", "text", (cb) => cb.unique())
.addColumn("email", "text", (cb) => cb.unique())
.addColumn("name", "text", (cb) => cb.notNull().defaultTo(""))
.addColumn("gender", "text", (cb) => cb.notNull())
.execute();
// TABLE: ASSESSMENTS
await db.schema.createTable("assessments")
.$call(addDefaultColumns)
.addColumn("account_id", "uuid")
.addColumn("name", "text", (cb) => cb.notNull().defaultTo(""))
.addColumn("gender", "text", (cb) => cb.notNull())
.addColumn("age", "numeric", (cb) => cb.notNull())
.addColumn("weight", "numeric", (cb) => cb.notNull())
.addForeignKeyConstraint(
"fk_assessments_account_id",
["account_id"],
"accounts",
["id"],
(cb) => cb.onDelete("cascade")
)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
// TABLE: ASSESSMENTS
await db.schema.dropTable("assessments").ifExists().execute()
// TABLE: ACCOUNTS
await db.schema.dropTable("accounts").ifExists().execute()
}
Executable
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
PG_APP="blade-and-brawn-db"
API_APP="blade-and-brawn-api"
LOCAL_PORT=5433
proxy_pid=""
cleanup() {
if [[ -n "$proxy_pid" ]]; then
kill "$proxy_pid" 2>/dev/null || true
fi
}
trap cleanup EXIT
echo "Fetching production DATABASE_URL from $API_APP..." >&2
prod_url=$(fly ssh console -a "$API_APP" -C "printenv DATABASE_URL" 2>/dev/null | grep -m1 '^postgres')
if [[ -z "$prod_url" ]]; then
echo "Failed to fetch DATABASE_URL from $API_APP" >&2
exit 1
fi
tunneled_url=$(echo "$prod_url" | sed -E "s#@[^/]+/#@localhost:${LOCAL_PORT}/#")
echo "Starting tunnel to $PG_APP on localhost:${LOCAL_PORT}..." >&2
fly proxy "${LOCAL_PORT}:5432" -a "$PG_APP" &
proxy_pid=$!
echo "Waiting for tunnel..." >&2
for _ in $(seq 1 30); do
nc -z localhost "$LOCAL_PORT" 2>/dev/null && break
sleep 0.5
done
target="${1:-migrate}"
case "$target" in
migrate)
DATABASE_URL="$tunneled_url" bun run --filter @blade-and-brawn/api db:migrate:latest
;;
seed)
DATABASE_URL="$tunneled_url" bun run --filter @blade-and-brawn/api db:seed
;;
both)
DATABASE_URL="$tunneled_url" bun run --filter @blade-and-brawn/api db:migrate:latest
DATABASE_URL="$tunneled_url" bun run --filter @blade-and-brawn/api db:seed
;;
*)
echo "Usage: $0 [migrate|seed|both]" >&2
exit 1
;;
esac