feat: add database seed script and utility scripts

- src/db/seed.ts: comprehensive seed data with idempotent upserts
  - 5 users (system, gjermund, operator, technician, analyst)
  - 5 queues (Support Desk, Operations, IT Infrastructure, Facilities, Field Ops)
  - 1 lifecycle (Demo service lifecycle with new→open→in_progress→resolved→closed)
  - 5 custom fields (impact, location, channel, urgency, outcome) with short keys
  - 10 realistic support tickets with varied statuses, custom fields, and history
  - 3 scrips (OnCreate email, OnResolve custom field, customer notification)
  - 2 templates (auto-response, resolve notification)
  - --reset flag to truncate all data before seeding
- scripts/smoke-test.ts: API smoke tests
- scripts/watch-frontend.sh: frontend dev helper

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gjermund Høsøien Wiggen
2026-06-09 10:43:18 +02:00
parent 54ef6fcc5b
commit b96ba21e99
3 changed files with 914 additions and 0 deletions

112
scripts/smoke-test.ts Normal file
View File

@@ -0,0 +1,112 @@
const backendUrl = process.env.BACKEND_URL ?? 'http://127.0.0.1:9876';
const frontendUrl = process.env.FRONTEND_URL ?? 'http://127.0.0.1:3100';
interface Ticket {
id: number;
subject: string;
}
interface Queue {
id: string;
name: string;
}
interface Transaction {
id: string;
ticket_id: number;
transaction_type: string;
}
async function requestJson<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${url} returned ${response.status} ${response.statusText}`);
}
return response.json() as Promise<T>;
}
async function requestOk(url: string): Promise<void> {
const response = await fetch(url, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`${url} returned ${response.status} ${response.statusText}`);
}
}
async function check(name: string, fn: () => Promise<void>): Promise<void> {
try {
await fn();
console.log(`ok ${name}`);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.error(`fail ${name}`);
console.error(` ${message}`);
process.exitCode = 1;
}
}
async function main() {
let ticketForDetail: Ticket | null = null;
await check('backend health', async () => {
const health = await requestJson<{ status: string }>(`${backendUrl}/health`);
if (health.status !== 'ok') {
throw new Error(`expected status ok, got ${JSON.stringify(health)}`);
}
});
await check('queues exist', async () => {
const queues = await requestJson<Queue[]>(`${backendUrl}/queues`);
if (queues.length < 1) {
throw new Error('expected at least one queue');
}
});
await check('tickets exist', async () => {
const tickets = await requestJson<Ticket[]>(`${backendUrl}/tickets`);
if (tickets.length < 1) {
throw new Error('expected at least one ticket');
}
ticketForDetail = tickets.find((ticket) => ticket.subject.includes('VPN access')) ?? tickets[0] ?? null;
});
await check('ticket detail has activity', async () => {
if (!ticketForDetail) {
throw new Error('no ticket available for detail check');
}
const transactions = await requestJson<Transaction[]>(
`${backendUrl}/tickets/${ticketForDetail.id}/transactions`,
);
if (transactions.length < 1) {
throw new Error(`expected ticket ${ticketForDetail.id} to have transactions`);
}
});
await check('frontend index responds', async () => {
await requestOk(frontendUrl);
});
await check('frontend ticket detail responds', async () => {
if (!ticketForDetail) {
throw new Error('no ticket available for frontend detail check');
}
await requestOk(`${frontendUrl}/tickets/${ticketForDetail.id}`);
});
await check('frontend api proxy responds', async () => {
const health = await requestJson<{ status: string }>(`${frontendUrl}/api/health`);
if (health.status !== 'ok') {
throw new Error(`expected status ok, got ${JSON.stringify(health)}`);
}
});
if (process.exitCode) {
process.exit(process.exitCode);
}
console.log('Smoke test passed');
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

15
scripts/watch-frontend.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Watch for source changes and auto-rebuild + restart Tessera frontend
DIR="/home/gjermund/projects/tessera/web/src"
LAST_BUILD=0
echo "Watching $DIR for changes..."
inotifywait -m -r -e modify,create,delete "$DIR" --format '%w%f' 2>/dev/null | while read FILE; do
NOW=$(date +%s)
if [ $((NOW - LAST_BUILD)) -gt 3 ]; then
echo "[$(date +%H:%M:%S)] Change detected, rebuilding..."
cd /home/gjermund/projects/tessera/web && npx next build 2>&1 | tail -1
LAST_BUILD=$NOW
fi
done