@infuro/cms-core 1.0.23 → 1.0.25
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/dist/admin.cjs +5161 -3307
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +52 -11
- package/dist/admin.d.ts +52 -11
- package/dist/admin.js +5095 -3238
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +595 -60
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +603 -68
- package/dist/api.js.map +1 -1
- package/dist/auth.cjs +9 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.js +9 -1
- package/dist/auth.js.map +1 -1
- package/dist/cli.cjs +7 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/{index-BGAh4fPQ.d.cts → index--vbixpxE.d.cts} +17 -2
- package/dist/{index-Cnwh7B3r.d.ts → index-DMJgi-fy.d.ts} +17 -2
- package/dist/index.cjs +683 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +684 -76
- package/dist/index.js.map +1 -1
- package/package.json +130 -129
package/dist/api.cjs
CHANGED
|
@@ -38,16 +38,51 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
// src/plugins/erp/erp-log.ts
|
|
42
|
+
function logErp(event, detail) {
|
|
43
|
+
if (detail && Object.keys(detail).length) console.info(ERP_LOG, event, detail);
|
|
44
|
+
else console.info(ERP_LOG, event);
|
|
45
|
+
}
|
|
46
|
+
function warnErp(event, detail) {
|
|
47
|
+
console.warn(ERP_LOG, event, detail);
|
|
48
|
+
}
|
|
49
|
+
function errorErp(event, detail) {
|
|
50
|
+
console.error(ERP_LOG, event, detail);
|
|
51
|
+
}
|
|
52
|
+
var ERP_LOG;
|
|
53
|
+
var init_erp_log = __esm({
|
|
54
|
+
"src/plugins/erp/erp-log.ts"() {
|
|
55
|
+
"use strict";
|
|
56
|
+
ERP_LOG = "[webcore:erp]";
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
41
60
|
// src/plugins/erp/erp-queue.ts
|
|
61
|
+
function queuePayloadSummary(payload) {
|
|
62
|
+
if (payload.kind === "order") {
|
|
63
|
+
const o = payload.order;
|
|
64
|
+
return {
|
|
65
|
+
kind: payload.kind,
|
|
66
|
+
platformOrderId: o.platformOrderId ?? o.platformOrderNumber,
|
|
67
|
+
itemCount: Array.isArray(o.items) ? o.items.length : 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { kind: payload.kind };
|
|
71
|
+
}
|
|
42
72
|
async function queueErp(cms, payload) {
|
|
43
73
|
const queue = cms.getPlugin("queue");
|
|
44
|
-
if (!queue)
|
|
74
|
+
if (!queue) {
|
|
75
|
+
warnErp("queue:add_skipped", { reason: "queue_plugin_missing", ...queuePayloadSummary(payload) });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
logErp("queue:add", { job: ERP_QUEUE_NAME, ...queuePayloadSummary(payload) });
|
|
45
79
|
await queue.add(ERP_QUEUE_NAME, payload);
|
|
46
80
|
}
|
|
47
81
|
var ERP_QUEUE_NAME;
|
|
48
82
|
var init_erp_queue = __esm({
|
|
49
83
|
"src/plugins/erp/erp-queue.ts"() {
|
|
50
84
|
"use strict";
|
|
85
|
+
init_erp_log();
|
|
51
86
|
ERP_QUEUE_NAME = "erp";
|
|
52
87
|
}
|
|
53
88
|
});
|
|
@@ -360,21 +395,36 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
360
395
|
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
361
396
|
for (const row of cfgRows) {
|
|
362
397
|
const r = row;
|
|
363
|
-
if (r.key === "enabled" && r.value === "false")
|
|
398
|
+
if (r.key === "enabled" && r.value === "false") {
|
|
399
|
+
logErp("paid-order:skip", { orderId, reason: "erp_config_disabled" });
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (!cms.getPlugin("erp")) {
|
|
404
|
+
logErp("paid-order:skip", { orderId, reason: "erp_plugin_missing" });
|
|
405
|
+
return;
|
|
364
406
|
}
|
|
365
|
-
if (!cms.getPlugin("erp")) return;
|
|
366
407
|
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
367
408
|
const ord = await orderRepo.findOne({
|
|
368
409
|
where: { id: orderId },
|
|
369
410
|
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
370
411
|
});
|
|
371
|
-
if (!ord)
|
|
412
|
+
if (!ord) {
|
|
413
|
+
logErp("paid-order:skip", { orderId, reason: "order_not_found" });
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
372
416
|
const o = ord;
|
|
373
417
|
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
374
|
-
if (!okKind)
|
|
418
|
+
if (!okKind) {
|
|
419
|
+
logErp("paid-order:skip", { orderId, reason: "order_kind_not_sale", orderKind: o.orderKind });
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
375
422
|
const rawPayments = o.payments ?? [];
|
|
376
423
|
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
377
|
-
if (!completedPayments.length)
|
|
424
|
+
if (!completedPayments.length) {
|
|
425
|
+
logErp("paid-order:skip", { orderId, reason: "no_completed_payments" });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
378
428
|
const rawItems = o.items ?? [];
|
|
379
429
|
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
380
430
|
const p = it.product;
|
|
@@ -393,7 +443,10 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
393
443
|
type: itemType
|
|
394
444
|
};
|
|
395
445
|
});
|
|
396
|
-
if (!lines.length)
|
|
446
|
+
if (!lines.length) {
|
|
447
|
+
logErp("paid-order:skip", { orderId, reason: "no_line_items_with_product" });
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
397
450
|
const contact = o.contact;
|
|
398
451
|
const orderTotalMajor = Number(o.total);
|
|
399
452
|
const paymentDtos = completedPayments.length === 1 && Number.isFinite(orderTotalMajor) ? [paymentRowToWebhookDto(completedPayments[0], orderTotalMajor)] : completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
@@ -415,13 +468,28 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
415
468
|
payments: paymentDtos,
|
|
416
469
|
metadata: { ...baseMeta, source: "storefront" }
|
|
417
470
|
};
|
|
471
|
+
logErp("paid-order:payload_built", {
|
|
472
|
+
orderId,
|
|
473
|
+
platformOrderId: orderDto.platformOrderId,
|
|
474
|
+
status: orderDto.status,
|
|
475
|
+
itemCount: lines.length,
|
|
476
|
+
skus: lines.map((l) => l.sku),
|
|
477
|
+
paymentCount: paymentDtos.length,
|
|
478
|
+
paymentIds: paymentDtos.map((p) => p.id),
|
|
479
|
+
total: orderTotalMajor
|
|
480
|
+
});
|
|
418
481
|
await queueErp(cms, { kind: "order", order: orderDto });
|
|
419
|
-
} catch {
|
|
482
|
+
} catch (e) {
|
|
483
|
+
errorErp("paid-order:enqueue_failed", {
|
|
484
|
+
orderId,
|
|
485
|
+
message: e instanceof Error ? e.message : String(e)
|
|
486
|
+
});
|
|
420
487
|
}
|
|
421
488
|
}
|
|
422
489
|
var init_paid_order_erp = __esm({
|
|
423
490
|
"src/plugins/erp/paid-order-erp.ts"() {
|
|
424
491
|
"use strict";
|
|
492
|
+
init_erp_log();
|
|
425
493
|
init_erp_queue();
|
|
426
494
|
}
|
|
427
495
|
});
|
|
@@ -529,6 +597,64 @@ async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, produc
|
|
|
529
597
|
}
|
|
530
598
|
}
|
|
531
599
|
|
|
600
|
+
// src/lib/address-geo-validation.ts
|
|
601
|
+
var import_country_state_city = require("country-state-city");
|
|
602
|
+
function norm(s) {
|
|
603
|
+
return typeof s === "string" ? s.trim() : "";
|
|
604
|
+
}
|
|
605
|
+
function resolveCountry(input) {
|
|
606
|
+
const t = input.trim();
|
|
607
|
+
if (!t) return void 0;
|
|
608
|
+
if (t.length === 2) {
|
|
609
|
+
const byCode = import_country_state_city.Country.getCountryByCode(t.toUpperCase());
|
|
610
|
+
if (byCode) return byCode;
|
|
611
|
+
}
|
|
612
|
+
const lower = t.toLowerCase();
|
|
613
|
+
return import_country_state_city.Country.getAllCountries().find((c) => c.name.toLowerCase() === lower);
|
|
614
|
+
}
|
|
615
|
+
function resolveState(countryIso, input) {
|
|
616
|
+
const t = input.trim();
|
|
617
|
+
if (!t || !countryIso) return void 0;
|
|
618
|
+
const states = import_country_state_city.State.getStatesOfCountry(countryIso);
|
|
619
|
+
const lower = t.toLowerCase();
|
|
620
|
+
return states.find((s) => s.isoCode.toLowerCase() === t.toLowerCase() || s.name.toLowerCase() === lower);
|
|
621
|
+
}
|
|
622
|
+
function resolveCity(countryIso, stateIso, input) {
|
|
623
|
+
const t = input.trim();
|
|
624
|
+
if (!t || !countryIso || !stateIso) return void 0;
|
|
625
|
+
const lower = t.toLowerCase();
|
|
626
|
+
const cities = import_country_state_city.City.getCitiesOfState(countryIso, stateIso);
|
|
627
|
+
return cities.find((c) => c.name.toLowerCase() === lower);
|
|
628
|
+
}
|
|
629
|
+
function assertValidAddressHierarchy(country, state, city) {
|
|
630
|
+
const c = resolveCountry(country);
|
|
631
|
+
if (!c) return { ok: false, error: "Invalid or unknown country." };
|
|
632
|
+
const st = resolveState(c.isoCode, state);
|
|
633
|
+
if (!st) return { ok: false, error: "State or province does not match the selected country." };
|
|
634
|
+
const ct = resolveCity(c.isoCode, st.isoCode, city);
|
|
635
|
+
if (!ct) return { ok: false, error: "City does not match the selected state." };
|
|
636
|
+
return { ok: true, country: c.name, state: st.name, city: ct.name };
|
|
637
|
+
}
|
|
638
|
+
function validateAndNormalizeAddressRow(row) {
|
|
639
|
+
const line1 = norm(row.line1);
|
|
640
|
+
const postalCode = norm(row.postalCode);
|
|
641
|
+
const countryIn = norm(row.country);
|
|
642
|
+
const stateIn = norm(row.state);
|
|
643
|
+
const cityIn = norm(row.city);
|
|
644
|
+
if (!line1) return "Street address (line 1) is required.";
|
|
645
|
+
if (!postalCode) return "Postal code is required.";
|
|
646
|
+
if (!countryIn || !stateIn || !cityIn) return "Country, state, and city are required.";
|
|
647
|
+
const geo = assertValidAddressHierarchy(countryIn, stateIn, cityIn);
|
|
648
|
+
if (!geo.ok) return geo.error;
|
|
649
|
+
row.line1 = line1;
|
|
650
|
+
row.line2 = norm(row.line2) || null;
|
|
651
|
+
row.postalCode = postalCode;
|
|
652
|
+
row.country = geo.country;
|
|
653
|
+
row.state = geo.state;
|
|
654
|
+
row.city = geo.city;
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
|
|
532
658
|
// src/api/crud.ts
|
|
533
659
|
var CRUD_LOG = "[cms-crud]";
|
|
534
660
|
function logCrudClientError(op, detail) {
|
|
@@ -626,6 +752,156 @@ function buildSearchWhereClause(repo, search) {
|
|
|
626
752
|
function entityHasSoftDelete(repo) {
|
|
627
753
|
return repo.metadata.columns.some((c) => c.propertyName === "deleted");
|
|
628
754
|
}
|
|
755
|
+
var LIST_QUERY_RESERVED = /* @__PURE__ */ new Set(["page", "limit", "sortField", "sortOrder", "search"]);
|
|
756
|
+
function dayStartUtc(isoDay) {
|
|
757
|
+
return /* @__PURE__ */ new Date(isoDay + "T00:00:00.000Z");
|
|
758
|
+
}
|
|
759
|
+
function dayEndUtc(isoDay) {
|
|
760
|
+
return /* @__PURE__ */ new Date(isoDay + "T23:59:59.999Z");
|
|
761
|
+
}
|
|
762
|
+
function columnTypeLabel(col) {
|
|
763
|
+
const t = col.type;
|
|
764
|
+
if (typeof t === "string") return t.toLowerCase();
|
|
765
|
+
if (typeof t === "function") return t.name?.toLowerCase?.() ?? "";
|
|
766
|
+
if (t && typeof t === "object" && "name" in t && typeof t.name === "string") {
|
|
767
|
+
return String(t.name).toLowerCase();
|
|
768
|
+
}
|
|
769
|
+
return "";
|
|
770
|
+
}
|
|
771
|
+
function isListDateColumn(col) {
|
|
772
|
+
const tl = columnTypeLabel(col);
|
|
773
|
+
return DATE_COLUMN_TYPES.has(tl) || col.type === Date || TIMESTAMP_PROP_NAMES.has(col.propertyName);
|
|
774
|
+
}
|
|
775
|
+
function isListNumericColumn(col) {
|
|
776
|
+
const tl = columnTypeLabel(col);
|
|
777
|
+
if (col.type === Number) return true;
|
|
778
|
+
const patterns = [
|
|
779
|
+
"int",
|
|
780
|
+
"integer",
|
|
781
|
+
"int2",
|
|
782
|
+
"int4",
|
|
783
|
+
"int8",
|
|
784
|
+
"smallint",
|
|
785
|
+
"bigint",
|
|
786
|
+
"float",
|
|
787
|
+
"double",
|
|
788
|
+
"decimal",
|
|
789
|
+
"numeric",
|
|
790
|
+
"real",
|
|
791
|
+
"tinyint",
|
|
792
|
+
"mediumint"
|
|
793
|
+
];
|
|
794
|
+
if (patterns.some((x) => tl.includes(x))) return true;
|
|
795
|
+
if (tl.includes("unsigned") && (tl.includes("int") || tl.includes("bigint") || tl.includes("small"))) return true;
|
|
796
|
+
if (!tl && /Id$/i.test(col.propertyName) && !TIMESTAMP_PROP_NAMES.has(col.propertyName)) return true;
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
function isListBooleanColumn(col) {
|
|
800
|
+
const tl = columnTypeLabel(col);
|
|
801
|
+
return tl === "boolean" || tl === "bool" || col.type === Boolean;
|
|
802
|
+
}
|
|
803
|
+
function isListStringColumn(col) {
|
|
804
|
+
const tl = columnTypeLabel(col);
|
|
805
|
+
if (isListDateColumn(col) || isListNumericColumn(col) || isListBooleanColumn(col)) return false;
|
|
806
|
+
if (["varchar", "character varying", "text", "citext", "uuid", "char", "character", "enum"].some(
|
|
807
|
+
(x) => tl.includes(x)
|
|
808
|
+
)) {
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
if (col.type === String) return true;
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
function mergeListWhereAnd(where, patch) {
|
|
815
|
+
if (Object.keys(patch).length === 0) return where;
|
|
816
|
+
if (Array.isArray(where)) {
|
|
817
|
+
if (where.length === 0) return [patch];
|
|
818
|
+
return where.map((w) => ({ ...w, ...patch }));
|
|
819
|
+
}
|
|
820
|
+
if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
821
|
+
return { ...where, ...patch };
|
|
822
|
+
}
|
|
823
|
+
return patch;
|
|
824
|
+
}
|
|
825
|
+
function buildListFilterAndFromSearchParams(repo, searchParams) {
|
|
826
|
+
const and = {};
|
|
827
|
+
const columnNames = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
828
|
+
for (const col of repo.metadata.columns) {
|
|
829
|
+
const name = col.propertyName;
|
|
830
|
+
if (!columnNames.has(name)) continue;
|
|
831
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
832
|
+
if (!isListDateColumn(col)) continue;
|
|
833
|
+
const from = searchParams.get(`${name}From`)?.trim();
|
|
834
|
+
const to = searchParams.get(`${name}To`)?.trim();
|
|
835
|
+
if (!from && !to) continue;
|
|
836
|
+
if (from && to) {
|
|
837
|
+
and[name] = (0, import_typeorm.Between)(dayStartUtc(from), dayEndUtc(to));
|
|
838
|
+
} else if (from) {
|
|
839
|
+
and[name] = (0, import_typeorm.MoreThanOrEqual)(dayStartUtc(from));
|
|
840
|
+
} else if (to) {
|
|
841
|
+
and[name] = (0, import_typeorm.LessThanOrEqual)(dayEndUtc(to));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
for (const col of repo.metadata.columns) {
|
|
845
|
+
const name = col.propertyName;
|
|
846
|
+
if (!columnNames.has(name)) continue;
|
|
847
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
848
|
+
if (!isListNumericColumn(col)) continue;
|
|
849
|
+
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
850
|
+
const minRaw = searchParams.get(`${name}Min`)?.trim();
|
|
851
|
+
const maxRaw = searchParams.get(`${name}Max`)?.trim();
|
|
852
|
+
if (!minRaw && !maxRaw) continue;
|
|
853
|
+
const parseNum = (s) => {
|
|
854
|
+
const n = Number(s);
|
|
855
|
+
return Number.isFinite(n) ? n : null;
|
|
856
|
+
};
|
|
857
|
+
const nMin = minRaw ? parseNum(minRaw) : null;
|
|
858
|
+
const nMax = maxRaw ? parseNum(maxRaw) : null;
|
|
859
|
+
if (nMin != null && nMax != null) {
|
|
860
|
+
and[name] = (0, import_typeorm.Between)(nMin, nMax);
|
|
861
|
+
} else if (nMin != null) {
|
|
862
|
+
and[name] = (0, import_typeorm.MoreThanOrEqual)(nMin);
|
|
863
|
+
} else if (nMax != null) {
|
|
864
|
+
and[name] = (0, import_typeorm.LessThanOrEqual)(nMax);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
for (const col of repo.metadata.columns) {
|
|
868
|
+
const name = col.propertyName;
|
|
869
|
+
if (!columnNames.has(name)) continue;
|
|
870
|
+
if (LIST_QUERY_RESERVED.has(name)) continue;
|
|
871
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
872
|
+
if (!isListStringColumn(col)) continue;
|
|
873
|
+
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
874
|
+
const raw = searchParams.get(name)?.trim();
|
|
875
|
+
if (!raw) continue;
|
|
876
|
+
and[name] = (0, import_typeorm.ILike)(`%${raw}%`);
|
|
877
|
+
}
|
|
878
|
+
return and;
|
|
879
|
+
}
|
|
880
|
+
function buildExactListParamWhere(repo, searchParams) {
|
|
881
|
+
const extraWhere = {};
|
|
882
|
+
const columnNames = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
883
|
+
for (const col of repo.metadata.columns) {
|
|
884
|
+
const name = col.propertyName;
|
|
885
|
+
if (!columnNames.has(name)) continue;
|
|
886
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
887
|
+
if (!isListNumericColumn(col)) continue;
|
|
888
|
+
const v = searchParams.get(name)?.trim();
|
|
889
|
+
if (v == null || v === "") continue;
|
|
890
|
+
const n = Number(v);
|
|
891
|
+
if (!Number.isFinite(n)) continue;
|
|
892
|
+
extraWhere[name] = n;
|
|
893
|
+
}
|
|
894
|
+
for (const col of repo.metadata.columns) {
|
|
895
|
+
if (String(col.type) !== "boolean") continue;
|
|
896
|
+
const name = col.propertyName;
|
|
897
|
+
if (!columnNames.has(name)) continue;
|
|
898
|
+
const raw = searchParams.get(name)?.trim();
|
|
899
|
+
if (raw === "true" || raw === "false") {
|
|
900
|
+
extraWhere[name] = raw === "true";
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return extraWhere;
|
|
904
|
+
}
|
|
629
905
|
function mergeDeletedFalseWhere(repo, where) {
|
|
630
906
|
if (!entityHasSoftDelete(repo)) return where;
|
|
631
907
|
const d = { deleted: false };
|
|
@@ -635,6 +911,12 @@ function mergeDeletedFalseWhere(repo, where) {
|
|
|
635
911
|
}
|
|
636
912
|
return Object.keys(where).length > 0 ? { ...where, ...d } : d;
|
|
637
913
|
}
|
|
914
|
+
function pickDefaultListSortField(columnNames, columns) {
|
|
915
|
+
for (const candidate of ["createdAt", "updatedAt", "id", "name", "sortOrder", "title"]) {
|
|
916
|
+
if (columnNames.has(candidate)) return candidate;
|
|
917
|
+
}
|
|
918
|
+
return columns[0]?.propertyName ?? "id";
|
|
919
|
+
}
|
|
638
920
|
function normalizeProductSku(value) {
|
|
639
921
|
if (value == null) return null;
|
|
640
922
|
const s = String(value).trim();
|
|
@@ -737,6 +1019,18 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
737
1019
|
if (statusFilter) qb.andWhere("order.status = :status", { status: statusFilter });
|
|
738
1020
|
if (dateFrom) qb.andWhere("order.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
739
1021
|
if (dateTo) qb.andWhere("order.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
1022
|
+
const totalMin = searchParams.get("totalMin")?.trim();
|
|
1023
|
+
const totalMax = searchParams.get("totalMax")?.trim();
|
|
1024
|
+
if (totalMin) {
|
|
1025
|
+
const n = Number(totalMin);
|
|
1026
|
+
if (Number.isFinite(n)) qb.andWhere("order.total >= :totalMin", { totalMin: n });
|
|
1027
|
+
}
|
|
1028
|
+
if (totalMax) {
|
|
1029
|
+
const n = Number(totalMax);
|
|
1030
|
+
if (Number.isFinite(n)) qb.andWhere("order.total <= :totalMax", { totalMax: n });
|
|
1031
|
+
}
|
|
1032
|
+
const currency = searchParams.get("currency")?.trim();
|
|
1033
|
+
if (currency) qb.andWhere("order.currency ILIKE :orderCurrency", { orderCurrency: `%${currency}%` });
|
|
740
1034
|
if (orderIdsFromPayment && orderIdsFromPayment.length) qb.andWhere("order.id IN (:...orderIds)", { orderIds: orderIdsFromPayment });
|
|
741
1035
|
const [rows, total2] = await qb.getManyAndCount();
|
|
742
1036
|
const data2 = rows.map((order) => {
|
|
@@ -775,8 +1069,30 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
775
1069
|
if (statusFilter) qb.andWhere("payment.status = :status", { status: statusFilter });
|
|
776
1070
|
if (dateFrom) qb.andWhere("payment.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
777
1071
|
if (dateTo) qb.andWhere("payment.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
1072
|
+
const paidAtFrom = searchParams.get("paidAtFrom")?.trim();
|
|
1073
|
+
const paidAtTo = searchParams.get("paidAtTo")?.trim();
|
|
1074
|
+
if (paidAtFrom) {
|
|
1075
|
+
qb.andWhere("payment.paidAt >= :paidAtFrom", { paidAtFrom: /* @__PURE__ */ new Date(paidAtFrom + "T00:00:00.000Z") });
|
|
1076
|
+
}
|
|
1077
|
+
if (paidAtTo) {
|
|
1078
|
+
qb.andWhere("payment.paidAt <= :paidAtTo", { paidAtTo: /* @__PURE__ */ new Date(paidAtTo + "T23:59:59.999Z") });
|
|
1079
|
+
}
|
|
778
1080
|
if (methodFilter) qb.andWhere("payment.method = :method", { method: methodFilter });
|
|
779
1081
|
if (orderNumberParam) qb.andWhere("ord.orderNumber ILIKE :orderNumber", { orderNumber: `%${orderNumberParam}%` });
|
|
1082
|
+
const amountMin = searchParams.get("amountMin")?.trim();
|
|
1083
|
+
const amountMax = searchParams.get("amountMax")?.trim();
|
|
1084
|
+
if (amountMin) {
|
|
1085
|
+
const n = Number(amountMin);
|
|
1086
|
+
if (Number.isFinite(n)) qb.andWhere("payment.amount >= :amountMin", { amountMin: n });
|
|
1087
|
+
}
|
|
1088
|
+
if (amountMax) {
|
|
1089
|
+
const n = Number(amountMax);
|
|
1090
|
+
if (Number.isFinite(n)) qb.andWhere("payment.amount <= :amountMax", { amountMax: n });
|
|
1091
|
+
}
|
|
1092
|
+
const extRef = searchParams.get("externalReference")?.trim();
|
|
1093
|
+
if (extRef) {
|
|
1094
|
+
qb.andWhere("payment.externalReference ILIKE :extRef", { extRef: `%${extRef}%` });
|
|
1095
|
+
}
|
|
780
1096
|
const [rows, total2] = await qb.getManyAndCount();
|
|
781
1097
|
const data2 = rows.map((payment) => {
|
|
782
1098
|
const order = payment.order;
|
|
@@ -795,18 +1111,36 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
795
1111
|
const repo2 = dataSource.getRepository(entity);
|
|
796
1112
|
const statusFilter = searchParams.get("status")?.trim();
|
|
797
1113
|
const inventory = searchParams.get("inventory")?.trim();
|
|
798
|
-
const productWhere = {
|
|
1114
|
+
const productWhere = {
|
|
1115
|
+
deleted: false,
|
|
1116
|
+
...buildListFilterAndFromSearchParams(repo2, searchParams)
|
|
1117
|
+
};
|
|
799
1118
|
if (statusFilter) productWhere.status = statusFilter;
|
|
800
1119
|
if (inventory === "in_stock") productWhere.quantity = (0, import_typeorm.MoreThan)(0);
|
|
801
1120
|
if (inventory === "out_of_stock") productWhere.quantity = 0;
|
|
1121
|
+
for (const key of ["brandId", "categoryId", "collectionId"]) {
|
|
1122
|
+
const raw = searchParams.get(key)?.trim();
|
|
1123
|
+
if (raw) {
|
|
1124
|
+
const n = Number(raw);
|
|
1125
|
+
if (Number.isFinite(n)) productWhere[key] = n;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
const featuredRaw = searchParams.get("featured")?.trim();
|
|
1129
|
+
if (featuredRaw === "true" || featuredRaw === "false") {
|
|
1130
|
+
productWhere.featured = featuredRaw === "true";
|
|
1131
|
+
}
|
|
802
1132
|
if (search && typeof search === "string" && search.trim()) {
|
|
803
1133
|
productWhere.name = (0, import_typeorm.ILike)(`%${search.trim()}%`);
|
|
804
1134
|
}
|
|
1135
|
+
const productColumnNames = new Set(repo2.metadata.columns.map((c) => c.propertyName));
|
|
1136
|
+
const defaultProductSort = pickDefaultListSortField(productColumnNames, repo2.metadata.columns);
|
|
1137
|
+
const sortParam2 = (searchParams.get("sortField") ?? "").trim();
|
|
1138
|
+
const productSortField = sortParam2 && productColumnNames.has(sortParam2) ? sortParam2 : defaultProductSort;
|
|
805
1139
|
const [data2, total2] = await repo2.findAndCount({
|
|
806
1140
|
where: Object.keys(productWhere).length ? productWhere : void 0,
|
|
807
1141
|
skip,
|
|
808
1142
|
take: limit,
|
|
809
|
-
order: { [
|
|
1143
|
+
order: { [productSortField]: sortOrder }
|
|
810
1144
|
});
|
|
811
1145
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
812
1146
|
}
|
|
@@ -899,27 +1233,22 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
899
1233
|
}
|
|
900
1234
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
901
1235
|
}
|
|
902
|
-
const
|
|
1236
|
+
const defaultSortField = pickDefaultListSortField(columnNames, repo.metadata.columns);
|
|
1237
|
+
const sortParam = (searchParams.get("sortField") ?? "").trim();
|
|
1238
|
+
const sortField = sortParam && columnNames.has(sortParam) ? sortParam : defaultSortField;
|
|
903
1239
|
let where = {};
|
|
904
1240
|
if (search) {
|
|
905
1241
|
where = buildSearchWhereClause(repo, search);
|
|
906
1242
|
}
|
|
907
|
-
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
const v = searchParams.get(key);
|
|
911
|
-
if (v != null && v !== "" && columnNames.has(key)) {
|
|
912
|
-
const n = Number(v);
|
|
913
|
-
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
if (Object.keys(extraWhere).length > 0) {
|
|
1243
|
+
where = mergeListWhereAnd(where, buildListFilterAndFromSearchParams(repo, searchParams));
|
|
1244
|
+
const exactParamWhere = buildExactListParamWhere(repo, searchParams);
|
|
1245
|
+
if (Object.keys(exactParamWhere).length > 0) {
|
|
917
1246
|
if (Array.isArray(where)) {
|
|
918
|
-
where = where.map((w) => ({ ...w, ...
|
|
1247
|
+
where = where.map((w) => ({ ...w, ...exactParamWhere }));
|
|
919
1248
|
} else if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
920
|
-
where = { ...where, ...
|
|
1249
|
+
where = { ...where, ...exactParamWhere };
|
|
921
1250
|
} else {
|
|
922
|
-
where =
|
|
1251
|
+
where = exactParamWhere;
|
|
923
1252
|
}
|
|
924
1253
|
}
|
|
925
1254
|
where = mergeDeletedFalseWhere(repo, where);
|
|
@@ -999,6 +1328,10 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
999
1328
|
}
|
|
1000
1329
|
const repo = dataSource.getRepository(entity);
|
|
1001
1330
|
const persistBody = resource === "media" ? body : pickColumnUpdates(repo, body);
|
|
1331
|
+
if (resource === "contacts" && "type" in persistBody) {
|
|
1332
|
+
const t = persistBody.type;
|
|
1333
|
+
if (t === "" || t === "none" || t == null) persistBody.type = null;
|
|
1334
|
+
}
|
|
1002
1335
|
if (resource !== "media" && Object.keys(persistBody).length === 0) {
|
|
1003
1336
|
logCrudClientError("POST create", {
|
|
1004
1337
|
reason: "no_scalar_columns_after_pick",
|
|
@@ -1021,6 +1354,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
1021
1354
|
}
|
|
1022
1355
|
}
|
|
1023
1356
|
}
|
|
1357
|
+
if (resource === "addresses") {
|
|
1358
|
+
const cid = Number(persistBody.contactId);
|
|
1359
|
+
if (!Number.isFinite(cid)) {
|
|
1360
|
+
return json({ error: "Valid contactId is required." }, { status: 400 });
|
|
1361
|
+
}
|
|
1362
|
+
if (persistBody.tag === "") persistBody.tag = null;
|
|
1363
|
+
const addrErr = validateAndNormalizeAddressRow(persistBody);
|
|
1364
|
+
if (addrErr) {
|
|
1365
|
+
return json({ error: addrErr }, { status: 400 });
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1024
1368
|
sanitizeBodyForEntity(repo, persistBody);
|
|
1025
1369
|
let created;
|
|
1026
1370
|
try {
|
|
@@ -1338,10 +1682,60 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1338
1682
|
if (!cur) return json({ message: "Not found" }, { status: 404 });
|
|
1339
1683
|
}
|
|
1340
1684
|
const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
|
|
1685
|
+
if (resource === "contacts" && "type" in updatePayload) {
|
|
1686
|
+
const t = updatePayload.type;
|
|
1687
|
+
if (t === "" || t === "none" || t == null) updatePayload.type = null;
|
|
1688
|
+
}
|
|
1341
1689
|
if (resource === "media") {
|
|
1342
1690
|
const u = updatePayload;
|
|
1343
|
-
delete u.parentId;
|
|
1344
1691
|
delete u.kind;
|
|
1692
|
+
if (rawBody && typeof rawBody === "object" && "parentId" in rawBody) {
|
|
1693
|
+
let pid = null;
|
|
1694
|
+
const p = rawBody.parentId;
|
|
1695
|
+
if (p != null && p !== "") {
|
|
1696
|
+
const n = Number(p);
|
|
1697
|
+
if (!Number.isFinite(n)) {
|
|
1698
|
+
return json({ error: "Invalid parentId" }, { status: 400 });
|
|
1699
|
+
}
|
|
1700
|
+
pid = n;
|
|
1701
|
+
}
|
|
1702
|
+
if (pid != null) {
|
|
1703
|
+
const parent = await repo.findOne({
|
|
1704
|
+
where: { id: pid, deleted: false }
|
|
1705
|
+
});
|
|
1706
|
+
if (!parent || parent.kind !== "folder") {
|
|
1707
|
+
return json({ error: "parent must be a folder" }, { status: 400 });
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
const row = await repo.findOne({
|
|
1711
|
+
where: { id: numericId, deleted: false }
|
|
1712
|
+
});
|
|
1713
|
+
if (!row) return json({ message: "Not found" }, { status: 404 });
|
|
1714
|
+
if (pid === numericId) {
|
|
1715
|
+
return json({ error: "Invalid parentId" }, { status: 400 });
|
|
1716
|
+
}
|
|
1717
|
+
if (row.kind === "folder" && pid != null) {
|
|
1718
|
+
let walk = pid;
|
|
1719
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1720
|
+
while (walk != null) {
|
|
1721
|
+
if (walk === numericId) {
|
|
1722
|
+
return json(
|
|
1723
|
+
{ error: "Cannot move a folder into itself or a descendant folder" },
|
|
1724
|
+
{ status: 400 }
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
if (seen.has(walk)) break;
|
|
1728
|
+
seen.add(walk);
|
|
1729
|
+
const anc = await repo.findOne({
|
|
1730
|
+
where: { id: walk, deleted: false }
|
|
1731
|
+
});
|
|
1732
|
+
walk = anc ? anc.parentId ?? null : null;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
u.parentId = pid;
|
|
1736
|
+
} else {
|
|
1737
|
+
delete u.parentId;
|
|
1738
|
+
}
|
|
1345
1739
|
}
|
|
1346
1740
|
if (resource === "products") {
|
|
1347
1741
|
const currentRow = await repo.findOne({
|
|
@@ -1360,6 +1754,26 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1360
1754
|
updatePayload.sku = effSku;
|
|
1361
1755
|
}
|
|
1362
1756
|
}
|
|
1757
|
+
if (resource === "addresses" && Object.keys(updatePayload).length > 0) {
|
|
1758
|
+
const currentRow = await repo.findOne({
|
|
1759
|
+
where: { id: numericId }
|
|
1760
|
+
});
|
|
1761
|
+
if (!currentRow) return json({ message: "Not found" }, { status: 404 });
|
|
1762
|
+
const merged = {
|
|
1763
|
+
...currentRow,
|
|
1764
|
+
...updatePayload
|
|
1765
|
+
};
|
|
1766
|
+
if (merged.tag === "") merged.tag = null;
|
|
1767
|
+
const addrErr = validateAndNormalizeAddressRow(merged);
|
|
1768
|
+
if (addrErr) {
|
|
1769
|
+
return json({ error: addrErr }, { status: 400 });
|
|
1770
|
+
}
|
|
1771
|
+
for (const k of Object.keys(updatePayload)) {
|
|
1772
|
+
if (k in merged) {
|
|
1773
|
+
updatePayload[k] = merged[k];
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1363
1777
|
if (Object.keys(updatePayload).length > 0) {
|
|
1364
1778
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
1365
1779
|
await repo.update(numericId, updatePayload);
|
|
@@ -1713,11 +2127,11 @@ async function readBufferFromPublicUrl(url) {
|
|
|
1713
2127
|
throw new Error("Unsupported media URL");
|
|
1714
2128
|
}
|
|
1715
2129
|
function sanitizeZipPath(entryName) {
|
|
1716
|
-
const
|
|
1717
|
-
for (const seg of
|
|
2130
|
+
const norm2 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
2131
|
+
for (const seg of norm2) {
|
|
1718
2132
|
if (seg === ".." || seg === ".") return null;
|
|
1719
2133
|
}
|
|
1720
|
-
return
|
|
2134
|
+
return norm2;
|
|
1721
2135
|
}
|
|
1722
2136
|
function shouldSkipEntry(parts) {
|
|
1723
2137
|
if (parts[0] === "__MACOSX") return true;
|
|
@@ -2737,18 +3151,37 @@ function createUsersApiHandlers(config) {
|
|
|
2737
3151
|
try {
|
|
2738
3152
|
const body = await req.json();
|
|
2739
3153
|
if (!body?.name || !body?.email) return json({ error: "Name and email are required" }, { status: 400 });
|
|
2740
|
-
const
|
|
2741
|
-
|
|
3154
|
+
const email = body.email;
|
|
3155
|
+
const existing = await userRepo().findOne({ where: { email } });
|
|
3156
|
+
if (existing && !existing.deleted) {
|
|
3157
|
+
return json({ error: "User with this email already exists" }, { status: 400 });
|
|
3158
|
+
}
|
|
2742
3159
|
const groupRepo = dataSource.getRepository(entityMap.user_groups);
|
|
2743
3160
|
const customerG = await groupRepo.findOne({ where: { name: "Customer", deleted: false } });
|
|
2744
3161
|
const gid = body.groupId ?? null;
|
|
2745
3162
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
2746
3163
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
2747
3164
|
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
2748
|
-
const newUser = await
|
|
3165
|
+
const newUser = existing?.deleted ? await (async () => {
|
|
3166
|
+
await userRepo().update(existing.id, {
|
|
3167
|
+
deleted: false,
|
|
3168
|
+
deletedAt: null,
|
|
3169
|
+
deletedBy: null,
|
|
3170
|
+
name: body.name,
|
|
3171
|
+
email,
|
|
3172
|
+
password: null,
|
|
3173
|
+
blocked,
|
|
3174
|
+
groupId: gid,
|
|
3175
|
+
adminAccess,
|
|
3176
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3177
|
+
});
|
|
3178
|
+
const row = await userRepo().findOne({ where: { id: existing.id } });
|
|
3179
|
+
if (!row) throw new Error("user missing after restore");
|
|
3180
|
+
return row;
|
|
3181
|
+
})() : await userRepo().save(
|
|
2749
3182
|
userRepo().create({
|
|
2750
3183
|
name: body.name,
|
|
2751
|
-
email
|
|
3184
|
+
email,
|
|
2752
3185
|
password: null,
|
|
2753
3186
|
blocked,
|
|
2754
3187
|
groupId: gid,
|
|
@@ -2901,21 +3334,110 @@ function createUserAvatarHandler(config) {
|
|
|
2901
3334
|
}
|
|
2902
3335
|
};
|
|
2903
3336
|
}
|
|
3337
|
+
var PROFILE_EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2904
3338
|
function createUserProfileHandler(config) {
|
|
2905
|
-
const { dataSource, entityMap, json, getSession } = config;
|
|
2906
|
-
|
|
3339
|
+
const { dataSource, entityMap, json, getSession, onProfileUpdated } = config;
|
|
3340
|
+
async function loadCurrentUser() {
|
|
2907
3341
|
const session = await getSession();
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
3342
|
+
const su = session?.user;
|
|
3343
|
+
if (!su?.email && su?.id == null) {
|
|
3344
|
+
return { ok: false, response: json({ error: "Unauthorized" }, { status: 401 }) };
|
|
3345
|
+
}
|
|
3346
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
3347
|
+
let user = null;
|
|
3348
|
+
const uidRaw = su.id != null ? String(su.id).trim() : "";
|
|
3349
|
+
const uid = uidRaw && /^\d+$/.test(uidRaw) ? parseInt(uidRaw, 10) : NaN;
|
|
3350
|
+
if (Number.isFinite(uid) && uid > 0) {
|
|
3351
|
+
user = await userRepo.findOne({
|
|
3352
|
+
where: { id: uid, deleted: false },
|
|
3353
|
+
select: ["id", "name", "email", "phone", "createdAt"]
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3356
|
+
if (!user && su.email) {
|
|
3357
|
+
const em = String(su.email).trim().toLowerCase();
|
|
3358
|
+
if (em) {
|
|
3359
|
+
user = await userRepo.findOne({
|
|
3360
|
+
where: { email: em, deleted: false },
|
|
3361
|
+
select: ["id", "name", "email", "phone", "createdAt"]
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
if (!user) return { ok: false, response: json({ error: "Not found" }, { status: 404 }) };
|
|
3366
|
+
return { ok: true, user };
|
|
3367
|
+
}
|
|
3368
|
+
return {
|
|
3369
|
+
async GET(_req) {
|
|
3370
|
+
try {
|
|
3371
|
+
const r = await loadCurrentUser();
|
|
3372
|
+
if (!r.ok) return r.response;
|
|
3373
|
+
const u = r.user;
|
|
3374
|
+
return json({
|
|
3375
|
+
id: u.id,
|
|
3376
|
+
name: u.name ?? "",
|
|
3377
|
+
email: u.email ?? "",
|
|
3378
|
+
phone: u.phone ?? null,
|
|
3379
|
+
createdAt: u.createdAt instanceof Date ? u.createdAt.toISOString() : u.createdAt ?? void 0
|
|
3380
|
+
});
|
|
3381
|
+
} catch {
|
|
3382
|
+
return json({ error: "Internal server error" }, { status: 500 });
|
|
3383
|
+
}
|
|
3384
|
+
},
|
|
3385
|
+
async PUT(req) {
|
|
3386
|
+
try {
|
|
3387
|
+
const r = await loadCurrentUser();
|
|
3388
|
+
if (!r.ok) return r.response;
|
|
3389
|
+
const current = r.user;
|
|
3390
|
+
let body;
|
|
3391
|
+
try {
|
|
3392
|
+
body = await req.json();
|
|
3393
|
+
} catch {
|
|
3394
|
+
return json({ error: "Invalid JSON" }, { status: 400 });
|
|
3395
|
+
}
|
|
3396
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
3397
|
+
if (!name) return json({ error: "Name is required" }, { status: 400 });
|
|
3398
|
+
const emailRaw = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
|
|
3399
|
+
if (!emailRaw || !PROFILE_EMAIL_RE.test(emailRaw)) {
|
|
3400
|
+
return json({ error: "Valid email is required" }, { status: 400 });
|
|
3401
|
+
}
|
|
3402
|
+
const phone = body.phone === null || body.phone === void 0 ? null : typeof body.phone === "string" ? body.phone.trim() || null : null;
|
|
3403
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
3404
|
+
if (emailRaw !== String(current.email ?? "").toLowerCase()) {
|
|
3405
|
+
const taken = await userRepo.findOne({
|
|
3406
|
+
where: { email: emailRaw, deleted: false },
|
|
3407
|
+
select: ["id"]
|
|
3408
|
+
});
|
|
3409
|
+
if (taken && taken.id !== current.id) {
|
|
3410
|
+
return json({ error: "Email is already in use" }, { status: 409 });
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
await userRepo.update(
|
|
3414
|
+
{ id: current.id },
|
|
3415
|
+
{
|
|
3416
|
+
name,
|
|
3417
|
+
email: emailRaw,
|
|
3418
|
+
phone,
|
|
3419
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3420
|
+
}
|
|
3421
|
+
);
|
|
3422
|
+
const updated = await userRepo.findOne({
|
|
3423
|
+
where: { id: current.id },
|
|
3424
|
+
select: ["id", "name", "email", "phone"]
|
|
3425
|
+
});
|
|
3426
|
+
if (!updated) return json({ error: "Not found" }, { status: 404 });
|
|
3427
|
+
const row = updated;
|
|
3428
|
+
if (onProfileUpdated) {
|
|
3429
|
+
try {
|
|
3430
|
+
await onProfileUpdated(req, row);
|
|
3431
|
+
} catch {
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
return json({
|
|
3435
|
+
message: "Profile updated successfully",
|
|
3436
|
+
user: { id: row.id, name: row.name, email: row.email, phone: row.phone }
|
|
3437
|
+
});
|
|
3438
|
+
} catch {
|
|
3439
|
+
return json({ error: "Internal server error" }, { status: 500 });
|
|
3440
|
+
}
|
|
2919
3441
|
}
|
|
2920
3442
|
};
|
|
2921
3443
|
}
|
|
@@ -6928,7 +7450,7 @@ function createCmsApiHandler(config) {
|
|
|
6928
7450
|
} : usersApi;
|
|
6929
7451
|
const usersHandlers = usersApiMerged ? createUsersApiHandlers(mergePerm(usersApiMerged) ?? usersApiMerged) : null;
|
|
6930
7452
|
const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
|
|
6931
|
-
const
|
|
7453
|
+
const profileHandlers = userProfile ? createUserProfileHandler(userProfile) : null;
|
|
6932
7454
|
const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
|
|
6933
7455
|
const smsMessageTemplateHandlers = createSmsMessageTemplateHandlers({
|
|
6934
7456
|
dataSource,
|
|
@@ -7027,7 +7549,10 @@ function createCmsApiHandler(config) {
|
|
|
7027
7549
|
}
|
|
7028
7550
|
if (path.length === 2) {
|
|
7029
7551
|
if (path[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
|
|
7030
|
-
if (path[1] === "profile" &&
|
|
7552
|
+
if (path[1] === "profile" && profileHandlers) {
|
|
7553
|
+
if (m === "GET") return profileHandlers.GET(req);
|
|
7554
|
+
if (m === "PUT") return profileHandlers.PUT(req);
|
|
7555
|
+
}
|
|
7031
7556
|
const id = path[1];
|
|
7032
7557
|
if (m === "GET") return usersHandlers.getById(req, id);
|
|
7033
7558
|
if (m === "PUT" || m === "PATCH") return usersHandlers.update(req, id);
|
|
@@ -8090,18 +8615,19 @@ function createStorefrontApiHandler(config) {
|
|
|
8090
8615
|
const contactOrErr = await getContactForAddresses();
|
|
8091
8616
|
if (contactOrErr instanceof Response) return contactOrErr;
|
|
8092
8617
|
const b = await req.json().catch(() => ({}));
|
|
8093
|
-
const
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
);
|
|
8618
|
+
const row = {
|
|
8619
|
+
contactId: contactOrErr.contactId,
|
|
8620
|
+
tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
|
|
8621
|
+
line1: typeof b.line1 === "string" ? b.line1 : "",
|
|
8622
|
+
line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
|
|
8623
|
+
city: typeof b.city === "string" ? b.city : "",
|
|
8624
|
+
state: typeof b.state === "string" ? b.state : "",
|
|
8625
|
+
postalCode: typeof b.postalCode === "string" ? b.postalCode : "",
|
|
8626
|
+
country: typeof b.country === "string" ? b.country : ""
|
|
8627
|
+
};
|
|
8628
|
+
const addrErr = validateAndNormalizeAddressRow(row);
|
|
8629
|
+
if (addrErr) return json({ error: addrErr }, { status: 400 });
|
|
8630
|
+
const created = await addressRepo().save(addressRepo().create(row));
|
|
8105
8631
|
return json(serializeAddress2(created));
|
|
8106
8632
|
}
|
|
8107
8633
|
if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
|
|
@@ -8120,7 +8646,16 @@ function createStorefrontApiHandler(config) {
|
|
|
8120
8646
|
if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
|
|
8121
8647
|
if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
|
|
8122
8648
|
if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
|
|
8123
|
-
if (Object.keys(updates).length)
|
|
8649
|
+
if (Object.keys(updates).length) {
|
|
8650
|
+
const merged = { ...existing, ...updates };
|
|
8651
|
+
if (merged.tag === "") merged.tag = null;
|
|
8652
|
+
const addrErr = validateAndNormalizeAddressRow(merged);
|
|
8653
|
+
if (addrErr) return json({ error: addrErr }, { status: 400 });
|
|
8654
|
+
for (const k of Object.keys(updates)) {
|
|
8655
|
+
if (k in merged) updates[k] = merged[k];
|
|
8656
|
+
}
|
|
8657
|
+
await addressRepo().update(id, updates);
|
|
8658
|
+
}
|
|
8124
8659
|
const updated = await addressRepo().findOne({ where: { id } });
|
|
8125
8660
|
return json(serializeAddress2(updated));
|
|
8126
8661
|
}
|