Features - Payment reminders via Resend (candidates, send, history, test email) - Bulk import of the customer list from Excel/CSV in the dashboard - Dashboard i18n (en/nl) with language selector Security - Enforce venue isolation on all /venues/:venueId routes - Require admin for planned-tab changes and pairing codes - Rate-limit login and device registration; opt-in TRUST_PROXY - Require JWT_SECRET in production; stop leaking internal errors - Escape customer names in reminder emails Sync and commands - Remove tabs the terminal no longer reports; validate nested sync payload - Return all pending commands on every sync (fixes missed-command race) - First command ack wins; atomic registration code claim - Reject commands for revoked devices, stale versions or duplicates - Remind only unpaid balances, with a 7-day cooldown Dashboard - Share a single token refresh across concurrent 401s (random logouts) - Keep the session alive for the refresh token lifetime Deployment - Replace `db push --accept-data-loss` with migrations (baselines legacy DBs) - Set NODE_ENV=production; pass Resend and TRUST_PROXY env in prod compose
56 lines
1.7 KiB
JavaScript
56 lines
1.7 KiB
JavaScript
/**
|
|
* Applies database migrations on container start.
|
|
*
|
|
* Databases created before migrations were used (via `prisma db push`) have no
|
|
* `_prisma_migrations` table. Those are brought up to date with a non-destructive
|
|
* `db push` (which refuses to drop data) and then baselined, so every later deploy
|
|
* can use plain `prisma migrate deploy`.
|
|
*/
|
|
const { execSync } = require('child_process');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { PrismaClient } = require('@prisma/client');
|
|
|
|
function run(command) {
|
|
console.log(`[deploy] ${command}`);
|
|
execSync(command, { stdio: 'inherit' });
|
|
}
|
|
|
|
async function main() {
|
|
const prisma = new PrismaClient();
|
|
let hasMigrationsTable;
|
|
let hasTables;
|
|
try {
|
|
const [{ migrations, tables }] = await prisma.$queryRaw`
|
|
SELECT
|
|
to_regclass('public._prisma_migrations') IS NOT NULL AS migrations,
|
|
EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public') AS tables`;
|
|
hasMigrationsTable = migrations;
|
|
hasTables = tables;
|
|
} finally {
|
|
await prisma.$disconnect();
|
|
}
|
|
|
|
if (!hasMigrationsTable && hasTables) {
|
|
console.log('[deploy] Existing database without migration history, baselining');
|
|
run('npx prisma db push --skip-generate');
|
|
|
|
const migrationsDir = path.join(__dirname, 'migrations');
|
|
const migrations = fs
|
|
.readdirSync(migrationsDir, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => entry.name)
|
|
.sort();
|
|
for (const name of migrations) {
|
|
run(`npx prisma migrate resolve --applied ${name}`);
|
|
}
|
|
}
|
|
|
|
run('npx prisma migrate deploy');
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('[deploy] Migration failed:', error);
|
|
process.exit(1);
|
|
});
|