@infuro/cms-core 1.0.22 → 1.0.23
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 +2373 -1536
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +13 -8
- package/dist/admin.d.ts +13 -8
- package/dist/admin.js +2367 -1532
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +142 -16
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +143 -17
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +143 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +144 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api.cjs
CHANGED
|
@@ -537,6 +537,40 @@ function logCrudClientError(op, detail) {
|
|
|
537
537
|
function logCrudServerError(op, detail) {
|
|
538
538
|
console.error(CRUD_LOG, op, detail);
|
|
539
539
|
}
|
|
540
|
+
async function aggregateMediaFolderFileSizes(dataSource, folderIds) {
|
|
541
|
+
const map = /* @__PURE__ */ new Map();
|
|
542
|
+
if (folderIds.length === 0) return map;
|
|
543
|
+
try {
|
|
544
|
+
const rows = await dataSource.query(
|
|
545
|
+
`
|
|
546
|
+
WITH RECURSIVE walk AS (
|
|
547
|
+
SELECT m."parentId" AS root_id, m.id, m.kind, m.size
|
|
548
|
+
FROM media m
|
|
549
|
+
WHERE m."parentId" = ANY($1) AND m.deleted = false
|
|
550
|
+
UNION ALL
|
|
551
|
+
SELECT w.root_id, m.id, m.kind, m.size
|
|
552
|
+
FROM walk w
|
|
553
|
+
INNER JOIN media m ON m."parentId" = w.id AND m.deleted = false
|
|
554
|
+
)
|
|
555
|
+
SELECT root_id AS "rootId", COALESCE(SUM(size) FILTER (WHERE kind = 'file'), 0)::bigint AS "totalSize"
|
|
556
|
+
FROM walk
|
|
557
|
+
GROUP BY root_id
|
|
558
|
+
`,
|
|
559
|
+
[folderIds]
|
|
560
|
+
);
|
|
561
|
+
for (const r of rows) {
|
|
562
|
+
map.set(Number(r.rootId), Number(r.totalSize));
|
|
563
|
+
}
|
|
564
|
+
for (const id of folderIds) {
|
|
565
|
+
if (!map.has(id)) map.set(id, 0);
|
|
566
|
+
}
|
|
567
|
+
} catch (err) {
|
|
568
|
+
logCrudServerError("media folder size aggregate failed", {
|
|
569
|
+
message: err instanceof Error ? err.message : String(err)
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
return map;
|
|
573
|
+
}
|
|
540
574
|
var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
|
|
541
575
|
"date",
|
|
542
576
|
"datetime",
|
|
@@ -601,6 +635,16 @@ function mergeDeletedFalseWhere(repo, where) {
|
|
|
601
635
|
}
|
|
602
636
|
return Object.keys(where).length > 0 ? { ...where, ...d } : d;
|
|
603
637
|
}
|
|
638
|
+
function normalizeProductSku(value) {
|
|
639
|
+
if (value == null) return null;
|
|
640
|
+
const s = String(value).trim();
|
|
641
|
+
return s === "" ? null : s;
|
|
642
|
+
}
|
|
643
|
+
async function assertProductSkuUnique(repo, sku, excludeId) {
|
|
644
|
+
const where = excludeId != null ? { sku, deleted: false, id: (0, import_typeorm.Not)(excludeId) } : { sku, deleted: false };
|
|
645
|
+
const row = await repo.findOne({ where });
|
|
646
|
+
return row == null;
|
|
647
|
+
}
|
|
604
648
|
function buildSoftDeletePayload(meta, deletedBy) {
|
|
605
649
|
const payload = { deleted: true };
|
|
606
650
|
if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
|
|
@@ -823,19 +867,36 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
823
867
|
qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
|
|
824
868
|
}
|
|
825
869
|
if (typeFilter) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
})
|
|
832
|
-
)
|
|
870
|
+
if (typeFilter === "folder") {
|
|
871
|
+
qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
|
|
872
|
+
} else if (typeFilter === "file") {
|
|
873
|
+
qb.andWhere("m.kind = :fileKind", { fileKind: "file" });
|
|
874
|
+
} else if (typeFilter === "image") {
|
|
875
|
+
qb.andWhere("m.mimeType LIKE :imageMimeType", { imageMimeType: "image/%" });
|
|
876
|
+
} else if (typeFilter === "video") {
|
|
877
|
+
qb.andWhere("m.mimeType LIKE :videoMimeType", { videoMimeType: "video/%" });
|
|
878
|
+
} else if (typeFilter === "audio") {
|
|
879
|
+
qb.andWhere("m.mimeType LIKE :audioMimeType", { audioMimeType: "audio/%" });
|
|
880
|
+
} else if (typeFilter === "Document") {
|
|
881
|
+
qb.andWhere("m.mimeType LIKE :documentMimeType", { documentMimeType: "application/pdf" });
|
|
882
|
+
} else if (typeFilter === "application") {
|
|
883
|
+
qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
|
|
884
|
+
}
|
|
833
885
|
}
|
|
834
886
|
const allowedSort = ["filename", "createdAt", "id"];
|
|
835
887
|
const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
|
|
836
888
|
const so = sortOrder === "DESC" ? "DESC" : "ASC";
|
|
837
|
-
qb.orderBy(
|
|
838
|
-
const [
|
|
889
|
+
qb.orderBy(`m.${sf}`, so).skip(skip).take(limit);
|
|
890
|
+
const [rows, total2] = await qb.getManyAndCount();
|
|
891
|
+
const mediaRows = rows;
|
|
892
|
+
const folderIds = mediaRows.filter((m) => m.kind === "folder").map((m) => m.id);
|
|
893
|
+
let data2 = rows;
|
|
894
|
+
if (folderIds.length > 0) {
|
|
895
|
+
const sizeMap = await aggregateMediaFolderFileSizes(dataSource, folderIds);
|
|
896
|
+
data2 = mediaRows.map(
|
|
897
|
+
(m) => m.kind === "folder" ? { ...m, size: sizeMap.get(m.id) ?? 0 } : m
|
|
898
|
+
);
|
|
899
|
+
}
|
|
839
900
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
840
901
|
}
|
|
841
902
|
const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
|
|
@@ -946,6 +1007,20 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
946
1007
|
});
|
|
947
1008
|
return json({ error: "Invalid request payload" }, { status: 400 });
|
|
948
1009
|
}
|
|
1010
|
+
if (resource === "products") {
|
|
1011
|
+
if ("sku" in persistBody) {
|
|
1012
|
+
const skuNorm = normalizeProductSku(persistBody.sku);
|
|
1013
|
+
if (skuNorm) {
|
|
1014
|
+
const ok = await assertProductSkuUnique(repo, skuNorm);
|
|
1015
|
+
if (!ok) {
|
|
1016
|
+
return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
|
|
1017
|
+
}
|
|
1018
|
+
persistBody.sku = skuNorm;
|
|
1019
|
+
} else {
|
|
1020
|
+
persistBody.sku = null;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
949
1024
|
sanitizeBodyForEntity(repo, persistBody);
|
|
950
1025
|
let created;
|
|
951
1026
|
try {
|
|
@@ -1140,7 +1215,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1140
1215
|
relations: ["order", "order.contact", "contact"]
|
|
1141
1216
|
});
|
|
1142
1217
|
if (!payment) return json({ message: "Not found" }, { status: 404 });
|
|
1143
|
-
|
|
1218
|
+
const p = payment;
|
|
1219
|
+
const order = p.order;
|
|
1220
|
+
const orderContact = order?.contact;
|
|
1221
|
+
const contact = p.contact;
|
|
1222
|
+
const customer = orderContact ?? contact;
|
|
1223
|
+
return json({
|
|
1224
|
+
...p,
|
|
1225
|
+
order: order ? {
|
|
1226
|
+
id: order.id,
|
|
1227
|
+
orderNumber: order.orderNumber,
|
|
1228
|
+
contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null
|
|
1229
|
+
} : null,
|
|
1230
|
+
contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
|
|
1231
|
+
});
|
|
1144
1232
|
}
|
|
1145
1233
|
if (resource === "blogs") {
|
|
1146
1234
|
const blog = await repo.findOne({
|
|
@@ -1255,6 +1343,23 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1255
1343
|
delete u.parentId;
|
|
1256
1344
|
delete u.kind;
|
|
1257
1345
|
}
|
|
1346
|
+
if (resource === "products") {
|
|
1347
|
+
const currentRow = await repo.findOne({
|
|
1348
|
+
where: { id: numericId, deleted: false }
|
|
1349
|
+
});
|
|
1350
|
+
if (!currentRow) return json({ message: "Not found" }, { status: 404 });
|
|
1351
|
+
const merged = { ...currentRow, ...updatePayload };
|
|
1352
|
+
const effSku = normalizeProductSku(merged.sku);
|
|
1353
|
+
if (effSku) {
|
|
1354
|
+
const ok = await assertProductSkuUnique(repo, effSku, numericId);
|
|
1355
|
+
if (!ok) {
|
|
1356
|
+
return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
if ("sku" in updatePayload) {
|
|
1360
|
+
updatePayload.sku = effSku;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1258
1363
|
if (Object.keys(updatePayload).length > 0) {
|
|
1259
1364
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
1260
1365
|
await repo.update(numericId, updatePayload);
|
|
@@ -1284,6 +1389,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1284
1389
|
where: { id: numericId, deleted: false }
|
|
1285
1390
|
});
|
|
1286
1391
|
if (!existing) return json({ message: "Not found" }, { status: 404 });
|
|
1392
|
+
if (resource === "contacts") {
|
|
1393
|
+
const result2 = await repo.delete(numericId);
|
|
1394
|
+
if (result2.affected === 0) return json({ message: "Not found" }, { status: 404 });
|
|
1395
|
+
return json({ message: "Deleted successfully" }, { status: 200 });
|
|
1396
|
+
}
|
|
1287
1397
|
let deletedBy = null;
|
|
1288
1398
|
if (getDeletedByUserId) {
|
|
1289
1399
|
try {
|
|
@@ -1831,12 +1941,14 @@ function createDashboardStatsHandler(config) {
|
|
|
1831
1941
|
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
|
|
1832
1942
|
const repo = (name) => entityMap[name] ? dataSource.getRepository(entityMap[name]) : void 0;
|
|
1833
1943
|
const [contactsCount, formsCount, formSubmissionsCount, usersCount, blogsCount, recentContacts, recentSubmissions, contactTypeRows] = await Promise.all([
|
|
1834
|
-
repo("contacts")?.count() ?? 0,
|
|
1944
|
+
repo("contacts")?.count({ where: { deleted: false } }) ?? 0,
|
|
1835
1945
|
repo("forms")?.count({ where: { deleted: false } }) ?? 0,
|
|
1836
1946
|
repo("form_submissions")?.count() ?? 0,
|
|
1837
1947
|
repo("users")?.count({ where: { deleted: false } }) ?? 0,
|
|
1838
1948
|
repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
|
|
1839
|
-
repo("contacts")?.count({
|
|
1949
|
+
repo("contacts")?.count({
|
|
1950
|
+
where: { deleted: false, createdAt: (0, import_typeorm5.MoreThanOrEqual)(sevenDaysAgo) }
|
|
1951
|
+
}) ?? 0,
|
|
1840
1952
|
repo("form_submissions")?.count({ where: { createdAt: (0, import_typeorm5.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
|
|
1841
1953
|
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() ?? []
|
|
1842
1954
|
]);
|
|
@@ -2469,11 +2581,14 @@ function createFormSubmissionHandler(config) {
|
|
|
2469
2581
|
const contactRepo = dataSource.getRepository(entityMap.contacts);
|
|
2470
2582
|
let contact = await contactRepo.findOne({ where: { email: contactData.email } });
|
|
2471
2583
|
if (!contact) {
|
|
2584
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
2472
2585
|
contact = await contactRepo.save(
|
|
2473
2586
|
contactRepo.create({
|
|
2474
2587
|
name: contactData.name,
|
|
2475
2588
|
email: contactData.email,
|
|
2476
|
-
phone: contactData.phone
|
|
2589
|
+
phone: contactData.phone,
|
|
2590
|
+
createdAt,
|
|
2591
|
+
updatedAt: createdAt
|
|
2477
2592
|
})
|
|
2478
2593
|
);
|
|
2479
2594
|
}
|
|
@@ -2629,12 +2744,13 @@ function createUsersApiHandlers(config) {
|
|
|
2629
2744
|
const gid = body.groupId ?? null;
|
|
2630
2745
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
2631
2746
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
2747
|
+
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
2632
2748
|
const newUser = await userRepo().save(
|
|
2633
2749
|
userRepo().create({
|
|
2634
2750
|
name: body.name,
|
|
2635
2751
|
email: body.email,
|
|
2636
2752
|
password: null,
|
|
2637
|
-
blocked
|
|
2753
|
+
blocked,
|
|
2638
2754
|
groupId: gid,
|
|
2639
2755
|
adminAccess
|
|
2640
2756
|
})
|
|
@@ -2649,7 +2765,14 @@ function createUsersApiHandlers(config) {
|
|
|
2649
2765
|
inviteLink,
|
|
2650
2766
|
newUser.name ?? ""
|
|
2651
2767
|
);
|
|
2652
|
-
return json(
|
|
2768
|
+
return json(
|
|
2769
|
+
{
|
|
2770
|
+
message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
|
|
2771
|
+
user: newUser,
|
|
2772
|
+
inviteLink
|
|
2773
|
+
},
|
|
2774
|
+
{ status: 201 }
|
|
2775
|
+
);
|
|
2653
2776
|
} catch {
|
|
2654
2777
|
return json({ error: "Server Error" }, { status: 500 });
|
|
2655
2778
|
}
|
|
@@ -2951,7 +3074,10 @@ function createChatHandlers(config) {
|
|
|
2951
3074
|
const existing = await repo.findOne({ where: { email } });
|
|
2952
3075
|
let contact;
|
|
2953
3076
|
if (!existing) {
|
|
2954
|
-
|
|
3077
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
3078
|
+
contact = await repo.save(
|
|
3079
|
+
repo.create({ name, email, phone, createdAt, updatedAt: createdAt })
|
|
3080
|
+
);
|
|
2955
3081
|
} else {
|
|
2956
3082
|
const row = existing;
|
|
2957
3083
|
if (row.deleted) {
|