@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.cjs CHANGED
@@ -4160,11 +4160,11 @@ async function readBufferFromPublicUrl(url) {
4160
4160
  throw new Error("Unsupported media URL");
4161
4161
  }
4162
4162
  function sanitizeZipPath(entryName) {
4163
- const norm2 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
4164
- for (const seg of norm2) {
4163
+ const norm3 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
4164
+ for (const seg of norm3) {
4165
4165
  if (seg === ".." || seg === ".") return null;
4166
4166
  }
4167
- return norm2;
4167
+ return norm3;
4168
4168
  }
4169
4169
  function shouldSkipEntry(parts) {
4170
4170
  if (parts[0] === "__MACOSX") return true;
@@ -8716,6 +8716,66 @@ function getNextAuthOptions(config) {
8716
8716
 
8717
8717
  // src/api/crud.ts
8718
8718
  var import_typeorm46 = require("typeorm");
8719
+
8720
+ // src/lib/address-geo-validation.ts
8721
+ var import_country_state_city = require("country-state-city");
8722
+ function norm2(s) {
8723
+ return typeof s === "string" ? s.trim() : "";
8724
+ }
8725
+ function resolveCountry(input) {
8726
+ const t = input.trim();
8727
+ if (!t) return void 0;
8728
+ if (t.length === 2) {
8729
+ const byCode = import_country_state_city.Country.getCountryByCode(t.toUpperCase());
8730
+ if (byCode) return byCode;
8731
+ }
8732
+ const lower = t.toLowerCase();
8733
+ return import_country_state_city.Country.getAllCountries().find((c) => c.name.toLowerCase() === lower);
8734
+ }
8735
+ function resolveState(countryIso, input) {
8736
+ const t = input.trim();
8737
+ if (!t || !countryIso) return void 0;
8738
+ const states = import_country_state_city.State.getStatesOfCountry(countryIso);
8739
+ const lower = t.toLowerCase();
8740
+ return states.find((s) => s.isoCode.toLowerCase() === t.toLowerCase() || s.name.toLowerCase() === lower);
8741
+ }
8742
+ function resolveCity(countryIso, stateIso, input) {
8743
+ const t = input.trim();
8744
+ if (!t || !countryIso || !stateIso) return void 0;
8745
+ const lower = t.toLowerCase();
8746
+ const cities = import_country_state_city.City.getCitiesOfState(countryIso, stateIso);
8747
+ return cities.find((c) => c.name.toLowerCase() === lower);
8748
+ }
8749
+ function assertValidAddressHierarchy(country, state, city) {
8750
+ const c = resolveCountry(country);
8751
+ if (!c) return { ok: false, error: "Invalid or unknown country." };
8752
+ const st = resolveState(c.isoCode, state);
8753
+ if (!st) return { ok: false, error: "State or province does not match the selected country." };
8754
+ const ct = resolveCity(c.isoCode, st.isoCode, city);
8755
+ if (!ct) return { ok: false, error: "City does not match the selected state." };
8756
+ return { ok: true, country: c.name, state: st.name, city: ct.name };
8757
+ }
8758
+ function validateAndNormalizeAddressRow(row) {
8759
+ const line1 = norm2(row.line1);
8760
+ const postalCode = norm2(row.postalCode);
8761
+ const countryIn = norm2(row.country);
8762
+ const stateIn = norm2(row.state);
8763
+ const cityIn = norm2(row.city);
8764
+ if (!line1) return "Street address (line 1) is required.";
8765
+ if (!postalCode) return "Postal code is required.";
8766
+ if (!countryIn || !stateIn || !cityIn) return "Country, state, and city are required.";
8767
+ const geo = assertValidAddressHierarchy(countryIn, stateIn, cityIn);
8768
+ if (!geo.ok) return geo.error;
8769
+ row.line1 = line1;
8770
+ row.line2 = norm2(row.line2) || null;
8771
+ row.postalCode = postalCode;
8772
+ row.country = geo.country;
8773
+ row.state = geo.state;
8774
+ row.city = geo.city;
8775
+ return null;
8776
+ }
8777
+
8778
+ // src/api/crud.ts
8719
8779
  var CRUD_LOG = "[cms-crud]";
8720
8780
  function logCrudClientError(op, detail) {
8721
8781
  console.warn(CRUD_LOG, op, detail);
@@ -8985,6 +9045,17 @@ function createCrudHandler(dataSource, entityMap, options) {
8985
9045
  if (statusFilter) productWhere.status = statusFilter;
8986
9046
  if (inventory === "in_stock") productWhere.quantity = (0, import_typeorm46.MoreThan)(0);
8987
9047
  if (inventory === "out_of_stock") productWhere.quantity = 0;
9048
+ for (const key of ["brandId", "categoryId", "collectionId"]) {
9049
+ const raw = searchParams.get(key)?.trim();
9050
+ if (raw) {
9051
+ const n = Number(raw);
9052
+ if (Number.isFinite(n)) productWhere[key] = n;
9053
+ }
9054
+ }
9055
+ const featuredRaw = searchParams.get("featured")?.trim();
9056
+ if (featuredRaw === "true" || featuredRaw === "false") {
9057
+ productWhere.featured = featuredRaw === "true";
9058
+ }
8988
9059
  if (search && typeof search === "string" && search.trim()) {
8989
9060
  productWhere.name = (0, import_typeorm46.ILike)(`%${search.trim()}%`);
8990
9061
  }
@@ -9090,7 +9161,7 @@ function createCrudHandler(dataSource, entityMap, options) {
9090
9161
  if (search) {
9091
9162
  where = buildSearchWhereClause(repo, search);
9092
9163
  }
9093
- const intFilterKeys = ["productId", "attributeId", "taxId"];
9164
+ const intFilterKeys = ["productId", "attributeId", "taxId", "brandId", "categoryId", "collectionId", "parentId"];
9094
9165
  const extraWhere = {};
9095
9166
  for (const key of intFilterKeys) {
9096
9167
  const v = searchParams.get(key);
@@ -9099,6 +9170,15 @@ function createCrudHandler(dataSource, entityMap, options) {
9099
9170
  if (Number.isFinite(n)) extraWhere[key] = n;
9100
9171
  }
9101
9172
  }
9173
+ for (const col of repo.metadata.columns) {
9174
+ if (String(col.type) !== "boolean") continue;
9175
+ const name = col.propertyName;
9176
+ if (!columnNames.has(name)) continue;
9177
+ const raw = searchParams.get(name)?.trim();
9178
+ if (raw === "true" || raw === "false") {
9179
+ extraWhere[name] = raw === "true";
9180
+ }
9181
+ }
9102
9182
  if (Object.keys(extraWhere).length > 0) {
9103
9183
  if (Array.isArray(where)) {
9104
9184
  where = where.map((w) => ({ ...w, ...extraWhere }));
@@ -9207,6 +9287,17 @@ function createCrudHandler(dataSource, entityMap, options) {
9207
9287
  }
9208
9288
  }
9209
9289
  }
9290
+ if (resource === "addresses") {
9291
+ const cid = Number(persistBody.contactId);
9292
+ if (!Number.isFinite(cid)) {
9293
+ return json({ error: "Valid contactId is required." }, { status: 400 });
9294
+ }
9295
+ if (persistBody.tag === "") persistBody.tag = null;
9296
+ const addrErr = validateAndNormalizeAddressRow(persistBody);
9297
+ if (addrErr) {
9298
+ return json({ error: addrErr }, { status: 400 });
9299
+ }
9300
+ }
9210
9301
  sanitizeBodyForEntity(repo, persistBody);
9211
9302
  let created;
9212
9303
  try {
@@ -9526,8 +9617,54 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9526
9617
  const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
9527
9618
  if (resource === "media") {
9528
9619
  const u = updatePayload;
9529
- delete u.parentId;
9530
9620
  delete u.kind;
9621
+ if (rawBody && typeof rawBody === "object" && "parentId" in rawBody) {
9622
+ let pid = null;
9623
+ const p = rawBody.parentId;
9624
+ if (p != null && p !== "") {
9625
+ const n = Number(p);
9626
+ if (!Number.isFinite(n)) {
9627
+ return json({ error: "Invalid parentId" }, { status: 400 });
9628
+ }
9629
+ pid = n;
9630
+ }
9631
+ if (pid != null) {
9632
+ const parent = await repo.findOne({
9633
+ where: { id: pid, deleted: false }
9634
+ });
9635
+ if (!parent || parent.kind !== "folder") {
9636
+ return json({ error: "parent must be a folder" }, { status: 400 });
9637
+ }
9638
+ }
9639
+ const row = await repo.findOne({
9640
+ where: { id: numericId, deleted: false }
9641
+ });
9642
+ if (!row) return json({ message: "Not found" }, { status: 404 });
9643
+ if (pid === numericId) {
9644
+ return json({ error: "Invalid parentId" }, { status: 400 });
9645
+ }
9646
+ if (row.kind === "folder" && pid != null) {
9647
+ let walk = pid;
9648
+ const seen = /* @__PURE__ */ new Set();
9649
+ while (walk != null) {
9650
+ if (walk === numericId) {
9651
+ return json(
9652
+ { error: "Cannot move a folder into itself or a descendant folder" },
9653
+ { status: 400 }
9654
+ );
9655
+ }
9656
+ if (seen.has(walk)) break;
9657
+ seen.add(walk);
9658
+ const anc = await repo.findOne({
9659
+ where: { id: walk, deleted: false }
9660
+ });
9661
+ walk = anc ? anc.parentId ?? null : null;
9662
+ }
9663
+ }
9664
+ u.parentId = pid;
9665
+ } else {
9666
+ delete u.parentId;
9667
+ }
9531
9668
  }
9532
9669
  if (resource === "products") {
9533
9670
  const currentRow = await repo.findOne({
@@ -9546,6 +9683,26 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9546
9683
  updatePayload.sku = effSku;
9547
9684
  }
9548
9685
  }
9686
+ if (resource === "addresses" && Object.keys(updatePayload).length > 0) {
9687
+ const currentRow = await repo.findOne({
9688
+ where: { id: numericId }
9689
+ });
9690
+ if (!currentRow) return json({ message: "Not found" }, { status: 404 });
9691
+ const merged = {
9692
+ ...currentRow,
9693
+ ...updatePayload
9694
+ };
9695
+ if (merged.tag === "") merged.tag = null;
9696
+ const addrErr = validateAndNormalizeAddressRow(merged);
9697
+ if (addrErr) {
9698
+ return json({ error: addrErr }, { status: 400 });
9699
+ }
9700
+ for (const k of Object.keys(updatePayload)) {
9701
+ if (k in merged) {
9702
+ updatePayload[k] = merged[k];
9703
+ }
9704
+ }
9705
+ }
9549
9706
  if (Object.keys(updatePayload).length > 0) {
9550
9707
  sanitizeBodyForEntity(repo, updatePayload);
9551
9708
  await repo.update(numericId, updatePayload);
@@ -11547,18 +11704,19 @@ function createStorefrontApiHandler(config) {
11547
11704
  const contactOrErr = await getContactForAddresses();
11548
11705
  if (contactOrErr instanceof Response) return contactOrErr;
11549
11706
  const b = await req.json().catch(() => ({}));
11550
- const created = await addressRepo().save(
11551
- addressRepo().create({
11552
- contactId: contactOrErr.contactId,
11553
- tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
11554
- line1: typeof b.line1 === "string" ? b.line1.trim() || null : null,
11555
- line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
11556
- city: typeof b.city === "string" ? b.city.trim() || null : null,
11557
- state: typeof b.state === "string" ? b.state.trim() || null : null,
11558
- postalCode: typeof b.postalCode === "string" ? b.postalCode.trim() || null : null,
11559
- country: typeof b.country === "string" ? b.country.trim() || null : null
11560
- })
11561
- );
11707
+ const row = {
11708
+ contactId: contactOrErr.contactId,
11709
+ tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
11710
+ line1: typeof b.line1 === "string" ? b.line1 : "",
11711
+ line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
11712
+ city: typeof b.city === "string" ? b.city : "",
11713
+ state: typeof b.state === "string" ? b.state : "",
11714
+ postalCode: typeof b.postalCode === "string" ? b.postalCode : "",
11715
+ country: typeof b.country === "string" ? b.country : ""
11716
+ };
11717
+ const addrErr = validateAndNormalizeAddressRow(row);
11718
+ if (addrErr) return json({ error: addrErr }, { status: 400 });
11719
+ const created = await addressRepo().save(addressRepo().create(row));
11562
11720
  return json(serializeAddress2(created));
11563
11721
  }
11564
11722
  if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
@@ -11577,7 +11735,16 @@ function createStorefrontApiHandler(config) {
11577
11735
  if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
11578
11736
  if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
11579
11737
  if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
11580
- if (Object.keys(updates).length) await addressRepo().update(id, updates);
11738
+ if (Object.keys(updates).length) {
11739
+ const merged = { ...existing, ...updates };
11740
+ if (merged.tag === "") merged.tag = null;
11741
+ const addrErr = validateAndNormalizeAddressRow(merged);
11742
+ if (addrErr) return json({ error: addrErr }, { status: 400 });
11743
+ for (const k of Object.keys(updates)) {
11744
+ if (k in merged) updates[k] = merged[k];
11745
+ }
11746
+ await addressRepo().update(id, updates);
11747
+ }
11581
11748
  const updated = await addressRepo().findOne({ where: { id } });
11582
11749
  return json(serializeAddress2(updated));
11583
11750
  }