@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/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 { Brackets, ILike, MoreThan } from "typeorm";
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
- qb.andWhere(
774
- new Brackets((sq) => {
775
- sq.where("m.kind = :folderKind", { folderKind: "folder" }).orWhere("m.mimeType LIKE :mtp", {
776
- mtp: `${typeFilter}/%`
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("CASE WHEN m.kind = :fk THEN 0 ELSE 1 END", "ASC").addOrderBy(`m.${sf}`, so).setParameter("fk", "folder").skip(skip).take(limit);
785
- const [data2, total2] = await qb.getManyAndCount();
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
- return json(payment);
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({ where: { createdAt: MoreThanOrEqual(sevenDaysAgo) } }) ?? 0,
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: true,
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({ message: "User created successfully (blocked until password is set)", user: newUser, inviteLink }, { status: 201 });
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
- let contact = await repo.findOne({ where: { email, deleted: false } });
2898
- if (!contact) {
2899
- const created = repo.create({ name, email, phone: body.phone?.trim() || null });
2900
- contact = await repo.save(created);
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 conv = await convRepoInst.save(convRepoInst.create({ contactId: contact.id }));
3046
+ const contactId = contact.id;
3047
+ const conv = await convRepoInst.save(convRepoInst.create({ contactId }));
2904
3048
  return json({
2905
- contactId: contact.id,
3049
+ contactId,
2906
3050
  conversationId: conv.id
2907
3051
  });
2908
3052
  } catch (err) {