@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.js
CHANGED
|
@@ -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) {
|
|
@@ -746,6 +804,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
746
804
|
if (statusFilter) productWhere.status = statusFilter;
|
|
747
805
|
if (inventory === "in_stock") productWhere.quantity = MoreThan(0);
|
|
748
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
|
+
}
|
|
749
818
|
if (search && typeof search === "string" && search.trim()) {
|
|
750
819
|
productWhere.name = ILike(`%${search.trim()}%`);
|
|
751
820
|
}
|
|
@@ -851,7 +920,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
851
920
|
if (search) {
|
|
852
921
|
where = buildSearchWhereClause(repo, search);
|
|
853
922
|
}
|
|
854
|
-
const intFilterKeys = ["productId", "attributeId", "taxId"];
|
|
923
|
+
const intFilterKeys = ["productId", "attributeId", "taxId", "brandId", "categoryId", "collectionId", "parentId"];
|
|
855
924
|
const extraWhere = {};
|
|
856
925
|
for (const key of intFilterKeys) {
|
|
857
926
|
const v = searchParams.get(key);
|
|
@@ -860,6 +929,15 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
860
929
|
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
861
930
|
}
|
|
862
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
|
+
}
|
|
863
941
|
if (Object.keys(extraWhere).length > 0) {
|
|
864
942
|
if (Array.isArray(where)) {
|
|
865
943
|
where = where.map((w) => ({ ...w, ...extraWhere }));
|
|
@@ -968,6 +1046,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
968
1046
|
}
|
|
969
1047
|
}
|
|
970
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
|
+
}
|
|
971
1060
|
sanitizeBodyForEntity(repo, persistBody);
|
|
972
1061
|
let created;
|
|
973
1062
|
try {
|
|
@@ -1287,8 +1376,54 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1287
1376
|
const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
|
|
1288
1377
|
if (resource === "media") {
|
|
1289
1378
|
const u = updatePayload;
|
|
1290
|
-
delete u.parentId;
|
|
1291
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
|
+
}
|
|
1292
1427
|
}
|
|
1293
1428
|
if (resource === "products") {
|
|
1294
1429
|
const currentRow = await repo.findOne({
|
|
@@ -1307,6 +1442,26 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1307
1442
|
updatePayload.sku = effSku;
|
|
1308
1443
|
}
|
|
1309
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
|
+
}
|
|
1464
|
+
}
|
|
1310
1465
|
if (Object.keys(updatePayload).length > 0) {
|
|
1311
1466
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
1312
1467
|
await repo.update(numericId, updatePayload);
|
|
@@ -1660,11 +1815,11 @@ async function readBufferFromPublicUrl(url) {
|
|
|
1660
1815
|
throw new Error("Unsupported media URL");
|
|
1661
1816
|
}
|
|
1662
1817
|
function sanitizeZipPath(entryName) {
|
|
1663
|
-
const
|
|
1664
|
-
for (const seg of
|
|
1818
|
+
const norm2 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
1819
|
+
for (const seg of norm2) {
|
|
1665
1820
|
if (seg === ".." || seg === ".") return null;
|
|
1666
1821
|
}
|
|
1667
|
-
return
|
|
1822
|
+
return norm2;
|
|
1668
1823
|
}
|
|
1669
1824
|
function shouldSkipEntry(parts) {
|
|
1670
1825
|
if (parts[0] === "__MACOSX") return true;
|
|
@@ -8046,18 +8201,19 @@ function createStorefrontApiHandler(config) {
|
|
|
8046
8201
|
const contactOrErr = await getContactForAddresses();
|
|
8047
8202
|
if (contactOrErr instanceof Response) return contactOrErr;
|
|
8048
8203
|
const b = await req.json().catch(() => ({}));
|
|
8049
|
-
const
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
);
|
|
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));
|
|
8061
8217
|
return json(serializeAddress2(created));
|
|
8062
8218
|
}
|
|
8063
8219
|
if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
|
|
@@ -8076,7 +8232,16 @@ function createStorefrontApiHandler(config) {
|
|
|
8076
8232
|
if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
|
|
8077
8233
|
if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
|
|
8078
8234
|
if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
|
|
8079
|
-
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
|
+
}
|
|
8080
8245
|
const updated = await addressRepo().findOne({ where: { id } });
|
|
8081
8246
|
return json(serializeAddress2(updated));
|
|
8082
8247
|
}
|