@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/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;
@@ -4207,12 +4207,14 @@ function createDashboardStatsHandler(config) {
4207
4207
  const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
4208
4208
  const repo = (name) => entityMap[name] ? dataSource.getRepository(entityMap[name]) : void 0;
4209
4209
  const [contactsCount, formsCount, formSubmissionsCount, usersCount, blogsCount, recentContacts, recentSubmissions, contactTypeRows] = await Promise.all([
4210
- repo("contacts")?.count() ?? 0,
4210
+ repo("contacts")?.count({ where: { deleted: false } }) ?? 0,
4211
4211
  repo("forms")?.count({ where: { deleted: false } }) ?? 0,
4212
4212
  repo("form_submissions")?.count() ?? 0,
4213
4213
  repo("users")?.count({ where: { deleted: false } }) ?? 0,
4214
4214
  repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
4215
- repo("contacts")?.count({ where: { createdAt: MoreThanOrEqual(sevenDaysAgo) } }) ?? 0,
4215
+ repo("contacts")?.count({
4216
+ where: { deleted: false, createdAt: MoreThanOrEqual(sevenDaysAgo) }
4217
+ }) ?? 0,
4216
4218
  repo("form_submissions")?.count({ where: { createdAt: MoreThanOrEqual(sevenDaysAgo) } }) ?? 0,
4217
4219
  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() ?? []
4218
4220
  ]);
@@ -4845,11 +4847,14 @@ function createFormSubmissionHandler(config) {
4845
4847
  const contactRepo = dataSource.getRepository(entityMap.contacts);
4846
4848
  let contact = await contactRepo.findOne({ where: { email: contactData.email } });
4847
4849
  if (!contact) {
4850
+ const createdAt = /* @__PURE__ */ new Date();
4848
4851
  contact = await contactRepo.save(
4849
4852
  contactRepo.create({
4850
4853
  name: contactData.name,
4851
4854
  email: contactData.email,
4852
- phone: contactData.phone
4855
+ phone: contactData.phone,
4856
+ createdAt,
4857
+ updatedAt: createdAt
4853
4858
  })
4854
4859
  );
4855
4860
  }
@@ -5005,12 +5010,13 @@ function createUsersApiHandlers(config) {
5005
5010
  const gid = body.groupId ?? null;
5006
5011
  const isCustomer = !!(customerG && gid === customerG.id);
5007
5012
  const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
5013
+ const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
5008
5014
  const newUser = await userRepo().save(
5009
5015
  userRepo().create({
5010
5016
  name: body.name,
5011
5017
  email: body.email,
5012
5018
  password: null,
5013
- blocked: true,
5019
+ blocked,
5014
5020
  groupId: gid,
5015
5021
  adminAccess
5016
5022
  })
@@ -5025,7 +5031,14 @@ function createUsersApiHandlers(config) {
5025
5031
  inviteLink,
5026
5032
  newUser.name ?? ""
5027
5033
  );
5028
- return json({ message: "User created successfully (blocked until password is set)", user: newUser, inviteLink }, { status: 201 });
5034
+ return json(
5035
+ {
5036
+ message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
5037
+ user: newUser,
5038
+ inviteLink
5039
+ },
5040
+ { status: 201 }
5041
+ );
5029
5042
  } catch {
5030
5043
  return json({ error: "Server Error" }, { status: 500 });
5031
5044
  }
@@ -5327,7 +5340,10 @@ function createChatHandlers(config) {
5327
5340
  const existing = await repo.findOne({ where: { email } });
5328
5341
  let contact;
5329
5342
  if (!existing) {
5330
- contact = await repo.save(repo.create({ name, email, phone }));
5343
+ const createdAt = /* @__PURE__ */ new Date();
5344
+ contact = await repo.save(
5345
+ repo.create({ name, email, phone, createdAt, updatedAt: createdAt })
5346
+ );
5331
5347
  } else {
5332
5348
  const row = existing;
5333
5349
  if (row.deleted) {
@@ -8527,7 +8543,67 @@ function getNextAuthOptions(config) {
8527
8543
  }
8528
8544
 
8529
8545
  // src/api/crud.ts
8530
- import { Brackets, ILike as ILike2, MoreThan as MoreThan2 } from "typeorm";
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
8531
8607
  var CRUD_LOG = "[cms-crud]";
8532
8608
  function logCrudClientError(op, detail) {
8533
8609
  console.warn(CRUD_LOG, op, detail);
@@ -8535,6 +8611,40 @@ function logCrudClientError(op, detail) {
8535
8611
  function logCrudServerError(op, detail) {
8536
8612
  console.error(CRUD_LOG, op, detail);
8537
8613
  }
8614
+ async function aggregateMediaFolderFileSizes(dataSource, folderIds) {
8615
+ const map = /* @__PURE__ */ new Map();
8616
+ if (folderIds.length === 0) return map;
8617
+ try {
8618
+ const rows = await dataSource.query(
8619
+ `
8620
+ WITH RECURSIVE walk AS (
8621
+ SELECT m."parentId" AS root_id, m.id, m.kind, m.size
8622
+ FROM media m
8623
+ WHERE m."parentId" = ANY($1) AND m.deleted = false
8624
+ UNION ALL
8625
+ SELECT w.root_id, m.id, m.kind, m.size
8626
+ FROM walk w
8627
+ INNER JOIN media m ON m."parentId" = w.id AND m.deleted = false
8628
+ )
8629
+ SELECT root_id AS "rootId", COALESCE(SUM(size) FILTER (WHERE kind = 'file'), 0)::bigint AS "totalSize"
8630
+ FROM walk
8631
+ GROUP BY root_id
8632
+ `,
8633
+ [folderIds]
8634
+ );
8635
+ for (const r of rows) {
8636
+ map.set(Number(r.rootId), Number(r.totalSize));
8637
+ }
8638
+ for (const id of folderIds) {
8639
+ if (!map.has(id)) map.set(id, 0);
8640
+ }
8641
+ } catch (err) {
8642
+ logCrudServerError("media folder size aggregate failed", {
8643
+ message: err instanceof Error ? err.message : String(err)
8644
+ });
8645
+ }
8646
+ return map;
8647
+ }
8538
8648
  var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
8539
8649
  "date",
8540
8650
  "datetime",
@@ -8599,6 +8709,16 @@ function mergeDeletedFalseWhere(repo, where) {
8599
8709
  }
8600
8710
  return Object.keys(where).length > 0 ? { ...where, ...d } : d;
8601
8711
  }
8712
+ function normalizeProductSku(value) {
8713
+ if (value == null) return null;
8714
+ const s = String(value).trim();
8715
+ return s === "" ? null : s;
8716
+ }
8717
+ async function assertProductSkuUnique(repo, sku, excludeId) {
8718
+ const where = excludeId != null ? { sku, deleted: false, id: Not(excludeId) } : { sku, deleted: false };
8719
+ const row = await repo.findOne({ where });
8720
+ return row == null;
8721
+ }
8602
8722
  function buildSoftDeletePayload(meta, deletedBy) {
8603
8723
  const payload = { deleted: true };
8604
8724
  if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
@@ -8753,6 +8873,17 @@ function createCrudHandler(dataSource, entityMap, options) {
8753
8873
  if (statusFilter) productWhere.status = statusFilter;
8754
8874
  if (inventory === "in_stock") productWhere.quantity = MoreThan2(0);
8755
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
+ }
8756
8887
  if (search && typeof search === "string" && search.trim()) {
8757
8888
  productWhere.name = ILike2(`%${search.trim()}%`);
8758
8889
  }
@@ -8821,19 +8952,36 @@ function createCrudHandler(dataSource, entityMap, options) {
8821
8952
  qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
8822
8953
  }
8823
8954
  if (typeFilter) {
8824
- qb.andWhere(
8825
- new Brackets((sq) => {
8826
- sq.where("m.kind = :folderKind", { folderKind: "folder" }).orWhere("m.mimeType LIKE :mtp", {
8827
- mtp: `${typeFilter}/%`
8828
- });
8829
- })
8830
- );
8955
+ if (typeFilter === "folder") {
8956
+ qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
8957
+ } else if (typeFilter === "file") {
8958
+ qb.andWhere("m.kind = :fileKind", { fileKind: "file" });
8959
+ } else if (typeFilter === "image") {
8960
+ qb.andWhere("m.mimeType LIKE :imageMimeType", { imageMimeType: "image/%" });
8961
+ } else if (typeFilter === "video") {
8962
+ qb.andWhere("m.mimeType LIKE :videoMimeType", { videoMimeType: "video/%" });
8963
+ } else if (typeFilter === "audio") {
8964
+ qb.andWhere("m.mimeType LIKE :audioMimeType", { audioMimeType: "audio/%" });
8965
+ } else if (typeFilter === "Document") {
8966
+ qb.andWhere("m.mimeType LIKE :documentMimeType", { documentMimeType: "application/pdf" });
8967
+ } else if (typeFilter === "application") {
8968
+ qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
8969
+ }
8831
8970
  }
8832
8971
  const allowedSort = ["filename", "createdAt", "id"];
8833
8972
  const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
8834
8973
  const so = sortOrder === "DESC" ? "DESC" : "ASC";
8835
- qb.orderBy("CASE WHEN m.kind = :fk THEN 0 ELSE 1 END", "ASC").addOrderBy(`m.${sf}`, so).setParameter("fk", "folder").skip(skip).take(limit);
8836
- const [data2, total2] = await qb.getManyAndCount();
8974
+ qb.orderBy(`m.${sf}`, so).skip(skip).take(limit);
8975
+ const [rows, total2] = await qb.getManyAndCount();
8976
+ const mediaRows = rows;
8977
+ const folderIds = mediaRows.filter((m) => m.kind === "folder").map((m) => m.id);
8978
+ let data2 = rows;
8979
+ if (folderIds.length > 0) {
8980
+ const sizeMap = await aggregateMediaFolderFileSizes(dataSource, folderIds);
8981
+ data2 = mediaRows.map(
8982
+ (m) => m.kind === "folder" ? { ...m, size: sizeMap.get(m.id) ?? 0 } : m
8983
+ );
8984
+ }
8837
8985
  return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
8838
8986
  }
8839
8987
  const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
@@ -8841,7 +8989,7 @@ function createCrudHandler(dataSource, entityMap, options) {
8841
8989
  if (search) {
8842
8990
  where = buildSearchWhereClause(repo, search);
8843
8991
  }
8844
- const intFilterKeys = ["productId", "attributeId", "taxId"];
8992
+ const intFilterKeys = ["productId", "attributeId", "taxId", "brandId", "categoryId", "collectionId", "parentId"];
8845
8993
  const extraWhere = {};
8846
8994
  for (const key of intFilterKeys) {
8847
8995
  const v = searchParams.get(key);
@@ -8850,6 +8998,15 @@ function createCrudHandler(dataSource, entityMap, options) {
8850
8998
  if (Number.isFinite(n)) extraWhere[key] = n;
8851
8999
  }
8852
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
+ }
8853
9010
  if (Object.keys(extraWhere).length > 0) {
8854
9011
  if (Array.isArray(where)) {
8855
9012
  where = where.map((w) => ({ ...w, ...extraWhere }));
@@ -8944,6 +9101,31 @@ function createCrudHandler(dataSource, entityMap, options) {
8944
9101
  });
8945
9102
  return json({ error: "Invalid request payload" }, { status: 400 });
8946
9103
  }
9104
+ if (resource === "products") {
9105
+ if ("sku" in persistBody) {
9106
+ const skuNorm = normalizeProductSku(persistBody.sku);
9107
+ if (skuNorm) {
9108
+ const ok = await assertProductSkuUnique(repo, skuNorm);
9109
+ if (!ok) {
9110
+ return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
9111
+ }
9112
+ persistBody.sku = skuNorm;
9113
+ } else {
9114
+ persistBody.sku = null;
9115
+ }
9116
+ }
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
+ }
8947
9129
  sanitizeBodyForEntity(repo, persistBody);
8948
9130
  let created;
8949
9131
  try {
@@ -9138,7 +9320,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9138
9320
  relations: ["order", "order.contact", "contact"]
9139
9321
  });
9140
9322
  if (!payment) return json({ message: "Not found" }, { status: 404 });
9141
- return json(payment);
9323
+ const p = payment;
9324
+ const order = p.order;
9325
+ const orderContact = order?.contact;
9326
+ const contact = p.contact;
9327
+ const customer = orderContact ?? contact;
9328
+ return json({
9329
+ ...p,
9330
+ order: order ? {
9331
+ id: order.id,
9332
+ orderNumber: order.orderNumber,
9333
+ contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null
9334
+ } : null,
9335
+ contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
9336
+ });
9142
9337
  }
9143
9338
  if (resource === "blogs") {
9144
9339
  const blog = await repo.findOne({
@@ -9250,8 +9445,91 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9250
9445
  const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
9251
9446
  if (resource === "media") {
9252
9447
  const u = updatePayload;
9253
- delete u.parentId;
9254
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
+ }
9496
+ }
9497
+ if (resource === "products") {
9498
+ const currentRow = await repo.findOne({
9499
+ where: { id: numericId, deleted: false }
9500
+ });
9501
+ if (!currentRow) return json({ message: "Not found" }, { status: 404 });
9502
+ const merged = { ...currentRow, ...updatePayload };
9503
+ const effSku = normalizeProductSku(merged.sku);
9504
+ if (effSku) {
9505
+ const ok = await assertProductSkuUnique(repo, effSku, numericId);
9506
+ if (!ok) {
9507
+ return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
9508
+ }
9509
+ }
9510
+ if ("sku" in updatePayload) {
9511
+ updatePayload.sku = effSku;
9512
+ }
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
+ }
9255
9533
  }
9256
9534
  if (Object.keys(updatePayload).length > 0) {
9257
9535
  sanitizeBodyForEntity(repo, updatePayload);
@@ -9282,6 +9560,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9282
9560
  where: { id: numericId, deleted: false }
9283
9561
  });
9284
9562
  if (!existing) return json({ message: "Not found" }, { status: 404 });
9563
+ if (resource === "contacts") {
9564
+ const result2 = await repo.delete(numericId);
9565
+ if (result2.affected === 0) return json({ message: "Not found" }, { status: 404 });
9566
+ return json({ message: "Deleted successfully" }, { status: 200 });
9567
+ }
9285
9568
  let deletedBy = null;
9286
9569
  if (getDeletedByUserId) {
9287
9570
  try {
@@ -11249,18 +11532,19 @@ function createStorefrontApiHandler(config) {
11249
11532
  const contactOrErr = await getContactForAddresses();
11250
11533
  if (contactOrErr instanceof Response) return contactOrErr;
11251
11534
  const b = await req.json().catch(() => ({}));
11252
- const created = await addressRepo().save(
11253
- addressRepo().create({
11254
- contactId: contactOrErr.contactId,
11255
- tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
11256
- line1: typeof b.line1 === "string" ? b.line1.trim() || null : null,
11257
- line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
11258
- city: typeof b.city === "string" ? b.city.trim() || null : null,
11259
- state: typeof b.state === "string" ? b.state.trim() || null : null,
11260
- postalCode: typeof b.postalCode === "string" ? b.postalCode.trim() || null : null,
11261
- country: typeof b.country === "string" ? b.country.trim() || null : null
11262
- })
11263
- );
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));
11264
11548
  return json(serializeAddress2(created));
11265
11549
  }
11266
11550
  if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
@@ -11279,7 +11563,16 @@ function createStorefrontApiHandler(config) {
11279
11563
  if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
11280
11564
  if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
11281
11565
  if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
11282
- 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
+ }
11283
11576
  const updated = await addressRepo().findOne({ where: { id } });
11284
11577
  return json(serializeAddress2(updated));
11285
11578
  }
@@ -12091,7 +12384,7 @@ var DEFAULT_ADMIN_NAV = [
12091
12384
  ];
12092
12385
 
12093
12386
  // src/index.ts
12094
- console.log("\u{1F525} USING LOCAL CMS CORE (index.ts loaded) \u{1F525}");
12387
+ console.log("\u{1F525} USING LOCAL CMS (index.ts loaded) \u{1F525}");
12095
12388
  export {
12096
12389
  ADMIN_GROUP_NAME,
12097
12390
  Address,