Code after remediation

Application source after the agent patch and final rescan.

README.md

# Treasury Operations Demo

This workspace contains a fictional multi-service treasury operations portal for local benchmarking only.

Services:

- `portal/` - TypeScript/JavaScript admin portal built on Express
- `payments-api/` - Go API for transfers and settlement jobs
- `docs-worker/` - Python Flask service for documents and background work
- `risk-api/` - Java HTTP API for risk operations

Each service uses local persistence and exposes a few thin HTTP routes with middleware, audit logging, and basic RBAC checks.

docs-worker/app.py

from __future__ import annotations

import json
import os
import queue
import sqlite3
import threading
from datetime import datetime
from pathlib import Path

from flask import Flask, Response, g, jsonify, request, send_file

BASE_DIR = Path(__file__).resolve().parent
DATA_DIR = Path(os.environ.get("DOCS_WORKER_DATA_DIR", str(BASE_DIR / "data"))).resolve()
STORAGE_DIR = DATA_DIR / "storage"
DB_PATH = DATA_DIR / "docs.db"

DATA_DIR.mkdir(parents=True, exist_ok=True)
STORAGE_DIR.mkdir(parents=True, exist_ok=True)

app = Flask(__name__)
job_queue: "queue.Queue[dict]" = queue.Queue()


def get_db() -> sqlite3.Connection:
    if "db" not in g:
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        g.db = conn
    return g.db


def init_db() -> None:
    db = sqlite3.connect(DB_PATH)
    db.executescript(
        """
        create table if not exists organizations (
            id integer primary key autoincrement,
            name text not null,
            org_code text not null unique,
            region text not null,
            tier text not null
        );
        create table if not exists teams (
            id integer primary key autoincrement,
            org_id integer not null,
            name text not null,
            cost_center text not null,
            status text not null default 'active'
        );
        create table if not exists users (
            id integer primary key autoincrement,
            org_id integer not null,
            team_id integer,
            email text not null,
            display_name text not null,
            role text not null,
            status text not null default 'active'
        );
        create table if not exists accounts (
            id integer primary key autoincrement,
            org_id integer not null,
            account_no text not null,
            currency text not null,
            balance numeric not null default 0
        );
        create table if not exists beneficiaries (
            id integer primary key autoincrement,
            org_id integer not null,
            name text not null,
            bank_name text not null,
            routing text not null
        );
        create table if not exists transfers (
            id integer primary key autoincrement,
            org_id integer not null,
            account_id integer not null,
            beneficiary_id integer not null,
            amount numeric not null,
            currency text not null,
            status text not null,
            memo text not null,
            requested_by text not null
        );
        create table if not exists documents (
            id integer primary key autoincrement,
            org_id integer not null,
            filename text not null,
            original_name text not null,
            mime_type text not null,
            kind text not null,
            created_at text not null default current_timestamp
        );
        create table if not exists risk_reviews (
            id integer primary key autoincrement,
            org_id integer not null,
            subject text not null,
            score numeric not null,
            note text not null,
            created_at text not null default current_timestamp
        );
        create table if not exists audit_records (
            id integer primary key autoincrement,
            org_id integer,
            actor text not null,
            action text not null,
            resource text not null,
            details text not null,
            created_at text not null default current_timestamp
        );
        """
    )
    if db.execute("select count(*) from organizations").fetchone()[0] == 0:
        db.execute(
            "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)",
            ("Acme Treasury", "acme", "us-east", "enterprise"),
        )
        db.execute(
            "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)",
            ("Northwind Holdings", "northwind", "eu-west", "growth"),
        )
        db.commit()
    db.close()


def current_identity() -> dict:
    return {
        "org_id": int(request.headers.get("X-Org-Id", "1")),
        "user_id": request.headers.get("X-User-Id", "docs-user"),
        "role": request.headers.get("X-Role", "viewer"),
    }


@app.before_request
def load_identity() -> None:
    g.identity = current_identity()
    g.started_at = datetime.utcnow().isoformat()


@app.after_request
def audit_request(response: Response) -> Response:
    identity = getattr(g, "identity", None)
    if identity:
        db = get_db()
        db.execute(
            "insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)",
            (
                identity["org_id"],
                identity["user_id"],
                f"{request.method} {request.path}",
                request.full_path,
                json.dumps({"status": response.status_code, "args": request.args.to_dict(), "json": request.get_json(silent=True)}),
            ),
        )
        db.commit()
    return response


@app.teardown_appcontext
def close_db(exc: Exception | None) -> None:
    db = g.pop("db", None)
    if db is not None:
        db.close()


def require_roles(*allowed: str):
    def decorator(fn):
        def wrapped(*args, **kwargs):
            if g.identity["role"] not in allowed:
                return jsonify({"error": "forbidden"}), 403
            return fn(*args, **kwargs)

        wrapped.__name__ = fn.__name__
        return wrapped

    return decorator


def render_report(org_name: str, title: str, notes: str) -> str:
    return f"""
    <html>
      <body>
        <h1>{title}</h1>
        <h2>{org_name}</h2>
        <div>{notes}</div>
      </body>
    </html>
    """


def resolve_storage_path(filename: str, *, must_exist: bool = False) -> Path:
    if not isinstance(filename, str) or not filename:
        raise ValueError("invalid filename")
    if filename in {".", ".."}:
        raise ValueError("invalid filename")
    if "\x00" in filename:
        raise ValueError("invalid filename")
    candidate_name = Path(filename).name
    if candidate_name != filename or Path(filename).is_absolute():
        raise ValueError("invalid filename")

    base_dir = STORAGE_DIR.resolve()
    target = base_dir / filename

    if must_exist:
        resolved = target.resolve(strict=True)
        if resolved.parent != base_dir:
            raise ValueError("invalid filename")
        return resolved

    if target.exists():
        resolved = target.resolve(strict=True)
        if resolved.parent != base_dir:
            raise ValueError("invalid filename")

    return target


def enqueue_job(payload: dict) -> None:
    job_queue.put(payload)


def job_worker() -> None:
    while True:
        payload = job_queue.get()
        if payload is None:
            break
        db = sqlite3.connect(DB_PATH)
        db.execute(
            "insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)",
            (
                payload.get("org_id"),
                "system",
                "job.dispatch",
                payload.get("kind", "document"),
                json.dumps(payload),
            ),
        )
        if payload.get("kind") == "document":
            filename = payload["filename"]
            target = STORAGE_DIR / filename
            target.write_text(payload.get("content", ""), encoding="utf-8")
            db.execute(
                "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",
                (
                    payload.get("org_id"),
                    filename,
                    payload.get("original_name", filename),
                    payload.get("mime_type", "text/plain"),
                    payload.get("kind", "document"),
                ),
            )
        db.commit()
        db.close()
        job_queue.task_done()


init_db()
threading.Thread(target=job_worker, daemon=True).start()


@app.route("/health", methods=["GET"])
def health() -> tuple:
    db = get_db()
    counts = {
        "documents": db.execute("select count(*) from documents").fetchone()[0],
        "audit_records": db.execute("select count(*) from audit_records").fetchone()[0],
        "jobs_pending": job_queue.qsize(),
    }
    return jsonify({"ok": True, "counts": counts})


@app.route("/documents", methods=["GET"])
@require_roles("admin", "manager", "viewer")
def search_documents() -> tuple:
    clauses = ["org_id = ?"]
    params: list[object] = [g.identity["org_id"]]
    kind = request.args.get("kind", "").strip()
    name = request.args.get("name", "").strip()
    if kind:
        clauses.append("kind = ?")
        params.append(kind)
    if name:
        clauses.append("original_name like ?")
        params.append(f"%{name}%")
    rows = get_db().execute(
        f"select id, filename, original_name, mime_type, kind, created_at from documents where {' and '.join(clauses)} order by id desc",
        params,
    ).fetchall()
    return jsonify({"data": [dict(row) for row in rows]})


@app.route("/docs/render", methods=["POST"])
@require_roles("admin", "manager")
def render_document() -> tuple:
    payload = request.get_json(force=True)
    org_name = payload["orgName"]
    title = payload.get("title", "Treasury Notice")
    notes = payload.get("notes", "")
    filename = payload.get("filename", f"report-{int(datetime.utcnow().timestamp())}.html")
    html = render_report(org_name, title, notes)
    try:
        target = resolve_storage_path(filename)
    except ValueError:
        return jsonify({"error": "invalid filename"}), 400
    target.write_text(html, encoding="utf-8")
    db = get_db()
    db.execute(
        "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",
        (g.identity["org_id"], filename, filename, "text/html", "report"),
    )
    db.commit()
    return jsonify({"filename": filename, "path": str(target)}), 201


@app.route("/docs/upload", methods=["POST"])
@require_roles("admin", "manager")
def upload_document() -> tuple:
    upload = request.files.get("file")
    if upload is None:
        return jsonify({"error": "missing file"}), 400
    filename = f"{int(datetime.utcnow().timestamp())}-{upload.filename}"
    try:
        target = resolve_storage_path(filename)
    except ValueError:
        return jsonify({"error": "invalid filename"}), 400
    upload.save(target)
    db = get_db()
    db.execute(
        "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",
        (g.identity["org_id"], filename, upload.filename, upload.mimetype or "application/octet-stream", "upload"),
    )
    db.commit()
    return jsonify({"filename": filename}), 201


@app.route("/docs/download/<path:filename>", methods=["GET"])
@require_roles("admin", "manager", "viewer")
def download_document(filename: str):
    try:
        target = resolve_storage_path(filename, must_exist=True)
    except FileNotFoundError:
        return jsonify({"error": "not found"}), 404
    except ValueError:
        return jsonify({"error": "invalid filename"}), 400
    return send_file(target, as_attachment=True)


@app.route("/jobs/dispatch", methods=["POST"])
@require_roles("admin", "manager")
def dispatch_job() -> tuple:
    payload = request.get_json(force=True)
    payload["org_id"] = g.identity["org_id"]
    enqueue_job(payload)
    return jsonify({"queued": True, "kind": payload.get("kind", "document")}), 202


@app.route("/reports/summary", methods=["GET"])
@require_roles("admin", "manager")
def summary_report() -> tuple:
    db = get_db()
    org_id = int(request.args.get("orgId", g.identity["org_id"]))
    title = request.args.get("title", "Document Summary")
    org = db.execute("select name, region, tier from organizations where id = ?", (org_id,)).fetchone()
    if org is None:
        return jsonify({"error": "missing org"}), 404
    notes = request.args.get("notes", "No notes supplied")
    html = render_report(org["name"], title, notes)
    return Response(html, mimetype="text/html")


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "4300")), debug=False)

docs-worker/requirements.txt

Flask==3.1.3

docs-worker/test_app.py

import os
import shutil
import tempfile
import importlib
import runpy
import unittest
from pathlib import Path
from urllib.parse import quote
from unittest.mock import patch


TEST_DATA_DIR = tempfile.mkdtemp(prefix="docs-worker-test-")
os.environ["DOCS_WORKER_DATA_DIR"] = TEST_DATA_DIR

docs_app = importlib.import_module("app")


class DownloadDocumentTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        docs_app.app.config["TESTING"] = True
        cls.client = docs_app.app.test_client()
        cls.storage_dir = Path(TEST_DATA_DIR) / "storage"

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(TEST_DATA_DIR, ignore_errors=True)

    def test_download_rejects_traversal_and_symlink_escape(self):
        outside = Path(TEST_DATA_DIR) / "outside.txt"
        outside.write_text("secret", encoding="utf-8")
        self.storage_dir.mkdir(parents=True, exist_ok=True)
        (self.storage_dir / "escape.txt").symlink_to(outside)

        traversal = self.client.get(
            f"/docs/download/{quote('../outside.txt', safe='')}",
            headers={"X-Role": "viewer", "X-Org-Id": "1"},
        )
        self.assertEqual(traversal.status_code, 400)
        self.assertEqual(traversal.get_json(), {"error": "invalid filename"})

        symlink = self.client.get(
            "/docs/download/escape.txt",
            headers={"X-Role": "viewer", "X-Org-Id": "1"},
        )
        self.assertEqual(symlink.status_code, 400)
        self.assertEqual(symlink.get_json(), {"error": "invalid filename"})

    def test_download_returns_existing_file(self):
        self.storage_dir.mkdir(parents=True, exist_ok=True)
        target = self.storage_dir / "statement.txt"
        target.write_text("document-body", encoding="utf-8")

        response = self.client.get(
            "/docs/download/statement.txt",
            headers={"X-Role": "viewer", "X-Org-Id": "1"},
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, b"document-body")

    def test_main_binds_to_loopback(self):
        with patch("flask.Flask.run") as run_mock:
            runpy.run_module("app", run_name="__main__")

        self.assertTrue(run_mock.called)
        _, kwargs = run_mock.call_args
        self.assertEqual(kwargs["host"], "127.0.0.1")
        self.assertEqual(kwargs["debug"], False)


if __name__ == "__main__":
    unittest.main()

payments-api/go.mod

module payments-api

go 1.22

require modernc.org/sqlite v1.36.0

require (
	github.com/dustin/go-humanize v1.0.1 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/ncruces/go-strftime v0.1.9 // indirect
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
	golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
	golang.org/x/sys v0.30.0 // indirect
	modernc.org/libc v1.61.13 // indirect
	modernc.org/mathutil v1.7.1 // indirect
	modernc.org/memory v1.8.2 // indirect
)

payments-api/go.sum

github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8=
modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

payments-api/main.go

package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"

	_ "modernc.org/sqlite"
)

type identity struct {
	OrgID  int64
	UserID string
	Role   string
}

type ctxKey string

const identityKey ctxKey = "identity"

type job struct {
	TransferID int64
	OrgID      int64
}

type responseRecorder struct {
	http.ResponseWriter
	status int
}

func (r *responseRecorder) WriteHeader(code int) {
	r.status = code
	r.ResponseWriter.WriteHeader(code)
}

var (
	db            *sql.DB
	jobs          = make(chan job, 64)
	startOnce     sync.Once
	dataDir       = filepath.Join("data")
	dbPath        = filepath.Join(dataDir, "payments.db")
)

func main() {
	mustInit()
	startWorker()

	http.HandleFunc("/healthz", withMiddleware(healthHandler))
	http.HandleFunc("/accounts/search", withMiddleware(requireRoles("admin", "manager", "viewer")(accountsSearchHandler)))
	http.HandleFunc("/beneficiaries", withMiddleware(requireRoles("admin", "manager", "viewer")(beneficiariesHandler)))
	http.HandleFunc("/transfers", withMiddleware(requireRoles("admin", "manager")(transfersHandler)))
	http.HandleFunc("/jobs/settlements", withMiddleware(requireRoles("admin", "manager")(settlementHandler)))
	http.HandleFunc("/audit", withMiddleware(requireRoles("admin")(auditHandler)))

	port := getenv("PORT", "4200")
	log.Printf("payments-api listening on %s", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

func mustInit() {
	if err := os.MkdirAll(dataDir, 0o755); err != nil {
		log.Fatal(err)
	}
	var err error
	db, err = sql.Open("sqlite", "file:"+dbPath+"?_pragma=busy_timeout(5000)")
	if err != nil {
		log.Fatal(err)
	}
	db.SetMaxOpenConns(1)

	for _, stmt := range []string{
		`create table if not exists organizations (
			id integer primary key autoincrement,
			name text not null,
			org_code text not null unique,
			region text not null,
			tier text not null
		);`,
		`create table if not exists teams (
			id integer primary key autoincrement,
			org_id integer not null,
			name text not null,
			cost_center text not null,
			status text not null
		);`,
		`create table if not exists users (
			id integer primary key autoincrement,
			org_id integer not null,
			team_id integer,
			email text not null,
			display_name text not null,
			role text not null
		);`,
		`create table if not exists accounts (
			id integer primary key autoincrement,
			org_id integer not null,
			account_no text not null,
			currency text not null,
			balance numeric not null default 0
		);`,
		`create table if not exists beneficiaries (
			id integer primary key autoincrement,
			org_id integer not null,
			name text not null,
			bank_name text not null,
			routing text not null
		);`,
		`create table if not exists transfers (
			id integer primary key autoincrement,
			org_id integer not null,
			account_id integer not null,
			beneficiary_id integer not null,
			amount numeric not null,
			currency text not null,
			status text not null,
			memo text not null,
			requested_by text not null,
			created_at text not null default current_timestamp
		);`,
		`create table if not exists audit_records (
			id integer primary key autoincrement,
			org_id integer,
			actor text not null,
			action text not null,
			resource text not null,
			details text not null,
			created_at text not null default current_timestamp
		);`,
	} {
		if _, err := db.Exec(stmt); err != nil {
			log.Fatal(err)
		}
	}
	seedData()
}

func seedData() {
	var count int
	_ = db.QueryRow("select count(*) from organizations").Scan(&count)
	if count > 0 {
		return
	}

	orgs := []struct {
		name, code, region, tier string
	}{
		{"Acme Treasury", "acme", "us-east", "enterprise"},
		{"Northwind Holdings", "northwind", "eu-west", "growth"},
	}
	for _, org := range orgs {
		res, _ := db.Exec("insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)", org.name, org.code, org.region, org.tier)
		orgID, _ := res.LastInsertId()
		_, _ = db.Exec("insert into teams (org_id, name, cost_center, status) values (?, ?, ?, ?)", orgID, "Operations", "100-ops", "active")
		_, _ = db.Exec("insert into teams (org_id, name, cost_center, status) values (?, ?, ?, ?)", orgID, "Payments", "200-pay", "active")
		_, _ = db.Exec("insert into accounts (org_id, account_no, currency, balance) values (?, ?, ?, ?)", orgID, "ACCT-"+strconv.FormatInt(orgID, 10), "USD", 1000000)
		_, _ = db.Exec("insert into beneficiaries (org_id, name, bank_name, routing) values (?, ?, ?, ?)", orgID, "Primary Vendor", "Metro Trust", "110000")
	}
}

func startWorker() {
	startOnce.Do(func() {
		go func() {
			for j := range jobs {
				_, _ = db.Exec("update transfers set status = ? where id = ? and org_id = ?", "settled", j.TransferID, j.OrgID)
				audit(j.OrgID, "system", "settlement", fmt.Sprintf("transfer:%d", j.TransferID), map[string]any{"status": "settled"})
			}
		}()
	})
}

func withMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		rec := &responseRecorder{ResponseWriter: w, status: http.StatusOK}
		id := identityFromRequest(r)
		r = r.WithContext(context.WithValue(r.Context(), identityKey, id))

		next(rec, r)

		audit(id.OrgID, id.UserID, r.Method+" "+r.URL.Path, r.URL.String(), map[string]any{
			"status": rec.status,
			"query":  r.URL.Query(),
		})
	}
}

func identityFromRequest(r *http.Request) identity {
	orgID, _ := strconv.ParseInt(r.Header.Get("X-Org-Id"), 10, 64)
	if orgID == 0 {
		orgID = 1
	}
	role := r.Header.Get("X-Role")
	if role == "" {
		role = "viewer"
	}
	userID := r.Header.Get("X-User-Id")
	if userID == "" {
		userID = "payments-user"
	}
	return identity{OrgID: orgID, UserID: userID, Role: role}
}

func requireRoles(allowed ...string) func(http.HandlerFunc) http.HandlerFunc {
	return func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			id := mustIdentity(r)
			for _, role := range allowed {
				if id.Role == role {
					next(w, r)
					return
				}
			}
			http.Error(w, "forbidden", http.StatusForbidden)
		}
	}
}

func mustIdentity(r *http.Request) identity {
	if v := r.Context().Value(identityKey); v != nil {
		if id, ok := v.(identity); ok {
			return id
		}
	}
	return identityFromRequest(r)
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
	var orgCount, transferCount int
	_ = db.QueryRow("select count(*) from organizations").Scan(&orgCount)
	_ = db.QueryRow("select count(*) from transfers").Scan(&transferCount)
	_ = json.NewEncoder(w).Encode(map[string]any{
		"ok":            true,
		"organizations": orgCount,
		"transfers":      transferCount,
	})
}

func accountsSearchHandler(w http.ResponseWriter, r *http.Request) {
	id := mustIdentity(r)
	clauses := []string{"org_id = ?"}
	args := []any{id.OrgID}
	if currency := strings.TrimSpace(r.URL.Query().Get("currency")); currency != "" {
		clauses = append(clauses, "currency = ?")
		args = append(args, currency)
	}
	if accountNo := strings.TrimSpace(r.URL.Query().Get("accountNo")); accountNo != "" {
		clauses = append(clauses, "account_no like ?")
		args = append(args, "%"+accountNo+"%")
	}
	query := "select id, account_no, currency, balance from accounts where " + strings.Join(clauses, " and ") + " order by id desc"
	rows, err := db.Query(query, args...)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var data []map[string]any
	for rows.Next() {
		var accountID int64
		var accountNo, currency string
		var balance float64
		_ = rows.Scan(&accountID, &accountNo, &currency, &balance)
		data = append(data, map[string]any{
			"id":        accountID,
			"accountNo": accountNo,
			"currency":  currency,
			"balance":   balance,
		})
	}
	_ = json.NewEncoder(w).Encode(map[string]any{"data": data})
}

func beneficiariesHandler(w http.ResponseWriter, r *http.Request) {
	id := mustIdentity(r)
	switch r.Method {
	case http.MethodPost:
		var body struct {
			Name     string `json:"name"`
			BankName string `json:"bankName"`
			Routing  string `json:"routing"`
		}
		_ = json.NewDecoder(r.Body).Decode(&body)
		res, err := db.Exec("insert into beneficiaries (org_id, name, bank_name, routing) values (?, ?, ?, ?)", id.OrgID, body.Name, body.BankName, body.Routing)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		insertedID, _ := res.LastInsertId()
		_ = json.NewEncoder(w).Encode(map[string]any{"id": insertedID, "name": body.Name})
	default:
		nameQuery := strings.TrimSpace(r.URL.Query().Get("name"))
		query := "select id, name, bank_name, routing from beneficiaries where org_id = ?"
		args := []any{id.OrgID}
		if nameQuery != "" {
			query += " and name like ?"
			args = append(args, "%"+nameQuery+"%")
		}
		rows, err := db.Query(query+" order by id desc", args...)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		var data []map[string]any
		for rows.Next() {
			var beneficiaryID int64
			var name, bankName, routing string
			_ = rows.Scan(&beneficiaryID, &name, &bankName, &routing)
			data = append(data, map[string]any{
				"id":       beneficiaryID,
				"name":     name,
				"bankName": bankName,
				"routing":  routing,
			})
		}
		_ = json.NewEncoder(w).Encode(map[string]any{"data": data})
	}
}

func transfersHandler(w http.ResponseWriter, r *http.Request) {
	id := mustIdentity(r)
	switch r.Method {
	case http.MethodPost:
		var body struct {
			AccountID     int64   `json:"accountId"`
			BeneficiaryID int64   `json:"beneficiaryId"`
			Amount        float64 `json:"amount"`
			Currency      string  `json:"currency"`
			Memo          string  `json:"memo"`
		}
		_ = json.NewDecoder(r.Body).Decode(&body)
		res, err := db.Exec(
			"insert into transfers (org_id, account_id, beneficiary_id, amount, currency, status, memo, requested_by) values (?, ?, ?, ?, ?, ?, ?, ?)",
			id.OrgID, body.AccountID, body.BeneficiaryID, body.Amount, body.Currency, "queued", body.Memo, id.UserID,
		)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		transferID, _ := res.LastInsertId()
		jobs <- job{TransferID: transferID, OrgID: id.OrgID}
		_ = json.NewEncoder(w).Encode(map[string]any{"id": transferID, "status": "queued"})
	default:
		rows, err := db.Query("select id, account_id, beneficiary_id, amount, currency, status, memo from transfers where org_id = ? order by id desc limit 50", id.OrgID)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		var data []map[string]any
		for rows.Next() {
			var transferID, accountID, beneficiaryID int64
			var amount float64
			var currency, status, memo string
			_ = rows.Scan(&transferID, &accountID, &beneficiaryID, &amount, &currency, &status, &memo)
			data = append(data, map[string]any{
				"id":           transferID,
				"accountId":    accountID,
				"beneficiaryId": beneficiaryID,
				"amount":       amount,
				"currency":     currency,
				"status":       status,
				"memo":         memo,
			})
		}
		_ = json.NewEncoder(w).Encode(map[string]any{"data": data})
	}
}

func settlementHandler(w http.ResponseWriter, r *http.Request) {
	id := mustIdentity(r)
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	var body struct {
		TransferID int64 `json:"transferId"`
	}
	_ = json.NewDecoder(r.Body).Decode(&body)
	jobs <- job{TransferID: body.TransferID, OrgID: id.OrgID}
	_ = json.NewEncoder(w).Encode(map[string]any{"queued": true, "transferId": body.TransferID})
}

func auditHandler(w http.ResponseWriter, r *http.Request) {
	id := mustIdentity(r)
	rows, err := db.Query("select id, actor, action, resource, details, created_at from audit_records where org_id = ? order by id desc limit 100", id.OrgID)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var data []map[string]any
	for rows.Next() {
		var auditID int64
		var actor, action, resource, details, createdAt string
		_ = rows.Scan(&auditID, &actor, &action, &resource, &details, &createdAt)
		data = append(data, map[string]any{
			"id": auditID, "actor": actor, "action": action, "resource": resource, "details": details, "createdAt": createdAt,
		})
	}
	_ = json.NewEncoder(w).Encode(map[string]any{"data": data})
}

func audit(orgID int64, actor, action, resource string, details map[string]any) {
	_, _ = db.Exec(
		"insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)",
		orgID, actor, action, resource, mustJSON(details),
	)
}

func mustJSON(v any) string {
	b, _ := json.Marshal(v)
	return string(b)
}

func getenv(key, fallback string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return fallback
}

portal/package-lock.json

{
  "name": "treasury-portal",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "treasury-portal",
      "version": "1.0.0",
      "dependencies": {
        "better-sqlite3": "^11.10.0",
        "express": "^4.21.2",
        "helmet": "8.3.0",
        "multer": "^1.4.5-lts.1"
      },
      "devDependencies": {
        "@types/express": "^5.0.3",
        "@types/multer": "^1.4.12",
        "@types/node": "^22.15.3",
        "ts-node": "^10.9.2",
        "typescript": "^5.8.3"
      }
    },
    "node_modules/@cspotcode/source-map-support": {
      "version": "0.8.1",
      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@jridgewell/trace-mapping": "0.3.9"
      },
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/@jridgewell/resolve-uri": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=6.0.0"
      }
    },
    "node_modules/@jridgewell/sourcemap-codec": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
      "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@jridgewell/trace-mapping": {
      "version": "0.3.9",
      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@jridgewell/resolve-uri": "^3.0.3",
        "@jridgewell/sourcemap-codec": "^1.4.10"
      }
    },
    "node_modules/@tsconfig/node10": {
      "version": "1.0.13",
      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz",
      "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@tsconfig/node12": {
      "version": "1.0.11",
      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@tsconfig/node14": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@tsconfig/node16": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
      "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@types/body-parser": {
      "version": "1.19.6",
      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
      "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/connect": "*",
        "@types/node": "*"
      }
    },
    "node_modules/@types/connect": {
      "version": "3.4.38",
      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/@types/express": {
      "version": "5.0.6",
      "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
      "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/body-parser": "*",
        "@types/express-serve-static-core": "^5.0.0",
        "@types/serve-static": "^2"
      }
    },
    "node_modules/@types/express-serve-static-core": {
      "version": "5.1.3",
      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz",
      "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/node": "*",
        "@types/qs": "*",
        "@types/range-parser": "*",
        "@types/send": "*"
      }
    },
    "node_modules/@types/http-errors": {
      "version": "2.0.5",
      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
      "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@types/multer": {
      "version": "1.4.13",
      "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
      "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/express": "*"
      }
    },
    "node_modules/@types/node": {
      "version": "22.20.2",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.2.tgz",
      "integrity": "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "undici-types": "~6.21.0"
      }
    },
    "node_modules/@types/qs": {
      "version": "6.15.1",
      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
      "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@types/range-parser": {
      "version": "1.2.7",
      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@types/send": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
      "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/@types/serve-static": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
      "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/http-errors": "*",
        "@types/node": "*"
      }
    },
    "node_modules/accepts": {
      "version": "1.3.8",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
      "license": "MIT",
      "dependencies": {
        "mime-types": "~2.1.34",
        "negotiator": "0.6.3"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/acorn": {
      "version": "8.18.0",
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
      "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
      "dev": true,
      "license": "MIT",
      "bin": {
        "acorn": "bin/acorn"
      },
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/acorn-walk": {
      "version": "8.3.5",
      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
      "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "acorn": "^8.11.0"
      },
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/append-field": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
      "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
      "license": "MIT"
    },
    "node_modules/arg": {
      "version": "4.1.3",
      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/array-flatten": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
      "license": "MIT"
    },
    "node_modules/base64-js": {
      "version": "1.5.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/better-sqlite3": {
      "version": "11.10.0",
      "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
      "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
      "hasInstallScript": true,
      "license": "MIT",
      "dependencies": {
        "bindings": "^1.5.0",
        "prebuild-install": "^7.1.1"
      }
    },
    "node_modules/bindings": {
      "version": "1.5.0",
      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
      "license": "MIT",
      "dependencies": {
        "file-uri-to-path": "1.0.0"
      }
    },
    "node_modules/bl": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
      "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
      "license": "MIT",
      "dependencies": {
        "buffer": "^5.5.0",
        "inherits": "^2.0.4",
        "readable-stream": "^3.4.0"
      }
    },
    "node_modules/bl/node_modules/readable-stream": {
      "version": "3.6.2",
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
      "license": "MIT",
      "dependencies": {
        "inherits": "^2.0.3",
        "string_decoder": "^1.1.1",
        "util-deprecate": "^1.0.1"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/body-parser": {
      "version": "1.20.8",
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz",
      "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==",
      "license": "MIT",
      "dependencies": {
        "bytes": "~3.1.2",
        "content-type": "~1.0.5",
        "debug": "2.6.9",
        "depd": "2.0.0",
        "destroy": "~1.2.0",
        "http-errors": "~2.0.1",
        "iconv-lite": "~0.4.24",
        "on-finished": "~2.4.1",
        "qs": "~6.16.0",
        "raw-body": "~2.5.3",
        "type-is": "~1.6.18",
        "unpipe": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8",
        "npm": "1.2.8000 || >= 1.4.16"
      }
    },
    "node_modules/body-parser/node_modules/qs": {
      "version": "6.16.0",
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
      "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
      "license": "BSD-3-Clause",
      "dependencies": {
        "es-define-property": "^1.0.1",
        "side-channel": "^1.1.1"
      },
      "engines": {
        "node": ">=0.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/buffer": {
      "version": "5.7.1",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "base64-js": "^1.3.1",
        "ieee754": "^1.1.13"
      }
    },
    "node_modules/buffer-from": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
      "license": "MIT"
    },
    "node_modules/busboy": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
      "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
      "dependencies": {
        "streamsearch": "^1.1.0"
      },
      "engines": {
        "node": ">=10.16.0"
      }
    },
    "node_modules/bytes": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/call-bound": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "get-intrinsic": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/chownr": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
      "license": "ISC"
    },
    "node_modules/concat-stream": {
      "version": "1.6.2",
      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
      "engines": [
        "node >= 0.8"
      ],
      "license": "MIT",
      "dependencies": {
        "buffer-from": "^1.0.0",
        "inherits": "^2.0.3",
        "readable-stream": "^2.2.2",
        "typedarray": "^0.0.6"
      }
    },
    "node_modules/content-disposition": {
      "version": "0.5.4",
      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
      "license": "MIT",
      "dependencies": {
        "safe-buffer": "5.2.1"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/content-type": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/cookie": {
      "version": "0.7.2",
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/cookie-signature": {
      "version": "1.0.7",
      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
      "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
      "license": "MIT"
    },
    "node_modules/core-util-is": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
      "license": "MIT"
    },
    "node_modules/create-require": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/debug": {
      "version": "2.6.9",
      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
      "license": "MIT",
      "dependencies": {
        "ms": "2.0.0"
      }
    },
    "node_modules/decompress-response": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
      "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
      "license": "MIT",
      "dependencies": {
        "mimic-response": "^3.1.0"
      },
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/deep-extend": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
      "license": "MIT",
      "engines": {
        "node": ">=4.0.0"
      }
    },
    "node_modules/depd": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/destroy": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8",
        "npm": "1.2.8000 || >= 1.4.16"
      }
    },
    "node_modules/detect-libc": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
      "license": "Apache-2.0",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/diff": {
      "version": "4.0.4",
      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
      "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
      "dev": true,
      "license": "BSD-3-Clause",
      "engines": {
        "node": ">=0.3.1"
      }
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/ee-first": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
      "license": "MIT"
    },
    "node_modules/encodeurl": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/end-of-stream": {
      "version": "1.4.5",
      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
      "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
      "license": "MIT",
      "dependencies": {
        "once": "^1.4.0"
      }
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-object-atoms": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
      "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/escape-html": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
      "license": "MIT"
    },
    "node_modules/etag": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/expand-template": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
      "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
      "license": "(MIT OR WTFPL)",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/express": {
      "version": "4.22.2",
      "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
      "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
      "license": "MIT",
      "dependencies": {
        "accepts": "~1.3.8",
        "array-flatten": "1.1.1",
        "body-parser": "~1.20.5",
        "content-disposition": "~0.5.4",
        "content-type": "~1.0.4",
        "cookie": "~0.7.1",
        "cookie-signature": "~1.0.6",
        "debug": "2.6.9",
        "depd": "2.0.0",
        "encodeurl": "~2.0.0",
        "escape-html": "~1.0.3",
        "etag": "~1.8.1",
        "finalhandler": "~1.3.1",
        "fresh": "~0.5.2",
        "http-errors": "~2.0.0",
        "merge-descriptors": "1.0.3",
        "methods": "~1.1.2",
        "on-finished": "~2.4.1",
        "parseurl": "~1.3.3",
        "path-to-regexp": "~0.1.12",
        "proxy-addr": "~2.0.7",
        "qs": "~6.15.1",
        "range-parser": "~1.2.1",
        "safe-buffer": "5.2.1",
        "send": "~0.19.0",
        "serve-static": "~1.16.2",
        "setprototypeof": "1.2.0",
        "statuses": "~2.0.1",
        "type-is": "~1.6.18",
        "utils-merge": "1.0.1",
        "vary": "~1.1.2"
      },
      "engines": {
        "node": ">= 0.10.0"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/file-uri-to-path": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
      "license": "MIT"
    },
    "node_modules/finalhandler": {
      "version": "1.3.2",
      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
      "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
      "license": "MIT",
      "dependencies": {
        "debug": "2.6.9",
        "encodeurl": "~2.0.0",
        "escape-html": "~1.0.3",
        "on-finished": "~2.4.1",
        "parseurl": "~1.3.3",
        "statuses": "~2.0.2",
        "unpipe": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/forwarded": {
      "version": "0.2.0",
      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/fresh": {
      "version": "0.5.2",
      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/fs-constants": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
      "license": "MIT"
    },
    "node_modules/function-bind": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-intrinsic": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
      "license": "MIT",
      "dependencies": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/github-from-package": {
      "version": "0.0.0",
      "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
      "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
      "license": "MIT"
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-symbols": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/hasown": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
      "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
      "license": "MIT",
      "dependencies": {
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/helmet": {
      "version": "8.3.0",
      "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.3.0.tgz",
      "integrity": "sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w==",
      "license": "MIT",
      "engines": {
        "node": ">=18.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/EvanHahn"
      }
    },
    "node_modules/http-errors": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
      "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
      "license": "MIT",
      "dependencies": {
        "depd": "~2.0.0",
        "inherits": "~2.0.4",
        "setprototypeof": "~1.2.0",
        "statuses": "~2.0.2",
        "toidentifier": "~1.0.1"
      },
      "engines": {
        "node": ">= 0.8"
      },
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/express"
      }
    },
    "node_modules/iconv-lite": {
      "version": "0.4.24",
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
      "license": "MIT",
      "dependencies": {
        "safer-buffer": ">= 2.1.2 < 3"
      },
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/ieee754": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "BSD-3-Clause"
    },
    "node_modules/inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
      "license": "ISC"
    },
    "node_modules/ini": {
      "version": "1.3.8",
      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
      "license": "ISC"
    },
    "node_modules/ipaddr.js": {
      "version": "1.9.1",
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/isarray": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
      "license": "MIT"
    },
    "node_modules/make-error": {
      "version": "1.3.6",
      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
      "dev": true,
      "license": "ISC"
    },
    "node_modules/math-intrinsics": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/media-typer": {
      "version": "0.3.0",
      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/merge-descriptors": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/methods": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
      "license": "MIT",
      "bin": {
        "mime": "cli.js"
      },
      "engines": {
        "node": ">=4"
      }
    },
    "node_modules/mime-db": {
      "version": "1.52.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "2.1.35",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
      "license": "MIT",
      "dependencies": {
        "mime-db": "1.52.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mimic-response": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
      "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
      "license": "MIT",
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/minimist": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/mkdirp": {
      "version": "0.5.6",
      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
      "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
      "license": "MIT",
      "dependencies": {
        "minimist": "^1.2.6"
      },
      "bin": {
        "mkdirp": "bin/cmd.js"
      }
    },
    "node_modules/mkdirp-classic": {
      "version": "0.5.3",
      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
      "license": "MIT"
    },
    "node_modules/ms": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
      "license": "MIT"
    },
    "node_modules/multer": {
      "version": "1.4.5-lts.2",
      "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
      "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
      "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.",
      "license": "MIT",
      "dependencies": {
        "append-field": "^1.0.0",
        "busboy": "^1.0.0",
        "concat-stream": "^1.5.2",
        "mkdirp": "^0.5.4",
        "object-assign": "^4.1.1",
        "type-is": "^1.6.4",
        "xtend": "^4.0.0"
      },
      "engines": {
        "node": ">= 6.0.0"
      }
    },
    "node_modules/napi-build-utils": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
      "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
      "license": "MIT"
    },
    "node_modules/negotiator": {
      "version": "0.6.3",
      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/node-abi": {
      "version": "3.96.0",
      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz",
      "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",
      "license": "MIT",
      "dependencies": {
        "semver": "^7.3.5"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/object-assign": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/object-inspect": {
      "version": "1.13.4",
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/on-finished": {
      "version": "2.4.1",
      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
      "license": "MIT",
      "dependencies": {
        "ee-first": "1.1.1"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/once": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
      "license": "ISC",
      "dependencies": {
        "wrappy": "1"
      }
    },
    "node_modules/parseurl": {
      "version": "1.3.3",
      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/path-to-regexp": {
      "version": "0.1.13",
      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
      "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
      "license": "MIT"
    },
    "node_modules/prebuild-install": {
      "version": "7.1.3",
      "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
      "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
      "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
      "license": "MIT",
      "dependencies": {
        "detect-libc": "^2.0.0",
        "expand-template": "^2.0.3",
        "github-from-package": "0.0.0",
        "minimist": "^1.2.3",
        "mkdirp-classic": "^0.5.3",
        "napi-build-utils": "^2.0.0",
        "node-abi": "^3.3.0",
        "pump": "^3.0.0",
        "rc": "^1.2.7",
        "simple-get": "^4.0.0",
        "tar-fs": "^2.0.0",
        "tunnel-agent": "^0.6.0"
      },
      "bin": {
        "prebuild-install": "bin.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/process-nextick-args": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
      "license": "MIT"
    },
    "node_modules/proxy-addr": {
      "version": "2.0.7",
      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
      "license": "MIT",
      "dependencies": {
        "forwarded": "0.2.0",
        "ipaddr.js": "1.9.1"
      },
      "engines": {
        "node": ">= 0.10"
      }
    },
    "node_modules/pump": {
      "version": "3.0.4",
      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
      "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
      "license": "MIT",
      "dependencies": {
        "end-of-stream": "^1.1.0",
        "once": "^1.3.1"
      }
    },
    "node_modules/qs": {
      "version": "6.15.3",
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
      "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
      "license": "BSD-3-Clause",
      "dependencies": {
        "es-define-property": "^1.0.1",
        "side-channel": "^1.1.1"
      },
      "engines": {
        "node": ">=0.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/range-parser": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/raw-body": {
      "version": "2.5.3",
      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
      "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
      "license": "MIT",
      "dependencies": {
        "bytes": "~3.1.2",
        "http-errors": "~2.0.1",
        "iconv-lite": "~0.4.24",
        "unpipe": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/rc": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
      "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
      "dependencies": {
        "deep-extend": "^0.6.0",
        "ini": "~1.3.0",
        "minimist": "^1.2.0",
        "strip-json-comments": "~2.0.1"
      },
      "bin": {
        "rc": "cli.js"
      }
    },
    "node_modules/readable-stream": {
      "version": "2.3.8",
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
      "license": "MIT",
      "dependencies": {
        "core-util-is": "~1.0.0",
        "inherits": "~2.0.3",
        "isarray": "~1.0.0",
        "process-nextick-args": "~2.0.0",
        "safe-buffer": "~5.1.1",
        "string_decoder": "~1.1.1",
        "util-deprecate": "~1.0.1"
      }
    },
    "node_modules/readable-stream/node_modules/safe-buffer": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
      "license": "MIT"
    },
    "node_modules/safe-buffer": {
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/safer-buffer": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
      "license": "MIT"
    },
    "node_modules/semver": {
      "version": "7.8.5",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
      "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/send": {
      "version": "0.19.2",
      "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
      "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
      "license": "MIT",
      "dependencies": {
        "debug": "2.6.9",
        "depd": "2.0.0",
        "destroy": "1.2.0",
        "encodeurl": "~2.0.0",
        "escape-html": "~1.0.3",
        "etag": "~1.8.1",
        "fresh": "~0.5.2",
        "http-errors": "~2.0.1",
        "mime": "1.6.0",
        "ms": "2.1.3",
        "on-finished": "~2.4.1",
        "range-parser": "~1.2.1",
        "statuses": "~2.0.2"
      },
      "engines": {
        "node": ">= 0.8.0"
      }
    },
    "node_modules/send/node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
      "license": "MIT"
    },
    "node_modules/serve-static": {
      "version": "1.16.3",
      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
      "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
      "license": "MIT",
      "dependencies": {
        "encodeurl": "~2.0.0",
        "escape-html": "~1.0.3",
        "parseurl": "~1.3.3",
        "send": "~0.19.1"
      },
      "engines": {
        "node": ">= 0.8.0"
      }
    },
    "node_modules/setprototypeof": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
      "license": "ISC"
    },
    "node_modules/side-channel": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
      "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "object-inspect": "^1.13.4",
        "side-channel-list": "^1.0.1",
        "side-channel-map": "^1.0.1",
        "side-channel-weakmap": "^1.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-list": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
      "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "object-inspect": "^1.13.4"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-map": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
      "license": "MIT",
      "dependencies": {
        "call-bound": "^1.0.2",
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.5",
        "object-inspect": "^1.13.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/side-channel-weakmap": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
      "license": "MIT",
      "dependencies": {
        "call-bound": "^1.0.2",
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.5",
        "object-inspect": "^1.13.3",
        "side-channel-map": "^1.0.1"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/simple-concat": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/simple-get": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
      "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "decompress-response": "^6.0.0",
        "once": "^1.3.1",
        "simple-concat": "^1.0.0"
      }
    },
    "node_modules/statuses": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/streamsearch": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
      "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
      "engines": {
        "node": ">=10.0.0"
      }
    },
    "node_modules/string_decoder": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
      "license": "MIT",
      "dependencies": {
        "safe-buffer": "~5.1.0"
      }
    },
    "node_modules/string_decoder/node_modules/safe-buffer": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
      "license": "MIT"
    },
    "node_modules/strip-json-comments": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/tar-fs": {
      "version": "2.1.5",
      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
      "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
      "license": "MIT",
      "dependencies": {
        "chownr": "^1.1.1",
        "mkdirp-classic": "^0.5.2",
        "pump": "^3.0.0",
        "tar-stream": "^2.1.4"
      }
    },
    "node_modules/tar-stream": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
      "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
      "license": "MIT",
      "dependencies": {
        "bl": "^4.0.3",
        "end-of-stream": "^1.4.1",
        "fs-constants": "^1.0.0",
        "inherits": "^2.0.3",
        "readable-stream": "^3.1.1"
      },
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/tar-stream/node_modules/readable-stream": {
      "version": "3.6.2",
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
      "license": "MIT",
      "dependencies": {
        "inherits": "^2.0.3",
        "string_decoder": "^1.1.1",
        "util-deprecate": "^1.0.1"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/toidentifier": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
      "license": "MIT",
      "engines": {
        "node": ">=0.6"
      }
    },
    "node_modules/ts-node": {
      "version": "10.9.2",
      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
      "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@cspotcode/source-map-support": "^0.8.0",
        "@tsconfig/node10": "^1.0.7",
        "@tsconfig/node12": "^1.0.7",
        "@tsconfig/node14": "^1.0.0",
        "@tsconfig/node16": "^1.0.2",
        "acorn": "^8.4.1",
        "acorn-walk": "^8.1.1",
        "arg": "^4.1.0",
        "create-require": "^1.1.0",
        "diff": "^4.0.1",
        "make-error": "^1.1.1",
        "v8-compile-cache-lib": "^3.0.1",
        "yn": "3.1.1"
      },
      "bin": {
        "ts-node": "dist/bin.js",
        "ts-node-cwd": "dist/bin-cwd.js",
        "ts-node-esm": "dist/bin-esm.js",
        "ts-node-script": "dist/bin-script.js",
        "ts-node-transpile-only": "dist/bin-transpile.js",
        "ts-script": "dist/bin-script-deprecated.js"
      },
      "peerDependencies": {
        "@swc/core": ">=1.2.50",
        "@swc/wasm": ">=1.2.50",
        "@types/node": "*",
        "typescript": ">=2.7"
      },
      "peerDependenciesMeta": {
        "@swc/core": {
          "optional": true
        },
        "@swc/wasm": {
          "optional": true
        }
      }
    },
    "node_modules/tunnel-agent": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
      "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
      "license": "Apache-2.0",
      "dependencies": {
        "safe-buffer": "^5.0.1"
      },
      "engines": {
        "node": "*"
      }
    },
    "node_modules/type-is": {
      "version": "1.6.18",
      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
      "license": "MIT",
      "dependencies": {
        "media-typer": "0.3.0",
        "mime-types": "~2.1.24"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/typedarray": {
      "version": "0.0.6",
      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
      "license": "MIT"
    },
    "node_modules/typescript": {
      "version": "5.9.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
      "dev": true,
      "license": "Apache-2.0",
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/undici-types": {
      "version": "6.21.0",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/unpipe": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/util-deprecate": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
      "license": "MIT"
    },
    "node_modules/utils-merge": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4.0"
      }
    },
    "node_modules/v8-compile-cache-lib": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/vary": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
      "license": "MIT",
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/wrappy": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
      "license": "ISC"
    },
    "node_modules/xtend": {
      "version": "4.0.2",
      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
      "license": "MIT",
      "engines": {
        "node": ">=0.4"
      }
    },
    "node_modules/yn": {
      "version": "3.1.1",
      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    }
  }
}

portal/package.json

{
  "name": "treasury-portal",
  "private": true,
  "version": "1.0.0",
  "type": "commonjs",
  "scripts": {
    "dev": "ts-node src/server.ts",
    "build": "tsc -p tsconfig.json",
    "start": "node dist/server.js"
  },
  "dependencies": {
    "better-sqlite3": "^11.10.0",
    "helmet": "8.3.0",
    "express": "^4.21.2",
    "multer": "^1.4.5-lts.1"
  },
  "devDependencies": {
    "@types/express": "^5.0.3",
    "@types/multer": "^1.4.12",
    "@types/node": "^22.15.3",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.3"
  }
}

portal/src/server.ts

import express, { NextFunction, Request, Response } from "express";
import fs from "fs";
import path from "path";
import multer from "multer";
import helmet from "helmet";
import { db, recordAudit, storagePath } from "./store.js";

type Identity = {
  orgId: number;
  userId: string;
  role: "admin" | "manager" | "viewer";
};

declare global {
  namespace Express {
    interface Request {
      identity?: Identity;
    }
  }
}

const app = express();
const upload = multer({ dest: path.resolve(__dirname, "..", "tmp") });

fs.mkdirSync(path.resolve(__dirname, "..", "tmp"), { recursive: true });

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(helmet());

app.use((req, res, next) => {
  const orgId = Number(req.header("x-org-id") || "1");
  const userId = req.header("x-user-id") || "portal-user";
  const role = (req.header("x-role") || "viewer") as Identity["role"];
  req.identity = { orgId, userId, role };
  next();
});

app.use((req, res, next) => {
  const finish = () => {
    if (!req.identity) {
      return;
    }
    recordAudit({
      orgId: req.identity.orgId,
      actor: req.identity.userId,
      action: `${req.method} ${req.path}`,
      resource: req.originalUrl,
      details: {
        statusCode: res.statusCode,
        query: req.query,
        body: req.body
      }
    });
  };
  res.on("finish", finish);
  next();
});

function requireRole(allowed: Identity["role"][]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.identity || !allowed.includes(req.identity.role)) {
      res.status(403).json({ error: "forbidden" });
      return;
    }
    next();
  };
}

function buildOrgFilters(query: Request["query"]) {
  const clauses: string[] = [];
  const params: unknown[] = [];

  if (typeof query.name === "string" && query.name.trim()) {
    clauses.push("name like ?");
    params.push(`%${query.name.trim()}%`);
  }
  if (typeof query.region === "string" && query.region.trim()) {
    clauses.push("region = ?");
    params.push(query.region.trim());
  }
  if (typeof query.tier === "string" && query.tier.trim()) {
    clauses.push("tier = ?");
    params.push(query.tier.trim());
  }

  const where = clauses.length ? ` where ${clauses.join(" and ")}` : "";
  return { where, params };
}

app.get("/health", (_req, res) => {
  const counts = {
    organizations: db.prepare("select count(*) as count from organizations").get().count,
    transfers: db.prepare("select count(*) as count from transfers").get().count,
    auditRecords: db.prepare("select count(*) as count from audit_records").get().count
  };
  res.json({ ok: true, counts });
});

app.get("/orgs", requireRole(["admin", "manager", "viewer"]), (req, res) => {
  const { where, params } = buildOrgFilters(req.query);
  const sqlParts = ["select * from organizations"];
  if (where) {
    sqlParts.push(where);
  }
  sqlParts.push("order by id desc");
  const rows = db.prepare(sqlParts.join(" ")).all(...params);
  res.json({ data: rows });
});

app.post("/orgs", requireRole(["admin"]), (req, res) => {
  const { name, orgCode, region, tier } = req.body;
  const stmt = db.prepare(
    "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)"
  );
  const result = stmt.run(name, orgCode, region, tier);
  res.status(201).json({ id: result.lastInsertRowid, name, orgCode, region, tier });
});

app.get("/teams", requireRole(["admin", "manager"]), (req, res) => {
  const filters: string[] = [];
  const params: unknown[] = [];

  if (typeof req.query.orgId === "string" && req.query.orgId.trim()) {
    filters.push("org_id = ?");
    params.push(Number(req.query.orgId));
  }
  if (typeof req.query.query === "string" && req.query.query.trim()) {
    filters.push("(name like ? or cost_center like ?)");
    params.push(`%${req.query.query.trim()}%`, `%${req.query.query.trim()}%`);
  }

  const where = filters.length ? ` where ${filters.join(" and ")}` : "";
  const sqlParts = ["select * from teams"];
  if (where) {
    sqlParts.push(where);
  }
  sqlParts.push("order by created_at desc");
  const rows = db.prepare(sqlParts.join(" ")).all(...params);
  res.json({ data: rows });
});

app.post("/users", requireRole(["admin", "manager"]), (req, res) => {
  const { orgId, teamId, email, displayName, role, status } = req.body;
  const result = db
    .prepare(
      "insert into users (org_id, team_id, email, display_name, role, status) values (?, ?, ?, ?, ?, ?)"
    )
    .run(orgId, teamId ?? null, email, displayName, role, status || "active");
  res.status(201).json({ id: result.lastInsertRowid, orgId, teamId, email, displayName, role });
});

app.post("/beneficiaries", requireRole(["admin", "manager"]), (req, res) => {
  const { orgId, name, bankName, routing } = req.body;
  const result = db
    .prepare("insert into beneficiaries (org_id, name, bank_name, routing) values (?, ?, ?, ?)")
    .run(orgId, name, bankName, routing);
  res.status(201).json({ id: result.lastInsertRowid });
});

app.post("/transfers", requireRole(["admin", "manager"]), (req, res) => {
  const { orgId, accountId, beneficiaryId, amount, currency, memo } = req.body;
  const result = db
    .prepare(
      "insert into transfers (org_id, account_id, beneficiary_id, amount, currency, status, memo, requested_by) values (?, ?, ?, ?, ?, ?, ?, ?)"
    )
    .run(orgId, accountId, beneficiaryId, amount, currency, "queued", memo || "", req.identity?.userId || "unknown");
  res.status(201).json({ id: result.lastInsertRowid, status: "queued" });
});

app.post("/documents/upload", requireRole(["admin", "manager"]), upload.single("file"), (req, res) => {
  const file = req.file;
  if (!file) {
    res.status(400).json({ error: "missing file" });
    return;
  }

  const targetName = `${Date.now()}-${file.originalname}`;
  let targetPath: string;
  try {
    targetPath = storagePath(targetName);
  } catch (_error) {
    res.status(400).json({ error: "invalid file name" });
    return;
  }
  fs.copyFileSync(file.path, targetPath);
  const result = db
    .prepare(
      "insert into documents (org_id, filename, original_name, mime_type) values (?, ?, ?, ?)"
    )
    .run(req.identity?.orgId || 1, targetName, file.originalname, file.mimetype);

  res.status(201).json({ id: result.lastInsertRowid, filename: targetName });
});

app.get("/documents/:name", requireRole(["admin", "manager", "viewer"]), (req, res) => {
  let filePath: string;
  try {
    filePath = storagePath(req.params.name);
  } catch (_error) {
    res.status(400).json({ error: "invalid file name" });
    return;
  }
  if (!fs.existsSync(filePath)) {
    res.status(404).json({ error: "not found" });
    return;
  }
  res.setHeader("content-type", "application/octet-stream");
  res.send(fs.readFileSync(filePath));
});

app.get("/reports/org-summary", requireRole(["admin", "manager"]), (req, res) => {
  const orgId = Number(req.query.orgId || req.identity?.orgId || 1);
  const org = db.prepare("select * from organizations where id = ?").get(orgId);
  const transfers = db
    .prepare("select * from transfers where org_id = ? order by created_at desc limit 25")
    .all(orgId) as Array<{
    currency: string;
    amount: number;
    status: string;
    memo: string;
  }>;
  const title = typeof req.query.title === "string" ? req.query.title : "Treasury Summary";
  const html = `
    <html>
      <body>
        <h1>${title}</h1>
        <h2>${org?.name ?? "Unknown org"}</h2>
        <p>Region: ${org?.region ?? "n/a"} Tier: ${org?.tier ?? "n/a"}</p>
        <ul>
          ${transfers
            .map(
              (transfer) =>
                `<li>${transfer.currency} ${transfer.amount} - ${transfer.status} - ${transfer.memo}</li>`
            )
            .join("")}
        </ul>
      </body>
    </html>
  `;
  res.type("html").send(html);
});

app.get("/audit", requireRole(["admin"]), (req, res) => {
  const orgId = typeof req.query.orgId === "string" ? Number(req.query.orgId) : req.identity?.orgId;
  const rows = db
    .prepare("select * from audit_records where org_id = ? order by created_at desc limit 100")
    .all(orgId);
  res.json({ data: rows });
});

app.use((_req, res) => {
  res.status(404).json({ error: "not found" });
});

const port = Number(process.env.PORT || 4100);

if (require.main === module) {
  app.listen(port, () => {
    console.log(`portal listening on ${port}`);
  });
}

export { app };

portal/src/store.js

const fs = require("fs");
const path = require("path");
const Database = require("better-sqlite3");

const baseDir = path.resolve(__dirname, "..", "data");
const storageDir = path.join(baseDir, "documents");
const dbPath = path.join(baseDir, "portal.db");

fs.mkdirSync(storageDir, { recursive: true });

const db = new Database(dbPath);
db.pragma("journal_mode = WAL");

db.exec(`
  create table if not exists organizations (
    id integer primary key autoincrement,
    name text not null,
    org_code text not null unique,
    region text not null,
    tier text not null,
    created_at text not null default (datetime('now'))
  );
  create table if not exists teams (
    id integer primary key autoincrement,
    org_id integer not null,
    name text not null,
    cost_center text not null,
    status text not null default 'active',
    created_at text not null default (datetime('now'))
  );
  create table if not exists users (
    id integer primary key autoincrement,
    org_id integer not null,
    team_id integer,
    email text not null,
    display_name text not null,
    role text not null,
    status text not null default 'active',
    created_at text not null default (datetime('now'))
  );
  create table if not exists accounts (
    id integer primary key autoincrement,
    org_id integer not null,
    account_no text not null,
    currency text not null,
    balance numeric not null default 0,
    created_at text not null default (datetime('now'))
  );
  create table if not exists beneficiaries (
    id integer primary key autoincrement,
    org_id integer not null,
    name text not null,
    bank_name text not null,
    routing text not null,
    created_at text not null default (datetime('now'))
  );
  create table if not exists transfers (
    id integer primary key autoincrement,
    org_id integer not null,
    account_id integer not null,
    beneficiary_id integer not null,
    amount numeric not null,
    currency text not null,
    status text not null,
    memo text not null,
    requested_by text not null,
    created_at text not null default (datetime('now'))
  );
  create table if not exists documents (
    id integer primary key autoincrement,
    org_id integer not null,
    filename text not null,
    original_name text not null,
    mime_type text not null,
    created_at text not null default (datetime('now'))
  );
  create table if not exists audit_records (
    id integer primary key autoincrement,
    org_id integer,
    actor text not null,
    action text not null,
    resource text not null,
    details text not null,
    created_at text not null default (datetime('now'))
  );
`);

function seed() {
  const count = db.prepare("select count(*) as count from organizations").get().count;
  if (count > 0) {
    return;
  }

  const insertOrg = db.prepare("insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)");
  const insertTeam = db.prepare("insert into teams (org_id, name, cost_center, status) values (?, ?, ?, ?)");
  const insertUser = db.prepare("insert into users (org_id, team_id, email, display_name, role, status) values (?, ?, ?, ?, ?, ?)");
  const insertAccount = db.prepare("insert into accounts (org_id, account_no, currency, balance) values (?, ?, ?, ?)");
  const insertBeneficiary = db.prepare("insert into beneficiaries (org_id, name, bank_name, routing) values (?, ?, ?, ?)");

  const acme = insertOrg.run("Acme Treasury", "acme", "us-east", "enterprise").lastInsertRowid;
  const north = insertOrg.run("Northwind Holdings", "northwind", "eu-west", "growth").lastInsertRowid;

  const ops = insertTeam.run(acme, "Operations", "100-ops", "active").lastInsertRowid;
  const rev = insertTeam.run(acme, "Revenue", "200-rev", "active").lastInsertRowid;
  const comp = insertTeam.run(north, "Compliance", "300-comp", "active").lastInsertRowid;

  insertUser.run(acme, ops, "treasury.lead@acme.test", "Dana Ledger", "admin", "active");
  insertUser.run(acme, rev, "payments.ops@acme.test", "Mika Flow", "manager", "active");
  insertUser.run(north, comp, "risk.officer@northwind.test", "Riley Guard", "viewer", "active");

  insertAccount.run(acme, "ACCT-10001", "USD", 1250000);
  insertAccount.run(acme, "ACCT-10002", "EUR", 860000);
  insertAccount.run(north, "ACCT-20001", "USD", 410000);

  insertBeneficiary.run(acme, "Vendor One", "Harbor Bank", "110000");
  insertBeneficiary.run(acme, "Payroll Service", "Metro Trust", "220000");
  insertBeneficiary.run(north, "Regional Supplier", "Union Bank", "330000");
}

seed();

function recordAudit({ orgId = null, actor, action, resource, details }) {
  db.prepare(
    "insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)"
  ).run(orgId, actor, action, resource, JSON.stringify(details));
}

function storagePath(name) {
  return path.join(storageDir, name);
}

module.exports = {
  db,
  storageDir,
  storagePath,
  recordAudit
};

portal/tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "allowJs": true,
    "skipLibCheck": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"]
}

risk-api/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.treasury</groupId>
  <artifactId>risk-api</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>2.3.232</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.18.3</version>
    </dependency>
  </dependencies>
</project>

risk-api/src/main/java/com/treasury/risk/RiskApi.java

package com.treasury.risk;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class RiskApi {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static final Path DATA_DIR = Path.of("data");
    private static final Path DB_PATH = DATA_DIR.resolve("risk.mv.db");
    private static Connection db;

    record Identity(long orgId, String userId, String role) {}

    @FunctionalInterface
    interface RouteHandler {
        void handle(HttpExchange exchange) throws Exception;
    }

    public static void main(String[] args) throws Exception {
        Files.createDirectories(DATA_DIR);
        initDb();

        int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "4400"));
        HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
        server.createContext("/health", wrap(RiskApi::health));
        server.createContext("/risk/exposures", wrap(requireRoles(RiskApi::exposures, "admin", "manager", "viewer")));
        server.createContext("/risk/reviews", wrap(requireRoles(RiskApi::reviews, "admin", "manager")));
        server.createContext("/risk/report", wrap(requireRoles(RiskApi::report, "admin", "manager")));
        server.createContext("/audit", wrap(requireRoles(RiskApi::audit, "admin")));
        server.start();
        System.out.println("risk-api listening on " + port);
    }

    private static void initDb() throws Exception {
        db = DriverManager.getConnection("jdbc:h2:file:" + DATA_DIR.resolve("risk").toAbsolutePath());
        try (Statement st = db.createStatement()) {
            st.execute("""
                create table if not exists organizations (
                    id bigint auto_increment primary key,
                    name varchar not null,
                    org_code varchar not null unique,
                    region varchar not null,
                    tier varchar not null
                )
            """);
            st.execute("""
                create table if not exists teams (
                    id bigint auto_increment primary key,
                    org_id bigint not null,
                    name varchar not null,
                    cost_center varchar not null,
                    status varchar not null
                )
            """);
            st.execute("""
                create table if not exists users (
                    id bigint auto_increment primary key,
                    org_id bigint not null,
                    team_id bigint,
                    email varchar not null,
                    display_name varchar not null,
                    role varchar not null
                )
            """);
            st.execute("""
                create table if not exists accounts (
                    id bigint auto_increment primary key,
                    org_id bigint not null,
                    account_no varchar not null,
                    currency varchar not null,
                    balance decimal not null
                )
            """);
            st.execute("""
                create table if not exists beneficiaries (
                    id bigint auto_increment primary key,
                    org_id bigint not null,
                    name varchar not null,
                    bank_name varchar not null,
                    routing varchar not null
                )
            """);
            st.execute("""
                create table if not exists transfers (
                    id bigint auto_increment primary key,
                    org_id bigint not null,
                    account_id bigint not null,
                    beneficiary_id bigint not null,
                    amount decimal not null,
                    currency varchar not null,
                    status varchar not null,
                    memo varchar not null,
                    requested_by varchar not null
                )
            """);
            st.execute("""
                create table if not exists risk_reviews (
                    id bigint auto_increment primary key,
                    org_id bigint not null,
                    subject varchar not null,
                    score decimal not null,
                    note varchar not null,
                    created_at timestamp default current_timestamp
                )
            """);
            st.execute("""
                create table if not exists audit_records (
                    id bigint auto_increment primary key,
                    org_id bigint,
                    actor varchar not null,
                    action varchar not null,
                    resource varchar not null,
                    details varchar not null,
                    created_at timestamp default current_timestamp
                )
            """);
        }
        try (Statement st = db.createStatement(); ResultSet rs = st.executeQuery("select count(*) from organizations")) {
            rs.next();
            if (rs.getInt(1) == 0) {
                seed();
            }
        }
    }

    private static void seed() throws Exception {
        try (PreparedStatement org = db.prepareStatement("insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)")) {
            org.setString(1, "Acme Treasury");
            org.setString(2, "acme");
            org.setString(3, "us-east");
            org.setString(4, "enterprise");
            org.executeUpdate();

            org.setString(1, "Northwind Holdings");
            org.setString(2, "northwind");
            org.setString(3, "eu-west");
            org.setString(4, "growth");
            org.executeUpdate();
        }
    }

    private static HttpHandler wrap(RouteHandler handler) {
        return exchange -> {
            try {
                Identity identity = identity(exchange);
                exchange.setAttribute("identity", identity);
                handler.handle(exchange);
                audit(identity.orgId(), identity.userId(), exchange.getRequestMethod() + " " + exchange.getRequestURI().getPath(), exchange.getRequestURI().toString(), Map.of("status", "completed"));
            } catch (Exception e) {
                sendJson(exchange, 500, Map.of("error", e.getMessage()));
            }
        };
    }

    private static RouteHandler requireRoles(RouteHandler handler, String... roles) {
        return exchange -> {
            Identity identity = (Identity) exchange.getAttribute("identity");
            if (identity == null) {
                identity = identity(exchange);
                exchange.setAttribute("identity", identity);
            }
            for (String role : roles) {
                if (role.equals(identity.role())) {
                    handler.handle(exchange);
                    return;
                }
            }
            sendJson(exchange, 403, Map.of("error", "forbidden"));
        };
    }

    private static Identity identity(HttpExchange exchange) {
        Headers headers = exchange.getRequestHeaders();
        long orgId = parseLong(headers.getFirst("X-Org-Id"), 1L);
        String userId = headers.getFirst("X-User-Id");
        if (userId == null || userId.isBlank()) {
            userId = "risk-user";
        }
        String role = headers.getFirst("X-Role");
        if (role == null || role.isBlank()) {
            role = "viewer";
        }
        return new Identity(orgId, userId, role);
    }

    private static void health(HttpExchange exchange) throws Exception {
        try (Statement st = db.createStatement(); ResultSet rs = st.executeQuery("select count(*) from risk_reviews")) {
            rs.next();
            sendJson(exchange, 200, Map.of("ok", true, "riskReviews", rs.getInt(1)));
        }
    }

    private static void exposures(HttpExchange exchange) throws Exception {
        Identity identity = (Identity) exchange.getAttribute("identity");
        Map<String, String> query = query(exchange.getRequestURI());
        List<String> clauses = new ArrayList<>();
        List<Object> args = new ArrayList<>();
        clauses.add("org_id = ?");
        args.add(identity.orgId());
        if (query.containsKey("status") && !query.get("status").isBlank()) {
            clauses.add("status = ?");
            args.add(query.get("status"));
        }
        if (query.containsKey("currency") && !query.get("currency").isBlank()) {
            clauses.add("currency = ?");
            args.add(query.get("currency"));
        }
        if (query.containsKey("memo") && !query.get("memo").isBlank()) {
            clauses.add("memo like ?");
            args.add("%" + query.get("memo") + "%");
        }
        String sql = "select id, account_id, beneficiary_id, amount, currency, status, memo from transfers where " + String.join(" and ", clauses) + " order by id desc";
        List<Map<String, Object>> rows = query(sql, args);
        sendJson(exchange, 200, Map.of("data", rows));
    }

    private static void reviews(HttpExchange exchange) throws Exception {
        Identity identity = (Identity) exchange.getAttribute("identity");
        if ("POST".equalsIgnoreCase(exchange.getRequestMethod())) {
            Map<String, Object> body = readJson(exchange.getRequestBody());
            try (PreparedStatement ps = db.prepareStatement("insert into risk_reviews (org_id, subject, score, note) values (?, ?, ?, ?)")) {
                ps.setLong(1, identity.orgId());
                ps.setString(2, String.valueOf(body.get("subject")));
                ps.setBigDecimal(3, new java.math.BigDecimal(String.valueOf(body.get("score"))));
                ps.setString(4, String.valueOf(body.get("note")));
                ps.executeUpdate();
            }
            sendJson(exchange, 201, Map.of("created", true));
            return;
        }
        List<Map<String, Object>> rows = query(
            "select id, subject, score, note, created_at from risk_reviews where org_id = ? order by id desc",
            List.of(identity.orgId())
        );
        sendJson(exchange, 200, Map.of("data", rows));
    }

    private static void report(HttpExchange exchange) throws Exception {
        Identity identity = (Identity) exchange.getAttribute("identity");
        Map<String, String> query = query(exchange.getRequestURI());
        String title = query.getOrDefault("title", "Risk Exposure Report");
        String subject = query.getOrDefault("subject", "Treasury Portfolio");
        String note = query.getOrDefault("note", "No note supplied");
        List<Map<String, Object>> rows = query(
            "select id, amount, currency, status from transfers where org_id = ? order by id desc limit 10",
            List.of(identity.orgId())
        );
        StringBuilder html = new StringBuilder();
        html.append("<html><body><h1>").append(title).append("</h1>");
        html.append("<h2>").append(subject).append("</h2>");
        html.append("<p>").append(note).append("</p>");
        html.append("<ul>");
        for (Map<String, Object> row : rows) {
            html.append("<li>").append(row.get("currency")).append(" ").append(row.get("amount")).append(" ").append(row.get("status")).append("</li>");
        }
        html.append("</ul></body></html>");
        sendText(exchange, 200, html.toString(), "text/html");
    }

    private static void audit(HttpExchange exchange) throws Exception {
        Identity identity = (Identity) exchange.getAttribute("identity");
        List<Map<String, Object>> rows = query(
            "select id, actor, action, resource, details, created_at from audit_records where org_id = ? order by id desc limit 100",
            List.of(identity.orgId())
        );
        sendJson(exchange, 200, Map.of("data", rows));
    }

    private static void audit(long orgId, String actor, String action, String resource, Map<String, Object> details) throws Exception {
        try (PreparedStatement ps = db.prepareStatement("insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)")) {
            ps.setLong(1, orgId);
            ps.setString(2, actor);
            ps.setString(3, action);
            ps.setString(4, resource);
            ps.setString(5, MAPPER.writeValueAsString(details));
            ps.executeUpdate();
        }
    }

    private static List<Map<String, Object>> query(String sql, List<Object> args) throws Exception {
        try (PreparedStatement ps = db.prepareStatement(sql)) {
            for (int i = 0; i < args.size(); i++) {
                ps.setObject(i + 1, args.get(i));
            }
            try (ResultSet rs = ps.executeQuery()) {
                List<Map<String, Object>> rows = new ArrayList<>();
                while (rs.next()) {
                    Map<String, Object> row = new LinkedHashMap<>();
                    int cols = rs.getMetaData().getColumnCount();
                    for (int i = 1; i <= cols; i++) {
                        row.put(rs.getMetaData().getColumnLabel(i), rs.getObject(i));
                    }
                    rows.add(row);
                }
                return rows;
            }
        }
    }

    private static Map<String, String> query(URI uri) {
        Map<String, String> out = new HashMap<>();
        String raw = uri.getRawQuery();
        if (raw == null || raw.isBlank()) {
            return out;
        }
        for (String part : raw.split("&")) {
            int idx = part.indexOf('=');
            String key = idx >= 0 ? part.substring(0, idx) : part;
            String value = idx >= 0 ? part.substring(idx + 1) : "";
            out.put(urlDecode(key), urlDecode(value));
        }
        return out;
    }

    private static Map<String, Object> readJson(InputStream inputStream) throws IOException {
        return MAPPER.readValue(inputStream, new TypeReference<>() {});
    }

    private static void sendJson(HttpExchange exchange, int status, Map<String, Object> payload) throws IOException {
        byte[] data = MAPPER.writeValueAsBytes(payload);
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(status, data.length);
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(data);
        }
    }

    private static void sendText(HttpExchange exchange, int status, String text, String contentType) throws IOException {
        byte[] data = text.getBytes(StandardCharsets.UTF_8);
        exchange.getResponseHeaders().set("Content-Type", contentType);
        exchange.sendResponseHeaders(status, data.length);
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(data);
        }
    }

    private static long parseLong(String value, long fallback) {
        try {
            return value == null ? fallback : Long.parseLong(value);
        } catch (NumberFormatException e) {
            return fallback;
        }
    }

    private static String urlDecode(String value) {
        return URLDecoder.decode(value, StandardCharsets.UTF_8);
    }
}