docs-worker/app.py
Before remediation
1from __future__ import annotations23import json4import os5import queue6import sqlite37import threading8from datetime import datetime9from pathlib import Path1011from flask import Flask, Response, g, jsonify, request, send_file1213BASE_DIR = Path(__file__).resolve().parent14DATA_DIR = BASE_DIR / "data"15STORAGE_DIR = DATA_DIR / "storage"16DB_PATH = DATA_DIR / "docs.db"1718DATA_DIR.mkdir(parents=True, exist_ok=True)19STORAGE_DIR.mkdir(parents=True, exist_ok=True)2021app = Flask(__name__)22job_queue: "queue.Queue[dict]" = queue.Queue()232425def get_db() -> sqlite3.Connection:26 if "db" not in g:27 conn = sqlite3.connect(DB_PATH)28 conn.row_factory = sqlite3.Row29 g.db = conn30 return g.db313233def init_db() -> None:34 db = sqlite3.connect(DB_PATH)35 db.executescript(36 """37 create table if not exists organizations (38 id integer primary key autoincrement,39 name text not null,40 org_code text not null unique,41 region text not null,42 tier text not null43 );44 create table if not exists teams (45 id integer primary key autoincrement,46 org_id integer not null,47 name text not null,48 cost_center text not null,49 status text not null default 'active'50 );51 create table if not exists users (52 id integer primary key autoincrement,53 org_id integer not null,54 team_id integer,55 email text not null,56 display_name text not null,57 role text not null,58 status text not null default 'active'59 );60 create table if not exists accounts (61 id integer primary key autoincrement,62 org_id integer not null,63 account_no text not null,64 currency text not null,65 balance numeric not null default 066 );67 create table if not exists beneficiaries (68 id integer primary key autoincrement,69 org_id integer not null,70 name text not null,71 bank_name text not null,72 routing text not null73 );74 create table if not exists transfers (75 id integer primary key autoincrement,76 org_id integer not null,77 account_id integer not null,78 beneficiary_id integer not null,79 amount numeric not null,80 currency text not null,81 status text not null,82 memo text not null,83 requested_by text not null84 );85 create table if not exists documents (86 id integer primary key autoincrement,87 org_id integer not null,88 filename text not null,89 original_name text not null,90 mime_type text not null,91 kind text not null,92 created_at text not null default current_timestamp93 );94 create table if not exists risk_reviews (95 id integer primary key autoincrement,96 org_id integer not null,97 subject text not null,98 score numeric not null,99 note text not null,100 created_at text not null default current_timestamp101 );102 create table if not exists audit_records (103 id integer primary key autoincrement,104 org_id integer,105 actor text not null,106 action text not null,107 resource text not null,108 details text not null,109 created_at text not null default current_timestamp110 );111 """112 )113 if db.execute("select count(*) from organizations").fetchone()[0] == 0:114 db.execute(115 "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)",116 ("Acme Treasury", "acme", "us-east", "enterprise"),117 )118 db.execute(119 "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)",120 ("Northwind Holdings", "northwind", "eu-west", "growth"),121 )122 db.commit()123 db.close()124125126def current_identity() -> dict:127 return {128 "org_id": int(request.headers.get("X-Org-Id", "1")),129 "user_id": request.headers.get("X-User-Id", "docs-user"),130 "role": request.headers.get("X-Role", "viewer"),131 }132133134@app.before_request135def load_identity() -> None:136 g.identity = current_identity()137 g.started_at = datetime.utcnow().isoformat()138139140@app.after_request141def audit_request(response: Response) -> Response:142 identity = getattr(g, "identity", None)143 if identity:144 db = get_db()145 db.execute(146 "insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)",147 (148 identity["org_id"],149 identity["user_id"],150 f"{request.method} {request.path}",151 request.full_path,152 json.dumps({"status": response.status_code, "args": request.args.to_dict(), "json": request.get_json(silent=True)}),153 ),154 )155 db.commit()156 return response157158159@app.teardown_appcontext160def close_db(exc: Exception | None) -> None:161 db = g.pop("db", None)162 if db is not None:163 db.close()164165166def require_roles(*allowed: str):167 def decorator(fn):168 def wrapped(*args, **kwargs):169 if g.identity["role"] not in allowed:170 return jsonify({"error": "forbidden"}), 403171 return fn(*args, **kwargs)172173 wrapped.__name__ = fn.__name__174 return wrapped175176 return decorator177178179def render_report(org_name: str, title: str, notes: str) -> str:180 return f"""181 <html>182 <body>183 <h1>{title}</h1>184 <h2>{org_name}</h2>185 <div>{notes}</div>186 </body>187 </html>188 """189190191def enqueue_job(payload: dict) -> None:192 job_queue.put(payload)193194195def job_worker() -> None:196 while True:197 payload = job_queue.get()198 if payload is None:199 break200 db = sqlite3.connect(DB_PATH)201 db.execute(202 "insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)",203 (204 payload.get("org_id"),205 "system",206 "job.dispatch",207 payload.get("kind", "document"),208 json.dumps(payload),209 ),210 )211 if payload.get("kind") == "document":212 filename = payload["filename"]213 target = STORAGE_DIR / filename214 target.write_text(payload.get("content", ""), encoding="utf-8")215 db.execute(216 "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",217 (218 payload.get("org_id"),219 filename,220 payload.get("original_name", filename),221 payload.get("mime_type", "text/plain"),222 payload.get("kind", "document"),223 ),224 )225 db.commit()226 db.close()227 job_queue.task_done()228229230init_db()231threading.Thread(target=job_worker, daemon=True).start()232233234@app.route("/health", methods=["GET"])235def health() -> tuple:236 db = get_db()237 counts = {238 "documents": db.execute("select count(*) from documents").fetchone()[0],239 "audit_records": db.execute("select count(*) from audit_records").fetchone()[0],240 "jobs_pending": job_queue.qsize(),241 }242 return jsonify({"ok": True, "counts": counts})243244245@app.route("/documents", methods=["GET"])246@require_roles("admin", "manager", "viewer")247def search_documents() -> tuple:248 clauses = ["org_id = ?"]249 params: list[object] = [g.identity["org_id"]]250 kind = request.args.get("kind", "").strip()251 name = request.args.get("name", "").strip()252 if kind:253 clauses.append("kind = ?")254 params.append(kind)255 if name:256 clauses.append("original_name like ?")257 params.append(f"%{name}%")258 rows = get_db().execute(259 f"select id, filename, original_name, mime_type, kind, created_at from documents where {' and '.join(clauses)} order by id desc",260 params,261 ).fetchall()262 return jsonify({"data": [dict(row) for row in rows]})263264265@app.route("/docs/render", methods=["POST"])266@require_roles("admin", "manager")267def render_document() -> tuple:268 payload = request.get_json(force=True)269 org_name = payload["orgName"]270 title = payload.get("title", "Treasury Notice")271 notes = payload.get("notes", "")272 filename = payload.get("filename", f"report-{int(datetime.utcnow().timestamp())}.html")273 html = render_report(org_name, title, notes)274 target = STORAGE_DIR / filename275 target.write_text(html, encoding="utf-8")276 db = get_db()277 db.execute(278 "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",279 (g.identity["org_id"], filename, filename, "text/html", "report"),280 )281 db.commit()282 return jsonify({"filename": filename, "path": str(target)}), 201283284285@app.route("/docs/upload", methods=["POST"])286@require_roles("admin", "manager")287def upload_document() -> tuple:288 upload = request.files.get("file")289 if upload is None:290 return jsonify({"error": "missing file"}), 400291 filename = f"{int(datetime.utcnow().timestamp())}-{upload.filename}"292 target = STORAGE_DIR / filename293 upload.save(target)294 db = get_db()295 db.execute(296 "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",297 (g.identity["org_id"], filename, upload.filename, upload.mimetype or "application/octet-stream", "upload"),298 )299 db.commit()300 return jsonify({"filename": filename}), 201301302303@app.route("/docs/download/<path:filename>", methods=["GET"])304@require_roles("admin", "manager", "viewer")305def download_document(filename: str):306 target = STORAGE_DIR / filename307 if not target.exists():308 return jsonify({"error": "not found"}), 404309 return send_file(target, as_attachment=True)310311312@app.route("/jobs/dispatch", methods=["POST"])313@require_roles("admin", "manager")314def dispatch_job() -> tuple:315 payload = request.get_json(force=True)316 payload["org_id"] = g.identity["org_id"]317 enqueue_job(payload)318 return jsonify({"queued": True, "kind": payload.get("kind", "document")}), 202319320321@app.route("/reports/summary", methods=["GET"])322@require_roles("admin", "manager")323def summary_report() -> tuple:324 db = get_db()325 org_id = int(request.args.get("orgId", g.identity["org_id"]))326 title = request.args.get("title", "Document Summary")327 org = db.execute("select name, region, tier from organizations where id = ?", (org_id,)).fetchone()328 if org is None:329 return jsonify({"error": "missing org"}), 404330 notes = request.args.get("notes", "No notes supplied")331 html = render_report(org["name"], title, notes)332 return Response(html, mimetype="text/html")333334335if __name__ == "__main__":336 app.run(host="0.0.0.0", port=int(os.environ.get("PORT", "4300")), debug=False)
After remediation
1from __future__ import annotations23import json4import os5import queue6import sqlite37import threading8from datetime import datetime9from pathlib import Path1011from flask import Flask, Response, g, jsonify, request, send_file1213BASE_DIR = Path(__file__).resolve().parent14DATA_DIR = Path(os.environ.get("DOCS_WORKER_DATA_DIR", str(BASE_DIR / "data"))).resolve()15STORAGE_DIR = DATA_DIR / "storage"16DB_PATH = DATA_DIR / "docs.db"1718DATA_DIR.mkdir(parents=True, exist_ok=True)19STORAGE_DIR.mkdir(parents=True, exist_ok=True)2021app = Flask(__name__)22job_queue: "queue.Queue[dict]" = queue.Queue()232425def get_db() -> sqlite3.Connection:26 if "db" not in g:27 conn = sqlite3.connect(DB_PATH)28 conn.row_factory = sqlite3.Row29 g.db = conn30 return g.db313233def init_db() -> None:34 db = sqlite3.connect(DB_PATH)35 db.executescript(36 """37 create table if not exists organizations (38 id integer primary key autoincrement,39 name text not null,40 org_code text not null unique,41 region text not null,42 tier text not null43 );44 create table if not exists teams (45 id integer primary key autoincrement,46 org_id integer not null,47 name text not null,48 cost_center text not null,49 status text not null default 'active'50 );51 create table if not exists users (52 id integer primary key autoincrement,53 org_id integer not null,54 team_id integer,55 email text not null,56 display_name text not null,57 role text not null,58 status text not null default 'active'59 );60 create table if not exists accounts (61 id integer primary key autoincrement,62 org_id integer not null,63 account_no text not null,64 currency text not null,65 balance numeric not null default 066 );67 create table if not exists beneficiaries (68 id integer primary key autoincrement,69 org_id integer not null,70 name text not null,71 bank_name text not null,72 routing text not null73 );74 create table if not exists transfers (75 id integer primary key autoincrement,76 org_id integer not null,77 account_id integer not null,78 beneficiary_id integer not null,79 amount numeric not null,80 currency text not null,81 status text not null,82 memo text not null,83 requested_by text not null84 );85 create table if not exists documents (86 id integer primary key autoincrement,87 org_id integer not null,88 filename text not null,89 original_name text not null,90 mime_type text not null,91 kind text not null,92 created_at text not null default current_timestamp93 );94 create table if not exists risk_reviews (95 id integer primary key autoincrement,96 org_id integer not null,97 subject text not null,98 score numeric not null,99 note text not null,100 created_at text not null default current_timestamp101 );102 create table if not exists audit_records (103 id integer primary key autoincrement,104 org_id integer,105 actor text not null,106 action text not null,107 resource text not null,108 details text not null,109 created_at text not null default current_timestamp110 );111 """112 )113 if db.execute("select count(*) from organizations").fetchone()[0] == 0:114 db.execute(115 "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)",116 ("Acme Treasury", "acme", "us-east", "enterprise"),117 )118 db.execute(119 "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)",120 ("Northwind Holdings", "northwind", "eu-west", "growth"),121 )122 db.commit()123 db.close()124125126def current_identity() -> dict:127 return {128 "org_id": int(request.headers.get("X-Org-Id", "1")),129 "user_id": request.headers.get("X-User-Id", "docs-user"),130 "role": request.headers.get("X-Role", "viewer"),131 }132133134@app.before_request135def load_identity() -> None:136 g.identity = current_identity()137 g.started_at = datetime.utcnow().isoformat()138139140@app.after_request141def audit_request(response: Response) -> Response:142 identity = getattr(g, "identity", None)143 if identity:144 db = get_db()145 db.execute(146 "insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)",147 (148 identity["org_id"],149 identity["user_id"],150 f"{request.method} {request.path}",151 request.full_path,152 json.dumps({"status": response.status_code, "args": request.args.to_dict(), "json": request.get_json(silent=True)}),153 ),154 )155 db.commit()156 return response157158159@app.teardown_appcontext160def close_db(exc: Exception | None) -> None:161 db = g.pop("db", None)162 if db is not None:163 db.close()164165166def require_roles(*allowed: str):167 def decorator(fn):168 def wrapped(*args, **kwargs):169 if g.identity["role"] not in allowed:170 return jsonify({"error": "forbidden"}), 403171 return fn(*args, **kwargs)172173 wrapped.__name__ = fn.__name__174 return wrapped175176 return decorator177178179def render_report(org_name: str, title: str, notes: str) -> str:180 return f"""181 <html>182 <body>183 <h1>{title}</h1>184 <h2>{org_name}</h2>185 <div>{notes}</div>186 </body>187 </html>188 """189190191def resolve_storage_path(filename: str, *, must_exist: bool = False) -> Path:192 if not isinstance(filename, str) or not filename:193 raise ValueError("invalid filename")194 if filename in {".", ".."}:195 raise ValueError("invalid filename")196 if "\x00" in filename:197 raise ValueError("invalid filename")198 candidate_name = Path(filename).name199 if candidate_name != filename or Path(filename).is_absolute():200 raise ValueError("invalid filename")201202 base_dir = STORAGE_DIR.resolve()203 target = base_dir / filename204205 if must_exist:206 resolved = target.resolve(strict=True)207 if resolved.parent != base_dir:208 raise ValueError("invalid filename")209 return resolved210211 if target.exists():212 resolved = target.resolve(strict=True)213 if resolved.parent != base_dir:214 raise ValueError("invalid filename")215216 return target217218219def enqueue_job(payload: dict) -> None:220 job_queue.put(payload)221222223def job_worker() -> None:224 while True:225 payload = job_queue.get()226 if payload is None:227 break228 db = sqlite3.connect(DB_PATH)229 db.execute(230 "insert into audit_records (org_id, actor, action, resource, details) values (?, ?, ?, ?, ?)",231 (232 payload.get("org_id"),233 "system",234 "job.dispatch",235 payload.get("kind", "document"),236 json.dumps(payload),237 ),238 )239 if payload.get("kind") == "document":240 filename = payload["filename"]241 target = STORAGE_DIR / filename242 target.write_text(payload.get("content", ""), encoding="utf-8")243 db.execute(244 "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",245 (246 payload.get("org_id"),247 filename,248 payload.get("original_name", filename),249 payload.get("mime_type", "text/plain"),250 payload.get("kind", "document"),251 ),252 )253 db.commit()254 db.close()255 job_queue.task_done()256257258init_db()259threading.Thread(target=job_worker, daemon=True).start()260261262@app.route("/health", methods=["GET"])263def health() -> tuple:264 db = get_db()265 counts = {266 "documents": db.execute("select count(*) from documents").fetchone()[0],267 "audit_records": db.execute("select count(*) from audit_records").fetchone()[0],268 "jobs_pending": job_queue.qsize(),269 }270 return jsonify({"ok": True, "counts": counts})271272273@app.route("/documents", methods=["GET"])274@require_roles("admin", "manager", "viewer")275def search_documents() -> tuple:276 clauses = ["org_id = ?"]277 params: list[object] = [g.identity["org_id"]]278 kind = request.args.get("kind", "").strip()279 name = request.args.get("name", "").strip()280 if kind:281 clauses.append("kind = ?")282 params.append(kind)283 if name:284 clauses.append("original_name like ?")285 params.append(f"%{name}%")286 rows = get_db().execute(287 f"select id, filename, original_name, mime_type, kind, created_at from documents where {' and '.join(clauses)} order by id desc",288 params,289 ).fetchall()290 return jsonify({"data": [dict(row) for row in rows]})291292293@app.route("/docs/render", methods=["POST"])294@require_roles("admin", "manager")295def render_document() -> tuple:296 payload = request.get_json(force=True)297 org_name = payload["orgName"]298 title = payload.get("title", "Treasury Notice")299 notes = payload.get("notes", "")300 filename = payload.get("filename", f"report-{int(datetime.utcnow().timestamp())}.html")301 html = render_report(org_name, title, notes)302 try:303 target = resolve_storage_path(filename)304 except ValueError:305 return jsonify({"error": "invalid filename"}), 400306 target.write_text(html, encoding="utf-8")307 db = get_db()308 db.execute(309 "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",310 (g.identity["org_id"], filename, filename, "text/html", "report"),311 )312 db.commit()313 return jsonify({"filename": filename, "path": str(target)}), 201314315316@app.route("/docs/upload", methods=["POST"])317@require_roles("admin", "manager")318def upload_document() -> tuple:319 upload = request.files.get("file")320 if upload is None:321 return jsonify({"error": "missing file"}), 400322 filename = f"{int(datetime.utcnow().timestamp())}-{upload.filename}"323 try:324 target = resolve_storage_path(filename)325 except ValueError:326 return jsonify({"error": "invalid filename"}), 400327 upload.save(target)328 db = get_db()329 db.execute(330 "insert into documents (org_id, filename, original_name, mime_type, kind) values (?, ?, ?, ?, ?)",331 (g.identity["org_id"], filename, upload.filename, upload.mimetype or "application/octet-stream", "upload"),332 )333 db.commit()334 return jsonify({"filename": filename}), 201335336337@app.route("/docs/download/<path:filename>", methods=["GET"])338@require_roles("admin", "manager", "viewer")339def download_document(filename: str):340 try:341 target = resolve_storage_path(filename, must_exist=True)342 except FileNotFoundError:343 return jsonify({"error": "not found"}), 404344 except ValueError:345 return jsonify({"error": "invalid filename"}), 400346 return send_file(target, as_attachment=True)347348349@app.route("/jobs/dispatch", methods=["POST"])350@require_roles("admin", "manager")351def dispatch_job() -> tuple:352 payload = request.get_json(force=True)353 payload["org_id"] = g.identity["org_id"]354 enqueue_job(payload)355 return jsonify({"queued": True, "kind": payload.get("kind", "document")}), 202356357358@app.route("/reports/summary", methods=["GET"])359@require_roles("admin", "manager")360def summary_report() -> tuple:361 db = get_db()362 org_id = int(request.args.get("orgId", g.identity["org_id"]))363 title = request.args.get("title", "Document Summary")364 org = db.execute("select name, region, tier from organizations where id = ?", (org_id,)).fetchone()365 if org is None:366 return jsonify({"error": "missing org"}), 404367 notes = request.args.get("notes", "No notes supplied")368 html = render_report(org["name"], title, notes)369 return Response(html, mimetype="text/html")370371372if __name__ == "__main__":373 app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "4300")), debug=False)
docs-worker/requirements.txt
Before remediation
1Flask==3.0.3
After remediation
1Flask==3.1.3
docs-worker/test_app.py
Before remediation
missing
After remediation
1import os2import shutil3import tempfile4import importlib5import runpy6import unittest7from pathlib import Path8from urllib.parse import quote9from unittest.mock import patch101112TEST_DATA_DIR = tempfile.mkdtemp(prefix="docs-worker-test-")13os.environ["DOCS_WORKER_DATA_DIR"] = TEST_DATA_DIR1415docs_app = importlib.import_module("app")161718class DownloadDocumentTests(unittest.TestCase):19 @classmethod20 def setUpClass(cls):21 docs_app.app.config["TESTING"] = True22 cls.client = docs_app.app.test_client()23 cls.storage_dir = Path(TEST_DATA_DIR) / "storage"2425 @classmethod26 def tearDownClass(cls):27 shutil.rmtree(TEST_DATA_DIR, ignore_errors=True)2829 def test_download_rejects_traversal_and_symlink_escape(self):30 outside = Path(TEST_DATA_DIR) / "outside.txt"31 outside.write_text("secret", encoding="utf-8")32 self.storage_dir.mkdir(parents=True, exist_ok=True)33 (self.storage_dir / "escape.txt").symlink_to(outside)3435 traversal = self.client.get(36 f"/docs/download/{quote('../outside.txt', safe='')}",37 headers={"X-Role": "viewer", "X-Org-Id": "1"},38 )39 self.assertEqual(traversal.status_code, 400)40 self.assertEqual(traversal.get_json(), {"error": "invalid filename"})4142 symlink = self.client.get(43 "/docs/download/escape.txt",44 headers={"X-Role": "viewer", "X-Org-Id": "1"},45 )46 self.assertEqual(symlink.status_code, 400)47 self.assertEqual(symlink.get_json(), {"error": "invalid filename"})4849 def test_download_returns_existing_file(self):50 self.storage_dir.mkdir(parents=True, exist_ok=True)51 target = self.storage_dir / "statement.txt"52 target.write_text("document-body", encoding="utf-8")5354 response = self.client.get(55 "/docs/download/statement.txt",56 headers={"X-Role": "viewer", "X-Org-Id": "1"},57 )58 self.assertEqual(response.status_code, 200)59 self.assertEqual(response.data, b"document-body")6061 def test_main_binds_to_loopback(self):62 with patch("flask.Flask.run") as run_mock:63 runpy.run_module("app", run_name="__main__")6465 self.assertTrue(run_mock.called)66 _, kwargs = run_mock.call_args67 self.assertEqual(kwargs["host"], "127.0.0.1")68 self.assertEqual(kwargs["debug"], False)697071if __name__ == "__main__":72 unittest.main()
portal/package-lock.json
Before remediation
missing
After remediation
1{2 "name": "treasury-portal",3 "version": "1.0.0",4 "lockfileVersion": 3,5 "requires": true,6 "packages": {7 "": {8 "name": "treasury-portal",9 "version": "1.0.0",10 "dependencies": {11 "better-sqlite3": "^11.10.0",12 "express": "^4.21.2",13 "helmet": "8.3.0",14 "multer": "^1.4.5-lts.1"15 },16 "devDependencies": {17 "@types/express": "^5.0.3",18 "@types/multer": "^1.4.12",19 "@types/node": "^22.15.3",20 "ts-node": "^10.9.2",21 "typescript": "^5.8.3"22 }23 },24 "node_modules/@cspotcode/source-map-support": {25 "version": "0.8.1",26 "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",27 "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",28 "dev": true,29 "license": "MIT",30 "dependencies": {31 "@jridgewell/trace-mapping": "0.3.9"32 },33 "engines": {34 "node": ">=12"35 }36 },37 "node_modules/@jridgewell/resolve-uri": {38 "version": "3.1.2",39 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",40 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",41 "dev": true,42 "license": "MIT",43 "engines": {44 "node": ">=6.0.0"45 }46 },47 "node_modules/@jridgewell/sourcemap-codec": {48 "version": "1.6.0",49 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",50 "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",51 "dev": true,52 "license": "MIT"53 },54 "node_modules/@jridgewell/trace-mapping": {55 "version": "0.3.9",56 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",57 "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",58 "dev": true,59 "license": "MIT",60 "dependencies": {61 "@jridgewell/resolve-uri": "^3.0.3",62 "@jridgewell/sourcemap-codec": "^1.4.10"63 }64 },65 "node_modules/@tsconfig/node10": {66 "version": "1.0.13",67 "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz",68 "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==",69 "dev": true,70 "license": "MIT"71 },72 "node_modules/@tsconfig/node12": {73 "version": "1.0.11",74 "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",75 "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",76 "dev": true,77 "license": "MIT"78 },79 "node_modules/@tsconfig/node14": {80 "version": "1.0.3",81 "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",82 "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",83 "dev": true,84 "license": "MIT"85 },86 "node_modules/@tsconfig/node16": {87 "version": "1.0.4",88 "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",89 "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",90 "dev": true,91 "license": "MIT"92 },93 "node_modules/@types/body-parser": {94 "version": "1.19.6",95 "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",96 "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",97 "dev": true,98 "license": "MIT",99 "dependencies": {100 "@types/connect": "*",101 "@types/node": "*"102 }103 },104 "node_modules/@types/connect": {105 "version": "3.4.38",106 "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",107 "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",108 "dev": true,109 "license": "MIT",110 "dependencies": {111 "@types/node": "*"112 }113 },114 "node_modules/@types/express": {115 "version": "5.0.6",116 "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",117 "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",118 "dev": true,119 "license": "MIT",120 "dependencies": {121 "@types/body-parser": "*",122 "@types/express-serve-static-core": "^5.0.0",123 "@types/serve-static": "^2"124 }125 },126 "node_modules/@types/express-serve-static-core": {127 "version": "5.1.3",128 "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz",129 "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==",130 "dev": true,131 "license": "MIT",132 "dependencies": {133 "@types/node": "*",134 "@types/qs": "*",135 "@types/range-parser": "*",136 "@types/send": "*"137 }138 },139 "node_modules/@types/http-errors": {140 "version": "2.0.5",141 "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",142 "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",143 "dev": true,144 "license": "MIT"145 },146 "node_modules/@types/multer": {147 "version": "1.4.13",148 "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",149 "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==",150 "dev": true,151 "license": "MIT",152 "dependencies": {153 "@types/express": "*"154 }155 },156 "node_modules/@types/node": {157 "version": "22.20.2",158 "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.2.tgz",159 "integrity": "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==",160 "dev": true,161 "license": "MIT",162 "dependencies": {163 "undici-types": "~6.21.0"164 }165 },166 "node_modules/@types/qs": {167 "version": "6.15.1",168 "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",169 "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",170 "dev": true,171 "license": "MIT"172 },173 "node_modules/@types/range-parser": {174 "version": "1.2.7",175 "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",176 "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",177 "dev": true,178 "license": "MIT"179 },180 "node_modules/@types/send": {181 "version": "1.2.1",182 "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",183 "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",184 "dev": true,185 "license": "MIT",186 "dependencies": {187 "@types/node": "*"188 }189 },190 "node_modules/@types/serve-static": {191 "version": "2.2.0",192 "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",193 "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",194 "dev": true,195 "license": "MIT",196 "dependencies": {197 "@types/http-errors": "*",198 "@types/node": "*"199 }200 },201 "node_modules/accepts": {202 "version": "1.3.8",203 "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",204 "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",205 "license": "MIT",206 "dependencies": {207 "mime-types": "~2.1.34",208 "negotiator": "0.6.3"209 },210 "engines": {211 "node": ">= 0.6"212 }213 },214 "node_modules/acorn": {215 "version": "8.18.0",216 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",217 "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",218 "dev": true,219 "license": "MIT",220 "bin": {221 "acorn": "bin/acorn"222 },223 "engines": {224 "node": ">=0.4.0"225 }226 },227 "node_modules/acorn-walk": {228 "version": "8.3.5",229 "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",230 "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",231 "dev": true,232 "license": "MIT",233 "dependencies": {234 "acorn": "^8.11.0"235 },236 "engines": {237 "node": ">=0.4.0"238 }239 },240 "node_modules/append-field": {241 "version": "1.0.0",242 "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",243 "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",244 "license": "MIT"245 },246 "node_modules/arg": {247 "version": "4.1.3",248 "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",249 "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",250 "dev": true,251 "license": "MIT"252 },253 "node_modules/array-flatten": {254 "version": "1.1.1",255 "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",256 "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",257 "license": "MIT"258 },259 "node_modules/base64-js": {260 "version": "1.5.1",261 "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",262 "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",263 "funding": [264 {265 "type": "github",266 "url": "https://github.com/sponsors/feross"267 },268 {269 "type": "patreon",270 "url": "https://www.patreon.com/feross"271 },272 {273 "type": "consulting",274 "url": "https://feross.org/support"275 }276 ],277 "license": "MIT"278 },279 "node_modules/better-sqlite3": {280 "version": "11.10.0",281 "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",282 "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",283 "hasInstallScript": true,284 "license": "MIT",285 "dependencies": {286 "bindings": "^1.5.0",287 "prebuild-install": "^7.1.1"288 }289 },290 "node_modules/bindings": {291 "version": "1.5.0",292 "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",293 "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",294 "license": "MIT",295 "dependencies": {296 "file-uri-to-path": "1.0.0"297 }298 },299 "node_modules/bl": {300 "version": "4.1.0",301 "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",302 "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",303 "license": "MIT",304 "dependencies": {305 "buffer": "^5.5.0",306 "inherits": "^2.0.4",307 "readable-stream": "^3.4.0"308 }309 },310 "node_modules/bl/node_modules/readable-stream": {311 "version": "3.6.2",312 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",313 "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",314 "license": "MIT",315 "dependencies": {316 "inherits": "^2.0.3",317 "string_decoder": "^1.1.1",318 "util-deprecate": "^1.0.1"319 },320 "engines": {321 "node": ">= 6"322 }323 },324 "node_modules/body-parser": {325 "version": "1.20.8",326 "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz",327 "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==",328 "license": "MIT",329 "dependencies": {330 "bytes": "~3.1.2",331 "content-type": "~1.0.5",332 "debug": "2.6.9",333 "depd": "2.0.0",334 "destroy": "~1.2.0",335 "http-errors": "~2.0.1",336 "iconv-lite": "~0.4.24",337 "on-finished": "~2.4.1",338 "qs": "~6.16.0",339 "raw-body": "~2.5.3",340 "type-is": "~1.6.18",341 "unpipe": "~1.0.0"342 },343 "engines": {344 "node": ">= 0.8",345 "npm": "1.2.8000 || >= 1.4.16"346 }347 },348 "node_modules/body-parser/node_modules/qs": {349 "version": "6.16.0",350 "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",351 "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",352 "license": "BSD-3-Clause",353 "dependencies": {354 "es-define-property": "^1.0.1",355 "side-channel": "^1.1.1"356 },357 "engines": {358 "node": ">=0.6"359 },360 "funding": {361 "url": "https://github.com/sponsors/ljharb"362 }363 },364 "node_modules/buffer": {365 "version": "5.7.1",366 "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",367 "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",368 "funding": [369 {370 "type": "github",371 "url": "https://github.com/sponsors/feross"372 },373 {374 "type": "patreon",375 "url": "https://www.patreon.com/feross"376 },377 {378 "type": "consulting",379 "url": "https://feross.org/support"380 }381 ],382 "license": "MIT",383 "dependencies": {384 "base64-js": "^1.3.1",385 "ieee754": "^1.1.13"386 }387 },388 "node_modules/buffer-from": {389 "version": "1.1.2",390 "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",391 "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",392 "license": "MIT"393 },394 "node_modules/busboy": {395 "version": "1.6.0",396 "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",397 "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",398 "dependencies": {399 "streamsearch": "^1.1.0"400 },401 "engines": {402 "node": ">=10.16.0"403 }404 },405 "node_modules/bytes": {406 "version": "3.1.2",407 "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",408 "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",409 "license": "MIT",410 "engines": {411 "node": ">= 0.8"412 }413 },414 "node_modules/call-bind-apply-helpers": {415 "version": "1.0.2",416 "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",417 "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",418 "license": "MIT",419 "dependencies": {420 "es-errors": "^1.3.0",421 "function-bind": "^1.1.2"422 },423 "engines": {424 "node": ">= 0.4"425 }426 },427 "node_modules/call-bound": {428 "version": "1.0.4",429 "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",430 "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",431 "license": "MIT",432 "dependencies": {433 "call-bind-apply-helpers": "^1.0.2",434 "get-intrinsic": "^1.3.0"435 },436 "engines": {437 "node": ">= 0.4"438 },439 "funding": {440 "url": "https://github.com/sponsors/ljharb"441 }442 },443 "node_modules/chownr": {444 "version": "1.1.4",445 "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",446 "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",447 "license": "ISC"448 },449 "node_modules/concat-stream": {450 "version": "1.6.2",451 "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",452 "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",453 "engines": [454 "node >= 0.8"455 ],456 "license": "MIT",457 "dependencies": {458 "buffer-from": "^1.0.0",459 "inherits": "^2.0.3",460 "readable-stream": "^2.2.2",461 "typedarray": "^0.0.6"462 }463 },464 "node_modules/content-disposition": {465 "version": "0.5.4",466 "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",467 "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",468 "license": "MIT",469 "dependencies": {470 "safe-buffer": "5.2.1"471 },472 "engines": {473 "node": ">= 0.6"474 }475 },476 "node_modules/content-type": {477 "version": "1.0.5",478 "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",479 "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",480 "license": "MIT",481 "engines": {482 "node": ">= 0.6"483 }484 },485 "node_modules/cookie": {486 "version": "0.7.2",487 "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",488 "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",489 "license": "MIT",490 "engines": {491 "node": ">= 0.6"492 }493 },494 "node_modules/cookie-signature": {495 "version": "1.0.7",496 "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",497 "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",498 "license": "MIT"499 },500 "node_modules/core-util-is": {501 "version": "1.0.3",502 "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",503 "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",504 "license": "MIT"505 },506 "node_modules/create-require": {507 "version": "1.1.1",508 "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",509 "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",510 "dev": true,511 "license": "MIT"512 },513 "node_modules/debug": {514 "version": "2.6.9",515 "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",516 "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",517 "license": "MIT",518 "dependencies": {519 "ms": "2.0.0"520 }521 },522 "node_modules/decompress-response": {523 "version": "6.0.0",524 "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",525 "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",526 "license": "MIT",527 "dependencies": {528 "mimic-response": "^3.1.0"529 },530 "engines": {531 "node": ">=10"532 },533 "funding": {534 "url": "https://github.com/sponsors/sindresorhus"535 }536 },537 "node_modules/deep-extend": {538 "version": "0.6.0",539 "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",540 "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",541 "license": "MIT",542 "engines": {543 "node": ">=4.0.0"544 }545 },546 "node_modules/depd": {547 "version": "2.0.0",548 "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",549 "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",550 "license": "MIT",551 "engines": {552 "node": ">= 0.8"553 }554 },555 "node_modules/destroy": {556 "version": "1.2.0",557 "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",558 "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",559 "license": "MIT",560 "engines": {561 "node": ">= 0.8",562 "npm": "1.2.8000 || >= 1.4.16"563 }564 },565 "node_modules/detect-libc": {566 "version": "2.1.2",567 "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",568 "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",569 "license": "Apache-2.0",570 "engines": {571 "node": ">=8"572 }573 },574 "node_modules/diff": {575 "version": "4.0.4",576 "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",577 "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",578 "dev": true,579 "license": "BSD-3-Clause",580 "engines": {581 "node": ">=0.3.1"582 }583 },584 "node_modules/dunder-proto": {585 "version": "1.0.1",586 "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",587 "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",588 "license": "MIT",589 "dependencies": {590 "call-bind-apply-helpers": "^1.0.1",591 "es-errors": "^1.3.0",592 "gopd": "^1.2.0"593 },594 "engines": {595 "node": ">= 0.4"596 }597 },598 "node_modules/ee-first": {599 "version": "1.1.1",600 "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",601 "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",602 "license": "MIT"603 },604 "node_modules/encodeurl": {605 "version": "2.0.0",606 "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",607 "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",608 "license": "MIT",609 "engines": {610 "node": ">= 0.8"611 }612 },613 "node_modules/end-of-stream": {614 "version": "1.4.5",615 "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",616 "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",617 "license": "MIT",618 "dependencies": {619 "once": "^1.4.0"620 }621 },622 "node_modules/es-define-property": {623 "version": "1.0.1",624 "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",625 "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",626 "license": "MIT",627 "engines": {628 "node": ">= 0.4"629 }630 },631 "node_modules/es-errors": {632 "version": "1.3.0",633 "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",634 "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",635 "license": "MIT",636 "engines": {637 "node": ">= 0.4"638 }639 },640 "node_modules/es-object-atoms": {641 "version": "1.1.2",642 "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",643 "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",644 "license": "MIT",645 "dependencies": {646 "es-errors": "^1.3.0"647 },648 "engines": {649 "node": ">= 0.4"650 }651 },652 "node_modules/escape-html": {653 "version": "1.0.3",654 "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",655 "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",656 "license": "MIT"657 },658 "node_modules/etag": {659 "version": "1.8.1",660 "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",661 "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",662 "license": "MIT",663 "engines": {664 "node": ">= 0.6"665 }666 },667 "node_modules/expand-template": {668 "version": "2.0.3",669 "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",670 "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",671 "license": "(MIT OR WTFPL)",672 "engines": {673 "node": ">=6"674 }675 },676 "node_modules/express": {677 "version": "4.22.2",678 "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",679 "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",680 "license": "MIT",681 "dependencies": {682 "accepts": "~1.3.8",683 "array-flatten": "1.1.1",684 "body-parser": "~1.20.5",685 "content-disposition": "~0.5.4",686 "content-type": "~1.0.4",687 "cookie": "~0.7.1",688 "cookie-signature": "~1.0.6",689 "debug": "2.6.9",690 "depd": "2.0.0",691 "encodeurl": "~2.0.0",692 "escape-html": "~1.0.3",693 "etag": "~1.8.1",694 "finalhandler": "~1.3.1",695 "fresh": "~0.5.2",696 "http-errors": "~2.0.0",697 "merge-descriptors": "1.0.3",698 "methods": "~1.1.2",699 "on-finished": "~2.4.1",700 "parseurl": "~1.3.3",701 "path-to-regexp": "~0.1.12",702 "proxy-addr": "~2.0.7",703 "qs": "~6.15.1",704 "range-parser": "~1.2.1",705 "safe-buffer": "5.2.1",706 "send": "~0.19.0",707 "serve-static": "~1.16.2",708 "setprototypeof": "1.2.0",709 "statuses": "~2.0.1",710 "type-is": "~1.6.18",711 "utils-merge": "1.0.1",712 "vary": "~1.1.2"713 },714 "engines": {715 "node": ">= 0.10.0"716 },717 "funding": {718 "type": "opencollective",719 "url": "https://opencollective.com/express"720 }721 },722 "node_modules/file-uri-to-path": {723 "version": "1.0.0",724 "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",725 "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",726 "license": "MIT"727 },728 "node_modules/finalhandler": {729 "version": "1.3.2",730 "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",731 "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",732 "license": "MIT",733 "dependencies": {734 "debug": "2.6.9",735 "encodeurl": "~2.0.0",736 "escape-html": "~1.0.3",737 "on-finished": "~2.4.1",738 "parseurl": "~1.3.3",739 "statuses": "~2.0.2",740 "unpipe": "~1.0.0"741 },742 "engines": {743 "node": ">= 0.8"744 }745 },746 "node_modules/forwarded": {747 "version": "0.2.0",748 "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",749 "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",750 "license": "MIT",751 "engines": {752 "node": ">= 0.6"753 }754 },755 "node_modules/fresh": {756 "version": "0.5.2",757 "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",758 "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",759 "license": "MIT",760 "engines": {761 "node": ">= 0.6"762 }763 },764 "node_modules/fs-constants": {765 "version": "1.0.0",766 "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",767 "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",768 "license": "MIT"769 },770 "node_modules/function-bind": {771 "version": "1.1.2",772 "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",773 "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",774 "license": "MIT",775 "funding": {776 "url": "https://github.com/sponsors/ljharb"777 }778 },779 "node_modules/get-intrinsic": {780 "version": "1.3.0",781 "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",782 "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",783 "license": "MIT",784 "dependencies": {785 "call-bind-apply-helpers": "^1.0.2",786 "es-define-property": "^1.0.1",787 "es-errors": "^1.3.0",788 "es-object-atoms": "^1.1.1",789 "function-bind": "^1.1.2",790 "get-proto": "^1.0.1",791 "gopd": "^1.2.0",792 "has-symbols": "^1.1.0",793 "hasown": "^2.0.2",794 "math-intrinsics": "^1.1.0"795 },796 "engines": {797 "node": ">= 0.4"798 },799 "funding": {800 "url": "https://github.com/sponsors/ljharb"801 }802 },803 "node_modules/get-proto": {804 "version": "1.0.1",805 "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",806 "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",807 "license": "MIT",808 "dependencies": {809 "dunder-proto": "^1.0.1",810 "es-object-atoms": "^1.0.0"811 },812 "engines": {813 "node": ">= 0.4"814 }815 },816 "node_modules/github-from-package": {817 "version": "0.0.0",818 "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",819 "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",820 "license": "MIT"821 },822 "node_modules/gopd": {823 "version": "1.2.0",824 "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",825 "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",826 "license": "MIT",827 "engines": {828 "node": ">= 0.4"829 },830 "funding": {831 "url": "https://github.com/sponsors/ljharb"832 }833 },834 "node_modules/has-symbols": {835 "version": "1.1.0",836 "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",837 "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",838 "license": "MIT",839 "engines": {840 "node": ">= 0.4"841 },842 "funding": {843 "url": "https://github.com/sponsors/ljharb"844 }845 },846 "node_modules/hasown": {847 "version": "2.0.4",848 "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",849 "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",850 "license": "MIT",851 "dependencies": {852 "function-bind": "^1.1.2"853 },854 "engines": {855 "node": ">= 0.4"856 }857 },858 "node_modules/helmet": {859 "version": "8.3.0",860 "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.3.0.tgz",861 "integrity": "sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w==",862 "license": "MIT",863 "engines": {864 "node": ">=18.0.0"865 },866 "funding": {867 "url": "https://github.com/sponsors/EvanHahn"868 }869 },870 "node_modules/http-errors": {871 "version": "2.0.1",872 "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",873 "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",874 "license": "MIT",875 "dependencies": {876 "depd": "~2.0.0",877 "inherits": "~2.0.4",878 "setprototypeof": "~1.2.0",879 "statuses": "~2.0.2",880 "toidentifier": "~1.0.1"881 },882 "engines": {883 "node": ">= 0.8"884 },885 "funding": {886 "type": "opencollective",887 "url": "https://opencollective.com/express"888 }889 },890 "node_modules/iconv-lite": {891 "version": "0.4.24",892 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",893 "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",894 "license": "MIT",895 "dependencies": {896 "safer-buffer": ">= 2.1.2 < 3"897 },898 "engines": {899 "node": ">=0.10.0"900 }901 },902 "node_modules/ieee754": {903 "version": "1.2.1",904 "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",905 "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",906 "funding": [907 {908 "type": "github",909 "url": "https://github.com/sponsors/feross"910 },911 {912 "type": "patreon",913 "url": "https://www.patreon.com/feross"914 },915 {916 "type": "consulting",917 "url": "https://feross.org/support"918 }919 ],920 "license": "BSD-3-Clause"921 },922 "node_modules/inherits": {923 "version": "2.0.4",924 "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",925 "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",926 "license": "ISC"927 },928 "node_modules/ini": {929 "version": "1.3.8",930 "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",931 "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",932 "license": "ISC"933 },934 "node_modules/ipaddr.js": {935 "version": "1.9.1",936 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",937 "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",938 "license": "MIT",939 "engines": {940 "node": ">= 0.10"941 }942 },943 "node_modules/isarray": {944 "version": "1.0.0",945 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",946 "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",947 "license": "MIT"948 },949 "node_modules/make-error": {950 "version": "1.3.6",951 "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",952 "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",953 "dev": true,954 "license": "ISC"955 },956 "node_modules/math-intrinsics": {957 "version": "1.1.0",958 "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",959 "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",960 "license": "MIT",961 "engines": {962 "node": ">= 0.4"963 }964 },965 "node_modules/media-typer": {966 "version": "0.3.0",967 "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",968 "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",969 "license": "MIT",970 "engines": {971 "node": ">= 0.6"972 }973 },974 "node_modules/merge-descriptors": {975 "version": "1.0.3",976 "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",977 "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",978 "license": "MIT",979 "funding": {980 "url": "https://github.com/sponsors/sindresorhus"981 }982 },983 "node_modules/methods": {984 "version": "1.1.2",985 "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",986 "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",987 "license": "MIT",988 "engines": {989 "node": ">= 0.6"990 }991 },992 "node_modules/mime": {993 "version": "1.6.0",994 "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",995 "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",996 "license": "MIT",997 "bin": {998 "mime": "cli.js"999 },1000 "engines": {1001 "node": ">=4"1002 }1003 },1004 "node_modules/mime-db": {1005 "version": "1.52.0",1006 "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",1007 "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",1008 "license": "MIT",1009 "engines": {1010 "node": ">= 0.6"1011 }1012 },1013 "node_modules/mime-types": {1014 "version": "2.1.35",1015 "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",1016 "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",1017 "license": "MIT",1018 "dependencies": {1019 "mime-db": "1.52.0"1020 },1021 "engines": {1022 "node": ">= 0.6"1023 }1024 },1025 "node_modules/mimic-response": {1026 "version": "3.1.0",1027 "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",1028 "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",1029 "license": "MIT",1030 "engines": {1031 "node": ">=10"1032 },1033 "funding": {1034 "url": "https://github.com/sponsors/sindresorhus"1035 }1036 },1037 "node_modules/minimist": {1038 "version": "1.2.8",1039 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",1040 "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",1041 "license": "MIT",1042 "funding": {1043 "url": "https://github.com/sponsors/ljharb"1044 }1045 },1046 "node_modules/mkdirp": {1047 "version": "0.5.6",1048 "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",1049 "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",1050 "license": "MIT",1051 "dependencies": {1052 "minimist": "^1.2.6"1053 },1054 "bin": {1055 "mkdirp": "bin/cmd.js"1056 }1057 },1058 "node_modules/mkdirp-classic": {1059 "version": "0.5.3",1060 "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",1061 "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",1062 "license": "MIT"1063 },1064 "node_modules/ms": {1065 "version": "2.0.0",1066 "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",1067 "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",1068 "license": "MIT"1069 },1070 "node_modules/multer": {1071 "version": "1.4.5-lts.2",1072 "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",1073 "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",1074 "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.",1075 "license": "MIT",1076 "dependencies": {1077 "append-field": "^1.0.0",1078 "busboy": "^1.0.0",1079 "concat-stream": "^1.5.2",1080 "mkdirp": "^0.5.4",1081 "object-assign": "^4.1.1",1082 "type-is": "^1.6.4",1083 "xtend": "^4.0.0"1084 },1085 "engines": {1086 "node": ">= 6.0.0"1087 }1088 },1089 "node_modules/napi-build-utils": {1090 "version": "2.0.0",1091 "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",1092 "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",1093 "license": "MIT"1094 },1095 "node_modules/negotiator": {1096 "version": "0.6.3",1097 "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",1098 "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",1099 "license": "MIT",1100 "engines": {1101 "node": ">= 0.6"1102 }1103 },1104 "node_modules/node-abi": {1105 "version": "3.96.0",1106 "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz",1107 "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",1108 "license": "MIT",1109 "dependencies": {1110 "semver": "^7.3.5"1111 },1112 "engines": {1113 "node": ">=10"1114 }1115 },1116 "node_modules/object-assign": {1117 "version": "4.1.1",1118 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",1119 "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",1120 "license": "MIT",1121 "engines": {1122 "node": ">=0.10.0"1123 }1124 },1125 "node_modules/object-inspect": {1126 "version": "1.13.4",1127 "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",1128 "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",1129 "license": "MIT",1130 "engines": {1131 "node": ">= 0.4"1132 },1133 "funding": {1134 "url": "https://github.com/sponsors/ljharb"1135 }1136 },1137 "node_modules/on-finished": {1138 "version": "2.4.1",1139 "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",1140 "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",1141 "license": "MIT",1142 "dependencies": {1143 "ee-first": "1.1.1"1144 },1145 "engines": {1146 "node": ">= 0.8"1147 }1148 },1149 "node_modules/once": {1150 "version": "1.4.0",1151 "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",1152 "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",1153 "license": "ISC",1154 "dependencies": {1155 "wrappy": "1"1156 }1157 },1158 "node_modules/parseurl": {1159 "version": "1.3.3",1160 "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",1161 "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",1162 "license": "MIT",1163 "engines": {1164 "node": ">= 0.8"1165 }1166 },1167 "node_modules/path-to-regexp": {1168 "version": "0.1.13",1169 "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",1170 "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",1171 "license": "MIT"1172 },1173 "node_modules/prebuild-install": {1174 "version": "7.1.3",1175 "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",1176 "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",1177 "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",1178 "license": "MIT",1179 "dependencies": {1180 "detect-libc": "^2.0.0",1181 "expand-template": "^2.0.3",1182 "github-from-package": "0.0.0",1183 "minimist": "^1.2.3",1184 "mkdirp-classic": "^0.5.3",1185 "napi-build-utils": "^2.0.0",1186 "node-abi": "^3.3.0",1187 "pump": "^3.0.0",1188 "rc": "^1.2.7",1189 "simple-get": "^4.0.0",1190 "tar-fs": "^2.0.0",1191 "tunnel-agent": "^0.6.0"1192 },1193 "bin": {1194 "prebuild-install": "bin.js"1195 },1196 "engines": {1197 "node": ">=10"1198 }1199 },1200 "node_modules/process-nextick-args": {1201 "version": "2.0.1",1202 "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",1203 "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",1204 "license": "MIT"1205 },1206 "node_modules/proxy-addr": {1207 "version": "2.0.7",1208 "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",1209 "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",1210 "license": "MIT",1211 "dependencies": {1212 "forwarded": "0.2.0",1213 "ipaddr.js": "1.9.1"1214 },1215 "engines": {1216 "node": ">= 0.10"1217 }1218 },1219 "node_modules/pump": {1220 "version": "3.0.4",1221 "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",1222 "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",1223 "license": "MIT",1224 "dependencies": {1225 "end-of-stream": "^1.1.0",1226 "once": "^1.3.1"1227 }1228 },1229 "node_modules/qs": {1230 "version": "6.15.3",1231 "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",1232 "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",1233 "license": "BSD-3-Clause",1234 "dependencies": {1235 "es-define-property": "^1.0.1",1236 "side-channel": "^1.1.1"1237 },1238 "engines": {1239 "node": ">=0.6"1240 },1241 "funding": {1242 "url": "https://github.com/sponsors/ljharb"1243 }1244 },1245 "node_modules/range-parser": {1246 "version": "1.2.1",1247 "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",1248 "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",1249 "license": "MIT",1250 "engines": {1251 "node": ">= 0.6"1252 }1253 },1254 "node_modules/raw-body": {1255 "version": "2.5.3",1256 "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",1257 "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",1258 "license": "MIT",1259 "dependencies": {1260 "bytes": "~3.1.2",1261 "http-errors": "~2.0.1",1262 "iconv-lite": "~0.4.24",1263 "unpipe": "~1.0.0"1264 },1265 "engines": {1266 "node": ">= 0.8"1267 }1268 },1269 "node_modules/rc": {1270 "version": "1.2.8",1271 "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",1272 "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",1273 "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",1274 "dependencies": {1275 "deep-extend": "^0.6.0",1276 "ini": "~1.3.0",1277 "minimist": "^1.2.0",1278 "strip-json-comments": "~2.0.1"1279 },1280 "bin": {1281 "rc": "cli.js"1282 }1283 },1284 "node_modules/readable-stream": {1285 "version": "2.3.8",1286 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",1287 "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",1288 "license": "MIT",1289 "dependencies": {1290 "core-util-is": "~1.0.0",1291 "inherits": "~2.0.3",1292 "isarray": "~1.0.0",1293 "process-nextick-args": "~2.0.0",1294 "safe-buffer": "~5.1.1",1295 "string_decoder": "~1.1.1",1296 "util-deprecate": "~1.0.1"1297 }1298 },1299 "node_modules/readable-stream/node_modules/safe-buffer": {1300 "version": "5.1.2",1301 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",1302 "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",1303 "license": "MIT"1304 },1305 "node_modules/safe-buffer": {1306 "version": "5.2.1",1307 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",1308 "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",1309 "funding": [1310 {1311 "type": "github",1312 "url": "https://github.com/sponsors/feross"1313 },1314 {1315 "type": "patreon",1316 "url": "https://www.patreon.com/feross"1317 },1318 {1319 "type": "consulting",1320 "url": "https://feross.org/support"1321 }1322 ],1323 "license": "MIT"1324 },1325 "node_modules/safer-buffer": {1326 "version": "2.1.2",1327 "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",1328 "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",1329 "license": "MIT"1330 },1331 "node_modules/semver": {1332 "version": "7.8.5",1333 "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",1334 "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",1335 "license": "ISC",1336 "bin": {1337 "semver": "bin/semver.js"1338 },1339 "engines": {1340 "node": ">=10"1341 }1342 },1343 "node_modules/send": {1344 "version": "0.19.2",1345 "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",1346 "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",1347 "license": "MIT",1348 "dependencies": {1349 "debug": "2.6.9",1350 "depd": "2.0.0",1351 "destroy": "1.2.0",1352 "encodeurl": "~2.0.0",1353 "escape-html": "~1.0.3",1354 "etag": "~1.8.1",1355 "fresh": "~0.5.2",1356 "http-errors": "~2.0.1",1357 "mime": "1.6.0",1358 "ms": "2.1.3",1359 "on-finished": "~2.4.1",1360 "range-parser": "~1.2.1",1361 "statuses": "~2.0.2"1362 },1363 "engines": {1364 "node": ">= 0.8.0"1365 }1366 },1367 "node_modules/send/node_modules/ms": {1368 "version": "2.1.3",1369 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",1370 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",1371 "license": "MIT"1372 },1373 "node_modules/serve-static": {1374 "version": "1.16.3",1375 "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",1376 "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",1377 "license": "MIT",1378 "dependencies": {1379 "encodeurl": "~2.0.0",1380 "escape-html": "~1.0.3",1381 "parseurl": "~1.3.3",1382 "send": "~0.19.1"1383 },1384 "engines": {1385 "node": ">= 0.8.0"1386 }1387 },1388 "node_modules/setprototypeof": {1389 "version": "1.2.0",1390 "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",1391 "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",1392 "license": "ISC"1393 },1394 "node_modules/side-channel": {1395 "version": "1.1.1",1396 "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",1397 "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",1398 "license": "MIT",1399 "dependencies": {1400 "es-errors": "^1.3.0",1401 "object-inspect": "^1.13.4",1402 "side-channel-list": "^1.0.1",1403 "side-channel-map": "^1.0.1",1404 "side-channel-weakmap": "^1.0.2"1405 },1406 "engines": {1407 "node": ">= 0.4"1408 },1409 "funding": {1410 "url": "https://github.com/sponsors/ljharb"1411 }1412 },1413 "node_modules/side-channel-list": {1414 "version": "1.0.1",1415 "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",1416 "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",1417 "license": "MIT",1418 "dependencies": {1419 "es-errors": "^1.3.0",1420 "object-inspect": "^1.13.4"1421 },1422 "engines": {1423 "node": ">= 0.4"1424 },1425 "funding": {1426 "url": "https://github.com/sponsors/ljharb"1427 }1428 },1429 "node_modules/side-channel-map": {1430 "version": "1.0.1",1431 "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",1432 "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",1433 "license": "MIT",1434 "dependencies": {1435 "call-bound": "^1.0.2",1436 "es-errors": "^1.3.0",1437 "get-intrinsic": "^1.2.5",1438 "object-inspect": "^1.13.3"1439 },1440 "engines": {1441 "node": ">= 0.4"1442 },1443 "funding": {1444 "url": "https://github.com/sponsors/ljharb"1445 }1446 },1447 "node_modules/side-channel-weakmap": {1448 "version": "1.0.2",1449 "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",1450 "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",1451 "license": "MIT",1452 "dependencies": {1453 "call-bound": "^1.0.2",1454 "es-errors": "^1.3.0",1455 "get-intrinsic": "^1.2.5",1456 "object-inspect": "^1.13.3",1457 "side-channel-map": "^1.0.1"1458 },1459 "engines": {1460 "node": ">= 0.4"1461 },1462 "funding": {1463 "url": "https://github.com/sponsors/ljharb"1464 }1465 },1466 "node_modules/simple-concat": {1467 "version": "1.0.1",1468 "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",1469 "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",1470 "funding": [1471 {1472 "type": "github",1473 "url": "https://github.com/sponsors/feross"1474 },1475 {1476 "type": "patreon",1477 "url": "https://www.patreon.com/feross"1478 },1479 {1480 "type": "consulting",1481 "url": "https://feross.org/support"1482 }1483 ],1484 "license": "MIT"1485 },1486 "node_modules/simple-get": {1487 "version": "4.0.1",1488 "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",1489 "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",1490 "funding": [1491 {1492 "type": "github",1493 "url": "https://github.com/sponsors/feross"1494 },1495 {1496 "type": "patreon",1497 "url": "https://www.patreon.com/feross"1498 },1499 {1500 "type": "consulting",1501 "url": "https://feross.org/support"1502 }1503 ],1504 "license": "MIT",1505 "dependencies": {1506 "decompress-response": "^6.0.0",1507 "once": "^1.3.1",1508 "simple-concat": "^1.0.0"1509 }1510 },1511 "node_modules/statuses": {1512 "version": "2.0.2",1513 "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",1514 "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",1515 "license": "MIT",1516 "engines": {1517 "node": ">= 0.8"1518 }1519 },1520 "node_modules/streamsearch": {1521 "version": "1.1.0",1522 "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",1523 "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",1524 "engines": {1525 "node": ">=10.0.0"1526 }1527 },1528 "node_modules/string_decoder": {1529 "version": "1.1.1",1530 "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",1531 "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",1532 "license": "MIT",1533 "dependencies": {1534 "safe-buffer": "~5.1.0"1535 }1536 },1537 "node_modules/string_decoder/node_modules/safe-buffer": {1538 "version": "5.1.2",1539 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",1540 "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",1541 "license": "MIT"1542 },1543 "node_modules/strip-json-comments": {1544 "version": "2.0.1",1545 "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",1546 "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",1547 "license": "MIT",1548 "engines": {1549 "node": ">=0.10.0"1550 }1551 },1552 "node_modules/tar-fs": {1553 "version": "2.1.5",1554 "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",1555 "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",1556 "license": "MIT",1557 "dependencies": {1558 "chownr": "^1.1.1",1559 "mkdirp-classic": "^0.5.2",1560 "pump": "^3.0.0",1561 "tar-stream": "^2.1.4"1562 }1563 },1564 "node_modules/tar-stream": {1565 "version": "2.2.0",1566 "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",1567 "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",1568 "license": "MIT",1569 "dependencies": {1570 "bl": "^4.0.3",1571 "end-of-stream": "^1.4.1",1572 "fs-constants": "^1.0.0",1573 "inherits": "^2.0.3",1574 "readable-stream": "^3.1.1"1575 },1576 "engines": {1577 "node": ">=6"1578 }1579 },1580 "node_modules/tar-stream/node_modules/readable-stream": {1581 "version": "3.6.2",1582 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",1583 "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",1584 "license": "MIT",1585 "dependencies": {1586 "inherits": "^2.0.3",1587 "string_decoder": "^1.1.1",1588 "util-deprecate": "^1.0.1"1589 },1590 "engines": {1591 "node": ">= 6"1592 }1593 },1594 "node_modules/toidentifier": {1595 "version": "1.0.1",1596 "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",1597 "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",1598 "license": "MIT",1599 "engines": {1600 "node": ">=0.6"1601 }1602 },1603 "node_modules/ts-node": {1604 "version": "10.9.2",1605 "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",1606 "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",1607 "dev": true,1608 "license": "MIT",1609 "dependencies": {1610 "@cspotcode/source-map-support": "^0.8.0",1611 "@tsconfig/node10": "^1.0.7",1612 "@tsconfig/node12": "^1.0.7",1613 "@tsconfig/node14": "^1.0.0",1614 "@tsconfig/node16": "^1.0.2",1615 "acorn": "^8.4.1",1616 "acorn-walk": "^8.1.1",1617 "arg": "^4.1.0",1618 "create-require": "^1.1.0",1619 "diff": "^4.0.1",1620 "make-error": "^1.1.1",1621 "v8-compile-cache-lib": "^3.0.1",1622 "yn": "3.1.1"1623 },1624 "bin": {1625 "ts-node": "dist/bin.js",1626 "ts-node-cwd": "dist/bin-cwd.js",1627 "ts-node-esm": "dist/bin-esm.js",1628 "ts-node-script": "dist/bin-script.js",1629 "ts-node-transpile-only": "dist/bin-transpile.js",1630 "ts-script": "dist/bin-script-deprecated.js"1631 },1632 "peerDependencies": {1633 "@swc/core": ">=1.2.50",1634 "@swc/wasm": ">=1.2.50",1635 "@types/node": "*",1636 "typescript": ">=2.7"1637 },1638 "peerDependenciesMeta": {1639 "@swc/core": {1640 "optional": true1641 },1642 "@swc/wasm": {1643 "optional": true1644 }1645 }1646 },1647 "node_modules/tunnel-agent": {1648 "version": "0.6.0",1649 "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",1650 "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",1651 "license": "Apache-2.0",1652 "dependencies": {1653 "safe-buffer": "^5.0.1"1654 },1655 "engines": {1656 "node": "*"1657 }1658 },1659 "node_modules/type-is": {1660 "version": "1.6.18",1661 "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",1662 "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",1663 "license": "MIT",1664 "dependencies": {1665 "media-typer": "0.3.0",1666 "mime-types": "~2.1.24"1667 },1668 "engines": {1669 "node": ">= 0.6"1670 }1671 },1672 "node_modules/typedarray": {1673 "version": "0.0.6",1674 "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",1675 "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",1676 "license": "MIT"1677 },1678 "node_modules/typescript": {1679 "version": "5.9.3",1680 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",1681 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",1682 "dev": true,1683 "license": "Apache-2.0",1684 "bin": {1685 "tsc": "bin/tsc",1686 "tsserver": "bin/tsserver"1687 },1688 "engines": {1689 "node": ">=14.17"1690 }1691 },1692 "node_modules/undici-types": {1693 "version": "6.21.0",1694 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",1695 "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",1696 "dev": true,1697 "license": "MIT"1698 },1699 "node_modules/unpipe": {1700 "version": "1.0.0",1701 "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",1702 "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",1703 "license": "MIT",1704 "engines": {1705 "node": ">= 0.8"1706 }1707 },1708 "node_modules/util-deprecate": {1709 "version": "1.0.2",1710 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",1711 "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",1712 "license": "MIT"1713 },1714 "node_modules/utils-merge": {1715 "version": "1.0.1",1716 "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",1717 "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",1718 "license": "MIT",1719 "engines": {1720 "node": ">= 0.4.0"1721 }1722 },1723 "node_modules/v8-compile-cache-lib": {1724 "version": "3.0.1",1725 "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",1726 "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",1727 "dev": true,1728 "license": "MIT"1729 },1730 "node_modules/vary": {1731 "version": "1.1.2",1732 "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",1733 "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",1734 "license": "MIT",1735 "engines": {1736 "node": ">= 0.8"1737 }1738 },1739 "node_modules/wrappy": {1740 "version": "1.0.2",1741 "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",1742 "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",1743 "license": "ISC"1744 },1745 "node_modules/xtend": {1746 "version": "4.0.2",1747 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",1748 "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",1749 "license": "MIT",1750 "engines": {1751 "node": ">=0.4"1752 }1753 },1754 "node_modules/yn": {1755 "version": "3.1.1",1756 "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",1757 "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",1758 "dev": true,1759 "license": "MIT",1760 "engines": {1761 "node": ">=6"1762 }1763 }1764 }1765}
portal/package.json
Before remediation
1{2 "name": "treasury-portal",3 "private": true,4 "version": "1.0.0",5 "type": "commonjs",6 "scripts": {7 "dev": "ts-node src/server.ts",8 "build": "tsc -p tsconfig.json",9 "start": "node dist/server.js"10 },11 "dependencies": {12 "better-sqlite3": "^11.10.0",13 "express": "^4.21.2",14 "multer": "^1.4.5-lts.1"15 },16 "devDependencies": {17 "@types/express": "^5.0.3",18 "@types/multer": "^1.4.12",19 "@types/node": "^22.15.3",20 "ts-node": "^10.9.2",21 "typescript": "^5.8.3"22 }23}
After remediation
1{2 "name": "treasury-portal",3 "private": true,4 "version": "1.0.0",5 "type": "commonjs",6 "scripts": {7 "dev": "ts-node src/server.ts",8 "build": "tsc -p tsconfig.json",9 "start": "node dist/server.js"10 },11 "dependencies": {12 "better-sqlite3": "^11.10.0",13 "helmet": "8.3.0",14 "express": "^4.21.2",15 "multer": "^1.4.5-lts.1"16 },17 "devDependencies": {18 "@types/express": "^5.0.3",19 "@types/multer": "^1.4.12",20 "@types/node": "^22.15.3",21 "ts-node": "^10.9.2",22 "typescript": "^5.8.3"23 }24}
portal/src/server.ts
Before remediation
1import express, { NextFunction, Request, Response } from "express";2import fs from "fs";3import path from "path";4import multer from "multer";5import { db, recordAudit, storagePath } from "./store.js";67type Identity = {8 orgId: number;9 userId: string;10 role: "admin" | "manager" | "viewer";11};1213declare global {14 namespace Express {15 interface Request {16 identity?: Identity;17 }18 }19}2021const app = express();22const upload = multer({ dest: path.resolve(__dirname, "..", "tmp") });2324fs.mkdirSync(path.resolve(__dirname, "..", "tmp"), { recursive: true });2526app.use(express.json());27app.use(express.urlencoded({ extended: true }));2829app.use((req, res, next) => {30 const orgId = Number(req.header("x-org-id") || "1");31 const userId = req.header("x-user-id") || "portal-user";32 const role = (req.header("x-role") || "viewer") as Identity["role"];33 req.identity = { orgId, userId, role };34 next();35});3637app.use((req, res, next) => {38 const finish = () => {39 if (!req.identity) {40 return;41 }42 recordAudit({43 orgId: req.identity.orgId,44 actor: req.identity.userId,45 action: `${req.method} ${req.path}`,46 resource: req.originalUrl,47 details: {48 statusCode: res.statusCode,49 query: req.query,50 body: req.body51 }52 });53 };54 res.on("finish", finish);55 next();56});5758function requireRole(allowed: Identity["role"][]) {59 return (req: Request, res: Response, next: NextFunction) => {60 if (!req.identity || !allowed.includes(req.identity.role)) {61 res.status(403).json({ error: "forbidden" });62 return;63 }64 next();65 };66}6768function buildOrgFilters(query: Request["query"]) {69 const clauses: string[] = [];70 const params: unknown[] = [];7172 if (typeof query.name === "string" && query.name.trim()) {73 clauses.push("name like ?");74 params.push(`%${query.name.trim()}%`);75 }76 if (typeof query.region === "string" && query.region.trim()) {77 clauses.push("region = ?");78 params.push(query.region.trim());79 }80 if (typeof query.tier === "string" && query.tier.trim()) {81 clauses.push("tier = ?");82 params.push(query.tier.trim());83 }8485 const where = clauses.length ? ` where ${clauses.join(" and ")}` : "";86 return { where, params };87}8889app.get("/health", (_req, res) => {90 const counts = {91 organizations: db.prepare("select count(*) as count from organizations").get().count,92 transfers: db.prepare("select count(*) as count from transfers").get().count,93 auditRecords: db.prepare("select count(*) as count from audit_records").get().count94 };95 res.json({ ok: true, counts });96});9798app.get("/orgs", requireRole(["admin", "manager", "viewer"]), (req, res) => {99 const { where, params } = buildOrgFilters(req.query);100 const rows = db.prepare(`select * from organizations${where} order by id desc`).all(...params);101 res.json({ data: rows });102});103104app.post("/orgs", requireRole(["admin"]), (req, res) => {105 const { name, orgCode, region, tier } = req.body;106 const stmt = db.prepare(107 "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)"108 );109 const result = stmt.run(name, orgCode, region, tier);110 res.status(201).json({ id: result.lastInsertRowid, name, orgCode, region, tier });111});112113app.get("/teams", requireRole(["admin", "manager"]), (req, res) => {114 const filters: string[] = [];115 const params: unknown[] = [];116117 if (typeof req.query.orgId === "string" && req.query.orgId.trim()) {118 filters.push("org_id = ?");119 params.push(Number(req.query.orgId));120 }121 if (typeof req.query.query === "string" && req.query.query.trim()) {122 filters.push("(name like ? or cost_center like ?)");123 params.push(`%${req.query.query.trim()}%`, `%${req.query.query.trim()}%`);124 }125126 const where = filters.length ? ` where ${filters.join(" and ")}` : "";127 const rows = db.prepare(`select * from teams${where} order by created_at desc`).all(...params);128 res.json({ data: rows });129});130131app.post("/users", requireRole(["admin", "manager"]), (req, res) => {132 const { orgId, teamId, email, displayName, role, status } = req.body;133 const result = db134 .prepare(135 "insert into users (org_id, team_id, email, display_name, role, status) values (?, ?, ?, ?, ?, ?)"136 )137 .run(orgId, teamId ?? null, email, displayName, role, status || "active");138 res.status(201).json({ id: result.lastInsertRowid, orgId, teamId, email, displayName, role });139});140141app.post("/beneficiaries", requireRole(["admin", "manager"]), (req, res) => {142 const { orgId, name, bankName, routing } = req.body;143 const result = db144 .prepare("insert into beneficiaries (org_id, name, bank_name, routing) values (?, ?, ?, ?)")145 .run(orgId, name, bankName, routing);146 res.status(201).json({ id: result.lastInsertRowid });147});148149app.post("/transfers", requireRole(["admin", "manager"]), (req, res) => {150 const { orgId, accountId, beneficiaryId, amount, currency, memo } = req.body;151 const result = db152 .prepare(153 "insert into transfers (org_id, account_id, beneficiary_id, amount, currency, status, memo, requested_by) values (?, ?, ?, ?, ?, ?, ?, ?)"154 )155 .run(orgId, accountId, beneficiaryId, amount, currency, "queued", memo || "", req.identity?.userId || "unknown");156 res.status(201).json({ id: result.lastInsertRowid, status: "queued" });157});158159app.post("/documents/upload", requireRole(["admin", "manager"]), upload.single("file"), (req, res) => {160 const file = req.file;161 if (!file) {162 res.status(400).json({ error: "missing file" });163 return;164 }165166 const targetName = `${Date.now()}-${file.originalname}`;167 fs.copyFileSync(file.path, storagePath(targetName));168 const result = db169 .prepare(170 "insert into documents (org_id, filename, original_name, mime_type) values (?, ?, ?, ?)"171 )172 .run(req.identity?.orgId || 1, targetName, file.originalname, file.mimetype);173174 res.status(201).json({ id: result.lastInsertRowid, filename: targetName });175});176177app.get("/documents/:name", requireRole(["admin", "manager", "viewer"]), (req, res) => {178 const filePath = storagePath(req.params.name);179 if (!fs.existsSync(filePath)) {180 res.status(404).json({ error: "not found" });181 return;182 }183 res.setHeader("content-type", "application/octet-stream");184 res.send(fs.readFileSync(filePath));185});186187app.get("/reports/org-summary", requireRole(["admin", "manager"]), (req, res) => {188 const orgId = Number(req.query.orgId || req.identity?.orgId || 1);189 const org = db.prepare("select * from organizations where id = ?").get(orgId);190 const transfers = db191 .prepare("select * from transfers where org_id = ? order by created_at desc limit 25")192 .all(orgId);193 const title = typeof req.query.title === "string" ? req.query.title : "Treasury Summary";194 const html = `195 <html>196 <body>197 <h1>${title}</h1>198 <h2>${org?.name ?? "Unknown org"}</h2>199 <p>Region: ${org?.region ?? "n/a"} Tier: ${org?.tier ?? "n/a"}</p>200 <ul>201 ${transfers202 .map(203 (transfer) =>204 `<li>${transfer.currency} ${transfer.amount} - ${transfer.status} - ${transfer.memo}</li>`205 )206 .join("")}207 </ul>208 </body>209 </html>210 `;211 res.type("html").send(html);212});213214app.get("/audit", requireRole(["admin"]), (req, res) => {215 const orgId = typeof req.query.orgId === "string" ? Number(req.query.orgId) : req.identity?.orgId;216 const rows = db217 .prepare("select * from audit_records where org_id = ? order by created_at desc limit 100")218 .all(orgId);219 res.json({ data: rows });220});221222app.use((_req, res) => {223 res.status(404).json({ error: "not found" });224});225226const port = Number(process.env.PORT || 4100);227app.listen(port, () => {228 console.log(`portal listening on ${port}`);229});
After remediation
1import express, { NextFunction, Request, Response } from "express";2import fs from "fs";3import path from "path";4import multer from "multer";5import helmet from "helmet";6import { db, recordAudit, storagePath } from "./store.js";78type Identity = {9 orgId: number;10 userId: string;11 role: "admin" | "manager" | "viewer";12};1314declare global {15 namespace Express {16 interface Request {17 identity?: Identity;18 }19 }20}2122const app = express();23const upload = multer({ dest: path.resolve(__dirname, "..", "tmp") });2425fs.mkdirSync(path.resolve(__dirname, "..", "tmp"), { recursive: true });2627app.use(express.json());28app.use(express.urlencoded({ extended: true }));29app.use(helmet());3031app.use((req, res, next) => {32 const orgId = Number(req.header("x-org-id") || "1");33 const userId = req.header("x-user-id") || "portal-user";34 const role = (req.header("x-role") || "viewer") as Identity["role"];35 req.identity = { orgId, userId, role };36 next();37});3839app.use((req, res, next) => {40 const finish = () => {41 if (!req.identity) {42 return;43 }44 recordAudit({45 orgId: req.identity.orgId,46 actor: req.identity.userId,47 action: `${req.method} ${req.path}`,48 resource: req.originalUrl,49 details: {50 statusCode: res.statusCode,51 query: req.query,52 body: req.body53 }54 });55 };56 res.on("finish", finish);57 next();58});5960function requireRole(allowed: Identity["role"][]) {61 return (req: Request, res: Response, next: NextFunction) => {62 if (!req.identity || !allowed.includes(req.identity.role)) {63 res.status(403).json({ error: "forbidden" });64 return;65 }66 next();67 };68}6970function buildOrgFilters(query: Request["query"]) {71 const clauses: string[] = [];72 const params: unknown[] = [];7374 if (typeof query.name === "string" && query.name.trim()) {75 clauses.push("name like ?");76 params.push(`%${query.name.trim()}%`);77 }78 if (typeof query.region === "string" && query.region.trim()) {79 clauses.push("region = ?");80 params.push(query.region.trim());81 }82 if (typeof query.tier === "string" && query.tier.trim()) {83 clauses.push("tier = ?");84 params.push(query.tier.trim());85 }8687 const where = clauses.length ? ` where ${clauses.join(" and ")}` : "";88 return { where, params };89}9091app.get("/health", (_req, res) => {92 const counts = {93 organizations: db.prepare("select count(*) as count from organizations").get().count,94 transfers: db.prepare("select count(*) as count from transfers").get().count,95 auditRecords: db.prepare("select count(*) as count from audit_records").get().count96 };97 res.json({ ok: true, counts });98});99100app.get("/orgs", requireRole(["admin", "manager", "viewer"]), (req, res) => {101 const { where, params } = buildOrgFilters(req.query);102 const sqlParts = ["select * from organizations"];103 if (where) {104 sqlParts.push(where);105 }106 sqlParts.push("order by id desc");107 const rows = db.prepare(sqlParts.join(" ")).all(...params);108 res.json({ data: rows });109});110111app.post("/orgs", requireRole(["admin"]), (req, res) => {112 const { name, orgCode, region, tier } = req.body;113 const stmt = db.prepare(114 "insert into organizations (name, org_code, region, tier) values (?, ?, ?, ?)"115 );116 const result = stmt.run(name, orgCode, region, tier);117 res.status(201).json({ id: result.lastInsertRowid, name, orgCode, region, tier });118});119120app.get("/teams", requireRole(["admin", "manager"]), (req, res) => {121 const filters: string[] = [];122 const params: unknown[] = [];123124 if (typeof req.query.orgId === "string" && req.query.orgId.trim()) {125 filters.push("org_id = ?");126 params.push(Number(req.query.orgId));127 }128 if (typeof req.query.query === "string" && req.query.query.trim()) {129 filters.push("(name like ? or cost_center like ?)");130 params.push(`%${req.query.query.trim()}%`, `%${req.query.query.trim()}%`);131 }132133 const where = filters.length ? ` where ${filters.join(" and ")}` : "";134 const sqlParts = ["select * from teams"];135 if (where) {136 sqlParts.push(where);137 }138 sqlParts.push("order by created_at desc");139 const rows = db.prepare(sqlParts.join(" ")).all(...params);140 res.json({ data: rows });141});142143app.post("/users", requireRole(["admin", "manager"]), (req, res) => {144 const { orgId, teamId, email, displayName, role, status } = req.body;145 const result = db146 .prepare(147 "insert into users (org_id, team_id, email, display_name, role, status) values (?, ?, ?, ?, ?, ?)"148 )149 .run(orgId, teamId ?? null, email, displayName, role, status || "active");150 res.status(201).json({ id: result.lastInsertRowid, orgId, teamId, email, displayName, role });151});152153app.post("/beneficiaries", requireRole(["admin", "manager"]), (req, res) => {154 const { orgId, name, bankName, routing } = req.body;155 const result = db156 .prepare("insert into beneficiaries (org_id, name, bank_name, routing) values (?, ?, ?, ?)")157 .run(orgId, name, bankName, routing);158 res.status(201).json({ id: result.lastInsertRowid });159});160161app.post("/transfers", requireRole(["admin", "manager"]), (req, res) => {162 const { orgId, accountId, beneficiaryId, amount, currency, memo } = req.body;163 const result = db164 .prepare(165 "insert into transfers (org_id, account_id, beneficiary_id, amount, currency, status, memo, requested_by) values (?, ?, ?, ?, ?, ?, ?, ?)"166 )167 .run(orgId, accountId, beneficiaryId, amount, currency, "queued", memo || "", req.identity?.userId || "unknown");168 res.status(201).json({ id: result.lastInsertRowid, status: "queued" });169});170171app.post("/documents/upload", requireRole(["admin", "manager"]), upload.single("file"), (req, res) => {172 const file = req.file;173 if (!file) {174 res.status(400).json({ error: "missing file" });175 return;176 }177178 const targetName = `${Date.now()}-${file.originalname}`;179 let targetPath: string;180 try {181 targetPath = storagePath(targetName);182 } catch (_error) {183 res.status(400).json({ error: "invalid file name" });184 return;185 }186 fs.copyFileSync(file.path, targetPath);187 const result = db188 .prepare(189 "insert into documents (org_id, filename, original_name, mime_type) values (?, ?, ?, ?)"190 )191 .run(req.identity?.orgId || 1, targetName, file.originalname, file.mimetype);192193 res.status(201).json({ id: result.lastInsertRowid, filename: targetName });194});195196app.get("/documents/:name", requireRole(["admin", "manager", "viewer"]), (req, res) => {197 let filePath: string;198 try {199 filePath = storagePath(req.params.name);200 } catch (_error) {201 res.status(400).json({ error: "invalid file name" });202 return;203 }204 if (!fs.existsSync(filePath)) {205 res.status(404).json({ error: "not found" });206 return;207 }208 res.setHeader("content-type", "application/octet-stream");209 res.send(fs.readFileSync(filePath));210});211212app.get("/reports/org-summary", requireRole(["admin", "manager"]), (req, res) => {213 const orgId = Number(req.query.orgId || req.identity?.orgId || 1);214 const org = db.prepare("select * from organizations where id = ?").get(orgId);215 const transfers = db216 .prepare("select * from transfers where org_id = ? order by created_at desc limit 25")217 .all(orgId) as Array<{218 currency: string;219 amount: number;220 status: string;221 memo: string;222 }>;223 const title = typeof req.query.title === "string" ? req.query.title : "Treasury Summary";224 const html = `225 <html>226 <body>227 <h1>${title}</h1>228 <h2>${org?.name ?? "Unknown org"}</h2>229 <p>Region: ${org?.region ?? "n/a"} Tier: ${org?.tier ?? "n/a"}</p>230 <ul>231 ${transfers232 .map(233 (transfer) =>234 `<li>${transfer.currency} ${transfer.amount} - ${transfer.status} - ${transfer.memo}</li>`235 )236 .join("")}237 </ul>238 </body>239 </html>240 `;241 res.type("html").send(html);242});243244app.get("/audit", requireRole(["admin"]), (req, res) => {245 const orgId = typeof req.query.orgId === "string" ? Number(req.query.orgId) : req.identity?.orgId;246 const rows = db247 .prepare("select * from audit_records where org_id = ? order by created_at desc limit 100")248 .all(orgId);249 res.json({ data: rows });250});251252app.use((_req, res) => {253 res.status(404).json({ error: "not found" });254});255256const port = Number(process.env.PORT || 4100);257258if (require.main === module) {259 app.listen(port, () => {260 console.log(`portal listening on ${port}`);261 });262}263264export { app };