feat: auth system, scrip scheduler, UI widgets, and new API routes

- Add session-based authentication (login page, middleware, auth context)
- Add cron-like scrip scheduler for time-based conditions
- Add layout builder, scrip wizard, searchable select components
- Add trend chart widget for dashboards
- Add notifications, attachments, queue-permissions API routes
- Add seed-users script
- Update schema with 10 new migrations (0008-0017)
- Apply redesign: Linear-inspired dark theme, conversation-centric UI
- Gitignore runtime data directory

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gjermund Høsøien Wiggen
2026-06-15 20:42:17 +02:00
parent 1d4dc38d06
commit 70f0924d4b
59 changed files with 21795 additions and 321 deletions

View File

@@ -37,6 +37,7 @@ export class ScripEngine {
async prepare(
ticketId: number,
transactions: Transaction[],
stage: 'TransactionCreate' | 'TransactionBatch' = 'TransactionCreate',
): Promise<PreparedScrip[]> {
const ticketRecord = await this.db.query.tickets.findFirst({
where: eq(tickets.id, ticketId),
@@ -53,6 +54,15 @@ export class ScripEngine {
const matchingScrips = allScrips.filter((scrip) => {
if (scrip.disabled) return false;
if (scrip.queue_id !== null && scrip.queue_id !== ticketRecord.queue_id) return false;
if (scrip.stage !== stage) return false;
// Filter by applicable transaction types — if set, at least one tx must match
if (scrip.applicable_trans_types) {
const types = scrip.applicable_trans_types.split(',').map((t) => t.trim()).filter(Boolean);
if (types.length > 0 && !types.includes('Any')) {
const txTypes = new Set(transactions.map((tx) => tx.transaction_type));
if (!types.some((t) => txTypes.has(t))) return false;
}
}
return true;
});
@@ -70,10 +80,6 @@ export class ScripEngine {
}
}
const conditionContext: ConditionEvaluateContext = {
lifecycleDef,
};
const cfValues = await this.db.query.customFieldValues.findMany({
where: eq(customFieldValues.ticket_id, ticketId),
});
@@ -94,6 +100,11 @@ export class ScripEngine {
}
}
const conditionContext: ConditionEvaluateContext = {
lifecycleDef,
customFields: customFieldsMap,
};
const prepared: PreparedScrip[] = [];
for (const scrip of matchingScrips) {