@infuro/cms-core 1.0.21 → 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 +165 -21
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +166 -22
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +166 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +167 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -406,7 +406,7 @@ var init_paid_order_erp = __esm({
|
|
|
406
406
|
});
|
|
407
407
|
|
|
408
408
|
// src/api/crud.ts
|
|
409
|
-
import {
|
|
409
|
+
import { ILike, MoreThan, Not } from "typeorm";
|
|
410
410
|
|
|
411
411
|
// src/plugins/erp/erp-contact-sync.ts
|
|
412
412
|
init_erp_queue();
|
|
@@ -484,6 +484,40 @@ function logCrudClientError(op, detail) {
|
|
|
484
484
|
function logCrudServerError(op, detail) {
|
|
485
485
|
console.error(CRUD_LOG, op, detail);
|
|
486
486
|
}
|
|
487
|
+
async function aggregateMediaFolderFileSizes(dataSource, folderIds) {
|
|
488
|
+
const map = /* @__PURE__ */ new Map();
|
|
489
|
+
if (folderIds.length === 0) return map;
|
|
490
|
+
try {
|
|
491
|
+
const rows = await dataSource.query(
|
|
492
|
+
`
|
|
493
|
+
WITH RECURSIVE walk AS (
|
|
494
|
+
SELECT m."parentId" AS root_id, m.id, m.kind, m.size
|
|
495
|
+
FROM media m
|
|
496
|
+
WHERE m."parentId" = ANY($1) AND m.deleted = false
|
|
497
|
+
UNION ALL
|
|
498
|
+
SELECT w.root_id, m.id, m.kind, m.size
|
|
499
|
+
FROM walk w
|
|
500
|
+
INNER JOIN media m ON m."parentId" = w.id AND m.deleted = false
|
|
501
|
+
)
|
|
502
|
+
SELECT root_id AS "rootId", COALESCE(SUM(size) FILTER (WHERE kind = 'file'), 0)::bigint AS "totalSize"
|
|
503
|
+
FROM walk
|
|
504
|
+
GROUP BY root_id
|
|
505
|
+
`,
|
|
506
|
+
[folderIds]
|
|
507
|
+
);
|
|
508
|
+
for (const r of rows) {
|
|
509
|
+
map.set(Number(r.rootId), Number(r.totalSize));
|
|
510
|
+
}
|
|
511
|
+
for (const id of folderIds) {
|
|
512
|
+
if (!map.has(id)) map.set(id, 0);
|
|
513
|
+
}
|
|
514
|
+
} catch (err) {
|
|
515
|
+
logCrudServerError("media folder size aggregate failed", {
|
|
516
|
+
message: err instanceof Error ? err.message : String(err)
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return map;
|
|
520
|
+
}
|
|
487
521
|
var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
|
|
488
522
|
"date",
|
|
489
523
|
"datetime",
|
|
@@ -548,6 +582,16 @@ function mergeDeletedFalseWhere(repo, where) {
|
|
|
548
582
|
}
|
|
549
583
|
return Object.keys(where).length > 0 ? { ...where, ...d } : d;
|
|
550
584
|
}
|
|
585
|
+
function normalizeProductSku(value) {
|
|
586
|
+
if (value == null) return null;
|
|
587
|
+
const s = String(value).trim();
|
|
588
|
+
return s === "" ? null : s;
|
|
589
|
+
}
|
|
590
|
+
async function assertProductSkuUnique(repo, sku, excludeId) {
|
|
591
|
+
const where = excludeId != null ? { sku, deleted: false, id: Not(excludeId) } : { sku, deleted: false };
|
|
592
|
+
const row = await repo.findOne({ where });
|
|
593
|
+
return row == null;
|
|
594
|
+
}
|
|
551
595
|
function buildSoftDeletePayload(meta, deletedBy) {
|
|
552
596
|
const payload = { deleted: true };
|
|
553
597
|
if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
|
|
@@ -770,19 +814,36 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
770
814
|
qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
|
|
771
815
|
}
|
|
772
816
|
if (typeFilter) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
})
|
|
779
|
-
)
|
|
817
|
+
if (typeFilter === "folder") {
|
|
818
|
+
qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
|
|
819
|
+
} else if (typeFilter === "file") {
|
|
820
|
+
qb.andWhere("m.kind = :fileKind", { fileKind: "file" });
|
|
821
|
+
} else if (typeFilter === "image") {
|
|
822
|
+
qb.andWhere("m.mimeType LIKE :imageMimeType", { imageMimeType: "image/%" });
|
|
823
|
+
} else if (typeFilter === "video") {
|
|
824
|
+
qb.andWhere("m.mimeType LIKE :videoMimeType", { videoMimeType: "video/%" });
|
|
825
|
+
} else if (typeFilter === "audio") {
|
|
826
|
+
qb.andWhere("m.mimeType LIKE :audioMimeType", { audioMimeType: "audio/%" });
|
|
827
|
+
} else if (typeFilter === "Document") {
|
|
828
|
+
qb.andWhere("m.mimeType LIKE :documentMimeType", { documentMimeType: "application/pdf" });
|
|
829
|
+
} else if (typeFilter === "application") {
|
|
830
|
+
qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
|
|
831
|
+
}
|
|
780
832
|
}
|
|
781
833
|
const allowedSort = ["filename", "createdAt", "id"];
|
|
782
834
|
const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
|
|
783
835
|
const so = sortOrder === "DESC" ? "DESC" : "ASC";
|
|
784
|
-
qb.orderBy(
|
|
785
|
-
const [
|
|
836
|
+
qb.orderBy(`m.${sf}`, so).skip(skip).take(limit);
|
|
837
|
+
const [rows, total2] = await qb.getManyAndCount();
|
|
838
|
+
const mediaRows = rows;
|
|
839
|
+
const folderIds = mediaRows.filter((m) => m.kind === "folder").map((m) => m.id);
|
|
840
|
+
let data2 = rows;
|
|
841
|
+
if (folderIds.length > 0) {
|
|
842
|
+
const sizeMap = await aggregateMediaFolderFileSizes(dataSource, folderIds);
|
|
843
|
+
data2 = mediaRows.map(
|
|
844
|
+
(m) => m.kind === "folder" ? { ...m, size: sizeMap.get(m.id) ?? 0 } : m
|
|
845
|
+
);
|
|
846
|
+
}
|
|
786
847
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
787
848
|
}
|
|
788
849
|
const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
|
|
@@ -893,6 +954,20 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
893
954
|
});
|
|
894
955
|
return json({ error: "Invalid request payload" }, { status: 400 });
|
|
895
956
|
}
|
|
957
|
+
if (resource === "products") {
|
|
958
|
+
if ("sku" in persistBody) {
|
|
959
|
+
const skuNorm = normalizeProductSku(persistBody.sku);
|
|
960
|
+
if (skuNorm) {
|
|
961
|
+
const ok = await assertProductSkuUnique(repo, skuNorm);
|
|
962
|
+
if (!ok) {
|
|
963
|
+
return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
|
|
964
|
+
}
|
|
965
|
+
persistBody.sku = skuNorm;
|
|
966
|
+
} else {
|
|
967
|
+
persistBody.sku = null;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
896
971
|
sanitizeBodyForEntity(repo, persistBody);
|
|
897
972
|
let created;
|
|
898
973
|
try {
|
|
@@ -1087,7 +1162,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1087
1162
|
relations: ["order", "order.contact", "contact"]
|
|
1088
1163
|
});
|
|
1089
1164
|
if (!payment) return json({ message: "Not found" }, { status: 404 });
|
|
1090
|
-
|
|
1165
|
+
const p = payment;
|
|
1166
|
+
const order = p.order;
|
|
1167
|
+
const orderContact = order?.contact;
|
|
1168
|
+
const contact = p.contact;
|
|
1169
|
+
const customer = orderContact ?? contact;
|
|
1170
|
+
return json({
|
|
1171
|
+
...p,
|
|
1172
|
+
order: order ? {
|
|
1173
|
+
id: order.id,
|
|
1174
|
+
orderNumber: order.orderNumber,
|
|
1175
|
+
contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null
|
|
1176
|
+
} : null,
|
|
1177
|
+
contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
|
|
1178
|
+
});
|
|
1091
1179
|
}
|
|
1092
1180
|
if (resource === "blogs") {
|
|
1093
1181
|
const blog = await repo.findOne({
|
|
@@ -1202,6 +1290,23 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1202
1290
|
delete u.parentId;
|
|
1203
1291
|
delete u.kind;
|
|
1204
1292
|
}
|
|
1293
|
+
if (resource === "products") {
|
|
1294
|
+
const currentRow = await repo.findOne({
|
|
1295
|
+
where: { id: numericId, deleted: false }
|
|
1296
|
+
});
|
|
1297
|
+
if (!currentRow) return json({ message: "Not found" }, { status: 404 });
|
|
1298
|
+
const merged = { ...currentRow, ...updatePayload };
|
|
1299
|
+
const effSku = normalizeProductSku(merged.sku);
|
|
1300
|
+
if (effSku) {
|
|
1301
|
+
const ok = await assertProductSkuUnique(repo, effSku, numericId);
|
|
1302
|
+
if (!ok) {
|
|
1303
|
+
return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if ("sku" in updatePayload) {
|
|
1307
|
+
updatePayload.sku = effSku;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1205
1310
|
if (Object.keys(updatePayload).length > 0) {
|
|
1206
1311
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
1207
1312
|
await repo.update(numericId, updatePayload);
|
|
@@ -1231,6 +1336,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1231
1336
|
where: { id: numericId, deleted: false }
|
|
1232
1337
|
});
|
|
1233
1338
|
if (!existing) return json({ message: "Not found" }, { status: 404 });
|
|
1339
|
+
if (resource === "contacts") {
|
|
1340
|
+
const result2 = await repo.delete(numericId);
|
|
1341
|
+
if (result2.affected === 0) return json({ message: "Not found" }, { status: 404 });
|
|
1342
|
+
return json({ message: "Deleted successfully" }, { status: 200 });
|
|
1343
|
+
}
|
|
1234
1344
|
let deletedBy = null;
|
|
1235
1345
|
if (getDeletedByUserId) {
|
|
1236
1346
|
try {
|
|
@@ -1778,12 +1888,14 @@ function createDashboardStatsHandler(config) {
|
|
|
1778
1888
|
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
|
|
1779
1889
|
const repo = (name) => entityMap[name] ? dataSource.getRepository(entityMap[name]) : void 0;
|
|
1780
1890
|
const [contactsCount, formsCount, formSubmissionsCount, usersCount, blogsCount, recentContacts, recentSubmissions, contactTypeRows] = await Promise.all([
|
|
1781
|
-
repo("contacts")?.count() ?? 0,
|
|
1891
|
+
repo("contacts")?.count({ where: { deleted: false } }) ?? 0,
|
|
1782
1892
|
repo("forms")?.count({ where: { deleted: false } }) ?? 0,
|
|
1783
1893
|
repo("form_submissions")?.count() ?? 0,
|
|
1784
1894
|
repo("users")?.count({ where: { deleted: false } }) ?? 0,
|
|
1785
1895
|
repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
|
|
1786
|
-
repo("contacts")?.count({
|
|
1896
|
+
repo("contacts")?.count({
|
|
1897
|
+
where: { deleted: false, createdAt: MoreThanOrEqual(sevenDaysAgo) }
|
|
1898
|
+
}) ?? 0,
|
|
1787
1899
|
repo("form_submissions")?.count({ where: { createdAt: MoreThanOrEqual(sevenDaysAgo) } }) ?? 0,
|
|
1788
1900
|
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() ?? []
|
|
1789
1901
|
]);
|
|
@@ -2416,11 +2528,14 @@ function createFormSubmissionHandler(config) {
|
|
|
2416
2528
|
const contactRepo = dataSource.getRepository(entityMap.contacts);
|
|
2417
2529
|
let contact = await contactRepo.findOne({ where: { email: contactData.email } });
|
|
2418
2530
|
if (!contact) {
|
|
2531
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
2419
2532
|
contact = await contactRepo.save(
|
|
2420
2533
|
contactRepo.create({
|
|
2421
2534
|
name: contactData.name,
|
|
2422
2535
|
email: contactData.email,
|
|
2423
|
-
phone: contactData.phone
|
|
2536
|
+
phone: contactData.phone,
|
|
2537
|
+
createdAt,
|
|
2538
|
+
updatedAt: createdAt
|
|
2424
2539
|
})
|
|
2425
2540
|
);
|
|
2426
2541
|
}
|
|
@@ -2576,12 +2691,13 @@ function createUsersApiHandlers(config) {
|
|
|
2576
2691
|
const gid = body.groupId ?? null;
|
|
2577
2692
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
2578
2693
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
2694
|
+
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
2579
2695
|
const newUser = await userRepo().save(
|
|
2580
2696
|
userRepo().create({
|
|
2581
2697
|
name: body.name,
|
|
2582
2698
|
email: body.email,
|
|
2583
2699
|
password: null,
|
|
2584
|
-
blocked
|
|
2700
|
+
blocked,
|
|
2585
2701
|
groupId: gid,
|
|
2586
2702
|
adminAccess
|
|
2587
2703
|
})
|
|
@@ -2596,7 +2712,14 @@ function createUsersApiHandlers(config) {
|
|
|
2596
2712
|
inviteLink,
|
|
2597
2713
|
newUser.name ?? ""
|
|
2598
2714
|
);
|
|
2599
|
-
return json(
|
|
2715
|
+
return json(
|
|
2716
|
+
{
|
|
2717
|
+
message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
|
|
2718
|
+
user: newUser,
|
|
2719
|
+
inviteLink
|
|
2720
|
+
},
|
|
2721
|
+
{ status: 201 }
|
|
2722
|
+
);
|
|
2600
2723
|
} catch {
|
|
2601
2724
|
return json({ error: "Server Error" }, { status: 500 });
|
|
2602
2725
|
}
|
|
@@ -2894,15 +3017,36 @@ function createChatHandlers(config) {
|
|
|
2894
3017
|
const email = body?.email?.trim();
|
|
2895
3018
|
if (!name || !email) return json({ error: "name and email required" }, { status: 400 });
|
|
2896
3019
|
const repo = contactRepo();
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
3020
|
+
const phone = body.phone?.trim() || null;
|
|
3021
|
+
const existing = await repo.findOne({ where: { email } });
|
|
3022
|
+
let contact;
|
|
3023
|
+
if (!existing) {
|
|
3024
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
3025
|
+
contact = await repo.save(
|
|
3026
|
+
repo.create({ name, email, phone, createdAt, updatedAt: createdAt })
|
|
3027
|
+
);
|
|
3028
|
+
} else {
|
|
3029
|
+
const row = existing;
|
|
3030
|
+
if (row.deleted) {
|
|
3031
|
+
await repo.update(row.id, {
|
|
3032
|
+
deleted: false,
|
|
3033
|
+
deletedAt: null,
|
|
3034
|
+
deletedBy: null,
|
|
3035
|
+
name,
|
|
3036
|
+
phone
|
|
3037
|
+
});
|
|
3038
|
+
const refreshed = await repo.findOne({ where: { id: row.id } });
|
|
3039
|
+
if (!refreshed) return json({ error: "Failed to identify", detail: "contact missing after reactivate" }, { status: 500 });
|
|
3040
|
+
contact = refreshed;
|
|
3041
|
+
} else {
|
|
3042
|
+
contact = existing;
|
|
3043
|
+
}
|
|
2901
3044
|
}
|
|
2902
3045
|
const convRepoInst = convRepo();
|
|
2903
|
-
const
|
|
3046
|
+
const contactId = contact.id;
|
|
3047
|
+
const conv = await convRepoInst.save(convRepoInst.create({ contactId }));
|
|
2904
3048
|
return json({
|
|
2905
|
-
contactId
|
|
3049
|
+
contactId,
|
|
2906
3050
|
conversationId: conv.id
|
|
2907
3051
|
});
|
|
2908
3052
|
} catch (err) {
|