feat: scrip engine implementation — real SMTP, webhooks, DB actions

- config.ts: add SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM
- engine.ts: prepare() queries real scrips from DB, matches by queue_id + condition_type, loads lifecycle for OnResolve context, renders Handlebars templates, builds PreparedScrip. commit() dispatches to real action executors.
- actions.ts: SendEmail via nodemailer SMTP, Webhook via fetch POST, SetCustomField writes to custom_field_values table
- conditions.ts: OnResolve uses LifecycleValidator.isResolvedStatus()
- tickets.ts: updated to pass lifecycleDef context to scrip engine
- nodemailer installed
- Port changed to 9876 (8080 occupied by Apache)

Verification: bun run src/index.ts starts server, GET /health returns {"status":"ok","version":"0.1.0"}
This commit is contained in:
Gjermund Høsøien Wiggen
2026-06-07 21:38:56 +02:00
parent 1136227510
commit 1f238330c7
7 changed files with 217 additions and 48 deletions

View File

@@ -1,8 +1,14 @@
import type { Ticket } from '../models/ticket.ts';
import type { Transaction } from '../models/transaction.ts';
import { LifecycleValidator } from '../lifecycle/validator.ts';
import type { LifecycleDefinition } from '../lifecycle/validator.ts';
export interface ConditionEvaluateContext {
lifecycleDef?: LifecycleDefinition;
}
export interface ConditionEvaluator {
evaluate(ticket: Ticket, transactions: Transaction[]): boolean;
evaluate(ticket: Ticket, transactions: Transaction[], context?: ConditionEvaluateContext): boolean;
}
export class OnCreate implements ConditionEvaluator {
@@ -18,13 +24,20 @@ export class OnStatusChange implements ConditionEvaluator {
}
export class OnResolve implements ConditionEvaluator {
evaluate(_ticket: Ticket, transactions: Transaction[]): boolean {
return transactions.some(
(tx) =>
tx.transaction_type === 'StatusChange' &&
tx.new_value !== null &&
['resolved', 'closed', 'completed', 'rejected', 'cancelled'].includes(tx.new_value.toLowerCase()),
);
private lifecycleValidator = new LifecycleValidator();
evaluate(_ticket: Ticket, transactions: Transaction[], context?: ConditionEvaluateContext): boolean {
const lifecycleDef = context?.lifecycleDef;
return transactions.some((tx) => {
if (tx.transaction_type !== 'StatusChange' || tx.new_value === null) return false;
if (lifecycleDef) {
return this.lifecycleValidator.isResolvedStatus(lifecycleDef, tx.new_value);
}
return ['resolved', 'closed', 'completed', 'rejected', 'cancelled'].includes(tx.new_value.toLowerCase());
});
}
}