@infuro/cms-core 1.0.22 → 1.0.24
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 +3595 -1844
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +42 -15
- package/dist/admin.d.ts +42 -15
- package/dist/admin.js +3626 -1874
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +325 -34
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +326 -35
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +328 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +329 -36
- package/dist/index.js.map +1 -1
- package/package.json +130 -129
package/dist/api.cjs
CHANGED
|
@@ -529,6 +529,64 @@ async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, produc
|
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
// src/lib/address-geo-validation.ts
|
|
533
|
+
var import_country_state_city = require("country-state-city");
|
|
534
|
+
function norm(s) {
|
|
535
|
+
return typeof s === "string" ? s.trim() : "";
|
|
536
|
+
}
|
|
537
|
+
function resolveCountry(input) {
|
|
538
|
+
const t = input.trim();
|
|
539
|
+
if (!t) return void 0;
|
|
540
|
+
if (t.length === 2) {
|
|
541
|
+
const byCode = import_country_state_city.Country.getCountryByCode(t.toUpperCase());
|
|
542
|
+
if (byCode) return byCode;
|
|
543
|
+
}
|
|
544
|
+
const lower = t.toLowerCase();
|
|
545
|
+
return import_country_state_city.Country.getAllCountries().find((c) => c.name.toLowerCase() === lower);
|
|
546
|
+
}
|
|
547
|
+
function resolveState(countryIso, input) {
|
|
548
|
+
const t = input.trim();
|
|
549
|
+
if (!t || !countryIso) return void 0;
|
|
550
|
+
const states = import_country_state_city.State.getStatesOfCountry(countryIso);
|
|
551
|
+
const lower = t.toLowerCase();
|
|
552
|
+
return states.find((s) => s.isoCode.toLowerCase() === t.toLowerCase() || s.name.toLowerCase() === lower);
|
|
553
|
+
}
|
|
554
|
+
function resolveCity(countryIso, stateIso, input) {
|
|
555
|
+
const t = input.trim();
|
|
556
|
+
if (!t || !countryIso || !stateIso) return void 0;
|
|
557
|
+
const lower = t.toLowerCase();
|
|
558
|
+
const cities = import_country_state_city.City.getCitiesOfState(countryIso, stateIso);
|
|
559
|
+
return cities.find((c) => c.name.toLowerCase() === lower);
|
|
560
|
+
}
|
|
561
|
+
function assertValidAddressHierarchy(country, state, city) {
|
|
562
|
+
const c = resolveCountry(country);
|
|
563
|
+
if (!c) return { ok: false, error: "Invalid or unknown country." };
|
|
564
|
+
const st = resolveState(c.isoCode, state);
|
|
565
|
+
if (!st) return { ok: false, error: "State or province does not match the selected country." };
|
|
566
|
+
const ct = resolveCity(c.isoCode, st.isoCode, city);
|
|
567
|
+
if (!ct) return { ok: false, error: "City does not match the selected state." };
|
|
568
|
+
return { ok: true, country: c.name, state: st.name, city: ct.name };
|
|
569
|
+
}
|
|
570
|
+
function validateAndNormalizeAddressRow(row) {
|
|
571
|
+
const line1 = norm(row.line1);
|
|
572
|
+
const postalCode = norm(row.postalCode);
|
|
573
|
+
const countryIn = norm(row.country);
|
|
574
|
+
const stateIn = norm(row.state);
|
|
575
|
+
const cityIn = norm(row.city);
|
|
576
|
+
if (!line1) return "Street address (line 1) is required.";
|
|
577
|
+
if (!postalCode) return "Postal code is required.";
|
|
578
|
+
if (!countryIn || !stateIn || !cityIn) return "Country, state, and city are required.";
|
|
579
|
+
const geo = assertValidAddressHierarchy(countryIn, stateIn, cityIn);
|
|
580
|
+
if (!geo.ok) return geo.error;
|
|
581
|
+
row.line1 = line1;
|
|
582
|
+
row.line2 = norm(row.line2) || null;
|
|
583
|
+
row.postalCode = postalCode;
|
|
584
|
+
row.country = geo.country;
|
|
585
|
+
row.state = geo.state;
|
|
586
|
+
row.city = geo.city;
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
|
|
532
590
|
// src/api/crud.ts
|
|
533
591
|
var CRUD_LOG = "[cms-crud]";
|
|
534
592
|
function logCrudClientError(op, detail) {
|
|
@@ -537,6 +595,40 @@ function logCrudClientError(op, detail) {
|
|
|
537
595
|
function logCrudServerError(op, detail) {
|
|
538
596
|
console.error(CRUD_LOG, op, detail);
|
|
539
597
|
}
|
|
598
|
+
async function aggregateMediaFolderFileSizes(dataSource, folderIds) {
|
|
599
|
+
const map = /* @__PURE__ */ new Map();
|
|
600
|
+
if (folderIds.length === 0) return map;
|
|
601
|
+
try {
|
|
602
|
+
const rows = await dataSource.query(
|
|
603
|
+
`
|
|
604
|
+
WITH RECURSIVE walk AS (
|
|
605
|
+
SELECT m."parentId" AS root_id, m.id, m.kind, m.size
|
|
606
|
+
FROM media m
|
|
607
|
+
WHERE m."parentId" = ANY($1) AND m.deleted = false
|
|
608
|
+
UNION ALL
|
|
609
|
+
SELECT w.root_id, m.id, m.kind, m.size
|
|
610
|
+
FROM walk w
|
|
611
|
+
INNER JOIN media m ON m."parentId" = w.id AND m.deleted = false
|
|
612
|
+
)
|
|
613
|
+
SELECT root_id AS "rootId", COALESCE(SUM(size) FILTER (WHERE kind = 'file'), 0)::bigint AS "totalSize"
|
|
614
|
+
FROM walk
|
|
615
|
+
GROUP BY root_id
|
|
616
|
+
`,
|
|
617
|
+
[folderIds]
|
|
618
|
+
);
|
|
619
|
+
for (const r of rows) {
|
|
620
|
+
map.set(Number(r.rootId), Number(r.totalSize));
|
|
621
|
+
}
|
|
622
|
+
for (const id of folderIds) {
|
|
623
|
+
if (!map.has(id)) map.set(id, 0);
|
|
624
|
+
}
|
|
625
|
+
} catch (err) {
|
|
626
|
+
logCrudServerError("media folder size aggregate failed", {
|
|
627
|
+
message: err instanceof Error ? err.message : String(err)
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return map;
|
|
631
|
+
}
|
|
540
632
|
var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
|
|
541
633
|
"date",
|
|
542
634
|
"datetime",
|
|
@@ -601,6 +693,16 @@ function mergeDeletedFalseWhere(repo, where) {
|
|
|
601
693
|
}
|
|
602
694
|
return Object.keys(where).length > 0 ? { ...where, ...d } : d;
|
|
603
695
|
}
|
|
696
|
+
function normalizeProductSku(value) {
|
|
697
|
+
if (value == null) return null;
|
|
698
|
+
const s = String(value).trim();
|
|
699
|
+
return s === "" ? null : s;
|
|
700
|
+
}
|
|
701
|
+
async function assertProductSkuUnique(repo, sku, excludeId) {
|
|
702
|
+
const where = excludeId != null ? { sku, deleted: false, id: (0, import_typeorm.Not)(excludeId) } : { sku, deleted: false };
|
|
703
|
+
const row = await repo.findOne({ where });
|
|
704
|
+
return row == null;
|
|
705
|
+
}
|
|
604
706
|
function buildSoftDeletePayload(meta, deletedBy) {
|
|
605
707
|
const payload = { deleted: true };
|
|
606
708
|
if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
|
|
@@ -755,6 +857,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
755
857
|
if (statusFilter) productWhere.status = statusFilter;
|
|
756
858
|
if (inventory === "in_stock") productWhere.quantity = (0, import_typeorm.MoreThan)(0);
|
|
757
859
|
if (inventory === "out_of_stock") productWhere.quantity = 0;
|
|
860
|
+
for (const key of ["brandId", "categoryId", "collectionId"]) {
|
|
861
|
+
const raw = searchParams.get(key)?.trim();
|
|
862
|
+
if (raw) {
|
|
863
|
+
const n = Number(raw);
|
|
864
|
+
if (Number.isFinite(n)) productWhere[key] = n;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const featuredRaw = searchParams.get("featured")?.trim();
|
|
868
|
+
if (featuredRaw === "true" || featuredRaw === "false") {
|
|
869
|
+
productWhere.featured = featuredRaw === "true";
|
|
870
|
+
}
|
|
758
871
|
if (search && typeof search === "string" && search.trim()) {
|
|
759
872
|
productWhere.name = (0, import_typeorm.ILike)(`%${search.trim()}%`);
|
|
760
873
|
}
|
|
@@ -823,19 +936,36 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
823
936
|
qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
|
|
824
937
|
}
|
|
825
938
|
if (typeFilter) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
})
|
|
832
|
-
)
|
|
939
|
+
if (typeFilter === "folder") {
|
|
940
|
+
qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
|
|
941
|
+
} else if (typeFilter === "file") {
|
|
942
|
+
qb.andWhere("m.kind = :fileKind", { fileKind: "file" });
|
|
943
|
+
} else if (typeFilter === "image") {
|
|
944
|
+
qb.andWhere("m.mimeType LIKE :imageMimeType", { imageMimeType: "image/%" });
|
|
945
|
+
} else if (typeFilter === "video") {
|
|
946
|
+
qb.andWhere("m.mimeType LIKE :videoMimeType", { videoMimeType: "video/%" });
|
|
947
|
+
} else if (typeFilter === "audio") {
|
|
948
|
+
qb.andWhere("m.mimeType LIKE :audioMimeType", { audioMimeType: "audio/%" });
|
|
949
|
+
} else if (typeFilter === "Document") {
|
|
950
|
+
qb.andWhere("m.mimeType LIKE :documentMimeType", { documentMimeType: "application/pdf" });
|
|
951
|
+
} else if (typeFilter === "application") {
|
|
952
|
+
qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
|
|
953
|
+
}
|
|
833
954
|
}
|
|
834
955
|
const allowedSort = ["filename", "createdAt", "id"];
|
|
835
956
|
const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
|
|
836
957
|
const so = sortOrder === "DESC" ? "DESC" : "ASC";
|
|
837
|
-
qb.orderBy(
|
|
838
|
-
const [
|
|
958
|
+
qb.orderBy(`m.${sf}`, so).skip(skip).take(limit);
|
|
959
|
+
const [rows, total2] = await qb.getManyAndCount();
|
|
960
|
+
const mediaRows = rows;
|
|
961
|
+
const folderIds = mediaRows.filter((m) => m.kind === "folder").map((m) => m.id);
|
|
962
|
+
let data2 = rows;
|
|
963
|
+
if (folderIds.length > 0) {
|
|
964
|
+
const sizeMap = await aggregateMediaFolderFileSizes(dataSource, folderIds);
|
|
965
|
+
data2 = mediaRows.map(
|
|
966
|
+
(m) => m.kind === "folder" ? { ...m, size: sizeMap.get(m.id) ?? 0 } : m
|
|
967
|
+
);
|
|
968
|
+
}
|
|
839
969
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
840
970
|
}
|
|
841
971
|
const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
|
|
@@ -843,7 +973,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
843
973
|
if (search) {
|
|
844
974
|
where = buildSearchWhereClause(repo, search);
|
|
845
975
|
}
|
|
846
|
-
const intFilterKeys = ["productId", "attributeId", "taxId"];
|
|
976
|
+
const intFilterKeys = ["productId", "attributeId", "taxId", "brandId", "categoryId", "collectionId", "parentId"];
|
|
847
977
|
const extraWhere = {};
|
|
848
978
|
for (const key of intFilterKeys) {
|
|
849
979
|
const v = searchParams.get(key);
|
|
@@ -852,6 +982,15 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
852
982
|
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
853
983
|
}
|
|
854
984
|
}
|
|
985
|
+
for (const col of repo.metadata.columns) {
|
|
986
|
+
if (String(col.type) !== "boolean") continue;
|
|
987
|
+
const name = col.propertyName;
|
|
988
|
+
if (!columnNames.has(name)) continue;
|
|
989
|
+
const raw = searchParams.get(name)?.trim();
|
|
990
|
+
if (raw === "true" || raw === "false") {
|
|
991
|
+
extraWhere[name] = raw === "true";
|
|
992
|
+
}
|
|
993
|
+
}
|
|
855
994
|
if (Object.keys(extraWhere).length > 0) {
|
|
856
995
|
if (Array.isArray(where)) {
|
|
857
996
|
where = where.map((w) => ({ ...w, ...extraWhere }));
|
|
@@ -946,6 +1085,31 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
946
1085
|
});
|
|
947
1086
|
return json({ error: "Invalid request payload" }, { status: 400 });
|
|
948
1087
|
}
|
|
1088
|
+
if (resource === "products") {
|
|
1089
|
+
if ("sku" in persistBody) {
|
|
1090
|
+
const skuNorm = normalizeProductSku(persistBody.sku);
|
|
1091
|
+
if (skuNorm) {
|
|
1092
|
+
const ok = await assertProductSkuUnique(repo, skuNorm);
|
|
1093
|
+
if (!ok) {
|
|
1094
|
+
return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
|
|
1095
|
+
}
|
|
1096
|
+
persistBody.sku = skuNorm;
|
|
1097
|
+
} else {
|
|
1098
|
+
persistBody.sku = null;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (resource === "addresses") {
|
|
1103
|
+
const cid = Number(persistBody.contactId);
|
|
1104
|
+
if (!Number.isFinite(cid)) {
|
|
1105
|
+
return json({ error: "Valid contactId is required." }, { status: 400 });
|
|
1106
|
+
}
|
|
1107
|
+
if (persistBody.tag === "") persistBody.tag = null;
|
|
1108
|
+
const addrErr = validateAndNormalizeAddressRow(persistBody);
|
|
1109
|
+
if (addrErr) {
|
|
1110
|
+
return json({ error: addrErr }, { status: 400 });
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
949
1113
|
sanitizeBodyForEntity(repo, persistBody);
|
|
950
1114
|
let created;
|
|
951
1115
|
try {
|
|
@@ -1140,7 +1304,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1140
1304
|
relations: ["order", "order.contact", "contact"]
|
|
1141
1305
|
});
|
|
1142
1306
|
if (!payment) return json({ message: "Not found" }, { status: 404 });
|
|
1143
|
-
|
|
1307
|
+
const p = payment;
|
|
1308
|
+
const order = p.order;
|
|
1309
|
+
const orderContact = order?.contact;
|
|
1310
|
+
const contact = p.contact;
|
|
1311
|
+
const customer = orderContact ?? contact;
|
|
1312
|
+
return json({
|
|
1313
|
+
...p,
|
|
1314
|
+
order: order ? {
|
|
1315
|
+
id: order.id,
|
|
1316
|
+
orderNumber: order.orderNumber,
|
|
1317
|
+
contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null
|
|
1318
|
+
} : null,
|
|
1319
|
+
contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
|
|
1320
|
+
});
|
|
1144
1321
|
}
|
|
1145
1322
|
if (resource === "blogs") {
|
|
1146
1323
|
const blog = await repo.findOne({
|
|
@@ -1252,8 +1429,91 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1252
1429
|
const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
|
|
1253
1430
|
if (resource === "media") {
|
|
1254
1431
|
const u = updatePayload;
|
|
1255
|
-
delete u.parentId;
|
|
1256
1432
|
delete u.kind;
|
|
1433
|
+
if (rawBody && typeof rawBody === "object" && "parentId" in rawBody) {
|
|
1434
|
+
let pid = null;
|
|
1435
|
+
const p = rawBody.parentId;
|
|
1436
|
+
if (p != null && p !== "") {
|
|
1437
|
+
const n = Number(p);
|
|
1438
|
+
if (!Number.isFinite(n)) {
|
|
1439
|
+
return json({ error: "Invalid parentId" }, { status: 400 });
|
|
1440
|
+
}
|
|
1441
|
+
pid = n;
|
|
1442
|
+
}
|
|
1443
|
+
if (pid != null) {
|
|
1444
|
+
const parent = await repo.findOne({
|
|
1445
|
+
where: { id: pid, deleted: false }
|
|
1446
|
+
});
|
|
1447
|
+
if (!parent || parent.kind !== "folder") {
|
|
1448
|
+
return json({ error: "parent must be a folder" }, { status: 400 });
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
const row = await repo.findOne({
|
|
1452
|
+
where: { id: numericId, deleted: false }
|
|
1453
|
+
});
|
|
1454
|
+
if (!row) return json({ message: "Not found" }, { status: 404 });
|
|
1455
|
+
if (pid === numericId) {
|
|
1456
|
+
return json({ error: "Invalid parentId" }, { status: 400 });
|
|
1457
|
+
}
|
|
1458
|
+
if (row.kind === "folder" && pid != null) {
|
|
1459
|
+
let walk = pid;
|
|
1460
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1461
|
+
while (walk != null) {
|
|
1462
|
+
if (walk === numericId) {
|
|
1463
|
+
return json(
|
|
1464
|
+
{ error: "Cannot move a folder into itself or a descendant folder" },
|
|
1465
|
+
{ status: 400 }
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
if (seen.has(walk)) break;
|
|
1469
|
+
seen.add(walk);
|
|
1470
|
+
const anc = await repo.findOne({
|
|
1471
|
+
where: { id: walk, deleted: false }
|
|
1472
|
+
});
|
|
1473
|
+
walk = anc ? anc.parentId ?? null : null;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
u.parentId = pid;
|
|
1477
|
+
} else {
|
|
1478
|
+
delete u.parentId;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
if (resource === "products") {
|
|
1482
|
+
const currentRow = await repo.findOne({
|
|
1483
|
+
where: { id: numericId, deleted: false }
|
|
1484
|
+
});
|
|
1485
|
+
if (!currentRow) return json({ message: "Not found" }, { status: 404 });
|
|
1486
|
+
const merged = { ...currentRow, ...updatePayload };
|
|
1487
|
+
const effSku = normalizeProductSku(merged.sku);
|
|
1488
|
+
if (effSku) {
|
|
1489
|
+
const ok = await assertProductSkuUnique(repo, effSku, numericId);
|
|
1490
|
+
if (!ok) {
|
|
1491
|
+
return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
if ("sku" in updatePayload) {
|
|
1495
|
+
updatePayload.sku = effSku;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
if (resource === "addresses" && Object.keys(updatePayload).length > 0) {
|
|
1499
|
+
const currentRow = await repo.findOne({
|
|
1500
|
+
where: { id: numericId }
|
|
1501
|
+
});
|
|
1502
|
+
if (!currentRow) return json({ message: "Not found" }, { status: 404 });
|
|
1503
|
+
const merged = {
|
|
1504
|
+
...currentRow,
|
|
1505
|
+
...updatePayload
|
|
1506
|
+
};
|
|
1507
|
+
if (merged.tag === "") merged.tag = null;
|
|
1508
|
+
const addrErr = validateAndNormalizeAddressRow(merged);
|
|
1509
|
+
if (addrErr) {
|
|
1510
|
+
return json({ error: addrErr }, { status: 400 });
|
|
1511
|
+
}
|
|
1512
|
+
for (const k of Object.keys(updatePayload)) {
|
|
1513
|
+
if (k in merged) {
|
|
1514
|
+
updatePayload[k] = merged[k];
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1257
1517
|
}
|
|
1258
1518
|
if (Object.keys(updatePayload).length > 0) {
|
|
1259
1519
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
@@ -1284,6 +1544,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1284
1544
|
where: { id: numericId, deleted: false }
|
|
1285
1545
|
});
|
|
1286
1546
|
if (!existing) return json({ message: "Not found" }, { status: 404 });
|
|
1547
|
+
if (resource === "contacts") {
|
|
1548
|
+
const result2 = await repo.delete(numericId);
|
|
1549
|
+
if (result2.affected === 0) return json({ message: "Not found" }, { status: 404 });
|
|
1550
|
+
return json({ message: "Deleted successfully" }, { status: 200 });
|
|
1551
|
+
}
|
|
1287
1552
|
let deletedBy = null;
|
|
1288
1553
|
if (getDeletedByUserId) {
|
|
1289
1554
|
try {
|
|
@@ -1603,11 +1868,11 @@ async function readBufferFromPublicUrl(url) {
|
|
|
1603
1868
|
throw new Error("Unsupported media URL");
|
|
1604
1869
|
}
|
|
1605
1870
|
function sanitizeZipPath(entryName) {
|
|
1606
|
-
const
|
|
1607
|
-
for (const seg of
|
|
1871
|
+
const norm2 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
1872
|
+
for (const seg of norm2) {
|
|
1608
1873
|
if (seg === ".." || seg === ".") return null;
|
|
1609
1874
|
}
|
|
1610
|
-
return
|
|
1875
|
+
return norm2;
|
|
1611
1876
|
}
|
|
1612
1877
|
function shouldSkipEntry(parts) {
|
|
1613
1878
|
if (parts[0] === "__MACOSX") return true;
|
|
@@ -1831,12 +2096,14 @@ function createDashboardStatsHandler(config) {
|
|
|
1831
2096
|
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
|
|
1832
2097
|
const repo = (name) => entityMap[name] ? dataSource.getRepository(entityMap[name]) : void 0;
|
|
1833
2098
|
const [contactsCount, formsCount, formSubmissionsCount, usersCount, blogsCount, recentContacts, recentSubmissions, contactTypeRows] = await Promise.all([
|
|
1834
|
-
repo("contacts")?.count() ?? 0,
|
|
2099
|
+
repo("contacts")?.count({ where: { deleted: false } }) ?? 0,
|
|
1835
2100
|
repo("forms")?.count({ where: { deleted: false } }) ?? 0,
|
|
1836
2101
|
repo("form_submissions")?.count() ?? 0,
|
|
1837
2102
|
repo("users")?.count({ where: { deleted: false } }) ?? 0,
|
|
1838
2103
|
repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
|
|
1839
|
-
repo("contacts")?.count({
|
|
2104
|
+
repo("contacts")?.count({
|
|
2105
|
+
where: { deleted: false, createdAt: (0, import_typeorm5.MoreThanOrEqual)(sevenDaysAgo) }
|
|
2106
|
+
}) ?? 0,
|
|
1840
2107
|
repo("form_submissions")?.count({ where: { createdAt: (0, import_typeorm5.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
|
|
1841
2108
|
repo("contacts")?.createQueryBuilder("c").select("COALESCE(NULLIF(TRIM(c.type), ''), 'unknown')", "type").addSelect("COUNT(*)", "count").where("c.deleted = :deleted", { deleted: false }).groupBy("COALESCE(NULLIF(TRIM(c.type), ''), 'unknown')").getRawMany() ?? []
|
|
1842
2109
|
]);
|
|
@@ -2469,11 +2736,14 @@ function createFormSubmissionHandler(config) {
|
|
|
2469
2736
|
const contactRepo = dataSource.getRepository(entityMap.contacts);
|
|
2470
2737
|
let contact = await contactRepo.findOne({ where: { email: contactData.email } });
|
|
2471
2738
|
if (!contact) {
|
|
2739
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
2472
2740
|
contact = await contactRepo.save(
|
|
2473
2741
|
contactRepo.create({
|
|
2474
2742
|
name: contactData.name,
|
|
2475
2743
|
email: contactData.email,
|
|
2476
|
-
phone: contactData.phone
|
|
2744
|
+
phone: contactData.phone,
|
|
2745
|
+
createdAt,
|
|
2746
|
+
updatedAt: createdAt
|
|
2477
2747
|
})
|
|
2478
2748
|
);
|
|
2479
2749
|
}
|
|
@@ -2629,12 +2899,13 @@ function createUsersApiHandlers(config) {
|
|
|
2629
2899
|
const gid = body.groupId ?? null;
|
|
2630
2900
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
2631
2901
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
2902
|
+
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
2632
2903
|
const newUser = await userRepo().save(
|
|
2633
2904
|
userRepo().create({
|
|
2634
2905
|
name: body.name,
|
|
2635
2906
|
email: body.email,
|
|
2636
2907
|
password: null,
|
|
2637
|
-
blocked
|
|
2908
|
+
blocked,
|
|
2638
2909
|
groupId: gid,
|
|
2639
2910
|
adminAccess
|
|
2640
2911
|
})
|
|
@@ -2649,7 +2920,14 @@ function createUsersApiHandlers(config) {
|
|
|
2649
2920
|
inviteLink,
|
|
2650
2921
|
newUser.name ?? ""
|
|
2651
2922
|
);
|
|
2652
|
-
return json(
|
|
2923
|
+
return json(
|
|
2924
|
+
{
|
|
2925
|
+
message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
|
|
2926
|
+
user: newUser,
|
|
2927
|
+
inviteLink
|
|
2928
|
+
},
|
|
2929
|
+
{ status: 201 }
|
|
2930
|
+
);
|
|
2653
2931
|
} catch {
|
|
2654
2932
|
return json({ error: "Server Error" }, { status: 500 });
|
|
2655
2933
|
}
|
|
@@ -2951,7 +3229,10 @@ function createChatHandlers(config) {
|
|
|
2951
3229
|
const existing = await repo.findOne({ where: { email } });
|
|
2952
3230
|
let contact;
|
|
2953
3231
|
if (!existing) {
|
|
2954
|
-
|
|
3232
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
3233
|
+
contact = await repo.save(
|
|
3234
|
+
repo.create({ name, email, phone, createdAt, updatedAt: createdAt })
|
|
3235
|
+
);
|
|
2955
3236
|
} else {
|
|
2956
3237
|
const row = existing;
|
|
2957
3238
|
if (row.deleted) {
|
|
@@ -7964,18 +8245,19 @@ function createStorefrontApiHandler(config) {
|
|
|
7964
8245
|
const contactOrErr = await getContactForAddresses();
|
|
7965
8246
|
if (contactOrErr instanceof Response) return contactOrErr;
|
|
7966
8247
|
const b = await req.json().catch(() => ({}));
|
|
7967
|
-
const
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
);
|
|
8248
|
+
const row = {
|
|
8249
|
+
contactId: contactOrErr.contactId,
|
|
8250
|
+
tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
|
|
8251
|
+
line1: typeof b.line1 === "string" ? b.line1 : "",
|
|
8252
|
+
line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
|
|
8253
|
+
city: typeof b.city === "string" ? b.city : "",
|
|
8254
|
+
state: typeof b.state === "string" ? b.state : "",
|
|
8255
|
+
postalCode: typeof b.postalCode === "string" ? b.postalCode : "",
|
|
8256
|
+
country: typeof b.country === "string" ? b.country : ""
|
|
8257
|
+
};
|
|
8258
|
+
const addrErr = validateAndNormalizeAddressRow(row);
|
|
8259
|
+
if (addrErr) return json({ error: addrErr }, { status: 400 });
|
|
8260
|
+
const created = await addressRepo().save(addressRepo().create(row));
|
|
7979
8261
|
return json(serializeAddress2(created));
|
|
7980
8262
|
}
|
|
7981
8263
|
if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
|
|
@@ -7994,7 +8276,16 @@ function createStorefrontApiHandler(config) {
|
|
|
7994
8276
|
if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
|
|
7995
8277
|
if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
|
|
7996
8278
|
if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
|
|
7997
|
-
if (Object.keys(updates).length)
|
|
8279
|
+
if (Object.keys(updates).length) {
|
|
8280
|
+
const merged = { ...existing, ...updates };
|
|
8281
|
+
if (merged.tag === "") merged.tag = null;
|
|
8282
|
+
const addrErr = validateAndNormalizeAddressRow(merged);
|
|
8283
|
+
if (addrErr) return json({ error: addrErr }, { status: 400 });
|
|
8284
|
+
for (const k of Object.keys(updates)) {
|
|
8285
|
+
if (k in merged) updates[k] = merged[k];
|
|
8286
|
+
}
|
|
8287
|
+
await addressRepo().update(id, updates);
|
|
8288
|
+
}
|
|
7998
8289
|
const updated = await addressRepo().findOne({ where: { id } });
|
|
7999
8290
|
return json(serializeAddress2(updated));
|
|
8000
8291
|
}
|