@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.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;
@@ -4388,12 +4388,14 @@ function createDashboardStatsHandler(config) {
4388
4388
  const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
4389
4389
  const repo = (name) => entityMap[name] ? dataSource.getRepository(entityMap[name]) : void 0;
4390
4390
  const [contactsCount, formsCount, formSubmissionsCount, usersCount, blogsCount, recentContacts, recentSubmissions, contactTypeRows] = await Promise.all([
4391
- repo("contacts")?.count() ?? 0,
4391
+ repo("contacts")?.count({ where: { deleted: false } }) ?? 0,
4392
4392
  repo("forms")?.count({ where: { deleted: false } }) ?? 0,
4393
4393
  repo("form_submissions")?.count() ?? 0,
4394
4394
  repo("users")?.count({ where: { deleted: false } }) ?? 0,
4395
4395
  repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
4396
- repo("contacts")?.count({ where: { createdAt: (0, import_typeorm6.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
4396
+ repo("contacts")?.count({
4397
+ where: { deleted: false, createdAt: (0, import_typeorm6.MoreThanOrEqual)(sevenDaysAgo) }
4398
+ }) ?? 0,
4397
4399
  repo("form_submissions")?.count({ where: { createdAt: (0, import_typeorm6.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
4398
4400
  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() ?? []
4399
4401
  ]);
@@ -5026,11 +5028,14 @@ function createFormSubmissionHandler(config) {
5026
5028
  const contactRepo = dataSource.getRepository(entityMap.contacts);
5027
5029
  let contact = await contactRepo.findOne({ where: { email: contactData.email } });
5028
5030
  if (!contact) {
5031
+ const createdAt = /* @__PURE__ */ new Date();
5029
5032
  contact = await contactRepo.save(
5030
5033
  contactRepo.create({
5031
5034
  name: contactData.name,
5032
5035
  email: contactData.email,
5033
- phone: contactData.phone
5036
+ phone: contactData.phone,
5037
+ createdAt,
5038
+ updatedAt: createdAt
5034
5039
  })
5035
5040
  );
5036
5041
  }
@@ -5186,12 +5191,13 @@ function createUsersApiHandlers(config) {
5186
5191
  const gid = body.groupId ?? null;
5187
5192
  const isCustomer = !!(customerG && gid === customerG.id);
5188
5193
  const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
5194
+ const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
5189
5195
  const newUser = await userRepo().save(
5190
5196
  userRepo().create({
5191
5197
  name: body.name,
5192
5198
  email: body.email,
5193
5199
  password: null,
5194
- blocked: true,
5200
+ blocked,
5195
5201
  groupId: gid,
5196
5202
  adminAccess
5197
5203
  })
@@ -5206,7 +5212,14 @@ function createUsersApiHandlers(config) {
5206
5212
  inviteLink,
5207
5213
  newUser.name ?? ""
5208
5214
  );
5209
- return json({ message: "User created successfully (blocked until password is set)", user: newUser, inviteLink }, { status: 201 });
5215
+ return json(
5216
+ {
5217
+ message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
5218
+ user: newUser,
5219
+ inviteLink
5220
+ },
5221
+ { status: 201 }
5222
+ );
5210
5223
  } catch {
5211
5224
  return json({ error: "Server Error" }, { status: 500 });
5212
5225
  }
@@ -5508,7 +5521,10 @@ function createChatHandlers(config) {
5508
5521
  const existing = await repo.findOne({ where: { email } });
5509
5522
  let contact;
5510
5523
  if (!existing) {
5511
- contact = await repo.save(repo.create({ name, email, phone }));
5524
+ const createdAt = /* @__PURE__ */ new Date();
5525
+ contact = await repo.save(
5526
+ repo.create({ name, email, phone, createdAt, updatedAt: createdAt })
5527
+ );
5512
5528
  } else {
5513
5529
  const row = existing;
5514
5530
  if (row.deleted) {
@@ -8700,6 +8716,66 @@ function getNextAuthOptions(config) {
8700
8716
 
8701
8717
  // src/api/crud.ts
8702
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
8703
8779
  var CRUD_LOG = "[cms-crud]";
8704
8780
  function logCrudClientError(op, detail) {
8705
8781
  console.warn(CRUD_LOG, op, detail);
@@ -8707,6 +8783,40 @@ function logCrudClientError(op, detail) {
8707
8783
  function logCrudServerError(op, detail) {
8708
8784
  console.error(CRUD_LOG, op, detail);
8709
8785
  }
8786
+ async function aggregateMediaFolderFileSizes(dataSource, folderIds) {
8787
+ const map = /* @__PURE__ */ new Map();
8788
+ if (folderIds.length === 0) return map;
8789
+ try {
8790
+ const rows = await dataSource.query(
8791
+ `
8792
+ WITH RECURSIVE walk AS (
8793
+ SELECT m."parentId" AS root_id, m.id, m.kind, m.size
8794
+ FROM media m
8795
+ WHERE m."parentId" = ANY($1) AND m.deleted = false
8796
+ UNION ALL
8797
+ SELECT w.root_id, m.id, m.kind, m.size
8798
+ FROM walk w
8799
+ INNER JOIN media m ON m."parentId" = w.id AND m.deleted = false
8800
+ )
8801
+ SELECT root_id AS "rootId", COALESCE(SUM(size) FILTER (WHERE kind = 'file'), 0)::bigint AS "totalSize"
8802
+ FROM walk
8803
+ GROUP BY root_id
8804
+ `,
8805
+ [folderIds]
8806
+ );
8807
+ for (const r of rows) {
8808
+ map.set(Number(r.rootId), Number(r.totalSize));
8809
+ }
8810
+ for (const id of folderIds) {
8811
+ if (!map.has(id)) map.set(id, 0);
8812
+ }
8813
+ } catch (err) {
8814
+ logCrudServerError("media folder size aggregate failed", {
8815
+ message: err instanceof Error ? err.message : String(err)
8816
+ });
8817
+ }
8818
+ return map;
8819
+ }
8710
8820
  var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
8711
8821
  "date",
8712
8822
  "datetime",
@@ -8771,6 +8881,16 @@ function mergeDeletedFalseWhere(repo, where) {
8771
8881
  }
8772
8882
  return Object.keys(where).length > 0 ? { ...where, ...d } : d;
8773
8883
  }
8884
+ function normalizeProductSku(value) {
8885
+ if (value == null) return null;
8886
+ const s = String(value).trim();
8887
+ return s === "" ? null : s;
8888
+ }
8889
+ async function assertProductSkuUnique(repo, sku, excludeId) {
8890
+ const where = excludeId != null ? { sku, deleted: false, id: (0, import_typeorm46.Not)(excludeId) } : { sku, deleted: false };
8891
+ const row = await repo.findOne({ where });
8892
+ return row == null;
8893
+ }
8774
8894
  function buildSoftDeletePayload(meta, deletedBy) {
8775
8895
  const payload = { deleted: true };
8776
8896
  if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
@@ -8925,6 +9045,17 @@ function createCrudHandler(dataSource, entityMap, options) {
8925
9045
  if (statusFilter) productWhere.status = statusFilter;
8926
9046
  if (inventory === "in_stock") productWhere.quantity = (0, import_typeorm46.MoreThan)(0);
8927
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
+ }
8928
9059
  if (search && typeof search === "string" && search.trim()) {
8929
9060
  productWhere.name = (0, import_typeorm46.ILike)(`%${search.trim()}%`);
8930
9061
  }
@@ -8993,19 +9124,36 @@ function createCrudHandler(dataSource, entityMap, options) {
8993
9124
  qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
8994
9125
  }
8995
9126
  if (typeFilter) {
8996
- qb.andWhere(
8997
- new import_typeorm46.Brackets((sq) => {
8998
- sq.where("m.kind = :folderKind", { folderKind: "folder" }).orWhere("m.mimeType LIKE :mtp", {
8999
- mtp: `${typeFilter}/%`
9000
- });
9001
- })
9002
- );
9127
+ if (typeFilter === "folder") {
9128
+ qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
9129
+ } else if (typeFilter === "file") {
9130
+ qb.andWhere("m.kind = :fileKind", { fileKind: "file" });
9131
+ } else if (typeFilter === "image") {
9132
+ qb.andWhere("m.mimeType LIKE :imageMimeType", { imageMimeType: "image/%" });
9133
+ } else if (typeFilter === "video") {
9134
+ qb.andWhere("m.mimeType LIKE :videoMimeType", { videoMimeType: "video/%" });
9135
+ } else if (typeFilter === "audio") {
9136
+ qb.andWhere("m.mimeType LIKE :audioMimeType", { audioMimeType: "audio/%" });
9137
+ } else if (typeFilter === "Document") {
9138
+ qb.andWhere("m.mimeType LIKE :documentMimeType", { documentMimeType: "application/pdf" });
9139
+ } else if (typeFilter === "application") {
9140
+ qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
9141
+ }
9003
9142
  }
9004
9143
  const allowedSort = ["filename", "createdAt", "id"];
9005
9144
  const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
9006
9145
  const so = sortOrder === "DESC" ? "DESC" : "ASC";
9007
- qb.orderBy("CASE WHEN m.kind = :fk THEN 0 ELSE 1 END", "ASC").addOrderBy(`m.${sf}`, so).setParameter("fk", "folder").skip(skip).take(limit);
9008
- const [data2, total2] = await qb.getManyAndCount();
9146
+ qb.orderBy(`m.${sf}`, so).skip(skip).take(limit);
9147
+ const [rows, total2] = await qb.getManyAndCount();
9148
+ const mediaRows = rows;
9149
+ const folderIds = mediaRows.filter((m) => m.kind === "folder").map((m) => m.id);
9150
+ let data2 = rows;
9151
+ if (folderIds.length > 0) {
9152
+ const sizeMap = await aggregateMediaFolderFileSizes(dataSource, folderIds);
9153
+ data2 = mediaRows.map(
9154
+ (m) => m.kind === "folder" ? { ...m, size: sizeMap.get(m.id) ?? 0 } : m
9155
+ );
9156
+ }
9009
9157
  return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
9010
9158
  }
9011
9159
  const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
@@ -9013,7 +9161,7 @@ function createCrudHandler(dataSource, entityMap, options) {
9013
9161
  if (search) {
9014
9162
  where = buildSearchWhereClause(repo, search);
9015
9163
  }
9016
- const intFilterKeys = ["productId", "attributeId", "taxId"];
9164
+ const intFilterKeys = ["productId", "attributeId", "taxId", "brandId", "categoryId", "collectionId", "parentId"];
9017
9165
  const extraWhere = {};
9018
9166
  for (const key of intFilterKeys) {
9019
9167
  const v = searchParams.get(key);
@@ -9022,6 +9170,15 @@ function createCrudHandler(dataSource, entityMap, options) {
9022
9170
  if (Number.isFinite(n)) extraWhere[key] = n;
9023
9171
  }
9024
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
+ }
9025
9182
  if (Object.keys(extraWhere).length > 0) {
9026
9183
  if (Array.isArray(where)) {
9027
9184
  where = where.map((w) => ({ ...w, ...extraWhere }));
@@ -9116,6 +9273,31 @@ function createCrudHandler(dataSource, entityMap, options) {
9116
9273
  });
9117
9274
  return json({ error: "Invalid request payload" }, { status: 400 });
9118
9275
  }
9276
+ if (resource === "products") {
9277
+ if ("sku" in persistBody) {
9278
+ const skuNorm = normalizeProductSku(persistBody.sku);
9279
+ if (skuNorm) {
9280
+ const ok = await assertProductSkuUnique(repo, skuNorm);
9281
+ if (!ok) {
9282
+ return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
9283
+ }
9284
+ persistBody.sku = skuNorm;
9285
+ } else {
9286
+ persistBody.sku = null;
9287
+ }
9288
+ }
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
+ }
9119
9301
  sanitizeBodyForEntity(repo, persistBody);
9120
9302
  let created;
9121
9303
  try {
@@ -9310,7 +9492,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9310
9492
  relations: ["order", "order.contact", "contact"]
9311
9493
  });
9312
9494
  if (!payment) return json({ message: "Not found" }, { status: 404 });
9313
- return json(payment);
9495
+ const p = payment;
9496
+ const order = p.order;
9497
+ const orderContact = order?.contact;
9498
+ const contact = p.contact;
9499
+ const customer = orderContact ?? contact;
9500
+ return json({
9501
+ ...p,
9502
+ order: order ? {
9503
+ id: order.id,
9504
+ orderNumber: order.orderNumber,
9505
+ contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null
9506
+ } : null,
9507
+ contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
9508
+ });
9314
9509
  }
9315
9510
  if (resource === "blogs") {
9316
9511
  const blog = await repo.findOne({
@@ -9422,8 +9617,91 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9422
9617
  const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
9423
9618
  if (resource === "media") {
9424
9619
  const u = updatePayload;
9425
- delete u.parentId;
9426
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
+ }
9668
+ }
9669
+ if (resource === "products") {
9670
+ const currentRow = await repo.findOne({
9671
+ where: { id: numericId, deleted: false }
9672
+ });
9673
+ if (!currentRow) return json({ message: "Not found" }, { status: 404 });
9674
+ const merged = { ...currentRow, ...updatePayload };
9675
+ const effSku = normalizeProductSku(merged.sku);
9676
+ if (effSku) {
9677
+ const ok = await assertProductSkuUnique(repo, effSku, numericId);
9678
+ if (!ok) {
9679
+ return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
9680
+ }
9681
+ }
9682
+ if ("sku" in updatePayload) {
9683
+ updatePayload.sku = effSku;
9684
+ }
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
+ }
9427
9705
  }
9428
9706
  if (Object.keys(updatePayload).length > 0) {
9429
9707
  sanitizeBodyForEntity(repo, updatePayload);
@@ -9454,6 +9732,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9454
9732
  where: { id: numericId, deleted: false }
9455
9733
  });
9456
9734
  if (!existing) return json({ message: "Not found" }, { status: 404 });
9735
+ if (resource === "contacts") {
9736
+ const result2 = await repo.delete(numericId);
9737
+ if (result2.affected === 0) return json({ message: "Not found" }, { status: 404 });
9738
+ return json({ message: "Deleted successfully" }, { status: 200 });
9739
+ }
9457
9740
  let deletedBy = null;
9458
9741
  if (getDeletedByUserId) {
9459
9742
  try {
@@ -11421,18 +11704,19 @@ function createStorefrontApiHandler(config) {
11421
11704
  const contactOrErr = await getContactForAddresses();
11422
11705
  if (contactOrErr instanceof Response) return contactOrErr;
11423
11706
  const b = await req.json().catch(() => ({}));
11424
- const created = await addressRepo().save(
11425
- addressRepo().create({
11426
- contactId: contactOrErr.contactId,
11427
- tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
11428
- line1: typeof b.line1 === "string" ? b.line1.trim() || null : null,
11429
- line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
11430
- city: typeof b.city === "string" ? b.city.trim() || null : null,
11431
- state: typeof b.state === "string" ? b.state.trim() || null : null,
11432
- postalCode: typeof b.postalCode === "string" ? b.postalCode.trim() || null : null,
11433
- country: typeof b.country === "string" ? b.country.trim() || null : null
11434
- })
11435
- );
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));
11436
11720
  return json(serializeAddress2(created));
11437
11721
  }
11438
11722
  if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
@@ -11451,7 +11735,16 @@ function createStorefrontApiHandler(config) {
11451
11735
  if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
11452
11736
  if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
11453
11737
  if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
11454
- 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
+ }
11455
11748
  const updated = await addressRepo().findOne({ where: { id } });
11456
11749
  return json(serializeAddress2(updated));
11457
11750
  }
@@ -12263,7 +12556,7 @@ var DEFAULT_ADMIN_NAV = [
12263
12556
  ];
12264
12557
 
12265
12558
  // src/index.ts
12266
- console.log("\u{1F525} USING LOCAL CMS CORE (index.ts loaded) \u{1F525}");
12559
+ console.log("\u{1F525} USING LOCAL CMS (index.ts loaded) \u{1F525}");
12267
12560
  // Annotate the CommonJS export names for ESM import in node:
12268
12561
  0 && (module.exports = {
12269
12562
  ADMIN_GROUP_NAME,