xytara 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/server.js ADDED
@@ -0,0 +1,626 @@
1
+ "use strict";
2
+
3
+ const http = require("http");
4
+ const { URL } = require("url");
5
+ const { readJsonBody, sendJson } = require("./lib/http_transport");
6
+ const {
7
+ getCatalog,
8
+ getTaskDetail,
9
+ getWorkflowDetail,
10
+ getWorkflowList,
11
+ summarizeCatalog
12
+ } = require("./lib/catalog_contract");
13
+ const {
14
+ createCommandResult,
15
+ createDispute,
16
+ createQuote,
17
+ createRefund,
18
+ createRemediationTicket,
19
+ createRuntimeState,
20
+ hydrateQuoteStates,
21
+ hydratePendingExecutions,
22
+ actOnDispute,
23
+ actOnRefund,
24
+ actOnRemediationTicket,
25
+ listRecords
26
+ } = require("./lib/commerce_runtime");
27
+ const {
28
+ buildOperatorExportPack,
29
+ buildReconciliationReport
30
+ } = require("./lib/commerce_reports");
31
+ const {
32
+ summarizeOpenIssuesPayload,
33
+ summarizeExecutionExceptionsPayload,
34
+ summarizeOperationsDashboardPayload,
35
+ summarizeDisputePayload,
36
+ summarizeRefundPayload,
37
+ summarizeRemediationTicketPayload,
38
+ summarizeTransactionPayload,
39
+ summarizeReceiptPayload,
40
+ summarizeDeliveryPayload
41
+ } = require("./lib/commerce_client");
42
+ const {
43
+ buildMissingResponse,
44
+ buildNamedRecordListResponse,
45
+ buildNamedRecordResponse
46
+ } = require("./lib/resource_views");
47
+ const {
48
+ buildTaskDetailResponse,
49
+ buildWorkflowDetailResponse,
50
+ buildWorkflowListResponse
51
+ } = require("./lib/catalog_views");
52
+ const {
53
+ getDecodedActionTargetId,
54
+ getDecodedSuffixId,
55
+ getQueryFilters
56
+ } = require("./lib/route_params");
57
+ const {
58
+ isDisputeActionPath,
59
+ isDisputeDetailPath,
60
+ isRefundActionPath,
61
+ isRefundDetailPath,
62
+ isRemediationTicketActionPath,
63
+ isRemediationTicketDetailPath
64
+ } = require("./lib/route_matchers");
65
+ const { executeCommandRequest } = require("./lib/command_flow");
66
+
67
+ const state = createRuntimeState();
68
+
69
+ function buildNamedTaskPayload(taskRef, body, fallbackCommand) {
70
+ const requestBody = body && typeof body === "object" && !Array.isArray(body) ? body : {};
71
+ return {
72
+ command: requestBody.command || fallbackCommand,
73
+ task_ref: taskRef,
74
+ body: requestBody.body && typeof requestBody.body === "object" && !Array.isArray(requestBody.body)
75
+ ? requestBody.body
76
+ : requestBody,
77
+ callback_url: requestBody.callback_url || null,
78
+ account_id: requestBody.account_id || null,
79
+ defer_completion: requestBody.defer_completion === true,
80
+ quote_id: requestBody.quote_id || null,
81
+ settlement_mode: requestBody.settlement_mode || null,
82
+ settlement_preference: requestBody.settlement_preference || null,
83
+ settlement: requestBody.settlement || null
84
+ };
85
+ }
86
+
87
+ function listTransactionRecords(filters) {
88
+ const transactions = Array.from(state.transactions.values()).map((item) => item.transaction);
89
+ return listRecords(new Map(transactions.map((item) => [item.transaction_id, item])), filters);
90
+ }
91
+
92
+ function listReceiptRecords(filters) {
93
+ const receipts = Array.from(state.receipts.values()).map((item) => item.receipt);
94
+ return listRecords(new Map(receipts.map((item) => [item.receipt_id, item])), filters);
95
+ }
96
+
97
+ function listPaymentQuoteRecords(filters) {
98
+ return listRecords(state.quotes, filters);
99
+ }
100
+
101
+ function buildOperationsDashboard(filters) {
102
+ const disputes = listRecords(state.disputes, filters);
103
+ const refunds = listRecords(state.refunds, filters);
104
+ const remediationTickets = listRecords(state.remediationTickets, filters);
105
+ const transactions = listTransactionRecords(filters);
106
+ const receipts = listReceiptRecords(filters);
107
+ const deliveries = listRecords(state.deliveries, filters);
108
+ return summarizeOperationsDashboardPayload({
109
+ open_issues: {
110
+ disputes,
111
+ refunds,
112
+ remediation_tickets: remediationTickets
113
+ },
114
+ execution_exceptions: {
115
+ transactions,
116
+ receipts,
117
+ deliveries,
118
+ remediation_tickets: remediationTickets
119
+ }
120
+ });
121
+ }
122
+
123
+ function buildAttentionRecords(filters) {
124
+ const dashboard = buildOperationsDashboard(filters);
125
+ const refs = dashboard && dashboard.attention_refs ? dashboard.attention_refs : {};
126
+ const disputes = (Array.isArray(refs.dispute_ids) ? refs.dispute_ids : [])
127
+ .map((id) => summarizeDisputePayload(state.disputes.get(id)))
128
+ .filter((entry) => entry && entry.dispute_id);
129
+ const refunds = (Array.isArray(refs.refund_ids) ? refs.refund_ids : [])
130
+ .map((id) => summarizeRefundPayload(state.refunds.get(id)))
131
+ .filter((entry) => entry && entry.refund_id);
132
+ const remediationTickets = (Array.isArray(refs.ticket_ids) ? refs.ticket_ids : [])
133
+ .map((id) => summarizeRemediationTicketPayload(state.remediationTickets.get(id)))
134
+ .filter((entry) => entry && entry.ticket_id);
135
+ const transactions = (Array.isArray(refs.transaction_ids) ? refs.transaction_ids : [])
136
+ .map((id) => summarizeTransactionPayload(state.transactions.get(id)))
137
+ .filter((entry) => entry && entry.transaction_id);
138
+ const receipts = (Array.isArray(refs.receipt_ids) ? refs.receipt_ids : [])
139
+ .map((id) => summarizeReceiptPayload(state.receipts.get(id)))
140
+ .filter((entry) => entry && entry.receipt_id);
141
+ const deliveries = (Array.isArray(refs.delivery_ids) ? refs.delivery_ids : [])
142
+ .map((id) => summarizeDeliveryPayload(state.deliveries.get(id)))
143
+ .filter((entry) => entry && entry.delivery_id);
144
+ return {
145
+ dashboard,
146
+ disputes,
147
+ refunds,
148
+ remediation_tickets: remediationTickets,
149
+ transactions,
150
+ receipts,
151
+ deliveries
152
+ };
153
+ }
154
+
155
+ async function routeRequest(req, res) {
156
+ const url = new URL(req.url, "http://127.0.0.1");
157
+ hydrateQuoteStates(state);
158
+ hydratePendingExecutions(state);
159
+
160
+ if (req.method === "GET" && url.pathname === "/health") {
161
+ sendJson(res, 200, {
162
+ status: "ok",
163
+ service: "xytara",
164
+ readiness: "ready"
165
+ });
166
+ return;
167
+ }
168
+
169
+ if (req.method === "GET" && url.pathname === "/v1/catalog") {
170
+ sendJson(res, 200, getCatalog());
171
+ return;
172
+ }
173
+
174
+ if (req.method === "GET" && url.pathname === "/v1/catalog/summary") {
175
+ sendJson(res, 200, summarizeCatalog());
176
+ return;
177
+ }
178
+
179
+ if (req.method === "GET" && url.pathname === "/v1/catalog/task") {
180
+ const response = buildTaskDetailResponse(getTaskDetail(url.searchParams.get("ref")));
181
+ sendJson(res, response.status, response.payload);
182
+ return;
183
+ }
184
+
185
+ if (req.method === "GET" && url.pathname === "/v1/catalog/workflows") {
186
+ const response = buildWorkflowListResponse(getWorkflowList());
187
+ sendJson(res, response.status, response.payload);
188
+ return;
189
+ }
190
+
191
+ if (req.method === "GET" && url.pathname === "/v1/catalog/workflow") {
192
+ const response = buildWorkflowDetailResponse(getWorkflowDetail(url.searchParams.get("ref")));
193
+ sendJson(res, response.status, response.payload);
194
+ return;
195
+ }
196
+
197
+ if (req.method === "POST" && url.pathname === "/v1/payment-quotes") {
198
+ const body = await readJsonBody(req);
199
+ sendJson(res, 200, { ok: true, quote: createQuote(state, body) });
200
+ return;
201
+ }
202
+
203
+ if (req.method === "POST" && url.pathname === "/v1/admission/preview") {
204
+ const body = await readJsonBody(req);
205
+ const result = executeCommandRequest(state, buildNamedTaskPayload("admission.preview", body, "admission.preview"), req.headers);
206
+ sendJson(res, result.status, result.payload);
207
+ return;
208
+ }
209
+
210
+ if (req.method === "POST" && url.pathname === "/v1/trust/handoffs/emit") {
211
+ const body = await readJsonBody(req);
212
+ const result = executeCommandRequest(state, buildNamedTaskPayload("trust.handoff.emit", body, "trust.handoff.emit"), req.headers);
213
+ sendJson(res, result.status, result.payload);
214
+ return;
215
+ }
216
+
217
+ if (req.method === "POST" && url.pathname === "/v1/trust/handoffs/validate") {
218
+ const body = await readJsonBody(req);
219
+ const result = executeCommandRequest(state, buildNamedTaskPayload("trust.handoff.validate", body, "trust.handoff.validate"), req.headers);
220
+ sendJson(res, result.status, result.payload);
221
+ return;
222
+ }
223
+
224
+ if (req.method === "POST" && url.pathname === "/v1/execution-receipts/handoffs/emit") {
225
+ const body = await readJsonBody(req);
226
+ const result = executeCommandRequest(state, buildNamedTaskPayload("execution_receipt.handoff.emit", body, "execution_receipt.handoff.emit"), req.headers);
227
+ sendJson(res, result.status, result.payload);
228
+ return;
229
+ }
230
+
231
+ if (req.method === "POST" && url.pathname === "/v1/execution-receipts/handoffs/validate") {
232
+ const body = await readJsonBody(req);
233
+ const result = executeCommandRequest(state, buildNamedTaskPayload("execution_receipt.handoff.validate", body, "execution_receipt.handoff.validate"), req.headers);
234
+ sendJson(res, result.status, result.payload);
235
+ return;
236
+ }
237
+
238
+ if (req.method === "POST" && url.pathname === "/v1/receipt-ledger/handoffs/emit") {
239
+ const body = await readJsonBody(req);
240
+ const result = executeCommandRequest(state, buildNamedTaskPayload("receipt_ledger.handoff.emit", body, "receipt_ledger.handoff.emit"), req.headers);
241
+ sendJson(res, result.status, result.payload);
242
+ return;
243
+ }
244
+
245
+ if (req.method === "POST" && url.pathname === "/v1/receipt-ledger/handoffs/validate") {
246
+ const body = await readJsonBody(req);
247
+ const result = executeCommandRequest(state, buildNamedTaskPayload("receipt_ledger.handoff.validate", body, "receipt_ledger.handoff.validate"), req.headers);
248
+ sendJson(res, result.status, result.payload);
249
+ return;
250
+ }
251
+
252
+ if (req.method === "POST" && url.pathname === "/v1/runtime/emit") {
253
+ const body = await readJsonBody(req);
254
+ const result = executeCommandRequest(state, buildNamedTaskPayload("runtime.emit", body, "runtime.emit"), req.headers);
255
+ sendJson(res, result.status, result.payload);
256
+ return;
257
+ }
258
+
259
+ if (req.method === "POST" && url.pathname === "/v1/runtime/anchor") {
260
+ const body = await readJsonBody(req);
261
+ const result = executeCommandRequest(state, buildNamedTaskPayload("runtime.anchor", body, "runtime.anchor"), req.headers);
262
+ sendJson(res, result.status, result.payload);
263
+ return;
264
+ }
265
+
266
+ if (req.method === "POST" && url.pathname === "/v1/runtime/emit-anchor") {
267
+ const body = await readJsonBody(req);
268
+ const result = executeCommandRequest(state, buildNamedTaskPayload("runtime.emit_anchor", body, "runtime.emit_anchor"), req.headers);
269
+ sendJson(res, result.status, result.payload);
270
+ return;
271
+ }
272
+
273
+ if (req.method === "POST" && url.pathname === "/v1/registry/register") {
274
+ const body = await readJsonBody(req);
275
+ const result = executeCommandRequest(state, buildNamedTaskPayload("registry.register", body, "registry.register"), req.headers);
276
+ sendJson(res, result.status, result.payload);
277
+ return;
278
+ }
279
+
280
+ if (req.method === "POST" && url.pathname === "/v1/anchoring/submit") {
281
+ const body = await readJsonBody(req);
282
+ const result = executeCommandRequest(state, buildNamedTaskPayload("anchoring.submit", body, "anchoring.submit"), req.headers);
283
+ sendJson(res, result.status, result.payload);
284
+ return;
285
+ }
286
+
287
+ if (req.method === "POST" && url.pathname === "/v1/mcp/tools/invoke") {
288
+ const body = await readJsonBody(req);
289
+ const payload = {
290
+ command: body.command || `mcp.invoke:${body.tool_name || "tool"}`,
291
+ task_ref: "adapter.mcp.invoke",
292
+ body: {
293
+ tool_name: body.tool_name || null,
294
+ arguments: body.arguments && typeof body.arguments === "object" && !Array.isArray(body.arguments)
295
+ ? body.arguments
296
+ : {}
297
+ },
298
+ callback_url: body.callback_url || null
299
+ };
300
+ const result = executeCommandRequest(state, payload, req.headers);
301
+ sendJson(res, result.status, result.payload);
302
+ return;
303
+ }
304
+
305
+ if (req.method === "GET" && url.pathname === "/v1/payment-quotes") {
306
+ sendJson(
307
+ res,
308
+ 200,
309
+ buildNamedRecordListResponse("quotes", listPaymentQuoteRecords(getQueryFilters(url))).payload
310
+ );
311
+ return;
312
+ }
313
+
314
+ if (req.method === "GET" && url.pathname.startsWith("/v1/payment-quotes/")) {
315
+ const quoteId = getDecodedSuffixId(url.pathname, "/v1/payment-quotes/");
316
+ const response = buildNamedRecordResponse("quote", state.quotes.get(quoteId), "quote_not_found");
317
+ sendJson(res, response.status, response.payload);
318
+ return;
319
+ }
320
+
321
+ if (req.method === "POST" && url.pathname === "/v1/commands/execute") {
322
+ const body = await readJsonBody(req);
323
+ const result = executeCommandRequest(state, body, req.headers);
324
+ sendJson(res, result.status, result.payload);
325
+ return;
326
+ }
327
+
328
+ if (req.method === "POST" && url.pathname === "/x402/commands/execute") {
329
+ const body = await readJsonBody(req);
330
+ if (!req.headers["payment-signature"]) {
331
+ const quote = createQuote(state, body);
332
+ sendJson(res, 402, {
333
+ ok: false,
334
+ payment_required: {
335
+ scheme: "exact",
336
+ quote
337
+ }
338
+ });
339
+ return;
340
+ }
341
+
342
+ const quote = body.quote_id && state.quotes.get(body.quote_id)
343
+ ? state.quotes.get(body.quote_id)
344
+ : createQuote(state, body);
345
+
346
+ if (quote && quote.status === "expired") {
347
+ sendJson(res, 409, {
348
+ ok: false,
349
+ reason: "quote_expired",
350
+ quote
351
+ });
352
+ return;
353
+ }
354
+
355
+ sendJson(res, 200, createCommandResult(state, body, quote));
356
+ return;
357
+ }
358
+
359
+ if (req.method === "POST" && url.pathname === "/x402/mcp/tools/invoke") {
360
+ const body = await readJsonBody(req);
361
+ const payload = {
362
+ account_id: body.account_id || null,
363
+ command: body.command || `mcp.invoke:${body.tool_name || "tool"}`,
364
+ task_ref: "adapter.mcp.invoke",
365
+ body: {
366
+ tool_name: body.tool_name || null,
367
+ arguments: body.arguments && typeof body.arguments === "object" && !Array.isArray(body.arguments)
368
+ ? body.arguments
369
+ : {}
370
+ },
371
+ callback_url: body.callback_url || null
372
+ };
373
+ if (!req.headers["payment-signature"]) {
374
+ const quote = createQuote(state, payload);
375
+ sendJson(res, 402, {
376
+ ok: false,
377
+ payment_required: {
378
+ scheme: "exact",
379
+ quote
380
+ }
381
+ });
382
+ return;
383
+ }
384
+
385
+ const quote = body.quote_id && state.quotes.get(body.quote_id)
386
+ ? state.quotes.get(body.quote_id)
387
+ : createQuote(state, payload);
388
+
389
+ if (quote && quote.status === "expired") {
390
+ sendJson(res, 409, {
391
+ ok: false,
392
+ reason: "quote_expired",
393
+ quote
394
+ });
395
+ return;
396
+ }
397
+
398
+ sendJson(res, 200, createCommandResult(state, payload, quote));
399
+ return;
400
+ }
401
+
402
+ if (req.method === "GET" && url.pathname === "/v1/governed-transactions") {
403
+ sendJson(
404
+ res,
405
+ 200,
406
+ buildNamedRecordListResponse("transactions", listTransactionRecords(getQueryFilters(url))).payload
407
+ );
408
+ return;
409
+ }
410
+
411
+ if (req.method === "GET" && url.pathname.startsWith("/v1/governed-transactions/")) {
412
+ const transactionId = getDecodedSuffixId(url.pathname, "/v1/governed-transactions/");
413
+ const result = state.transactions.get(transactionId);
414
+ const response = result || buildMissingResponse("transaction_not_found");
415
+ sendJson(res, response.status || 200, response.payload || response);
416
+ return;
417
+ }
418
+
419
+ if (req.method === "GET" && url.pathname === "/v1/receipts") {
420
+ sendJson(
421
+ res,
422
+ 200,
423
+ buildNamedRecordListResponse("receipts", listReceiptRecords(getQueryFilters(url))).payload
424
+ );
425
+ return;
426
+ }
427
+
428
+ if (req.method === "GET" && url.pathname.startsWith("/v1/receipts/")) {
429
+ const receiptId = getDecodedSuffixId(url.pathname, "/v1/receipts/");
430
+ const result = state.receipts.get(receiptId);
431
+ const response = result || buildMissingResponse("receipt_not_found");
432
+ sendJson(res, response.status || 200, response.payload || response);
433
+ return;
434
+ }
435
+
436
+ if (req.method === "GET" && url.pathname === "/v1/payment-ledger") {
437
+ const paymentLedger = Array.isArray(state.paymentLedger) ? state.paymentLedger : [];
438
+ sendJson(
439
+ res,
440
+ 200,
441
+ buildNamedRecordListResponse(
442
+ "payment_ledger",
443
+ listRecords(new Map(paymentLedger.map((item) => [item.payment_id, item])), getQueryFilters(url))
444
+ ).payload
445
+ );
446
+ return;
447
+ }
448
+
449
+ if (req.method === "GET" && url.pathname.startsWith("/v1/payment-ledger/")) {
450
+ const paymentId = getDecodedSuffixId(url.pathname, "/v1/payment-ledger/");
451
+ const paymentLedger = Array.isArray(state.paymentLedger) ? state.paymentLedger : [];
452
+ const payment = paymentLedger.find((item) => item && item.payment_id === paymentId);
453
+ const response = buildNamedRecordResponse("payment", payment, "payment_not_found");
454
+ sendJson(res, response.status, response.payload);
455
+ return;
456
+ }
457
+
458
+ if (req.method === "GET" && url.pathname === "/v1/deliveries") {
459
+ sendJson(
460
+ res,
461
+ 200,
462
+ buildNamedRecordListResponse("deliveries", listRecords(state.deliveries, getQueryFilters(url))).payload
463
+ );
464
+ return;
465
+ }
466
+
467
+ if (req.method === "GET" && url.pathname.startsWith("/v1/deliveries/")) {
468
+ const deliveryId = getDecodedSuffixId(url.pathname, "/v1/deliveries/");
469
+ const response = buildNamedRecordResponse("delivery", state.deliveries.get(deliveryId), "delivery_not_found");
470
+ sendJson(res, response.status, response.payload);
471
+ return;
472
+ }
473
+
474
+ if (req.method === "GET" && url.pathname === "/v1/disputes") {
475
+ sendJson(
476
+ res,
477
+ 200,
478
+ buildNamedRecordListResponse("disputes", listRecords(state.disputes, getQueryFilters(url))).payload
479
+ );
480
+ return;
481
+ }
482
+
483
+ if (req.method === "POST" && url.pathname === "/v1/disputes") {
484
+ const body = await readJsonBody(req);
485
+ sendJson(res, 200, {
486
+ ok: true,
487
+ dispute: createDispute(state, body)
488
+ });
489
+ return;
490
+ }
491
+
492
+ if (req.method === "GET" && isDisputeDetailPath(url.pathname)) {
493
+ const disputeId = getDecodedSuffixId(url.pathname, "/v1/disputes/");
494
+ const response = buildNamedRecordResponse("dispute", state.disputes.get(disputeId), "dispute_not_found");
495
+ sendJson(res, response.status, response.payload);
496
+ return;
497
+ }
498
+
499
+ if (req.method === "POST" && isDisputeActionPath(url.pathname)) {
500
+ const disputeId = getDecodedActionTargetId(url.pathname, "/v1/disputes/");
501
+ const body = await readJsonBody(req);
502
+ const response = buildNamedRecordResponse("dispute", actOnDispute(state, disputeId, body), "dispute_not_found");
503
+ sendJson(res, response.status, response.payload);
504
+ return;
505
+ }
506
+
507
+ if (req.method === "GET" && url.pathname === "/v1/refunds") {
508
+ sendJson(
509
+ res,
510
+ 200,
511
+ buildNamedRecordListResponse("refunds", listRecords(state.refunds, getQueryFilters(url))).payload
512
+ );
513
+ return;
514
+ }
515
+
516
+ if (req.method === "POST" && url.pathname === "/v1/refunds") {
517
+ const body = await readJsonBody(req);
518
+ sendJson(res, 200, {
519
+ ok: true,
520
+ refund: createRefund(state, body)
521
+ });
522
+ return;
523
+ }
524
+
525
+ if (req.method === "GET" && isRefundDetailPath(url.pathname)) {
526
+ const refundId = getDecodedSuffixId(url.pathname, "/v1/refunds/");
527
+ const response = buildNamedRecordResponse("refund", state.refunds.get(refundId), "refund_not_found");
528
+ sendJson(res, response.status, response.payload);
529
+ return;
530
+ }
531
+
532
+ if (req.method === "POST" && isRefundActionPath(url.pathname)) {
533
+ const refundId = getDecodedActionTargetId(url.pathname, "/v1/refunds/");
534
+ const body = await readJsonBody(req);
535
+ const response = buildNamedRecordResponse("refund", actOnRefund(state, refundId, body), "refund_not_found");
536
+ sendJson(res, response.status, response.payload);
537
+ return;
538
+ }
539
+
540
+ if (req.method === "POST" && url.pathname === "/v1/remediation-tickets") {
541
+ const body = await readJsonBody(req);
542
+ sendJson(res, 200, {
543
+ ok: true,
544
+ remediation_ticket: createRemediationTicket(state, body)
545
+ });
546
+ return;
547
+ }
548
+
549
+ if (req.method === "GET" && url.pathname === "/v1/remediation-tickets") {
550
+ sendJson(
551
+ res,
552
+ 200,
553
+ buildNamedRecordListResponse(
554
+ "remediation_tickets",
555
+ listRecords(state.remediationTickets, getQueryFilters(url))
556
+ ).payload
557
+ );
558
+ return;
559
+ }
560
+
561
+ if (req.method === "GET" && isRemediationTicketDetailPath(url.pathname)) {
562
+ const ticketId = getDecodedSuffixId(url.pathname, "/v1/remediation-tickets/");
563
+ const response = buildNamedRecordResponse(
564
+ "remediation_ticket",
565
+ state.remediationTickets.get(ticketId),
566
+ "remediation_ticket_not_found"
567
+ );
568
+ sendJson(res, response.status, response.payload);
569
+ return;
570
+ }
571
+
572
+ if (req.method === "POST" && isRemediationTicketActionPath(url.pathname)) {
573
+ const ticketId = getDecodedActionTargetId(url.pathname, "/v1/remediation-tickets/");
574
+ const body = await readJsonBody(req);
575
+ const response = buildNamedRecordResponse(
576
+ "remediation_ticket",
577
+ actOnRemediationTicket(state, ticketId, body),
578
+ "remediation_ticket_not_found"
579
+ );
580
+ sendJson(res, response.status, response.payload);
581
+ return;
582
+ }
583
+
584
+ if (req.method === "GET" && url.pathname === "/v1/reconciliation-report") {
585
+ sendJson(res, 200, buildReconciliationReport(state, getQueryFilters(url), listRecords));
586
+ return;
587
+ }
588
+
589
+ if (req.method === "GET" && url.pathname === "/v1/operator-export-pack") {
590
+ sendJson(res, 200, buildOperatorExportPack(state, getQueryFilters(url), listRecords));
591
+ return;
592
+ }
593
+
594
+ if (req.method === "GET" && url.pathname === "/v1/operations/dashboard") {
595
+ sendJson(res, 200, buildOperationsDashboard(getQueryFilters(url)));
596
+ return;
597
+ }
598
+
599
+ if (req.method === "GET" && url.pathname === "/v1/operations/attention-records") {
600
+ sendJson(res, 200, buildAttentionRecords(getQueryFilters(url)));
601
+ return;
602
+ }
603
+
604
+ sendJson(res, 404, { ok: false, reason: "not_found", path: url.pathname });
605
+ }
606
+
607
+ function createServer() {
608
+ return http.createServer((req, res) => {
609
+ routeRequest(req, res).catch((error) => {
610
+ sendJson(res, 500, { ok: false, reason: error.message });
611
+ });
612
+ });
613
+ }
614
+
615
+ if (require.main === module) {
616
+ const port = Number(process.env.XYTARA_PORT || 4320);
617
+ const host = process.env.XYTARA_HOST || "127.0.0.1";
618
+ const server = createServer();
619
+ server.listen(port, host, () => {
620
+ console.log(`xytara listening on http://${host}:${port}`);
621
+ });
622
+ }
623
+
624
+ module.exports = {
625
+ createServer
626
+ };