@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/admin.cjs +3595 -1844
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +42 -15
- package/dist/admin.d.ts +42 -15
- package/dist/admin.js +3626 -1874
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +325 -34
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +326 -35
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +328 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +329 -36
- package/dist/index.js.map +1 -1
- package/package.json +130 -129
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
|
|
3983
|
-
for (const seg of
|
|
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
|
|
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({
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
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(
|
|
8836
|
-
const [
|
|
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
|
-
|
|
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
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
11260
|
-
|
|
11261
|
-
|
|
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)
|
|
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
|
|
12387
|
+
console.log("\u{1F525} USING LOCAL CMS (index.ts loaded) \u{1F525}");
|
|
12095
12388
|
export {
|
|
12096
12389
|
ADMIN_GROUP_NAME,
|
|
12097
12390
|
Address,
|