Files
kooltab-backoffice/apps/api/prisma/deploy.js
T
brammie15 8b1e38cb4d feat: reminders, Excel customer import and security hardening
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
2026-10-07 23:18:37 +02:00

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);
});