@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/index.js CHANGED
@@ -3979,11 +3979,11 @@ async function readBufferFromPublicUrl(url) {
3979
3979
  throw new Error("Unsupported media URL");
3980
3980
  }
3981
3981
  function sanitizeZipPath(entryName) {
3982
- const norm2 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
3983
- for (const seg of norm2) {
3982
+ const norm3 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
3983
+ for (const seg of norm3) {
3984
3984
  if (seg === ".." || seg === ".") return null;
3985
3985
  }
3986
- return norm2;
3986
+ return norm3;
3987
3987
  }
3988
3988
  function shouldSkipEntry(parts) {
3989
3989
  if (parts[0] === "__MACOSX") return true;
@@ -8544,6 +8544,66 @@ function getNextAuthOptions(config) {
8544
8544
 
8545
8545
  // src/api/crud.ts
8546
8546
  import { ILike as ILike2, MoreThan as MoreThan2, Not } from "typeorm";
8547
+
8548
+ // src/lib/address-geo-validation.ts
8549
+ import { Country, State, City } from "country-state-city";
8550
+ function norm2(s) {
8551
+ return typeof s === "string" ? s.trim() : "";
8552
+ }
8553
+ function resolveCountry(input) {
8554
+ const t = input.trim();
8555
+ if (!t) return void 0;
8556
+ if (t.length === 2) {
8557
+ const byCode = Country.getCountryByCode(t.toUpperCase());
8558
+ if (byCode) return byCode;
8559
+ }
8560
+ const lower = t.toLowerCase();
8561
+ return Country.getAllCountries().find((c) => c.name.toLowerCase() === lower);
8562
+ }
8563
+ function resolveState(countryIso, input) {
8564
+ const t = input.trim();
8565
+ if (!t || !countryIso) return void 0;
8566
+ const states = State.getStatesOfCountry(countryIso);
8567
+ const lower = t.toLowerCase();
8568
+ return states.find((s) => s.isoCode.toLowerCase() === t.toLowerCase() || s.name.toLowerCase() === lower);
8569
+ }
8570
+ function resolveCity(countryIso, stateIso, input) {
8571
+ const t = input.trim();
8572
+ if (!t || !countryIso || !stateIso) return void 0;
8573
+ const lower = t.toLowerCase();
8574
+ const cities = City.getCitiesOfState(countryIso, stateIso);
8575
+ return cities.find((c) => c.name.toLowerCase() === lower);
8576
+ }
8577
+ function assertValidAddressHierarchy(country, state, city) {
8578
+ const c = resolveCountry(country);
8579
+ if (!c) return { ok: false, error: "Invalid or unknown country." };
8580
+ const st = resolveState(c.isoCode, state);
8581
+ if (!st) return { ok: false, error: "State or province does not match the selected country." };
8582
+ const ct = resolveCity(c.isoCode, st.isoCode, city);
8583
+ if (!ct) return { ok: false, error: "City does not match the selected state." };
8584
+ return { ok: true, country: c.name, state: st.name, city: ct.name };
8585
+ }
8586
+ function validateAndNormalizeAddressRow(row) {
8587
+ const line1 = norm2(row.line1);
8588
+ const postalCode = norm2(row.postalCode);
8589
+ const countryIn = norm2(row.country);
8590
+ const stateIn = norm2(row.state);
8591
+ const cityIn = norm2(row.city);
8592
+ if (!line1) return "Street address (line 1) is required.";
8593
+ if (!postalCode) return "Postal code is required.";
8594
+ if (!countryIn || !stateIn || !cityIn) return "Country, state, and city are required.";
8595
+ const geo = assertValidAddressHierarchy(countryIn, stateIn, cityIn);
8596
+ if (!geo.ok) return geo.error;
8597
+ row.line1 = line1;
8598
+ row.line2 = norm2(row.line2) || null;
8599
+ row.postalCode = postalCode;
8600
+ row.country = geo.country;
8601
+ row.state = geo.state;
8602
+ row.city = geo.city;
8603
+ return null;
8604
+ }
8605
+
8606
+ // src/api/crud.ts
8547
8607
  var CRUD_LOG = "[cms-crud]";
8548
8608
  function logCrudClientError(op, detail) {
8549
8609
  console.warn(CRUD_LOG, op, detail);
@@ -8813,6 +8873,17 @@ function createCrudHandler(dataSource, entityMap, options) {
8813
8873
  if (statusFilter) productWhere.status = statusFilter;
8814
8874
  if (inventory === "in_stock") productWhere.quantity = MoreThan2(0);
8815
8875
  if (inventory === "out_of_stock") productWhere.quantity = 0;
8876
+ for (const key of ["brandId", "categoryId", "collectionId"]) {
8877
+ const raw = searchParams.get(key)?.trim();
8878
+ if (raw) {
8879
+ const n = Number(raw);
8880
+ if (Number.isFinite(n)) productWhere[key] = n;
8881
+ }
8882
+ }
8883
+ const featuredRaw = searchParams.get("featured")?.trim();
8884
+ if (featuredRaw === "true" || featuredRaw === "false") {
8885
+ productWhere.featured = featuredRaw === "true";
8886
+ }
8816
8887
  if (search && typeof search === "string" && search.trim()) {
8817
8888
  productWhere.name = ILike2(`%${search.trim()}%`);
8818
8889
  }
@@ -8918,7 +8989,7 @@ function createCrudHandler(dataSource, entityMap, options) {
8918
8989
  if (search) {
8919
8990
  where = buildSearchWhereClause(repo, search);
8920
8991
  }
8921
- const intFilterKeys = ["productId", "attributeId", "taxId"];
8992
+ const intFilterKeys = ["productId", "attributeId", "taxId", "brandId", "categoryId", "collectionId", "parentId"];
8922
8993
  const extraWhere = {};
8923
8994
  for (const key of intFilterKeys) {
8924
8995
  const v = searchParams.get(key);
@@ -8927,6 +8998,15 @@ function createCrudHandler(dataSource, entityMap, options) {
8927
8998
  if (Number.isFinite(n)) extraWhere[key] = n;
8928
8999
  }
8929
9000
  }
9001
+ for (const col of repo.metadata.columns) {
9002
+ if (String(col.type) !== "boolean") continue;
9003
+ const name = col.propertyName;
9004
+ if (!columnNames.has(name)) continue;
9005
+ const raw = searchParams.get(name)?.trim();
9006
+ if (raw === "true" || raw === "false") {
9007
+ extraWhere[name] = raw === "true";
9008
+ }
9009
+ }
8930
9010
  if (Object.keys(extraWhere).length > 0) {
8931
9011
  if (Array.isArray(where)) {
8932
9012
  where = where.map((w) => ({ ...w, ...extraWhere }));
@@ -9035,6 +9115,17 @@ function createCrudHandler(dataSource, entityMap, options) {
9035
9115
  }
9036
9116
  }
9037
9117
  }
9118
+ if (resource === "addresses") {
9119
+ const cid = Number(persistBody.contactId);
9120
+ if (!Number.isFinite(cid)) {
9121
+ return json({ error: "Valid contactId is required." }, { status: 400 });
9122
+ }
9123
+ if (persistBody.tag === "") persistBody.tag = null;
9124
+ const addrErr = validateAndNormalizeAddressRow(persistBody);
9125
+ if (addrErr) {
9126
+ return json({ error: addrErr }, { status: 400 });
9127
+ }
9128
+ }
9038
9129
  sanitizeBodyForEntity(repo, persistBody);
9039
9130
  let created;
9040
9131
  try {
@@ -9354,8 +9445,54 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9354
9445
  const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
9355
9446
  if (resource === "media") {
9356
9447
  const u = updatePayload;
9357
- delete u.parentId;
9358
9448
  delete u.kind;
9449
+ if (rawBody && typeof rawBody === "object" && "parentId" in rawBody) {
9450
+ let pid = null;
9451
+ const p = rawBody.parentId;
9452
+ if (p != null && p !== "") {
9453
+ const n = Number(p);
9454
+ if (!Number.isFinite(n)) {
9455
+ return json({ error: "Invalid parentId" }, { status: 400 });
9456
+ }
9457
+ pid = n;
9458
+ }
9459
+ if (pid != null) {
9460
+ const parent = await repo.findOne({
9461
+ where: { id: pid, deleted: false }
9462
+ });
9463
+ if (!parent || parent.kind !== "folder") {
9464
+ return json({ error: "parent must be a folder" }, { status: 400 });
9465
+ }
9466
+ }
9467
+ const row = await repo.findOne({
9468
+ where: { id: numericId, deleted: false }
9469
+ });
9470
+ if (!row) return json({ message: "Not found" }, { status: 404 });
9471
+ if (pid === numericId) {
9472
+ return json({ error: "Invalid parentId" }, { status: 400 });
9473
+ }
9474
+ if (row.kind === "folder" && pid != null) {
9475
+ let walk = pid;
9476
+ const seen = /* @__PURE__ */ new Set();
9477
+ while (walk != null) {
9478
+ if (walk === numericId) {
9479
+ return json(
9480
+ { error: "Cannot move a folder into itself or a descendant folder" },
9481
+ { status: 400 }
9482
+ );
9483
+ }
9484
+ if (seen.has(walk)) break;
9485
+ seen.add(walk);
9486
+ const anc = await repo.findOne({
9487
+ where: { id: walk, deleted: false }
9488
+ });
9489
+ walk = anc ? anc.parentId ?? null : null;
9490
+ }
9491
+ }
9492
+ u.parentId = pid;
9493
+ } else {
9494
+ delete u.parentId;
9495
+ }
9359
9496
  }
9360
9497
  if (resource === "products") {
9361
9498
  const currentRow = await repo.findOne({
@@ -9374,6 +9511,26 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9374
9511
  updatePayload.sku = effSku;
9375
9512
  }
9376
9513
  }
9514
+ if (resource === "addresses" && Object.keys(updatePayload).length > 0) {
9515
+ const currentRow = await repo.findOne({
9516
+ where: { id: numericId }
9517
+ });
9518
+ if (!currentRow) return json({ message: "Not found" }, { status: 404 });
9519
+ const merged = {
9520
+ ...currentRow,
9521
+ ...updatePayload
9522
+ };
9523
+ if (merged.tag === "") merged.tag = null;
9524
+ const addrErr = validateAndNormalizeAddressRow(merged);
9525
+ if (addrErr) {
9526
+ return json({ error: addrErr }, { status: 400 });
9527
+ }
9528
+ for (const k of Object.keys(updatePayload)) {
9529
+ if (k in merged) {
9530
+ updatePayload[k] = merged[k];
9531
+ }
9532
+ }
9533
+ }
9377
9534
  if (Object.keys(updatePayload).length > 0) {
9378
9535
  sanitizeBodyForEntity(repo, updatePayload);
9379
9536
  await repo.update(numericId, updatePayload);
@@ -11375,18 +11532,19 @@ function createStorefrontApiHandler(config) {
11375
11532
  const contactOrErr = await getContactForAddresses();
11376
11533
  if (contactOrErr instanceof Response) return contactOrErr;
11377
11534
  const b = await req.json().catch(() => ({}));
11378
- const created = await addressRepo().save(
11379
- addressRepo().create({
11380
- contactId: contactOrErr.contactId,
11381
- tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
11382
- line1: typeof b.line1 === "string" ? b.line1.trim() || null : null,
11383
- line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
11384
- city: typeof b.city === "string" ? b.city.trim() || null : null,
11385
- state: typeof b.state === "string" ? b.state.trim() || null : null,
11386
- postalCode: typeof b.postalCode === "string" ? b.postalCode.trim() || null : null,
11387
- country: typeof b.country === "string" ? b.country.trim() || null : null
11388
- })
11389
- );
11535
+ const row = {
11536
+ contactId: contactOrErr.contactId,
11537
+ tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
11538
+ line1: typeof b.line1 === "string" ? b.line1 : "",
11539
+ line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
11540
+ city: typeof b.city === "string" ? b.city : "",
11541
+ state: typeof b.state === "string" ? b.state : "",
11542
+ postalCode: typeof b.postalCode === "string" ? b.postalCode : "",
11543
+ country: typeof b.country === "string" ? b.country : ""
11544
+ };
11545
+ const addrErr = validateAndNormalizeAddressRow(row);
11546
+ if (addrErr) return json({ error: addrErr }, { status: 400 });
11547
+ const created = await addressRepo().save(addressRepo().create(row));
11390
11548
  return json(serializeAddress2(created));
11391
11549
  }
11392
11550
  if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
@@ -11405,7 +11563,16 @@ function createStorefrontApiHandler(config) {
11405
11563
  if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
11406
11564
  if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
11407
11565
  if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
11408
- if (Object.keys(updates).length) await addressRepo().update(id, updates);
11566
+ if (Object.keys(updates).length) {
11567
+ const merged = { ...existing, ...updates };
11568
+ if (merged.tag === "") merged.tag = null;
11569
+ const addrErr = validateAndNormalizeAddressRow(merged);
11570
+ if (addrErr) return json({ error: addrErr }, { status: 400 });
11571
+ for (const k of Object.keys(updates)) {
11572
+ if (k in merged) updates[k] = merged[k];
11573
+ }
11574
+ await addressRepo().update(id, updates);
11575
+ }
11409
11576
  const updated = await addressRepo().findOne({ where: { id } });
11410
11577
  return json(serializeAddress2(updated));
11411
11578
  }