@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.js
CHANGED
|
@@ -406,7 +406,7 @@ var init_paid_order_erp = __esm({
|
|
|
406
406
|
});
|
|
407
407
|
|
|
408
408
|
// src/api/crud.ts
|
|
409
|
-
import {
|
|
409
|
+
import { ILike, MoreThan, Not } from "typeorm";
|
|
410
410
|
|
|
411
411
|
// src/plugins/erp/erp-contact-sync.ts
|
|
412
412
|
init_erp_queue();
|
|
@@ -476,6 +476,64 @@ async function queueErpProductUpsertIfEnabled(cms, dataSource, entityMap, produc
|
|
|
476
476
|
}
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
+
// src/lib/address-geo-validation.ts
|
|
480
|
+
import { Country, State, City } from "country-state-city";
|
|
481
|
+
function norm(s) {
|
|
482
|
+
return typeof s === "string" ? s.trim() : "";
|
|
483
|
+
}
|
|
484
|
+
function resolveCountry(input) {
|
|
485
|
+
const t = input.trim();
|
|
486
|
+
if (!t) return void 0;
|
|
487
|
+
if (t.length === 2) {
|
|
488
|
+
const byCode = Country.getCountryByCode(t.toUpperCase());
|
|
489
|
+
if (byCode) return byCode;
|
|
490
|
+
}
|
|
491
|
+
const lower = t.toLowerCase();
|
|
492
|
+
return Country.getAllCountries().find((c) => c.name.toLowerCase() === lower);
|
|
493
|
+
}
|
|
494
|
+
function resolveState(countryIso, input) {
|
|
495
|
+
const t = input.trim();
|
|
496
|
+
if (!t || !countryIso) return void 0;
|
|
497
|
+
const states = State.getStatesOfCountry(countryIso);
|
|
498
|
+
const lower = t.toLowerCase();
|
|
499
|
+
return states.find((s) => s.isoCode.toLowerCase() === t.toLowerCase() || s.name.toLowerCase() === lower);
|
|
500
|
+
}
|
|
501
|
+
function resolveCity(countryIso, stateIso, input) {
|
|
502
|
+
const t = input.trim();
|
|
503
|
+
if (!t || !countryIso || !stateIso) return void 0;
|
|
504
|
+
const lower = t.toLowerCase();
|
|
505
|
+
const cities = City.getCitiesOfState(countryIso, stateIso);
|
|
506
|
+
return cities.find((c) => c.name.toLowerCase() === lower);
|
|
507
|
+
}
|
|
508
|
+
function assertValidAddressHierarchy(country, state, city) {
|
|
509
|
+
const c = resolveCountry(country);
|
|
510
|
+
if (!c) return { ok: false, error: "Invalid or unknown country." };
|
|
511
|
+
const st = resolveState(c.isoCode, state);
|
|
512
|
+
if (!st) return { ok: false, error: "State or province does not match the selected country." };
|
|
513
|
+
const ct = resolveCity(c.isoCode, st.isoCode, city);
|
|
514
|
+
if (!ct) return { ok: false, error: "City does not match the selected state." };
|
|
515
|
+
return { ok: true, country: c.name, state: st.name, city: ct.name };
|
|
516
|
+
}
|
|
517
|
+
function validateAndNormalizeAddressRow(row) {
|
|
518
|
+
const line1 = norm(row.line1);
|
|
519
|
+
const postalCode = norm(row.postalCode);
|
|
520
|
+
const countryIn = norm(row.country);
|
|
521
|
+
const stateIn = norm(row.state);
|
|
522
|
+
const cityIn = norm(row.city);
|
|
523
|
+
if (!line1) return "Street address (line 1) is required.";
|
|
524
|
+
if (!postalCode) return "Postal code is required.";
|
|
525
|
+
if (!countryIn || !stateIn || !cityIn) return "Country, state, and city are required.";
|
|
526
|
+
const geo = assertValidAddressHierarchy(countryIn, stateIn, cityIn);
|
|
527
|
+
if (!geo.ok) return geo.error;
|
|
528
|
+
row.line1 = line1;
|
|
529
|
+
row.line2 = norm(row.line2) || null;
|
|
530
|
+
row.postalCode = postalCode;
|
|
531
|
+
row.country = geo.country;
|
|
532
|
+
row.state = geo.state;
|
|
533
|
+
row.city = geo.city;
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
|
|
479
537
|
// src/api/crud.ts
|
|
480
538
|
var CRUD_LOG = "[cms-crud]";
|
|
481
539
|
function logCrudClientError(op, detail) {
|
|
@@ -484,6 +542,40 @@ function logCrudClientError(op, detail) {
|
|
|
484
542
|
function logCrudServerError(op, detail) {
|
|
485
543
|
console.error(CRUD_LOG, op, detail);
|
|
486
544
|
}
|
|
545
|
+
async function aggregateMediaFolderFileSizes(dataSource, folderIds) {
|
|
546
|
+
const map = /* @__PURE__ */ new Map();
|
|
547
|
+
if (folderIds.length === 0) return map;
|
|
548
|
+
try {
|
|
549
|
+
const rows = await dataSource.query(
|
|
550
|
+
`
|
|
551
|
+
WITH RECURSIVE walk AS (
|
|
552
|
+
SELECT m."parentId" AS root_id, m.id, m.kind, m.size
|
|
553
|
+
FROM media m
|
|
554
|
+
WHERE m."parentId" = ANY($1) AND m.deleted = false
|
|
555
|
+
UNION ALL
|
|
556
|
+
SELECT w.root_id, m.id, m.kind, m.size
|
|
557
|
+
FROM walk w
|
|
558
|
+
INNER JOIN media m ON m."parentId" = w.id AND m.deleted = false
|
|
559
|
+
)
|
|
560
|
+
SELECT root_id AS "rootId", COALESCE(SUM(size) FILTER (WHERE kind = 'file'), 0)::bigint AS "totalSize"
|
|
561
|
+
FROM walk
|
|
562
|
+
GROUP BY root_id
|
|
563
|
+
`,
|
|
564
|
+
[folderIds]
|
|
565
|
+
);
|
|
566
|
+
for (const r of rows) {
|
|
567
|
+
map.set(Number(r.rootId), Number(r.totalSize));
|
|
568
|
+
}
|
|
569
|
+
for (const id of folderIds) {
|
|
570
|
+
if (!map.has(id)) map.set(id, 0);
|
|
571
|
+
}
|
|
572
|
+
} catch (err) {
|
|
573
|
+
logCrudServerError("media folder size aggregate failed", {
|
|
574
|
+
message: err instanceof Error ? err.message : String(err)
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
return map;
|
|
578
|
+
}
|
|
487
579
|
var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
|
|
488
580
|
"date",
|
|
489
581
|
"datetime",
|
|
@@ -548,6 +640,16 @@ function mergeDeletedFalseWhere(repo, where) {
|
|
|
548
640
|
}
|
|
549
641
|
return Object.keys(where).length > 0 ? { ...where, ...d } : d;
|
|
550
642
|
}
|
|
643
|
+
function normalizeProductSku(value) {
|
|
644
|
+
if (value == null) return null;
|
|
645
|
+
const s = String(value).trim();
|
|
646
|
+
return s === "" ? null : s;
|
|
647
|
+
}
|
|
648
|
+
async function assertProductSkuUnique(repo, sku, excludeId) {
|
|
649
|
+
const where = excludeId != null ? { sku, deleted: false, id: Not(excludeId) } : { sku, deleted: false };
|
|
650
|
+
const row = await repo.findOne({ where });
|
|
651
|
+
return row == null;
|
|
652
|
+
}
|
|
551
653
|
function buildSoftDeletePayload(meta, deletedBy) {
|
|
552
654
|
const payload = { deleted: true };
|
|
553
655
|
if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
|
|
@@ -702,6 +804,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
702
804
|
if (statusFilter) productWhere.status = statusFilter;
|
|
703
805
|
if (inventory === "in_stock") productWhere.quantity = MoreThan(0);
|
|
704
806
|
if (inventory === "out_of_stock") productWhere.quantity = 0;
|
|
807
|
+
for (const key of ["brandId", "categoryId", "collectionId"]) {
|
|
808
|
+
const raw = searchParams.get(key)?.trim();
|
|
809
|
+
if (raw) {
|
|
810
|
+
const n = Number(raw);
|
|
811
|
+
if (Number.isFinite(n)) productWhere[key] = n;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
const featuredRaw = searchParams.get("featured")?.trim();
|
|
815
|
+
if (featuredRaw === "true" || featuredRaw === "false") {
|
|
816
|
+
productWhere.featured = featuredRaw === "true";
|
|
817
|
+
}
|
|
705
818
|
if (search && typeof search === "string" && search.trim()) {
|
|
706
819
|
productWhere.name = ILike(`%${search.trim()}%`);
|
|
707
820
|
}
|
|
@@ -770,19 +883,36 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
770
883
|
qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
|
|
771
884
|
}
|
|
772
885
|
if (typeFilter) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
})
|
|
779
|
-
)
|
|
886
|
+
if (typeFilter === "folder") {
|
|
887
|
+
qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
|
|
888
|
+
} else if (typeFilter === "file") {
|
|
889
|
+
qb.andWhere("m.kind = :fileKind", { fileKind: "file" });
|
|
890
|
+
} else if (typeFilter === "image") {
|
|
891
|
+
qb.andWhere("m.mimeType LIKE :imageMimeType", { imageMimeType: "image/%" });
|
|
892
|
+
} else if (typeFilter === "video") {
|
|
893
|
+
qb.andWhere("m.mimeType LIKE :videoMimeType", { videoMimeType: "video/%" });
|
|
894
|
+
} else if (typeFilter === "audio") {
|
|
895
|
+
qb.andWhere("m.mimeType LIKE :audioMimeType", { audioMimeType: "audio/%" });
|
|
896
|
+
} else if (typeFilter === "Document") {
|
|
897
|
+
qb.andWhere("m.mimeType LIKE :documentMimeType", { documentMimeType: "application/pdf" });
|
|
898
|
+
} else if (typeFilter === "application") {
|
|
899
|
+
qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
|
|
900
|
+
}
|
|
780
901
|
}
|
|
781
902
|
const allowedSort = ["filename", "createdAt", "id"];
|
|
782
903
|
const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
|
|
783
904
|
const so = sortOrder === "DESC" ? "DESC" : "ASC";
|
|
784
|
-
qb.orderBy(
|
|
785
|
-
const [
|
|
905
|
+
qb.orderBy(`m.${sf}`, so).skip(skip).take(limit);
|
|
906
|
+
const [rows, total2] = await qb.getManyAndCount();
|
|
907
|
+
const mediaRows = rows;
|
|
908
|
+
const folderIds = mediaRows.filter((m) => m.kind === "folder").map((m) => m.id);
|
|
909
|
+
let data2 = rows;
|
|
910
|
+
if (folderIds.length > 0) {
|
|
911
|
+
const sizeMap = await aggregateMediaFolderFileSizes(dataSource, folderIds);
|
|
912
|
+
data2 = mediaRows.map(
|
|
913
|
+
(m) => m.kind === "folder" ? { ...m, size: sizeMap.get(m.id) ?? 0 } : m
|
|
914
|
+
);
|
|
915
|
+
}
|
|
786
916
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
787
917
|
}
|
|
788
918
|
const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
|
|
@@ -790,7 +920,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
790
920
|
if (search) {
|
|
791
921
|
where = buildSearchWhereClause(repo, search);
|
|
792
922
|
}
|
|
793
|
-
const intFilterKeys = ["productId", "attributeId", "taxId"];
|
|
923
|
+
const intFilterKeys = ["productId", "attributeId", "taxId", "brandId", "categoryId", "collectionId", "parentId"];
|
|
794
924
|
const extraWhere = {};
|
|
795
925
|
for (const key of intFilterKeys) {
|
|
796
926
|
const v = searchParams.get(key);
|
|
@@ -799,6 +929,15 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
799
929
|
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
800
930
|
}
|
|
801
931
|
}
|
|
932
|
+
for (const col of repo.metadata.columns) {
|
|
933
|
+
if (String(col.type) !== "boolean") continue;
|
|
934
|
+
const name = col.propertyName;
|
|
935
|
+
if (!columnNames.has(name)) continue;
|
|
936
|
+
const raw = searchParams.get(name)?.trim();
|
|
937
|
+
if (raw === "true" || raw === "false") {
|
|
938
|
+
extraWhere[name] = raw === "true";
|
|
939
|
+
}
|
|
940
|
+
}
|
|
802
941
|
if (Object.keys(extraWhere).length > 0) {
|
|
803
942
|
if (Array.isArray(where)) {
|
|
804
943
|
where = where.map((w) => ({ ...w, ...extraWhere }));
|
|
@@ -893,6 +1032,31 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
893
1032
|
});
|
|
894
1033
|
return json({ error: "Invalid request payload" }, { status: 400 });
|
|
895
1034
|
}
|
|
1035
|
+
if (resource === "products") {
|
|
1036
|
+
if ("sku" in persistBody) {
|
|
1037
|
+
const skuNorm = normalizeProductSku(persistBody.sku);
|
|
1038
|
+
if (skuNorm) {
|
|
1039
|
+
const ok = await assertProductSkuUnique(repo, skuNorm);
|
|
1040
|
+
if (!ok) {
|
|
1041
|
+
return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
|
|
1042
|
+
}
|
|
1043
|
+
persistBody.sku = skuNorm;
|
|
1044
|
+
} else {
|
|
1045
|
+
persistBody.sku = null;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (resource === "addresses") {
|
|
1050
|
+
const cid = Number(persistBody.contactId);
|
|
1051
|
+
if (!Number.isFinite(cid)) {
|
|
1052
|
+
return json({ error: "Valid contactId is required." }, { status: 400 });
|
|
1053
|
+
}
|
|
1054
|
+
if (persistBody.tag === "") persistBody.tag = null;
|
|
1055
|
+
const addrErr = validateAndNormalizeAddressRow(persistBody);
|
|
1056
|
+
if (addrErr) {
|
|
1057
|
+
return json({ error: addrErr }, { status: 400 });
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
896
1060
|
sanitizeBodyForEntity(repo, persistBody);
|
|
897
1061
|
let created;
|
|
898
1062
|
try {
|
|
@@ -1087,7 +1251,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1087
1251
|
relations: ["order", "order.contact", "contact"]
|
|
1088
1252
|
});
|
|
1089
1253
|
if (!payment) return json({ message: "Not found" }, { status: 404 });
|
|
1090
|
-
|
|
1254
|
+
const p = payment;
|
|
1255
|
+
const order = p.order;
|
|
1256
|
+
const orderContact = order?.contact;
|
|
1257
|
+
const contact = p.contact;
|
|
1258
|
+
const customer = orderContact ?? contact;
|
|
1259
|
+
return json({
|
|
1260
|
+
...p,
|
|
1261
|
+
order: order ? {
|
|
1262
|
+
id: order.id,
|
|
1263
|
+
orderNumber: order.orderNumber,
|
|
1264
|
+
contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null
|
|
1265
|
+
} : null,
|
|
1266
|
+
contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
|
|
1267
|
+
});
|
|
1091
1268
|
}
|
|
1092
1269
|
if (resource === "blogs") {
|
|
1093
1270
|
const blog = await repo.findOne({
|
|
@@ -1199,8 +1376,91 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1199
1376
|
const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
|
|
1200
1377
|
if (resource === "media") {
|
|
1201
1378
|
const u = updatePayload;
|
|
1202
|
-
delete u.parentId;
|
|
1203
1379
|
delete u.kind;
|
|
1380
|
+
if (rawBody && typeof rawBody === "object" && "parentId" in rawBody) {
|
|
1381
|
+
let pid = null;
|
|
1382
|
+
const p = rawBody.parentId;
|
|
1383
|
+
if (p != null && p !== "") {
|
|
1384
|
+
const n = Number(p);
|
|
1385
|
+
if (!Number.isFinite(n)) {
|
|
1386
|
+
return json({ error: "Invalid parentId" }, { status: 400 });
|
|
1387
|
+
}
|
|
1388
|
+
pid = n;
|
|
1389
|
+
}
|
|
1390
|
+
if (pid != null) {
|
|
1391
|
+
const parent = await repo.findOne({
|
|
1392
|
+
where: { id: pid, deleted: false }
|
|
1393
|
+
});
|
|
1394
|
+
if (!parent || parent.kind !== "folder") {
|
|
1395
|
+
return json({ error: "parent must be a folder" }, { status: 400 });
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
const row = await repo.findOne({
|
|
1399
|
+
where: { id: numericId, deleted: false }
|
|
1400
|
+
});
|
|
1401
|
+
if (!row) return json({ message: "Not found" }, { status: 404 });
|
|
1402
|
+
if (pid === numericId) {
|
|
1403
|
+
return json({ error: "Invalid parentId" }, { status: 400 });
|
|
1404
|
+
}
|
|
1405
|
+
if (row.kind === "folder" && pid != null) {
|
|
1406
|
+
let walk = pid;
|
|
1407
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1408
|
+
while (walk != null) {
|
|
1409
|
+
if (walk === numericId) {
|
|
1410
|
+
return json(
|
|
1411
|
+
{ error: "Cannot move a folder into itself or a descendant folder" },
|
|
1412
|
+
{ status: 400 }
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
if (seen.has(walk)) break;
|
|
1416
|
+
seen.add(walk);
|
|
1417
|
+
const anc = await repo.findOne({
|
|
1418
|
+
where: { id: walk, deleted: false }
|
|
1419
|
+
});
|
|
1420
|
+
walk = anc ? anc.parentId ?? null : null;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
u.parentId = pid;
|
|
1424
|
+
} else {
|
|
1425
|
+
delete u.parentId;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
if (resource === "products") {
|
|
1429
|
+
const currentRow = await repo.findOne({
|
|
1430
|
+
where: { id: numericId, deleted: false }
|
|
1431
|
+
});
|
|
1432
|
+
if (!currentRow) return json({ message: "Not found" }, { status: 404 });
|
|
1433
|
+
const merged = { ...currentRow, ...updatePayload };
|
|
1434
|
+
const effSku = normalizeProductSku(merged.sku);
|
|
1435
|
+
if (effSku) {
|
|
1436
|
+
const ok = await assertProductSkuUnique(repo, effSku, numericId);
|
|
1437
|
+
if (!ok) {
|
|
1438
|
+
return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if ("sku" in updatePayload) {
|
|
1442
|
+
updatePayload.sku = effSku;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (resource === "addresses" && Object.keys(updatePayload).length > 0) {
|
|
1446
|
+
const currentRow = await repo.findOne({
|
|
1447
|
+
where: { id: numericId }
|
|
1448
|
+
});
|
|
1449
|
+
if (!currentRow) return json({ message: "Not found" }, { status: 404 });
|
|
1450
|
+
const merged = {
|
|
1451
|
+
...currentRow,
|
|
1452
|
+
...updatePayload
|
|
1453
|
+
};
|
|
1454
|
+
if (merged.tag === "") merged.tag = null;
|
|
1455
|
+
const addrErr = validateAndNormalizeAddressRow(merged);
|
|
1456
|
+
if (addrErr) {
|
|
1457
|
+
return json({ error: addrErr }, { status: 400 });
|
|
1458
|
+
}
|
|
1459
|
+
for (const k of Object.keys(updatePayload)) {
|
|
1460
|
+
if (k in merged) {
|
|
1461
|
+
updatePayload[k] = merged[k];
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1204
1464
|
}
|
|
1205
1465
|
if (Object.keys(updatePayload).length > 0) {
|
|
1206
1466
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
@@ -1231,6 +1491,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1231
1491
|
where: { id: numericId, deleted: false }
|
|
1232
1492
|
});
|
|
1233
1493
|
if (!existing) return json({ message: "Not found" }, { status: 404 });
|
|
1494
|
+
if (resource === "contacts") {
|
|
1495
|
+
const result2 = await repo.delete(numericId);
|
|
1496
|
+
if (result2.affected === 0) return json({ message: "Not found" }, { status: 404 });
|
|
1497
|
+
return json({ message: "Deleted successfully" }, { status: 200 });
|
|
1498
|
+
}
|
|
1234
1499
|
let deletedBy = null;
|
|
1235
1500
|
if (getDeletedByUserId) {
|
|
1236
1501
|
try {
|
|
@@ -1550,11 +1815,11 @@ async function readBufferFromPublicUrl(url) {
|
|
|
1550
1815
|
throw new Error("Unsupported media URL");
|
|
1551
1816
|
}
|
|
1552
1817
|
function sanitizeZipPath(entryName) {
|
|
1553
|
-
const
|
|
1554
|
-
for (const seg of
|
|
1818
|
+
const norm2 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
1819
|
+
for (const seg of norm2) {
|
|
1555
1820
|
if (seg === ".." || seg === ".") return null;
|
|
1556
1821
|
}
|
|
1557
|
-
return
|
|
1822
|
+
return norm2;
|
|
1558
1823
|
}
|
|
1559
1824
|
function shouldSkipEntry(parts) {
|
|
1560
1825
|
if (parts[0] === "__MACOSX") return true;
|
|
@@ -1778,12 +2043,14 @@ function createDashboardStatsHandler(config) {
|
|
|
1778
2043
|
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
|
|
1779
2044
|
const repo = (name) => entityMap[name] ? dataSource.getRepository(entityMap[name]) : void 0;
|
|
1780
2045
|
const [contactsCount, formsCount, formSubmissionsCount, usersCount, blogsCount, recentContacts, recentSubmissions, contactTypeRows] = await Promise.all([
|
|
1781
|
-
repo("contacts")?.count() ?? 0,
|
|
2046
|
+
repo("contacts")?.count({ where: { deleted: false } }) ?? 0,
|
|
1782
2047
|
repo("forms")?.count({ where: { deleted: false } }) ?? 0,
|
|
1783
2048
|
repo("form_submissions")?.count() ?? 0,
|
|
1784
2049
|
repo("users")?.count({ where: { deleted: false } }) ?? 0,
|
|
1785
2050
|
repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
|
|
1786
|
-
repo("contacts")?.count({
|
|
2051
|
+
repo("contacts")?.count({
|
|
2052
|
+
where: { deleted: false, createdAt: MoreThanOrEqual(sevenDaysAgo) }
|
|
2053
|
+
}) ?? 0,
|
|
1787
2054
|
repo("form_submissions")?.count({ where: { createdAt: MoreThanOrEqual(sevenDaysAgo) } }) ?? 0,
|
|
1788
2055
|
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() ?? []
|
|
1789
2056
|
]);
|
|
@@ -2416,11 +2683,14 @@ function createFormSubmissionHandler(config) {
|
|
|
2416
2683
|
const contactRepo = dataSource.getRepository(entityMap.contacts);
|
|
2417
2684
|
let contact = await contactRepo.findOne({ where: { email: contactData.email } });
|
|
2418
2685
|
if (!contact) {
|
|
2686
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
2419
2687
|
contact = await contactRepo.save(
|
|
2420
2688
|
contactRepo.create({
|
|
2421
2689
|
name: contactData.name,
|
|
2422
2690
|
email: contactData.email,
|
|
2423
|
-
phone: contactData.phone
|
|
2691
|
+
phone: contactData.phone,
|
|
2692
|
+
createdAt,
|
|
2693
|
+
updatedAt: createdAt
|
|
2424
2694
|
})
|
|
2425
2695
|
);
|
|
2426
2696
|
}
|
|
@@ -2576,12 +2846,13 @@ function createUsersApiHandlers(config) {
|
|
|
2576
2846
|
const gid = body.groupId ?? null;
|
|
2577
2847
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
2578
2848
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
2849
|
+
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
2579
2850
|
const newUser = await userRepo().save(
|
|
2580
2851
|
userRepo().create({
|
|
2581
2852
|
name: body.name,
|
|
2582
2853
|
email: body.email,
|
|
2583
2854
|
password: null,
|
|
2584
|
-
blocked
|
|
2855
|
+
blocked,
|
|
2585
2856
|
groupId: gid,
|
|
2586
2857
|
adminAccess
|
|
2587
2858
|
})
|
|
@@ -2596,7 +2867,14 @@ function createUsersApiHandlers(config) {
|
|
|
2596
2867
|
inviteLink,
|
|
2597
2868
|
newUser.name ?? ""
|
|
2598
2869
|
);
|
|
2599
|
-
return json(
|
|
2870
|
+
return json(
|
|
2871
|
+
{
|
|
2872
|
+
message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
|
|
2873
|
+
user: newUser,
|
|
2874
|
+
inviteLink
|
|
2875
|
+
},
|
|
2876
|
+
{ status: 201 }
|
|
2877
|
+
);
|
|
2600
2878
|
} catch {
|
|
2601
2879
|
return json({ error: "Server Error" }, { status: 500 });
|
|
2602
2880
|
}
|
|
@@ -2898,7 +3176,10 @@ function createChatHandlers(config) {
|
|
|
2898
3176
|
const existing = await repo.findOne({ where: { email } });
|
|
2899
3177
|
let contact;
|
|
2900
3178
|
if (!existing) {
|
|
2901
|
-
|
|
3179
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
3180
|
+
contact = await repo.save(
|
|
3181
|
+
repo.create({ name, email, phone, createdAt, updatedAt: createdAt })
|
|
3182
|
+
);
|
|
2902
3183
|
} else {
|
|
2903
3184
|
const row = existing;
|
|
2904
3185
|
if (row.deleted) {
|
|
@@ -7920,18 +8201,19 @@ function createStorefrontApiHandler(config) {
|
|
|
7920
8201
|
const contactOrErr = await getContactForAddresses();
|
|
7921
8202
|
if (contactOrErr instanceof Response) return contactOrErr;
|
|
7922
8203
|
const b = await req.json().catch(() => ({}));
|
|
7923
|
-
const
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
);
|
|
8204
|
+
const row = {
|
|
8205
|
+
contactId: contactOrErr.contactId,
|
|
8206
|
+
tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
|
|
8207
|
+
line1: typeof b.line1 === "string" ? b.line1 : "",
|
|
8208
|
+
line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
|
|
8209
|
+
city: typeof b.city === "string" ? b.city : "",
|
|
8210
|
+
state: typeof b.state === "string" ? b.state : "",
|
|
8211
|
+
postalCode: typeof b.postalCode === "string" ? b.postalCode : "",
|
|
8212
|
+
country: typeof b.country === "string" ? b.country : ""
|
|
8213
|
+
};
|
|
8214
|
+
const addrErr = validateAndNormalizeAddressRow(row);
|
|
8215
|
+
if (addrErr) return json({ error: addrErr }, { status: 400 });
|
|
8216
|
+
const created = await addressRepo().save(addressRepo().create(row));
|
|
7935
8217
|
return json(serializeAddress2(created));
|
|
7936
8218
|
}
|
|
7937
8219
|
if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
|
|
@@ -7950,7 +8232,16 @@ function createStorefrontApiHandler(config) {
|
|
|
7950
8232
|
if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
|
|
7951
8233
|
if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
|
|
7952
8234
|
if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
|
|
7953
|
-
if (Object.keys(updates).length)
|
|
8235
|
+
if (Object.keys(updates).length) {
|
|
8236
|
+
const merged = { ...existing, ...updates };
|
|
8237
|
+
if (merged.tag === "") merged.tag = null;
|
|
8238
|
+
const addrErr = validateAndNormalizeAddressRow(merged);
|
|
8239
|
+
if (addrErr) return json({ error: addrErr }, { status: 400 });
|
|
8240
|
+
for (const k of Object.keys(updates)) {
|
|
8241
|
+
if (k in merged) updates[k] = merged[k];
|
|
8242
|
+
}
|
|
8243
|
+
await addressRepo().update(id, updates);
|
|
8244
|
+
}
|
|
7954
8245
|
const updated = await addressRepo().findOne({ where: { id } });
|
|
7955
8246
|
return json(serializeAddress2(updated));
|
|
7956
8247
|
}
|