@infuro/cms-core 1.0.23 → 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 +2595 -1681
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +29 -7
- package/dist/admin.d.ts +29 -7
- package/dist/admin.js +2585 -1668
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +183 -18
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +183 -18
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +185 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +185 -18
- 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) {
|
|
@@ -799,6 +857,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
799
857
|
if (statusFilter) productWhere.status = statusFilter;
|
|
800
858
|
if (inventory === "in_stock") productWhere.quantity = (0, import_typeorm.MoreThan)(0);
|
|
801
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
|
+
}
|
|
802
871
|
if (search && typeof search === "string" && search.trim()) {
|
|
803
872
|
productWhere.name = (0, import_typeorm.ILike)(`%${search.trim()}%`);
|
|
804
873
|
}
|
|
@@ -904,7 +973,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
904
973
|
if (search) {
|
|
905
974
|
where = buildSearchWhereClause(repo, search);
|
|
906
975
|
}
|
|
907
|
-
const intFilterKeys = ["productId", "attributeId", "taxId"];
|
|
976
|
+
const intFilterKeys = ["productId", "attributeId", "taxId", "brandId", "categoryId", "collectionId", "parentId"];
|
|
908
977
|
const extraWhere = {};
|
|
909
978
|
for (const key of intFilterKeys) {
|
|
910
979
|
const v = searchParams.get(key);
|
|
@@ -913,6 +982,15 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
913
982
|
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
914
983
|
}
|
|
915
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
|
+
}
|
|
916
994
|
if (Object.keys(extraWhere).length > 0) {
|
|
917
995
|
if (Array.isArray(where)) {
|
|
918
996
|
where = where.map((w) => ({ ...w, ...extraWhere }));
|
|
@@ -1021,6 +1099,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
1021
1099
|
}
|
|
1022
1100
|
}
|
|
1023
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
|
+
}
|
|
1024
1113
|
sanitizeBodyForEntity(repo, persistBody);
|
|
1025
1114
|
let created;
|
|
1026
1115
|
try {
|
|
@@ -1340,8 +1429,54 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1340
1429
|
const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
|
|
1341
1430
|
if (resource === "media") {
|
|
1342
1431
|
const u = updatePayload;
|
|
1343
|
-
delete u.parentId;
|
|
1344
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
|
+
}
|
|
1345
1480
|
}
|
|
1346
1481
|
if (resource === "products") {
|
|
1347
1482
|
const currentRow = await repo.findOne({
|
|
@@ -1360,6 +1495,26 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1360
1495
|
updatePayload.sku = effSku;
|
|
1361
1496
|
}
|
|
1362
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
|
+
}
|
|
1517
|
+
}
|
|
1363
1518
|
if (Object.keys(updatePayload).length > 0) {
|
|
1364
1519
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
1365
1520
|
await repo.update(numericId, updatePayload);
|
|
@@ -1713,11 +1868,11 @@ async function readBufferFromPublicUrl(url) {
|
|
|
1713
1868
|
throw new Error("Unsupported media URL");
|
|
1714
1869
|
}
|
|
1715
1870
|
function sanitizeZipPath(entryName) {
|
|
1716
|
-
const
|
|
1717
|
-
for (const seg of
|
|
1871
|
+
const norm2 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
1872
|
+
for (const seg of norm2) {
|
|
1718
1873
|
if (seg === ".." || seg === ".") return null;
|
|
1719
1874
|
}
|
|
1720
|
-
return
|
|
1875
|
+
return norm2;
|
|
1721
1876
|
}
|
|
1722
1877
|
function shouldSkipEntry(parts) {
|
|
1723
1878
|
if (parts[0] === "__MACOSX") return true;
|
|
@@ -8090,18 +8245,19 @@ function createStorefrontApiHandler(config) {
|
|
|
8090
8245
|
const contactOrErr = await getContactForAddresses();
|
|
8091
8246
|
if (contactOrErr instanceof Response) return contactOrErr;
|
|
8092
8247
|
const b = await req.json().catch(() => ({}));
|
|
8093
|
-
const
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
);
|
|
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));
|
|
8105
8261
|
return json(serializeAddress2(created));
|
|
8106
8262
|
}
|
|
8107
8263
|
if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
|
|
@@ -8120,7 +8276,16 @@ function createStorefrontApiHandler(config) {
|
|
|
8120
8276
|
if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
|
|
8121
8277
|
if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
|
|
8122
8278
|
if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
|
|
8123
|
-
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
|
+
}
|
|
8124
8289
|
const updated = await addressRepo().findOne({ where: { id } });
|
|
8125
8290
|
return json(serializeAddress2(updated));
|
|
8126
8291
|
}
|