fix: run migrations on boot and stop failures rendering as empty (#23)
The storefront showed no inventory after deploying the categories/tags release. No data was lost: the code queried categories/item_tags/ items.category_id against a database where the migration had not been run, and that failure was invisible at every layer. Three changes, each addressing one layer: Migrations now run at container start, so deployed code cannot be ahead of the schema and the easily-forgotten manual `docker exec migrate.js up` step disappears. migrate.js waits for Postgres to accept connections first, since the NAS brings the DB container up slower than the app, and still exits non-zero so a bad migration stops the container rather than serving a half-migrated schema. Express 4 does not forward a rejected async handler, and no error middleware was mounted, so a failing query never responded at all. Async routes are now wrapped and an error middleware guarantees a 500. A hung request is indistinguishable from an empty result in the UI, which is how a schema mismatch came to read as "the store has no items". The storefront now separates "request failed" from "no items" and offers a retry. fetchItems/fetchFilterOptions throw on a non-OK response rather than returning the parsed error body, which would have been set as the item list and crashed the grid on .map. Also restores the project-context update from 7fb5764, which was left out of PR #24 and ended up dangling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+46
-14
@@ -1,22 +1,54 @@
|
||||
const { runner } = require('node-pg-migrate');
|
||||
const { Client } = require('pg');
|
||||
const path = require('path');
|
||||
|
||||
const direction = process.argv[2] || 'up';
|
||||
|
||||
runner({
|
||||
databaseUrl: {
|
||||
host: process.env.PGHOST || 'localhost',
|
||||
port: parseInt(process.env.PGPORT || '5432', 10),
|
||||
user: process.env.PGUSER,
|
||||
password: process.env.PGPASSWORD,
|
||||
database: process.env.PGDATABASE
|
||||
},
|
||||
dir: path.resolve(__dirname, 'migrations'),
|
||||
direction,
|
||||
migrationsTable: 'pgmigrations',
|
||||
count: direction === 'down' ? 1 : Infinity,
|
||||
log: (msg) => console.log(msg)
|
||||
})
|
||||
const dbConfig = {
|
||||
host: process.env.PGHOST || 'localhost',
|
||||
port: parseInt(process.env.PGPORT || '5432', 10),
|
||||
user: process.env.PGUSER,
|
||||
password: process.env.PGPASSWORD,
|
||||
database: process.env.PGDATABASE
|
||||
};
|
||||
|
||||
// This now runs at container start, ahead of the app, so it can come up before
|
||||
// Postgres is accepting connections — on the NAS the database container is
|
||||
// routinely slower to be ready than the app container. Without a wait the
|
||||
// migration would fail, take the app down with it, and look like a broken
|
||||
// deploy rather than a startup race.
|
||||
const WAIT_ATTEMPTS = 30;
|
||||
const WAIT_INTERVAL_MS = 2000;
|
||||
|
||||
async function waitForDb() {
|
||||
for (let attempt = 1; attempt <= WAIT_ATTEMPTS; attempt++) {
|
||||
const client = new Client(dbConfig);
|
||||
try {
|
||||
await client.connect();
|
||||
await client.end();
|
||||
return;
|
||||
} catch (err) {
|
||||
await client.end().catch(() => {});
|
||||
if (attempt === WAIT_ATTEMPTS) {
|
||||
throw new Error(`Database unreachable after ${WAIT_ATTEMPTS} attempts: ${err.message}`);
|
||||
}
|
||||
console.log(`Waiting for database (attempt ${attempt}/${WAIT_ATTEMPTS})...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, WAIT_INTERVAL_MS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitForDb()
|
||||
.then(() =>
|
||||
runner({
|
||||
databaseUrl: dbConfig,
|
||||
dir: path.resolve(__dirname, 'migrations'),
|
||||
direction,
|
||||
migrationsTable: 'pgmigrations',
|
||||
count: direction === 'down' ? 1 : Infinity,
|
||||
log: (msg) => console.log(msg)
|
||||
})
|
||||
)
|
||||
.then((applied) => {
|
||||
console.log(`Migration complete — ${applied.length} migration(s) ${direction === 'down' ? 'reverted' : 'applied'}.`);
|
||||
process.exit(0);
|
||||
|
||||
Reference in New Issue
Block a user