- Replace gorilla/csrf with net/http CrossOriginProtection - Require valkey-password and add TLS options for session store - End session at /logout and revoke refresh tokens - Re-derive identity and roles from provider every five minutes - Process each Stripe webhook event in its own Temporal workflow - Give each outbox entry its own workflow with Temporal retries - Guard against stale Stripe events with provider timestamps - Derive transport security from base-url scheme
131 lines
5.9 KiB
Bash
Executable File
131 lines
5.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# test/reset-app-db.sh — restore the APPLICATION database to the demo state.
|
|
#
|
|
# The screen-capture utility (`make screens`, test/e2e/screens) runs this
|
|
# first so every capture comes from the same rows: walkthrough fixtures,
|
|
# manual experiments, drifted timestamps, and, because database-generated
|
|
# IDs are never pinned (Decision 30), freshly generated identifiers would
|
|
# otherwise show up as changed screens on pages nobody touched (maintainer
|
|
# decision 2026-08-30).
|
|
#
|
|
# The demo state is built once into a snapshot database (<app db>_demo):
|
|
# migrate, demo seed with the operator's own person (--with-operator), the
|
|
# boot data state, and the row clocks pinned (pin-timestamps.sql). Every
|
|
# reset then drops the application database and recreates it FROM that
|
|
# snapshot, so rows and their IDs are identical between resets. The snapshot
|
|
# is rebuilt when it is missing, when the migrations, the seed, or the clock
|
|
# pin have changed since it was built (their fingerprint is the snapshot's
|
|
# database comment), or on `--rebuild`.
|
|
#
|
|
# This is the one deliberate exception to the rule that nothing under test/
|
|
# touches `member_console`: reset-test-db.sh refuses that database so
|
|
# `make test` can never clobber a developer's working state; this script
|
|
# exists to clobber it on purpose, and only when asked. The running app
|
|
# reconnects on its next query; browser sessions that referenced the old
|
|
# rows are invalid until their next sign-in.
|
|
#
|
|
# Usage: cd test && ./reset-app-db.sh [--rebuild]
|
|
set -euo pipefail
|
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
repo_root="$(cd "$here/.." && pwd)"
|
|
env_file="$here/.env"
|
|
if [[ ! -f "$env_file" ]]; then
|
|
echo "error: $env_file not found. Run ./bootstrap-stack.sh first." >&2
|
|
exit 1
|
|
fi
|
|
set -a
|
|
# shellcheck source=/dev/null
|
|
source "$env_file"
|
|
set +a
|
|
for var in POSTGRES_ADMIN_DSN MC_DB_DSN; do
|
|
if [[ -z "${!var:-}" ]]; then
|
|
echo "error: $var is not set in $env_file." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# The database reset re-creates every row from the demo seed (or, on
|
|
# rebuild, from a fresh seed run), and ids are UUIDv7 -- never reused -- so
|
|
# a session minted before the reset holds a person id that no longer exists
|
|
# afterward. flush_sessions empties the Valkey session store so no such
|
|
# stale session survives a reset; this Valkey instance exists only for
|
|
# member-console sessions in the test stack, so a full flush loses nothing
|
|
# else. Tried in order: a local valkey-cli, a local redis-cli (protocol-
|
|
# compatible), or docker compose exec into the valkey service when neither
|
|
# CLI is installed on the host.
|
|
flush_sessions() {
|
|
echo "Flushing the session store (Valkey) so no pre-reset session survives..."
|
|
# The store is password-protected (valkey-password is required
|
|
# configuration), so every path here authenticates.
|
|
local auth=()
|
|
[[ -n "${VALKEY_PASSWORD:-}" ]] && auth=(-a "$VALKEY_PASSWORD" --no-auth-warning)
|
|
if command -v valkey-cli >/dev/null 2>&1; then
|
|
valkey-cli -h localhost -p "$VALKEY_PORT" "${auth[@]}" FLUSHALL >/dev/null
|
|
elif command -v redis-cli >/dev/null 2>&1; then
|
|
redis-cli -h localhost -p "$VALKEY_PORT" "${auth[@]}" FLUSHALL >/dev/null
|
|
else
|
|
(cd "$here" && docker compose exec -T valkey valkey-cli "${auth[@]}" FLUSHALL >/dev/null)
|
|
fi
|
|
}
|
|
|
|
path="${MC_DB_DSN#*://}"; path="${path#*/}"; name="${path%%\?*}"
|
|
if [[ -z "$name" ]]; then
|
|
echo "error: cannot read a database name from MC_DB_DSN." >&2
|
|
exit 1
|
|
fi
|
|
snapshot="${name}_demo"
|
|
# The snapshot's DSN is the application's with the database name (the last
|
|
# path segment) swapped; the user name may equal the database name, so a
|
|
# plain substitution would rename the wrong part.
|
|
dsn_prefix="${MC_DB_DSN%/*}"
|
|
dsn_tail="${MC_DB_DSN##*/}"
|
|
dsn_query="${dsn_tail#"${name}"}"
|
|
snapshot_dsn="${dsn_prefix}/${snapshot}${dsn_query}"
|
|
|
|
# The snapshot is current when it was built from the migrations, the seed,
|
|
# and the clock pin the repository holds now; the fingerprint of those
|
|
# sources is stored as the snapshot database's comment.
|
|
fingerprint="$(cat "$repo_root"/internal/db/migrations/*.sql "$repo_root"/internal/demoseed/*.go "$here/pin-timestamps.sql" | md5sum | cut -c1-32)"
|
|
rebuild=0
|
|
if [[ "${1:-}" == "--rebuild" ]]; then
|
|
rebuild=1
|
|
else
|
|
current="$(psql "$POSTGRES_ADMIN_DSN" -tAc "SELECT coalesce(shobj_description(oid, 'pg_database'), '') FROM pg_database WHERE datname = '${snapshot}'")"
|
|
if [[ "$current" != "$fingerprint" ]]; then
|
|
echo "Demo snapshot '${snapshot}' is missing or was built from older migrations or seed; rebuilding it."
|
|
rebuild=1
|
|
fi
|
|
fi
|
|
|
|
if [[ "$rebuild" == 1 ]]; then
|
|
echo "Building the demo snapshot '${snapshot}' (migrate, seed with the operator, pin clocks)..."
|
|
psql "$POSTGRES_ADMIN_DSN" -v ON_ERROR_STOP=1 --quiet <<SQL
|
|
DROP DATABASE IF EXISTS ${name} WITH (FORCE);
|
|
CREATE DATABASE ${name} OWNER member_console;
|
|
SQL
|
|
(cd "$repo_root" && MC_DB_DSN="$MC_DB_DSN" go run . migrate up --config test/mc-config.yaml >/dev/null)
|
|
"$here/seed-demo.sh" --with-operator >/dev/null
|
|
psql "$MC_DB_DSN" -v ON_ERROR_STOP=1 --quiet <<SQL
|
|
SET session_replication_role = replica;
|
|
\\i $here/pin-timestamps.sql
|
|
SQL
|
|
psql "$POSTGRES_ADMIN_DSN" -v ON_ERROR_STOP=1 --quiet <<SQL
|
|
DROP DATABASE IF EXISTS ${snapshot} WITH (FORCE);
|
|
CREATE DATABASE ${snapshot} OWNER member_console;
|
|
SQL
|
|
pg_dump "$MC_DB_DSN" --no-owner --no-privileges | psql "$snapshot_dsn" -v ON_ERROR_STOP=1 --quiet >/dev/null
|
|
psql "$POSTGRES_ADMIN_DSN" -v ON_ERROR_STOP=1 --quiet -c "COMMENT ON DATABASE ${snapshot} IS '${fingerprint}'"
|
|
echo "Demo snapshot '${snapshot}' built; application database '${name}' is at the demo state."
|
|
flush_sessions
|
|
exit 0
|
|
fi
|
|
|
|
echo "Restoring application database '${name}' from the demo snapshot '${snapshot}'..."
|
|
psql "$POSTGRES_ADMIN_DSN" -v ON_ERROR_STOP=1 --quiet <<SQL
|
|
DROP DATABASE IF EXISTS ${name} WITH (FORCE);
|
|
CREATE DATABASE ${name} TEMPLATE ${snapshot} OWNER member_console;
|
|
SQL
|
|
echo "Application database '${name}' is at the demo state."
|
|
flush_sessions
|