@legenki/print2medusa 0.8.1 → 0.8.2

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.
@@ -307,6 +307,191 @@ const PrintfulSyncWidget = () => {
307
307
  adminSdk.defineWidgetConfig({
308
308
  zone: "product.list.before"
309
309
  });
310
+ const STATUS_COLOUR = {
311
+ success: "green",
312
+ running: "orange",
313
+ partial: "orange",
314
+ failed: "red"
315
+ };
316
+ const looksStuck = (row, staleMinutes) => {
317
+ if (!row || row.status !== "running") {
318
+ return false;
319
+ }
320
+ const beat = row.heartbeat_at ?? row.started_at;
321
+ if (!beat) {
322
+ return true;
323
+ }
324
+ return Date.now() - new Date(beat).getTime() > staleMinutes * 6e4;
325
+ };
326
+ const minutesSince = (iso) => {
327
+ if (!iso) {
328
+ return null;
329
+ }
330
+ return Math.floor((Date.now() - new Date(iso).getTime()) / 6e4);
331
+ };
332
+ const PrintfulPage = () => {
333
+ var _a, _b, _c;
334
+ const [syncs, setSyncs] = react.useState([]);
335
+ const [health, setHealth] = react.useState(null);
336
+ const [stats, setStats] = react.useState(null);
337
+ const [loading, setLoading] = react.useState(true);
338
+ const [clearing, setClearing] = react.useState(false);
339
+ const load = react.useCallback(async () => {
340
+ var _a2;
341
+ const [h, w, s] = await Promise.allSettled([
342
+ fetch("/admin/printful/history", { credentials: "include" }),
343
+ fetch("/admin/printful/health", { credentials: "include" }),
344
+ fetch("/admin/printful/statistics", { credentials: "include" })
345
+ ]);
346
+ if (h.status === "fulfilled" && h.value.ok) {
347
+ const body = await h.value.json();
348
+ setSyncs(body.syncs ?? []);
349
+ }
350
+ if (w.status === "fulfilled" && w.value.ok) {
351
+ setHealth(await w.value.json());
352
+ }
353
+ if (s.status === "fulfilled" && s.value.ok) {
354
+ const body = await s.value.json();
355
+ setStats(((_a2 = body.statistics) == null ? void 0 : _a2[0]) ?? null);
356
+ }
357
+ setLoading(false);
358
+ }, []);
359
+ react.useEffect(() => {
360
+ void load();
361
+ }, [load]);
362
+ const latest = syncs[0];
363
+ const running = (latest == null ? void 0 : latest.status) === "running";
364
+ react.useEffect(() => {
365
+ if (!running) {
366
+ return;
367
+ }
368
+ const t = setInterval(() => void load(), 3e3);
369
+ return () => clearInterval(t);
370
+ }, [running, load]);
371
+ const clearStuck = async () => {
372
+ setClearing(true);
373
+ try {
374
+ const res = await fetch("/admin/printful/sync/clear", {
375
+ method: "POST",
376
+ credentials: "include",
377
+ headers: { "content-type": "application/json" },
378
+ body: JSON.stringify({ confirm: "clear-stuck-sync" })
379
+ });
380
+ const body = await res.json().catch(() => ({}));
381
+ if (res.ok) {
382
+ ui.toast.success("Cleared the stuck sync", {
383
+ description: body.error_message
384
+ });
385
+ } else if (res.status === 409) {
386
+ ui.toast.info("The sync is still alive", {
387
+ description: "It checked in while you were looking. Nothing cleared."
388
+ });
389
+ } else {
390
+ ui.toast.error("Could not clear the sync", { description: body.message });
391
+ }
392
+ await load();
393
+ } catch (err) {
394
+ ui.toast.error("Could not clear the sync", {
395
+ description: err instanceof Error ? err.message : String(err)
396
+ });
397
+ } finally {
398
+ setClearing(false);
399
+ }
400
+ };
401
+ const stuck = looksStuck(latest, (health == null ? void 0 : health.window) ?? 60);
402
+ const silentFor = minutesSince((latest == null ? void 0 : latest.heartbeat_at) ?? (latest == null ? void 0 : latest.started_at));
403
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
404
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
405
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Printful" }),
406
+ /* @__PURE__ */ jsxRuntime.jsx(
407
+ ui.Button,
408
+ {
409
+ size: "small",
410
+ variant: "secondary",
411
+ onClick: () => void load(),
412
+ disabled: loading,
413
+ children: "Refresh"
414
+ }
415
+ )
416
+ ] }),
417
+ stuck && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
418
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "This sync looks stuck" }),
419
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
420
+ "It has not checked in",
421
+ silentFor != null ? ` for ${silentFor} minutes` : "",
422
+ ". Until it is cleared or reclaimed, no other sync can start."
423
+ ] }),
424
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt, { children: [
425
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "danger", disabled: clearing, children: "Clear it" }) }),
426
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Content, { children: [
427
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Header, { children: [
428
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Title, { children: "Clear the stuck sync?" }),
429
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Description, { children: "This marks the run as failed so a new sync can start. If it turns out to be alive, nothing is cleared. Products already imported are not touched." })
430
+ ] }),
431
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Footer, { children: [
432
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Cancel, { children: "Cancel" }),
433
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Action, { onClick: () => void clearStuck(), children: "Clear" })
434
+ ] })
435
+ ] })
436
+ ] })
437
+ ] }),
438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
439
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Sales" }),
440
+ stats ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
441
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
442
+ "Paid orders: ",
443
+ ((_a = stats.total_paid_orders) == null ? void 0 : _a.value) ?? "—"
444
+ ] }),
445
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
446
+ "Profit: ",
447
+ ((_b = stats.profit) == null ? void 0 : _b.value) ?? "—",
448
+ " ",
449
+ ((_c = stats.currency) == null ? void 0 : _c.toUpperCase()) ?? ""
450
+ ] })
451
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "Printful reported no statistics." })
452
+ ] }),
453
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
454
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Webhooks" }),
455
+ health && !health.ever_received && // The state a misconfigured webhook leaves behind, and the reason
456
+ // this panel exists: a store can look fine and never hear back.
457
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: health.secret_set ? "A secret is configured, but nothing has arrived yet." : "No webhook secret is configured." }),
458
+ health && health.ever_received && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
459
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
460
+ "Last event:",
461
+ " ",
462
+ health.last_event_at ? new Date(health.last_event_at).toLocaleString() : "—"
463
+ ] }),
464
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
465
+ health.processed,
466
+ " processed · ",
467
+ health.deferred,
468
+ " waiting ·",
469
+ " ",
470
+ health.failed,
471
+ " failed"
472
+ ] })
473
+ ] })
474
+ ] }),
475
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
476
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Recent syncs" }),
477
+ syncs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No syncs yet." }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
478
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
479
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Started" }),
480
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
481
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Result" })
482
+ ] }) }),
483
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: syncs.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
484
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: row.started_at ? new Date(row.started_at).toLocaleString() : "—" }),
485
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: STATUS_COLOUR[row.status] ?? "grey", children: row.status }) }),
486
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: row.status === "running" ? `${row.products_processed ?? 0} of ${row.products_total ?? "?"}` : `${row.products_created ?? 0} created · ${row.products_updated ?? 0} updated · ${row.products_failed ?? 0} failed` })
487
+ ] }, row.id)) })
488
+ ] })
489
+ ] })
490
+ ] });
491
+ };
492
+ const config = adminSdk.defineRouteConfig({
493
+ label: "Printful"
494
+ });
310
495
  const i18nTranslations0 = {};
311
496
  const widgetModule = { widgets: [
312
497
  {
@@ -321,10 +506,25 @@ const widgetModule = { widgets: [
321
506
  }
322
507
  ] };
323
508
  const routeModule = {
324
- routes: []
509
+ routes: [
510
+ {
511
+ Component: PrintfulPage,
512
+ path: "/printful",
513
+ handle: { label: config.label, translationNs: config.translationNs }
514
+ }
515
+ ]
325
516
  };
326
517
  const menuItemModule = {
327
- menuItems: []
518
+ menuItems: [
519
+ {
520
+ label: config.label,
521
+ icon: void 0,
522
+ path: "/printful",
523
+ nested: void 0,
524
+ rank: void 0,
525
+ translationNs: void 0
526
+ }
527
+ ]
328
528
  };
329
529
  const formModule = { customFields: {} };
330
530
  const displayModule = {
@@ -1,8 +1,8 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { defineWidgetConfig } from "@medusajs/admin-sdk";
3
- import { Container, Heading, Badge, Text, Button, toast } from "@medusajs/ui";
2
+ import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
+ import { Container, Heading, Badge, Text, Button, toast, Prompt, Table } from "@medusajs/ui";
4
4
  import { defaultCurrencies } from "@medusajs/framework/utils";
5
- import { useState, useEffect } from "react";
5
+ import { useState, useEffect, useCallback } from "react";
6
6
  function minorUnitFactor(currencyCode) {
7
7
  return isZeroDecimalCurrency(currencyCode) ? 1 : 100;
8
8
  }
@@ -306,6 +306,191 @@ const PrintfulSyncWidget = () => {
306
306
  defineWidgetConfig({
307
307
  zone: "product.list.before"
308
308
  });
309
+ const STATUS_COLOUR = {
310
+ success: "green",
311
+ running: "orange",
312
+ partial: "orange",
313
+ failed: "red"
314
+ };
315
+ const looksStuck = (row, staleMinutes) => {
316
+ if (!row || row.status !== "running") {
317
+ return false;
318
+ }
319
+ const beat = row.heartbeat_at ?? row.started_at;
320
+ if (!beat) {
321
+ return true;
322
+ }
323
+ return Date.now() - new Date(beat).getTime() > staleMinutes * 6e4;
324
+ };
325
+ const minutesSince = (iso) => {
326
+ if (!iso) {
327
+ return null;
328
+ }
329
+ return Math.floor((Date.now() - new Date(iso).getTime()) / 6e4);
330
+ };
331
+ const PrintfulPage = () => {
332
+ var _a, _b, _c;
333
+ const [syncs, setSyncs] = useState([]);
334
+ const [health, setHealth] = useState(null);
335
+ const [stats, setStats] = useState(null);
336
+ const [loading, setLoading] = useState(true);
337
+ const [clearing, setClearing] = useState(false);
338
+ const load = useCallback(async () => {
339
+ var _a2;
340
+ const [h, w, s] = await Promise.allSettled([
341
+ fetch("/admin/printful/history", { credentials: "include" }),
342
+ fetch("/admin/printful/health", { credentials: "include" }),
343
+ fetch("/admin/printful/statistics", { credentials: "include" })
344
+ ]);
345
+ if (h.status === "fulfilled" && h.value.ok) {
346
+ const body = await h.value.json();
347
+ setSyncs(body.syncs ?? []);
348
+ }
349
+ if (w.status === "fulfilled" && w.value.ok) {
350
+ setHealth(await w.value.json());
351
+ }
352
+ if (s.status === "fulfilled" && s.value.ok) {
353
+ const body = await s.value.json();
354
+ setStats(((_a2 = body.statistics) == null ? void 0 : _a2[0]) ?? null);
355
+ }
356
+ setLoading(false);
357
+ }, []);
358
+ useEffect(() => {
359
+ void load();
360
+ }, [load]);
361
+ const latest = syncs[0];
362
+ const running = (latest == null ? void 0 : latest.status) === "running";
363
+ useEffect(() => {
364
+ if (!running) {
365
+ return;
366
+ }
367
+ const t = setInterval(() => void load(), 3e3);
368
+ return () => clearInterval(t);
369
+ }, [running, load]);
370
+ const clearStuck = async () => {
371
+ setClearing(true);
372
+ try {
373
+ const res = await fetch("/admin/printful/sync/clear", {
374
+ method: "POST",
375
+ credentials: "include",
376
+ headers: { "content-type": "application/json" },
377
+ body: JSON.stringify({ confirm: "clear-stuck-sync" })
378
+ });
379
+ const body = await res.json().catch(() => ({}));
380
+ if (res.ok) {
381
+ toast.success("Cleared the stuck sync", {
382
+ description: body.error_message
383
+ });
384
+ } else if (res.status === 409) {
385
+ toast.info("The sync is still alive", {
386
+ description: "It checked in while you were looking. Nothing cleared."
387
+ });
388
+ } else {
389
+ toast.error("Could not clear the sync", { description: body.message });
390
+ }
391
+ await load();
392
+ } catch (err) {
393
+ toast.error("Could not clear the sync", {
394
+ description: err instanceof Error ? err.message : String(err)
395
+ });
396
+ } finally {
397
+ setClearing(false);
398
+ }
399
+ };
400
+ const stuck = looksStuck(latest, (health == null ? void 0 : health.window) ?? 60);
401
+ const silentFor = minutesSince((latest == null ? void 0 : latest.heartbeat_at) ?? (latest == null ? void 0 : latest.started_at));
402
+ return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
403
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
404
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Printful" }),
405
+ /* @__PURE__ */ jsx(
406
+ Button,
407
+ {
408
+ size: "small",
409
+ variant: "secondary",
410
+ onClick: () => void load(),
411
+ disabled: loading,
412
+ children: "Refresh"
413
+ }
414
+ )
415
+ ] }),
416
+ stuck && /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
417
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "This sync looks stuck" }),
418
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
419
+ "It has not checked in",
420
+ silentFor != null ? ` for ${silentFor} minutes` : "",
421
+ ". Until it is cleared or reclaimed, no other sync can start."
422
+ ] }),
423
+ /* @__PURE__ */ jsxs(Prompt, { children: [
424
+ /* @__PURE__ */ jsx(Prompt.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { size: "small", variant: "danger", disabled: clearing, children: "Clear it" }) }),
425
+ /* @__PURE__ */ jsxs(Prompt.Content, { children: [
426
+ /* @__PURE__ */ jsxs(Prompt.Header, { children: [
427
+ /* @__PURE__ */ jsx(Prompt.Title, { children: "Clear the stuck sync?" }),
428
+ /* @__PURE__ */ jsx(Prompt.Description, { children: "This marks the run as failed so a new sync can start. If it turns out to be alive, nothing is cleared. Products already imported are not touched." })
429
+ ] }),
430
+ /* @__PURE__ */ jsxs(Prompt.Footer, { children: [
431
+ /* @__PURE__ */ jsx(Prompt.Cancel, { children: "Cancel" }),
432
+ /* @__PURE__ */ jsx(Prompt.Action, { onClick: () => void clearStuck(), children: "Clear" })
433
+ ] })
434
+ ] })
435
+ ] })
436
+ ] }),
437
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
438
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Sales" }),
439
+ stats ? /* @__PURE__ */ jsxs(Fragment, { children: [
440
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
441
+ "Paid orders: ",
442
+ ((_a = stats.total_paid_orders) == null ? void 0 : _a.value) ?? "—"
443
+ ] }),
444
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
445
+ "Profit: ",
446
+ ((_b = stats.profit) == null ? void 0 : _b.value) ?? "—",
447
+ " ",
448
+ ((_c = stats.currency) == null ? void 0 : _c.toUpperCase()) ?? ""
449
+ ] })
450
+ ] }) : /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "Printful reported no statistics." })
451
+ ] }),
452
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
453
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Webhooks" }),
454
+ health && !health.ever_received && // The state a misconfigured webhook leaves behind, and the reason
455
+ // this panel exists: a store can look fine and never hear back.
456
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: health.secret_set ? "A secret is configured, but nothing has arrived yet." : "No webhook secret is configured." }),
457
+ health && health.ever_received && /* @__PURE__ */ jsxs(Fragment, { children: [
458
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
459
+ "Last event:",
460
+ " ",
461
+ health.last_event_at ? new Date(health.last_event_at).toLocaleString() : "—"
462
+ ] }),
463
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
464
+ health.processed,
465
+ " processed · ",
466
+ health.deferred,
467
+ " waiting ·",
468
+ " ",
469
+ health.failed,
470
+ " failed"
471
+ ] })
472
+ ] })
473
+ ] }),
474
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
475
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Recent syncs" }),
476
+ syncs.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No syncs yet." }) : /* @__PURE__ */ jsxs(Table, { children: [
477
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
478
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Started" }),
479
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Status" }),
480
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Result" })
481
+ ] }) }),
482
+ /* @__PURE__ */ jsx(Table.Body, { children: syncs.map((row) => /* @__PURE__ */ jsxs(Table.Row, { children: [
483
+ /* @__PURE__ */ jsx(Table.Cell, { children: row.started_at ? new Date(row.started_at).toLocaleString() : "—" }),
484
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: STATUS_COLOUR[row.status] ?? "grey", children: row.status }) }),
485
+ /* @__PURE__ */ jsx(Table.Cell, { children: row.status === "running" ? `${row.products_processed ?? 0} of ${row.products_total ?? "?"}` : `${row.products_created ?? 0} created · ${row.products_updated ?? 0} updated · ${row.products_failed ?? 0} failed` })
486
+ ] }, row.id)) })
487
+ ] })
488
+ ] })
489
+ ] });
490
+ };
491
+ const config = defineRouteConfig({
492
+ label: "Printful"
493
+ });
309
494
  const i18nTranslations0 = {};
310
495
  const widgetModule = { widgets: [
311
496
  {
@@ -320,10 +505,25 @@ const widgetModule = { widgets: [
320
505
  }
321
506
  ] };
322
507
  const routeModule = {
323
- routes: []
508
+ routes: [
509
+ {
510
+ Component: PrintfulPage,
511
+ path: "/printful",
512
+ handle: { label: config.label, translationNs: config.translationNs }
513
+ }
514
+ ]
324
515
  };
325
516
  const menuItemModule = {
326
- menuItems: []
517
+ menuItems: [
518
+ {
519
+ label: config.label,
520
+ icon: void 0,
521
+ path: "/printful",
522
+ nested: void 0,
523
+ rank: void 0,
524
+ translationNs: void 0
525
+ }
526
+ ]
327
527
  };
328
528
  const formModule = { customFields: {} };
329
529
  const displayModule = {
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const printful_1 = require("../../../../modules/printful");
5
+ const webhook_health_1 = require("../../../../utils/webhook-health");
6
+ /**
7
+ * Rows the summary is computed over: the most recent slice of the event table.
8
+ *
9
+ * Deliberately a window rather than the whole table. This is a health panel —
10
+ * "are deliveries arriving and are they landing" — and the answer lives in
11
+ * recent traffic. Reading every row ever received would grow unbounded with
12
+ * store age and make the page slower the longer the plugin has been installed,
13
+ * for an answer that gets less useful the further back it reaches.
14
+ *
15
+ * The counts are therefore explicitly counts within this window, and the
16
+ * response says so via `window`, so the page cannot present them as all-time
17
+ * totals.
18
+ */
19
+ const HEALTH_WINDOW = 200;
20
+ /**
21
+ * Webhook delivery health.
22
+ *
23
+ * This route does NOT call Printful. `client.getWebhookConfig()` exists and
24
+ * would report the registered URL authoritatively, but a panel whose entire
25
+ * job is to tell the owner whether Printful is reaching them must not go dark
26
+ * when Printful is down — an outage would turn the one screen you check
27
+ * during an outage into an error. Everything here is answered from local rows
28
+ * and plugin options, so it renders during a Printful outage and, in fact,
29
+ * shows exactly the symptom (no recent events) that an outage produces.
30
+ *
31
+ * `/admin/printful/webhook` still exposes the live config for the moment an
32
+ * admin actually wants to inspect or overwrite the registration, and that
33
+ * route is the right place for a network call because the admin asked for it.
34
+ */
35
+ async function GET(req, res) {
36
+ const printful = req.scope.resolve(printful_1.PRINTFUL_MODULE);
37
+ const options = await printful.getOptions();
38
+ const rows = (await printful.listPrintfulWebhookEvents({}, { take: HEALTH_WINDOW, order: { created_at: "DESC" } }));
39
+ const summary = (0, webhook_health_1.summarizeWebhookHealth)(rows, {
40
+ secret_set: Boolean(options.webhookSecret),
41
+ });
42
+ res.status(200).json({
43
+ ...summary,
44
+ window: HEALTH_WINDOW,
45
+ });
46
+ }
47
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL2hlYWx0aC9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQXNDQSxrQkFpQkM7QUF0REQsMkRBQThEO0FBRTlELHFFQUd5QztBQUV6Qzs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCxNQUFNLGFBQWEsR0FBRyxHQUFHLENBQUE7QUFFekI7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSSxLQUFLLFVBQVUsR0FBRyxDQUFDLEdBQWtCLEVBQUUsR0FBbUI7SUFDL0QsTUFBTSxRQUFRLEdBQTBCLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLDBCQUFlLENBQUMsQ0FBQTtJQUMxRSxNQUFNLE9BQU8sR0FBRyxNQUFNLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQTtJQUUzQyxNQUFNLElBQUksR0FBRyxDQUFDLE1BQU0sUUFBUSxDQUFDLHlCQUF5QixDQUNwRCxFQUFFLEVBQ0YsRUFBRSxJQUFJLEVBQUUsYUFBYSxFQUFFLEtBQUssRUFBRSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUN2RCxDQUF1QixDQUFBO0lBRXhCLE1BQU0sT0FBTyxHQUFHLElBQUEsdUNBQXNCLEVBQUMsSUFBSSxFQUFFO1FBQzNDLFVBQVUsRUFBRSxPQUFPLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQztLQUMzQyxDQUFDLENBQUE7SUFFRixHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztRQUNuQixHQUFHLE9BQU87UUFDVixNQUFNLEVBQUUsYUFBYTtLQUN0QixDQUFDLENBQUE7QUFDSixDQUFDIn0=
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const printful_1 = require("../../../../modules/printful");
5
+ /** Rows per page. Enough to cover a week of hourly syncs on one screen. */
6
+ const DEFAULT_LIMIT = 20;
7
+ /**
8
+ * Hard ceiling on `limit`. The admin page never asks for more than the
9
+ * default; this only stops a hand-crafted `?limit=100000` from paging the
10
+ * whole table into memory to render a list nobody reads.
11
+ */
12
+ const MAX_LIMIT = 100;
13
+ function parsePositiveInt(value, fallback, max) {
14
+ const parsed = Number(value);
15
+ if (!Number.isFinite(parsed) || parsed <= 0) {
16
+ return fallback;
17
+ }
18
+ return Math.min(Math.floor(parsed), max);
19
+ }
20
+ /**
21
+ * Recent sync runs, newest first.
22
+ *
23
+ * Ordered by `started_at DESC` to match `getLatestSyncLog`, so the first row
24
+ * here is always the same run the status widget calls "latest". Ordering by
25
+ * `finished_at` instead would sort a still-running sync (finished_at null) to
26
+ * an arbitrary end of the list.
27
+ */
28
+ async function GET(req, res) {
29
+ const printful = req.scope.resolve(printful_1.PRINTFUL_MODULE);
30
+ const limit = parsePositiveInt(req.query.limit, DEFAULT_LIMIT, MAX_LIMIT);
31
+ const offset = Math.max(0, Number(req.query.offset) || 0);
32
+ const logs = await printful.listPrintfulSyncLogs({}, { take: limit, skip: offset, order: { started_at: "DESC" } });
33
+ res.status(200).json({
34
+ syncs: logs.map((log) => ({
35
+ id: log.id,
36
+ status: log.status,
37
+ started_at: log.started_at,
38
+ finished_at: log.finished_at,
39
+ heartbeat_at: log.heartbeat_at,
40
+ products_created: log.products_created,
41
+ products_updated: log.products_updated,
42
+ products_failed: log.products_failed,
43
+ products_processed: log.products_processed,
44
+ products_total: log.products_total,
45
+ error_message: log.error_message,
46
+ })),
47
+ limit,
48
+ offset,
49
+ });
50
+ }
51
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL2hpc3Rvcnkvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFrQ0Esa0JBNEJDO0FBN0RELDJEQUE4RDtBQUc5RCwyRUFBMkU7QUFDM0UsTUFBTSxhQUFhLEdBQUcsRUFBRSxDQUFBO0FBRXhCOzs7O0dBSUc7QUFDSCxNQUFNLFNBQVMsR0FBRyxHQUFHLENBQUE7QUFFckIsU0FBUyxnQkFBZ0IsQ0FDdkIsS0FBYyxFQUNkLFFBQWdCLEVBQ2hCLEdBQVc7SUFFWCxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDNUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLElBQUksTUFBTSxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQzVDLE9BQU8sUUFBUSxDQUFBO0lBQ2pCLENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQTtBQUMxQyxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNJLEtBQUssVUFBVSxHQUFHLENBQUMsR0FBa0IsRUFBRSxHQUFtQjtJQUMvRCxNQUFNLFFBQVEsR0FBMEIsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsMEJBQWUsQ0FBQyxDQUFBO0lBRTFFLE1BQU0sS0FBSyxHQUFHLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxFQUFFLGFBQWEsRUFBRSxTQUFTLENBQUMsQ0FBQTtJQUN6RSxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQTtJQUV6RCxNQUFNLElBQUksR0FBRyxNQUFNLFFBQVEsQ0FBQyxvQkFBb0IsQ0FDOUMsRUFBRSxFQUNGLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUM3RCxDQUFBO0lBRUQsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDbkIsS0FBSyxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDeEIsRUFBRSxFQUFFLEdBQUcsQ0FBQyxFQUFFO1lBQ1YsTUFBTSxFQUFFLEdBQUcsQ0FBQyxNQUFNO1lBQ2xCLFVBQVUsRUFBRSxHQUFHLENBQUMsVUFBVTtZQUMxQixXQUFXLEVBQUUsR0FBRyxDQUFDLFdBQVc7WUFDNUIsWUFBWSxFQUFFLEdBQUcsQ0FBQyxZQUFZO1lBQzlCLGdCQUFnQixFQUFFLEdBQUcsQ0FBQyxnQkFBZ0I7WUFDdEMsZ0JBQWdCLEVBQUUsR0FBRyxDQUFDLGdCQUFnQjtZQUN0QyxlQUFlLEVBQUUsR0FBRyxDQUFDLGVBQWU7WUFDcEMsa0JBQWtCLEVBQUUsR0FBRyxDQUFDLGtCQUFrQjtZQUMxQyxjQUFjLEVBQUUsR0FBRyxDQUFDLGNBQWM7WUFDbEMsYUFBYSxFQUFFLEdBQUcsQ0FBQyxhQUFhO1NBQ2pDLENBQUMsQ0FBQztRQUNILEtBQUs7UUFDTCxNQUFNO0tBQ1AsQ0FBQyxDQUFBO0FBQ0osQ0FBQyJ9
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const printful_1 = require("../../../../modules/printful");
6
+ /** Days of history when the caller does not ask for a range. */
7
+ const DEFAULT_DAYS = 30;
8
+ /**
9
+ * Reports the admin page renders. Asking for only these keeps the response
10
+ * small; Printful also offers per-product and per-variant breakdowns, which
11
+ * nothing displays yet and which would be paid for on every page load.
12
+ */
13
+ const REPORT_TYPES = "profit,total_paid_orders,average_fulfillment_time";
14
+ const isoDate = (d) => d.toISOString().slice(0, 10);
15
+ /**
16
+ * Sales statistics for the admin page.
17
+ *
18
+ * Unlike `/health`, this one genuinely has to call Printful — the figures do
19
+ * not exist locally. So the failure is handled rather than propagated: an
20
+ * outage answers `200` with an empty list and a `reason`, not a `500`. The
21
+ * page fetches three panels independently and a thrown error here would take
22
+ * out the one screen an owner opens *because* something is wrong.
23
+ */
24
+ async function GET(req, res) {
25
+ const printful = req.scope.resolve(printful_1.PRINTFUL_MODULE);
26
+ const logger = req.scope.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
27
+ const query = req.query;
28
+ const now = new Date();
29
+ const from = new Date(now.getTime() - DEFAULT_DAYS * 24 * 60 * 60 * 1000);
30
+ const dateFrom = typeof query.date_from === "string" && query.date_from
31
+ ? query.date_from
32
+ : isoDate(from);
33
+ const dateTo = typeof query.date_to === "string" && query.date_to
34
+ ? query.date_to
35
+ : isoDate(now);
36
+ try {
37
+ const client = await printful.getClient();
38
+ const statistics = await client.getStatistics({
39
+ dateFrom,
40
+ dateTo,
41
+ reportTypes: REPORT_TYPES,
42
+ });
43
+ res.status(200).json({ statistics, date_from: dateFrom, date_to: dateTo });
44
+ }
45
+ catch (err) {
46
+ const message = err instanceof Error ? err.message : String(err);
47
+ logger.error(`Printful statistics unavailable: ${message}`);
48
+ // 200 with an empty list, not an error status: the page treats this as
49
+ // "nothing to show" and keeps rendering sync history and webhook health,
50
+ // which are exactly what an operator needs during a Printful outage.
51
+ res.status(200).json({
52
+ statistics: [],
53
+ date_from: dateFrom,
54
+ date_to: dateTo,
55
+ reason: "printful_unreachable",
56
+ });
57
+ }
58
+ }
59
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL3N0YXRpc3RpY3Mvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUEwQkEsa0JBd0NDO0FBakVELHFEQUFxRTtBQUNyRSwyREFBOEQ7QUFHOUQsZ0VBQWdFO0FBQ2hFLE1BQU0sWUFBWSxHQUFHLEVBQUUsQ0FBQTtBQUV2Qjs7OztHQUlHO0FBQ0gsTUFBTSxZQUFZLEdBQUcsbURBQW1ELENBQUE7QUFFeEUsTUFBTSxPQUFPLEdBQUcsQ0FBQyxDQUFPLEVBQVUsRUFBRSxDQUFDLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO0FBRWpFOzs7Ozs7OztHQVFHO0FBQ0ksS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUFrQixFQUFFLEdBQW1CO0lBQy9ELE1BQU0sUUFBUSxHQUEwQixHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQywwQkFBZSxDQUFDLENBQUE7SUFDMUUsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsTUFBTSxDQUFDLENBQUE7SUFFbEUsTUFBTSxLQUFLLEdBQUcsR0FBRyxDQUFDLEtBQWdDLENBQUE7SUFDbEQsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQTtJQUN0QixNQUFNLElBQUksR0FBRyxJQUFJLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLEdBQUcsWUFBWSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFBO0lBRXpFLE1BQU0sUUFBUSxHQUNaLE9BQU8sS0FBSyxDQUFDLFNBQVMsS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLFNBQVM7UUFDcEQsQ0FBQyxDQUFDLEtBQUssQ0FBQyxTQUFTO1FBQ2pCLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDbkIsTUFBTSxNQUFNLEdBQ1YsT0FBTyxLQUFLLENBQUMsT0FBTyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsT0FBTztRQUNoRCxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU87UUFDZixDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBRWxCLElBQUksQ0FBQztRQUNILE1BQU0sTUFBTSxHQUFHLE1BQU0sUUFBUSxDQUFDLFNBQVMsRUFBRSxDQUFBO1FBQ3pDLE1BQU0sVUFBVSxHQUFHLE1BQU0sTUFBTSxDQUFDLGFBQWEsQ0FBQztZQUM1QyxRQUFRO1lBQ1IsTUFBTTtZQUNOLFdBQVcsRUFBRSxZQUFZO1NBQzFCLENBQUMsQ0FBQTtRQUVGLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsVUFBVSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUE7SUFDNUUsQ0FBQztJQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7UUFDYixNQUFNLE9BQU8sR0FBRyxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUE7UUFDaEUsTUFBTSxDQUFDLEtBQUssQ0FBQyxvQ0FBb0MsT0FBTyxFQUFFLENBQUMsQ0FBQTtRQUUzRCx1RUFBdUU7UUFDdkUseUVBQXlFO1FBQ3pFLHFFQUFxRTtRQUNyRSxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztZQUNuQixVQUFVLEVBQUUsRUFBRTtZQUNkLFNBQVMsRUFBRSxRQUFRO1lBQ25CLE9BQU8sRUFBRSxNQUFNO1lBQ2YsTUFBTSxFQUFFLHNCQUFzQjtTQUMvQixDQUFDLENBQUE7SUFDSixDQUFDO0FBQ0gsQ0FBQyJ9
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const printful_1 = require("../../../../../modules/printful");
5
+ const clear_sync_1 = require("../../../../../utils/clear-sync");
6
+ /**
7
+ * Clear a sync that is stuck in `running`.
8
+ *
9
+ * Nested under `sync/` rather than living at its own top-level path because it
10
+ * acts on exactly the resource `POST /admin/printful/sync` claims — the two
11
+ * belong together, and the 409 body from that route is what sends an operator
12
+ * here. POST, not DELETE: this does not remove the row, it transitions it to a
13
+ * terminal state and writes an audit marker.
14
+ *
15
+ * This closes the limit recorded in 0.4.0: after a crash the log stays
16
+ * `running` and nothing reclaims it until the next sync attempt, so with the
17
+ * default `syncStaleMinutes: 60` the catalog is blocked for up to an hour with
18
+ * no way to intervene.
19
+ *
20
+ * It deliberately does not just call `reapStaleSyncLogs(0)`. That would clear
21
+ * any running sync unconditionally, including a healthy one mid-catalog, and
22
+ * would stamp the row with the same `stale_running` marker the timeout reaper
23
+ * uses — making a human intervention indistinguishable from an automatic
24
+ * recovery in the audit trail. `planSyncClear` owns both decisions.
25
+ */
26
+ async function POST(req, res) {
27
+ const printful = req.scope.resolve(printful_1.PRINTFUL_MODULE);
28
+ const options = await printful.getOptions();
29
+ const body = (req.body ?? {});
30
+ const confirmation = typeof body.confirm === "string" ? body.confirm : null;
31
+ const running = await printful.getRunningSyncLog();
32
+ const decision = (0, clear_sync_1.planSyncClear)({
33
+ log: running,
34
+ confirmation,
35
+ staleMinutes: options.syncStaleMinutes ?? 60,
36
+ });
37
+ if (!decision.allowed) {
38
+ if (decision.reason === "nothing_running") {
39
+ // Not an error. The page asked to clear a stuck sync and there is no
40
+ // sync to clear, which is the state the operator wanted.
41
+ res.status(200).json({
42
+ cleared: false,
43
+ reason: "nothing_running",
44
+ message: "No Printful sync is running",
45
+ });
46
+ return;
47
+ }
48
+ if (decision.reason === "unconfirmed") {
49
+ res.status(400).json({
50
+ cleared: false,
51
+ reason: "unconfirmed",
52
+ message: `Clearing a sync is destructive. Send { "confirm": "${clear_sync_1.CLEAR_SYNC_CONFIRMATION}" } to proceed.`,
53
+ sync_id: decision.sync_id,
54
+ });
55
+ return;
56
+ }
57
+ // `alive`: the heartbeat says this sync is still running. 409 matches the
58
+ // 409 that POST /admin/printful/sync returns for the same condition — a
59
+ // sync is holding the claim and the caller cannot have it.
60
+ res.status(409).json({
61
+ cleared: false,
62
+ reason: "alive",
63
+ message: "This sync is still sending heartbeats. Clearing it would mark a live sync as failed while it keeps writing.",
64
+ sync_id: decision.sync_id,
65
+ silent_minutes: decision.silent_minutes,
66
+ });
67
+ return;
68
+ }
69
+ // Conditional on the heartbeat we just read. If the sync emitted one more
70
+ // between that read and this write, it was never dead and the update matches
71
+ // nothing — see clearStuckSyncLog.
72
+ const cleared = await printful.clearStuckSyncLog({
73
+ id: decision.sync_id,
74
+ observedHeartbeatAt: running?.heartbeat_at ?? null,
75
+ errorMessage: decision.error_message,
76
+ });
77
+ if (!cleared) {
78
+ res.status(409).json({
79
+ cleared: false,
80
+ reason: "alive",
81
+ message: "This sync sent a heartbeat while the request was being handled, so it is still alive and was left running.",
82
+ sync_id: decision.sync_id,
83
+ });
84
+ return;
85
+ }
86
+ res.status(200).json({
87
+ cleared: true,
88
+ sync_id: decision.sync_id,
89
+ error_message: decision.error_message,
90
+ silent_minutes: decision.silent_minutes,
91
+ });
92
+ }
93
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL3N5bmMvY2xlYXIvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUE0QkEsb0JBNkVDO0FBeEdELDhEQUFpRTtBQUVqRSxnRUFHd0M7QUFFeEM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFDSSxLQUFLLFVBQVUsSUFBSSxDQUFDLEdBQWtCLEVBQUUsR0FBbUI7SUFDaEUsTUFBTSxRQUFRLEdBQTBCLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLDBCQUFlLENBQUMsQ0FBQTtJQUMxRSxNQUFNLE9BQU8sR0FBRyxNQUFNLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQTtJQUUzQyxNQUFNLElBQUksR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLElBQUksRUFBRSxDQUEwQixDQUFBO0lBQ3RELE1BQU0sWUFBWSxHQUFHLE9BQU8sSUFBSSxDQUFDLE9BQU8sS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQTtJQUUzRSxNQUFNLE9BQU8sR0FBRyxNQUFNLFFBQVEsQ0FBQyxpQkFBaUIsRUFBRSxDQUFBO0lBRWxELE1BQU0sUUFBUSxHQUFHLElBQUEsMEJBQWEsRUFBQztRQUM3QixHQUFHLEVBQUUsT0FBTztRQUNaLFlBQVk7UUFDWixZQUFZLEVBQUUsT0FBTyxDQUFDLGdCQUFnQixJQUFJLEVBQUU7S0FDN0MsQ0FBQyxDQUFBO0lBRUYsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUN0QixJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssaUJBQWlCLEVBQUUsQ0FBQztZQUMxQyxxRUFBcUU7WUFDckUseURBQXlEO1lBQ3pELEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO2dCQUNuQixPQUFPLEVBQUUsS0FBSztnQkFDZCxNQUFNLEVBQUUsaUJBQWlCO2dCQUN6QixPQUFPLEVBQUUsNkJBQTZCO2FBQ3ZDLENBQUMsQ0FBQTtZQUNGLE9BQU07UUFDUixDQUFDO1FBRUQsSUFBSSxRQUFRLENBQUMsTUFBTSxLQUFLLGFBQWEsRUFBRSxDQUFDO1lBQ3RDLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO2dCQUNuQixPQUFPLEVBQUUsS0FBSztnQkFDZCxNQUFNLEVBQUUsYUFBYTtnQkFDckIsT0FBTyxFQUFFLHNEQUFzRCxvQ0FBdUIsaUJBQWlCO2dCQUN2RyxPQUFPLEVBQUUsUUFBUSxDQUFDLE9BQU87YUFDMUIsQ0FBQyxDQUFBO1lBQ0YsT0FBTTtRQUNSLENBQUM7UUFFRCwwRUFBMEU7UUFDMUUsd0VBQXdFO1FBQ3hFLDJEQUEyRDtRQUMzRCxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztZQUNuQixPQUFPLEVBQUUsS0FBSztZQUNkLE1BQU0sRUFBRSxPQUFPO1lBQ2YsT0FBTyxFQUNMLDZHQUE2RztZQUMvRyxPQUFPLEVBQUUsUUFBUSxDQUFDLE9BQU87WUFDekIsY0FBYyxFQUFFLFFBQVEsQ0FBQyxjQUFjO1NBQ3hDLENBQUMsQ0FBQTtRQUNGLE9BQU07SUFDUixDQUFDO0lBRUQsMEVBQTBFO0lBQzFFLDZFQUE2RTtJQUM3RSxtQ0FBbUM7SUFDbkMsTUFBTSxPQUFPLEdBQUcsTUFBTSxRQUFRLENBQUMsaUJBQWlCLENBQUM7UUFDL0MsRUFBRSxFQUFFLFFBQVEsQ0FBQyxPQUFPO1FBQ3BCLG1CQUFtQixFQUFFLE9BQU8sRUFBRSxZQUFZLElBQUksSUFBSTtRQUNsRCxZQUFZLEVBQUUsUUFBUSxDQUFDLGFBQWE7S0FDckMsQ0FBQyxDQUFBO0lBRUYsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2IsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUM7WUFDbkIsT0FBTyxFQUFFLEtBQUs7WUFDZCxNQUFNLEVBQUUsT0FBTztZQUNmLE9BQU8sRUFDTCw0R0FBNEc7WUFDOUcsT0FBTyxFQUFFLFFBQVEsQ0FBQyxPQUFPO1NBQzFCLENBQUMsQ0FBQTtRQUNGLE9BQU07SUFDUixDQUFDO0lBRUQsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDbkIsT0FBTyxFQUFFLElBQUk7UUFDYixPQUFPLEVBQUUsUUFBUSxDQUFDLE9BQU87UUFDekIsYUFBYSxFQUFFLFFBQVEsQ0FBQyxhQUFhO1FBQ3JDLGNBQWMsRUFBRSxRQUFRLENBQUMsY0FBYztLQUN4QyxDQUFDLENBQUE7QUFDSixDQUFDIn0=
@@ -299,6 +299,44 @@ class PrintfulModuleService extends (0, utils_1.MedusaService)({
299
299
  const [log] = await this.listPrintfulSyncLogs({ status: "running" });
300
300
  return log ?? null;
301
301
  }
302
+ /**
303
+ * Clear a specific stuck sync on an operator's instruction.
304
+ *
305
+ * Separate from `reapStaleSyncLogs` on purpose. That one is the timeout
306
+ * reaper: it selects its own victims by age and stamps them `stale_running`.
307
+ * This one clears exactly the row the operator was looking at and records a
308
+ * marker naming them as the cause, so the two are distinguishable in the log
309
+ * long after the fact.
310
+ *
311
+ * The `heartbeat_at` predicate makes the write conditional on the row still
312
+ * being as stale as the caller observed. Between the route's read and this
313
+ * write, a sync everyone believed dead can emit one more heartbeat; without
314
+ * this predicate that row would be marked `failed` underneath a live
315
+ * process. With it, the late heartbeat moves `heartbeat_at` past the
316
+ * observed value, the update matches nothing, and the caller is told the
317
+ * sync is alive after all.
318
+ *
319
+ * Returns true when a row was actually cleared.
320
+ */
321
+ async clearStuckSyncLog(input) {
322
+ const stillStale = await this.listPrintfulSyncLogs({
323
+ id: input.id,
324
+ status: "running",
325
+ heartbeat_at: input.observedHeartbeatAt
326
+ ? { $lte: input.observedHeartbeatAt }
327
+ : null,
328
+ });
329
+ if (stillStale.length === 0) {
330
+ return false;
331
+ }
332
+ await this.updatePrintfulSyncLogs({
333
+ id: input.id,
334
+ status: "failed",
335
+ error_message: input.errorMessage,
336
+ finished_at: new Date(),
337
+ });
338
+ return true;
339
+ }
302
340
  /** Refresh the claim so it is not reaped, and report progress. */
303
341
  async heartbeatSyncLog(id, progress = {}) {
304
342
  return this.updatePrintfulSyncLogs({
@@ -309,4 +347,4 @@ class PrintfulModuleService extends (0, utils_1.MedusaService)({
309
347
  }
310
348
  }
311
349
  exports.default = PrintfulModuleService;
312
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL3ByaW50ZnVsL3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBZ0RBLDRDQUlDO0FBYUQsZ0NBR0M7QUFpQkQsOENBMkJDO0FBaEhELHFEQUF5RDtBQUN6RCxtQ0FBbUM7QUFDbkMsaUVBQTREO0FBRTVELHVGQUE0RDtBQUM1RCwyRkFBZ0U7QUFDaEUsbUZBQXdEO0FBQ3hELDJGQUFnRTtBQUNoRSw2RkFBa0U7QUEyQmxFOzs7Ozs7R0FNRztBQUNVLFFBQUEseUJBQXlCLEdBQUcsU0FBUyxDQUFBO0FBRWxELGdFQUFnRTtBQUNuRCxRQUFBLG9CQUFvQixHQUFHLEVBQUUsQ0FBQTtBQUV0QyxrRUFBa0U7QUFDbEUsU0FBZ0IsZ0JBQWdCLENBQUMsUUFBZ0I7SUFDL0MsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUE7SUFDbEMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFBO0lBQ3BDLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsUUFBUSxDQUFDLEVBQUUsU0FBUyxDQUFDLENBQUE7QUFDbEUsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUFnQixVQUFVLENBQUMsZUFBdUI7SUFDaEQsTUFBTSxNQUFNLEdBQUcsSUFBQSxtQkFBVSxFQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQTtJQUM1RSxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFDOUIsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gsU0FBZ0IsaUJBQWlCLENBQUMsR0FB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
350
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL3ByaW50ZnVsL3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBZ0RBLDRDQUlDO0FBYUQsZ0NBR0M7QUFpQkQsOENBMkJDO0FBaEhELHFEQUF5RDtBQUN6RCxtQ0FBbUM7QUFDbkMsaUVBQTREO0FBRTVELHVGQUE0RDtBQUM1RCwyRkFBZ0U7QUFDaEUsbUZBQXdEO0FBQ3hELDJGQUFnRTtBQUNoRSw2RkFBa0U7QUEyQmxFOzs7Ozs7R0FNRztBQUNVLFFBQUEseUJBQXlCLEdBQUcsU0FBUyxDQUFBO0FBRWxELGdFQUFnRTtBQUNuRCxRQUFBLG9CQUFvQixHQUFHLEVBQUUsQ0FBQTtBQUV0QyxrRUFBa0U7QUFDbEUsU0FBZ0IsZ0JBQWdCLENBQUMsUUFBZ0I7SUFDL0MsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUE7SUFDbEMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFBO0lBQ3BDLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsUUFBUSxDQUFDLEVBQUUsU0FBUyxDQUFDLENBQUE7QUFDbEUsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUFnQixVQUFVLENBQUMsZUFBdUI7SUFDaEQsTUFBTSxNQUFNLEdBQUcsSUFBQSxtQkFBVSxFQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQTtJQUM1RSxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFDOUIsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gsU0FBZ0IsaUJBQWlCLENBQUMsR0FB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
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CLEARED_BY_OPERATOR = exports.CLEAR_SYNC_CONFIRMATION = void 0;
4
+ exports.planSyncClear = planSyncClear;
5
+ /**
6
+ * Exact phrase the caller must send to clear a stuck sync.
7
+ *
8
+ * Clearing is destructive, so it must not be reachable by an empty POST. A
9
+ * typed phrase makes the operator's intent explicit and unambiguous in the
10
+ * request itself — a boolean `force: true` is too easy to send by accident
11
+ * from a curl line or a mis-wired button.
12
+ */
13
+ exports.CLEAR_SYNC_CONFIRMATION = "clear-stuck-sync";
14
+ /**
15
+ * Audit marker written to `error_message` when a human clears a sync.
16
+ *
17
+ * `reapStaleSyncLogs` writes the bare string "stale_running" when the timeout
18
+ * reclaims a row on its own. These two must never be confused: one says the
19
+ * system recovered by itself after `syncStaleMinutes`, the other says a person
20
+ * decided the sync was dead and intervened. Anyone reading this log six months
21
+ * from now needs to be able to tell those apart.
22
+ */
23
+ exports.CLEARED_BY_OPERATOR = "cleared_by_operator";
24
+ function toDate(value) {
25
+ if (value == null) {
26
+ return null;
27
+ }
28
+ const date = value instanceof Date ? value : new Date(value);
29
+ return Number.isNaN(date.getTime()) ? null : date;
30
+ }
31
+ /**
32
+ * Decide whether a running sync may be cleared by hand, and what to record.
33
+ *
34
+ * This exists instead of simply exposing `reapStaleSyncLogs(0)`, which would
35
+ * clear ANY running sync unconditionally. That call has no notion of whether
36
+ * the sync is alive: a healthy run mid-catalog would be marked `failed` while
37
+ * its process keeps writing, which both corrupts the log and frees the claim
38
+ * so a second sync can start against the same catalog.
39
+ *
40
+ * Two guards, deliberately layered:
41
+ *
42
+ * 1. The caller must send the confirmation phrase. Checked FIRST, so an
43
+ * unconfirmed request against a live sync is told it is unconfirmed rather
44
+ * than being told it is alive — the latter would wrongly imply that
45
+ * confirming alone would have been enough.
46
+ * 2. The heartbeat must actually be stale. Confirmation proves intent, not
47
+ * that the operator is right. An operator staring at a widget cannot see
48
+ * the heartbeat; the server can, and refuses on their behalf.
49
+ *
50
+ * The stale window is clamped to a minimum of one minute so that passing 0 —
51
+ * the exact foot-gun this function replaces — cannot make a live sync
52
+ * clearable.
53
+ *
54
+ * A row with no heartbeat at all falls back to `started_at`. A crash between
55
+ * the claim insert and the first heartbeat leaves `heartbeat_at` null, and
56
+ * such a row must not be permanently unclearable; but a claim made seconds ago
57
+ * is far more likely to be a sync starting up than a dead one.
58
+ */
59
+ function planSyncClear(input) {
60
+ const { log, confirmation, staleMinutes } = input;
61
+ const now = input.now ?? new Date();
62
+ if (!log || log.status !== "running") {
63
+ return { allowed: false, reason: "nothing_running" };
64
+ }
65
+ if (confirmation !== exports.CLEAR_SYNC_CONFIRMATION) {
66
+ return { allowed: false, reason: "unconfirmed", sync_id: log.id };
67
+ }
68
+ // Never trust a zero or negative window: that is precisely the call this
69
+ // function exists to prevent.
70
+ const window = Math.max(1, Number.isFinite(staleMinutes) ? staleMinutes : 1);
71
+ const lastSign = toDate(log.heartbeat_at) ?? toDate(log.started_at);
72
+ const silentMs = lastSign ? now.getTime() - lastSign.getTime() : Infinity;
73
+ const silentMinutes = Number.isFinite(silentMs)
74
+ ? Math.max(0, Math.floor(silentMs / 60_000))
75
+ : Number.MAX_SAFE_INTEGER;
76
+ if (silentMinutes < window) {
77
+ return {
78
+ allowed: false,
79
+ reason: "alive",
80
+ sync_id: log.id,
81
+ silent_minutes: silentMinutes,
82
+ };
83
+ }
84
+ return {
85
+ allowed: true,
86
+ sync_id: log.id,
87
+ error_message: `${exports.CLEARED_BY_OPERATOR}: no heartbeat for ${silentMinutes}m`,
88
+ silent_minutes: silentMinutes,
89
+ };
90
+ }
91
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xlYXItc3luYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy91dGlscy9jbGVhci1zeW5jLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQTJGQSxzQ0EyQ0M7QUF0SUQ7Ozs7Ozs7R0FPRztBQUNVLFFBQUEsdUJBQXVCLEdBQUcsa0JBQWtCLENBQUE7QUFFekQ7Ozs7Ozs7O0dBUUc7QUFDVSxRQUFBLG1CQUFtQixHQUFHLHFCQUFxQixDQUFBO0FBb0N4RCxTQUFTLE1BQU0sQ0FBQyxLQUF1QztJQUNyRCxJQUFJLEtBQUssSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUNsQixPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7SUFDRCxNQUFNLElBQUksR0FBRyxLQUFLLFlBQVksSUFBSSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQzVELE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7QUFDbkQsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0EyQkc7QUFDSCxTQUFnQixhQUFhLENBQUMsS0FNN0I7SUFDQyxNQUFNLEVBQUUsR0FBRyxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsR0FBRyxLQUFLLENBQUE7SUFDakQsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLEdBQUcsSUFBSSxJQUFJLElBQUksRUFBRSxDQUFBO0lBRW5DLElBQUksQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUNyQyxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsaUJBQWlCLEVBQUUsQ0FBQTtJQUN0RCxDQUFDO0lBRUQsSUFBSSxZQUFZLEtBQUssK0JBQXVCLEVBQUUsQ0FBQztRQUM3QyxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsYUFBYSxFQUFFLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRSxFQUFFLENBQUE7SUFDbkUsQ0FBQztJQUVELHlFQUF5RTtJQUN6RSw4QkFBOEI7SUFDOUIsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtJQUU1RSxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxJQUFJLE1BQU0sQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUE7SUFDbkUsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLEdBQUcsUUFBUSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUE7SUFDekUsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUM7UUFDN0MsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxHQUFHLE1BQU0sQ0FBQyxDQUFDO1FBQzVDLENBQUMsQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLENBQUE7SUFFM0IsSUFBSSxhQUFhLEdBQUcsTUFBTSxFQUFFLENBQUM7UUFDM0IsT0FBTztZQUNMLE9BQU8sRUFBRSxLQUFLO1lBQ2QsTUFBTSxFQUFFLE9BQU87WUFDZixPQUFPLEVBQUUsR0FBRyxDQUFDLEVBQUU7WUFDZixjQUFjLEVBQUUsYUFBYTtTQUM5QixDQUFBO0lBQ0gsQ0FBQztJQUVELE9BQU87UUFDTCxPQUFPLEVBQUUsSUFBSTtRQUNiLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRTtRQUNmLGFBQWEsRUFBRSxHQUFHLDJCQUFtQixzQkFBc0IsYUFBYSxHQUFHO1FBQzNFLGNBQWMsRUFBRSxhQUFhO0tBQzlCLENBQUE7QUFDSCxDQUFDIn0=
@@ -107,6 +107,28 @@ class PrintfulClient {
107
107
  });
108
108
  return data.result ?? { url: null, types: [] };
109
109
  }
110
+ /**
111
+ * Sales, costs and profit for a date range.
112
+ *
113
+ * Returns the per-store list, empty when Printful reports none — the admin
114
+ * page maps over this, and a missing key must not crash the one screen whose
115
+ * job is telling the owner what is going on.
116
+ *
117
+ * `currency` is omitted rather than defaulted when absent: Printful then
118
+ * reports in the store's own currency, which is a different request from
119
+ * asking for a specific one.
120
+ */
121
+ async getStatistics(params) {
122
+ const search = new URLSearchParams();
123
+ search.set("date_from", params.dateFrom);
124
+ search.set("date_to", params.dateTo);
125
+ search.set("report_types", params.reportTypes);
126
+ if (params.currency) {
127
+ search.set("currency", params.currency);
128
+ }
129
+ const data = await this.request(`/reports/statistics?${search.toString()}`);
130
+ return data.result?.store_statistics ?? [];
131
+ }
110
132
  /**
111
133
  * Quote shipping for a cart. Returns every method Printful offers for that
112
134
  * destination in one response — the caller picks the one it needs.
@@ -208,4 +230,4 @@ class PrintfulClient {
208
230
  }
209
231
  }
210
232
  exports.PrintfulClient = PrintfulClient;
211
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbnRmdWwtY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL3ByaW50ZnVsLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxQ0FBZ0U7QUFrQ2hFLE1BQU0sZ0JBQWdCLEdBQUcsMEJBQTBCLENBQUE7QUFFbkQsTUFBYSxjQUFjO0lBU3pCLFlBQVksT0FBOEI7UUFDeEMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUN0QixNQUFNLElBQUksNEJBQW1CLENBQUMsK0JBQStCLENBQUMsQ0FBQTtRQUNoRSxDQUFDO1FBRUQsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFBO1FBQ2hDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQTtRQUM5QixJQUFJLENBQUMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUE7UUFDdkUsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLEtBQUssQ0FBQTtRQUMzQyxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxVQUFVLElBQUksQ0FBQyxDQUFBO1FBQ3pDLElBQUksQ0FBQyxXQUFXLEdBQUcsT0FBTyxDQUFDLFdBQVcsSUFBSSxHQUFHLENBQUE7UUFDN0MsSUFBSSxDQUFDLFNBQVM7WUFDWixNQUFNLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFBO0lBQ3JFLENBQUM7SUFFRCxLQUFLLENBQUMsZ0JBQWdCLENBQUMsU0FBaUMsRUFBRTtRQUl4RCxNQUFNLE1BQU0sR0FBRyxJQUFJLGVBQWUsRUFBRSxD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
233
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbnRmdWwtY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL3ByaW50ZnVsLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxQ0FBZ0U7QUFvQ2hFLE1BQU0sZ0JBQWdCLEdBQUcsMEJBQTBCLENBQUE7QUFFbkQsTUFBYSxjQUFjO0lBU3pCLFlBQVksT0FBOEI7UUFDeEMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUN0QixNQUFNLElBQUksNEJBQW1CLENBQUMsK0JBQStCLENBQUMsQ0FBQTtRQUNoRSxDQUFDO1FBRUQsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFBO1FBQ2hDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQTtRQUM5QixJQUFJLENBQUMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUE7UUFDdkUsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLEtBQUssQ0FBQTtRQUMzQyxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxVQUFVLElBQUksQ0FBQyxDQUFBO1FBQ3pDLElBQUksQ0FBQyxXQUFXLEdBQUcsT0FBTyxDQUFDLFdBQVcsSUFBSSxHQUFHLENBQUE7UUFDN0MsSUFBSSxDQUFDLFNBQVM7WUFDWixNQUFNLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFBO0lBQ3JFLENBQUM7SUFFRCxLQUFLLENBQUMsZ0JBQWdCLENBQUMsU0FBaUMsRUFBRTtRQUl4RCxNQUFNLE1BQU0sR0FBRyxJQUFJLGVBQWUsRUFBRSxDQUFBO1FBQ3BDLElBQUksTUFBTSxDQUFDLE1BQU0sSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUMxQixNQUFNLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUE7UUFDN0MsQ0FBQztRQUNELElBQUksTUFBTSxDQUFDLEtBQUssSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUN6QixNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUE7UUFDM0MsQ0FBQztRQUNELElBQUksTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ2xCLE1BQU0sQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUNyQyxDQUFDO1FBRUQsTUFBTSxFQUFFLEdBQUcsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFBO1FBQzVCLE1BQU0sSUFBSSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUMsbUJBQW1CLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQTtRQUM3RCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQStCLElBQUksQ0FBQyxDQUFBO1FBRW5FLE9BQU87WUFDTCxLQUFLLEVBQUUsSUFBSSxDQUFDLE1BQU0sSUFBSSxFQUFFO1lBQ3hCLE1BQU0sRUFBRSxJQUFJLENBQUMsTUFBTTtTQUNwQixDQUFBO0lBQ0gsQ0FBQztJQUVELEtBQUssQ0FBQyxtQkFBbUIsQ0FDdkIsU0FBaUQsRUFBRTtRQUVuRCxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsS0FBSyxJQUFJLEdBQUcsQ0FBQTtRQUNqQyxJQUFJLE1BQU0sR0FBRyxDQUFDLENBQUE7UUFDZCxNQUFNLEdBQUcsR0FBaUMsRUFBRSxDQUFBO1FBRTVDLFNBQVMsQ0FBQztZQUNSLE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixDQUFDO2dCQUN2QyxHQUFHLE1BQU07Z0JBQ1QsTUFBTTtnQkFDTixLQUFLO2FBQ04sQ0FBQyxDQUFBO1lBQ0YsR0FBRyxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQTtZQUV2QixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQTtZQUNoQyxNQUFNLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUE7WUFFM0IsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDNUIsTUFBSztZQUNQLENBQUM7WUFDRCxJQUFJLEtBQUssSUFBSSxJQUFJLElBQUksTUFBTSxJQUFJLEtBQUssRUFBRSxDQUFDO2dCQUNyQyxNQUFLO1lBQ1AsQ0FBQztZQUNELElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsS0FBSyxFQUFFLENBQUM7Z0JBQzlCLE1BQUs7WUFDUCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sR0FBRyxDQUFBO0lBQ1osQ0FBQztJQUVELEtBQUssQ0FBQyxjQUFjLENBQ2xCLEVBQW1CO1FBRW5CLE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FDN0IsbUJBQW1CLEVBQUUsRUFBRSxDQUN4QixDQUFBO1FBQ0QsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFBO0lBQ3BCLENBQUM7SUFFRCxLQUFLLENBQUMsV0FBVyxDQUFDLEtBQStCO1FBQy9DLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLEtBQUssSUFBSSxDQUFBO1FBQ3RDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQTtRQUN0RCxNQUFNLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxHQUFHLElBQUksRUFBRSxHQUFHLEtBQUssQ0FBQTtRQUM1QyxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQWdCLElBQUksRUFBRTtZQUNuRCxNQUFNLEVBQUUsTUFBTTtZQUNkLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQztTQUMzQixDQUFDLENBQUE7UUFDRixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUE7SUFDcEIsQ0FBQztJQUVELEtBQUssQ0FBQyxRQUFRLENBQUMsRUFBbUI7UUFDaEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFnQixXQUFXLEVBQUUsRUFBRSxDQUFDLENBQUE7UUFDL0QsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFBO0lBQ3BCLENBQUM7SUFFRCxLQUFLLENBQUMsV0FBVyxDQUFDLEVBQW1CO1FBQ25DLE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBZ0IsV0FBVyxFQUFFLEVBQUUsRUFBRTtZQUM5RCxNQUFNLEVBQUUsUUFBUTtTQUNqQixDQUFDLENBQUE7UUFDRixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUE7SUFDcEIsQ0FBQztJQUVELEtBQUssQ0FBQyxnQkFBZ0I7UUFDcEIsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUF3QixXQUFXLENBQUMsQ0FBQTtRQUNuRSxPQUFPLElBQUksQ0FBQyxNQUFNLElBQUksRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLEVBQUUsQ0FBQTtJQUNoRCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILEtBQUssQ0FBQyxnQkFBZ0IsQ0FDcEIsR0FBVyxFQUNYLEtBQTZEO1FBRTdELE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBd0IsV0FBVyxFQUFFO1lBQ2xFLE1BQU0sRUFBRSxNQUFNO1lBQ2QsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxHQUFHLEVBQUUsS0FBSyxFQUFFLENBQUM7U0FDckMsQ0FBQyxDQUFBO1FBQ0YsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFBO0lBQ3BCLENBQUM7SUFFRCxLQUFLLENBQUMsY0FBYztRQUNsQixNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQXdCLFdBQVcsRUFBRTtZQUNsRSxNQUFNLEVBQUUsUUFBUTtTQUNqQixDQUFDLENBQUE7UUFDRixPQUFPLElBQUksQ0FBQyxNQUFNLElBQUksRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLEVBQUUsQ0FBQTtJQUNoRCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNILEtBQUssQ0FBQyxhQUFhLENBQ2pCLE1BQWdDO1FBRWhDLE1BQU0sTUFBTSxHQUFHLElBQUksZUFBZSxFQUFFLENBQUE7UUFDcEMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBQ3hDLE1BQU0sQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUNwQyxNQUFNLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUE7UUFDOUMsSUFBSSxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDcEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBQ3pDLENBQUM7UUFFRCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBRTVCLHVCQUF1QixNQUFNLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBRTlDLE9BQU8sSUFBSSxDQUFDLE1BQU0sRUFBRSxnQkFBZ0IsSUFBSSxFQUFFLENBQUE7SUFDNUMsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxLQUEyQjtRQUNoRCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQWlCLGlCQUFpQixFQUFFO1lBQ2pFLE1BQU0sRUFBRSxNQUFNO1lBQ2QsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDO1NBQzVCLENBQUMsQ0FBQTtRQUNGLE9BQU8sSUFBSSxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUE7SUFDMUIsQ0FBQztJQUVPLEtBQUssQ0FBQyxPQUFPLENBQ25CLElBQVksRUFDWixPQUFvQixFQUFFO1FBRXRCLE1BQU0sR0FBRyxHQUFHLEdBQUcsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxFQUFFLEVBQUUsQ0FBQTtRQUN4RSxNQUFNLE9BQU8sR0FBMkI7WUFDdEMsYUFBYSxFQUFFLFVBQVUsSUFBSSxDQUFDLFFBQVEsRUFBRTtZQUN4QyxjQUFjLEVBQUUsa0JBQWtCO1lBQ2xDLE1BQU0sRUFBRSxrQkFBa0I7WUFDMUIsR0FBSSxJQUFJLENBQUMsT0FBOEM7U0FDeEQsQ0FBQTtRQUVELElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ2pCLE9BQU8sQ0FBQyxlQUFlLENBQUMsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFBO1FBQ3pDLENBQUM7UUFFRCxJQUFJLFNBQWtCLENBQUE7UUFFdEIsS0FBSyxJQUFJLE9BQU8sR0FBRyxDQUFDLEVBQUUsT0FBTyxJQUFJLElBQUksQ0FBQyxVQUFVLEVBQUUsT0FBTyxFQUFFLEVBQUUsQ0FBQztZQUM1RCxJQUFJLENBQUM7Z0JBQ0gsc0VBQXNFO2dCQUN0RSxzRUFBc0U7Z0JBQ3RFLGlFQUFpRTtnQkFDakUsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsRUFBRTtvQkFDekMsTUFBTSxFQUFFLFdBQVcsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztvQkFDM0MsR0FBRyxJQUFJO29CQUNQLE9BQU87aUJBQ1IsQ0FBQyxDQUFBO2dCQUVGLE1BQU0sSUFBSSxHQUFHLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFBO2dCQUNsQyxJQUFJLElBQUksR0FBa0MsSUFBSSxDQUFBO2dCQUU5QyxJQUFJLElBQUksRUFBRSxDQUFDO29CQUNULElBQUksQ0FBQzt3QkFDSCxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQTJCLENBQUE7b0JBQ25ELENBQUM7b0JBQUMsTUFBTSxDQUFDO3dCQUNQLElBQUksR0FBRyxJQUFJLENBQUE7b0JBQ2IsQ0FBQztnQkFDSCxDQUFDO2dCQUVELElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFLENBQUM7b0JBQ2pCLE1BQU0sT0FBTyxHQUNYLElBQUksRUFBRSxLQUFLLEVBQUUsT0FBTzt3QkFDcEIsQ0FBQyxPQUFPLElBQUksRUFBRSxNQUFNLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7d0JBQ3ZELHNCQUFzQixRQUFRLENBQUMsTUFBTSxFQUFFLENBQUE7b0JBRXpDLE1BQU0sS0FBSyxHQUFHLElBQUkseUJBQWdCLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxFQUFFO3dCQUNsRCxNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU07d0JBQ3ZCLE1BQU0sRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLE1BQU07d0JBQzNCLElBQUksRUFBRSxJQUFJLElBQUksSUFBSTtxQkFDbkIsQ0FBQyxDQUFBO29CQUVGLElBQUksS0FBSyxDQUFDLFdBQVcsSUFBSSxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO3dCQUNuRCxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQTt3QkFDcEQsU0FBUyxHQUFHLEtBQUssQ0FBQTt3QkFDakIsU0FBUTtvQkFDVixDQUFDO29CQUVELE1BQU0sS0FBSyxDQUFBO2dCQUNiLENBQUM7Z0JBRUQsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO29CQUNWLE1BQU0sSUFBSSx5QkFBZ0IsQ0FBQyxxQ0FBcUMsRUFBRTt3QkFDaEUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO3dCQUN2QixJQUFJLEVBQUUsSUFBSTtxQkFDWCxDQUFDLENBQUE7Z0JBQ0osQ0FBQztnQkFFRCxPQUFPLElBQUksQ0FBQTtZQUNiLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNiLFNBQVMsR0FBRyxHQUFHLENBQUE7Z0JBQ2YsSUFBSSxHQUFHLFlBQVkseUJBQWdCLElBQUksQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLENBQUM7b0JBQ3hELE1BQU0sR0FBRyxDQUFBO2dCQUNYLENBQUM7Z0JBQ0QsSUFBSSxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO29CQUM5QixNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFBO29CQUMxQyxTQUFRO2dCQUNWLENBQUM7Z0JBQ0QsTUFBTSxHQUFHLENBQUE7WUFDWCxDQUFDO1FBQ0gsQ0FBQztRQUVELE1BQU0sU0FBUyxZQUFZLEtBQUs7WUFDOUIsQ0FBQyxDQUFDLFNBQVM7WUFDWCxDQUFDLENBQUMsSUFBSSx5QkFBZ0IsQ0FBQyx5QkFBeUIsRUFBRSxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBQ3BFLENBQUM7SUFFTyxVQUFVLENBQUMsT0FBZSxFQUFFLFFBQW1CO1FBQ3JELE1BQU0sVUFBVSxHQUFHLFFBQVEsRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsYUFBYSxDQUFDLENBQUE7UUFDMUQsSUFBSSxVQUFVLEVBQUUsQ0FBQztZQUNmLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQTtZQUNsQyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxPQUFPLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQzFDLE9BQU8sT0FBTyxHQUFHLElBQUksQ0FBQTtZQUN2QixDQUFDO1FBQ0gsQ0FBQztRQUNELE9BQU8sSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxPQUFPLENBQUMsQ0FBQTtJQUNoRCxDQUFDO0lBRU8sS0FBSyxDQUFDLEVBQVU7UUFDdEIsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFBO0lBQzFELENBQUM7Q0FDRjtBQTlSRCx3Q0E4UkMifQ==
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summarizeWebhookHealth = summarizeWebhookHealth;
4
+ function toDate(value) {
5
+ if (value == null) {
6
+ return null;
7
+ }
8
+ const date = value instanceof Date ? value : new Date(value);
9
+ return Number.isNaN(date.getTime()) ? null : date;
10
+ }
11
+ /**
12
+ * Summarise the webhook event table for the admin page.
13
+ *
14
+ * This is intentionally derived from LOCAL rows only. The obvious alternative
15
+ * is to call `client.getWebhookConfig()` and report Printful's registered URL
16
+ * verbatim, and this route deliberately does not: a health panel whose job is
17
+ * to tell the owner whether Printful is reaching them must not itself go dark
18
+ * when Printful is down. `/admin/printful/webhook` already exposes the live
19
+ * config for the moment an admin actually wants to inspect or change it.
20
+ *
21
+ * The cost of that choice is that `registered` is evidence-based rather than
22
+ * authoritative. A configured secret proves the owner intended to register a
23
+ * webhook; it does not prove the URL was ever pushed to Printful. So
24
+ * `registered` requires a secret AND a delivered event — the one thing that
25
+ * genuinely proves the round trip works. A store that has a secret but no
26
+ * events reads as "configured, nothing received yet", which is the honest
27
+ * statement and is exactly the state a misconfigured webhook leaves behind.
28
+ */
29
+ function summarizeWebhookHealth(rows, config) {
30
+ let received = 0;
31
+ let deferred = 0;
32
+ let failed = 0;
33
+ let processed = 0;
34
+ let lastEventAt = null;
35
+ for (const row of rows) {
36
+ switch (row.status) {
37
+ case "received":
38
+ received += 1;
39
+ break;
40
+ case "deferred":
41
+ deferred += 1;
42
+ break;
43
+ case "failed":
44
+ failed += 1;
45
+ break;
46
+ case "processed":
47
+ processed += 1;
48
+ break;
49
+ // A status added by a later release counts toward `total` only. Guessing
50
+ // it is a failure would show the owner an alarm that is not real.
51
+ default:
52
+ break;
53
+ }
54
+ const createdAt = toDate(row.created_at);
55
+ if (createdAt && (!lastEventAt || createdAt > lastEventAt)) {
56
+ lastEventAt = createdAt;
57
+ }
58
+ }
59
+ return {
60
+ total: rows.length,
61
+ received,
62
+ deferred,
63
+ failed,
64
+ processed,
65
+ last_event_at: lastEventAt,
66
+ // Based on row count, not on the timestamp: a row with an unreadable
67
+ // created_at still proves something arrived.
68
+ ever_received: rows.length > 0,
69
+ secret_set: config.secret_set,
70
+ registered: config.secret_set && rows.length > 0,
71
+ };
72
+ }
73
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2ViaG9vay1oZWFsdGguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdXRpbHMvd2ViaG9vay1oZWFsdGgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUE4REEsd0RBaURDO0FBM0VELFNBQVMsTUFBTSxDQUFDLEtBQXVDO0lBQ3JELElBQUksS0FBSyxJQUFJLElBQUksRUFBRSxDQUFDO1FBQ2xCLE9BQU8sSUFBSSxDQUFBO0lBQ2IsQ0FBQztJQUNELE1BQU0sSUFBSSxHQUFHLEtBQUssWUFBWSxJQUFJLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDNUQsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQTtBQUNuRCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsU0FBZ0Isc0JBQXNCLENBQ3BDLElBQXFDLEVBQ3JDLE1BQStCO0lBRS9CLElBQUksUUFBUSxHQUFHLENBQUMsQ0FBQTtJQUNoQixJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUE7SUFDaEIsSUFBSSxNQUFNLEdBQUcsQ0FBQyxDQUFBO0lBQ2QsSUFBSSxTQUFTLEdBQUcsQ0FBQyxDQUFBO0lBQ2pCLElBQUksV0FBVyxHQUFnQixJQUFJLENBQUE7SUFFbkMsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUN2QixRQUFRLEdBQUcsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNuQixLQUFLLFVBQVU7Z0JBQ2IsUUFBUSxJQUFJLENBQUMsQ0FBQTtnQkFDYixNQUFLO1lBQ1AsS0FBSyxVQUFVO2dCQUNiLFFBQVEsSUFBSSxDQUFDLENBQUE7Z0JBQ2IsTUFBSztZQUNQLEtBQUssUUFBUTtnQkFDWCxNQUFNLElBQUksQ0FBQyxDQUFBO2dCQUNYLE1BQUs7WUFDUCxLQUFLLFdBQVc7Z0JBQ2QsU0FBUyxJQUFJLENBQUMsQ0FBQTtnQkFDZCxNQUFLO1lBQ1AseUVBQXlFO1lBQ3pFLGtFQUFrRTtZQUNsRTtnQkFDRSxNQUFLO1FBQ1QsQ0FBQztRQUVELE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUE7UUFDeEMsSUFBSSxTQUFTLElBQUksQ0FBQyxDQUFDLFdBQVcsSUFBSSxTQUFTLEdBQUcsV0FBVyxDQUFDLEVBQUUsQ0FBQztZQUMzRCxXQUFXLEdBQUcsU0FBUyxDQUFBO1FBQ3pCLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTztRQUNMLEtBQUssRUFBRSxJQUFJLENBQUMsTUFBTTtRQUNsQixRQUFRO1FBQ1IsUUFBUTtRQUNSLE1BQU07UUFDTixTQUFTO1FBQ1QsYUFBYSxFQUFFLFdBQVc7UUFDMUIscUVBQXFFO1FBQ3JFLDZDQUE2QztRQUM3QyxhQUFhLEVBQUUsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQzlCLFVBQVUsRUFBRSxNQUFNLENBQUMsVUFBVTtRQUM3QixVQUFVLEVBQUUsTUFBTSxDQUFDLFVBQVUsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUM7S0FDakQsQ0FBQTtBQUNILENBQUMifQ==
package/README.md CHANGED
@@ -96,6 +96,7 @@ See `examples/basic-store/` for a fuller snippet.
96
96
  | Orders | On `payment.captured` → creates Printful order with **`sync_variant_id`** |
97
97
  | Fulfillment provider | Select Printful shipping option in Admin locations |
98
98
  | Status | `GET /admin/printful/status` + product list widget |
99
+ | Admin page | **Printful** in the sidebar: sales, sync history, webhook health, stuck-sync recovery |
99
100
  | Shipment tracking | Printful webhooks → Medusa fulfillment + shipment per parcel, with tracking |
100
101
  | Order visibility | Printful status and per-parcel tracking on the Admin order page |
101
102
 
@@ -320,6 +321,35 @@ so they cannot disagree; the individual `printful_cost_*` fee keys are refreshed
320
321
  per-key and a fee absent from a later response keeps its previous value, so a
321
322
  breakdown need not sum to the total.
322
323
 
324
+ ## The Printful admin page
325
+
326
+ A **Printful** section in the sidebar, gathering what the widgets could not:
327
+
328
+ - **Sales** — paid orders and profit from Printful's own reporting
329
+ - **Webhooks** — when the last event arrived, how many are waiting or failed
330
+ - **Recent syncs** — status, counters, and live progress while one runs
331
+ - **Stuck sync recovery** — see below
332
+
333
+ Two of these behave differently on purpose. **Webhook health never calls
334
+ Printful**: a panel whose job is reporting whether Printful reaches you must
335
+ still render when Printful is down, so it answers from local rows. **Sales does
336
+ call Printful**, because the figures do not exist locally — and an outage
337
+ answers with an empty panel rather than blanking the page.
338
+
339
+ ### When a sync gets stuck
340
+
341
+ If Medusa dies mid-sync, the run stays `running` and nothing else can start
342
+ until it is reclaimed — up to `syncStaleMinutes`, one hour by default. The page
343
+ notices and offers to clear it.
344
+
345
+ Clearing is deliberately awkward, because it is destructive. The request must
346
+ carry a typed confirmation, and the **server** re-checks the heartbeat: if the
347
+ sync is actually alive, it refuses and tells you so. A wrong guess costs a
348
+ message, never a killed sync.
349
+
350
+ A run cleared by a person is recorded as `cleared_by_operator` with how long it
351
+ had been silent, so it is never confused with one the timeout reaped.
352
+
323
353
  ## Admin usage
324
354
 
325
355
  1. Create products in Printful (Store Products).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legenki/print2medusa",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Printful → Medusa v2 plugin: product sync, auto fulfillment, and admin tools",
5
5
  "author": "Andy Legenki",
6
6
  "license": "MIT",