@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/index.js CHANGED
@@ -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({ where: { createdAt: MoreThanOrEqual(sevenDaysAgo) } }) ?? 0,
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: true,
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({ message: "User created successfully (blocked until password is set)", user: newUser, inviteLink }, { status: 201 });
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
- contact = await repo.save(repo.create({ name, email, phone }));
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,7 @@ function getNextAuthOptions(config) {
8527
8543
  }
8528
8544
 
8529
8545
  // src/api/crud.ts
8530
- import { Brackets, ILike as ILike2, MoreThan as MoreThan2 } from "typeorm";
8546
+ import { ILike as ILike2, MoreThan as MoreThan2, Not } from "typeorm";
8531
8547
  var CRUD_LOG = "[cms-crud]";
8532
8548
  function logCrudClientError(op, detail) {
8533
8549
  console.warn(CRUD_LOG, op, detail);
@@ -8535,6 +8551,40 @@ function logCrudClientError(op, detail) {
8535
8551
  function logCrudServerError(op, detail) {
8536
8552
  console.error(CRUD_LOG, op, detail);
8537
8553
  }
8554
+ async function aggregateMediaFolderFileSizes(dataSource, folderIds) {
8555
+ const map = /* @__PURE__ */ new Map();
8556
+ if (folderIds.length === 0) return map;
8557
+ try {
8558
+ const rows = await dataSource.query(
8559
+ `
8560
+ WITH RECURSIVE walk AS (
8561
+ SELECT m."parentId" AS root_id, m.id, m.kind, m.size
8562
+ FROM media m
8563
+ WHERE m."parentId" = ANY($1) AND m.deleted = false
8564
+ UNION ALL
8565
+ SELECT w.root_id, m.id, m.kind, m.size
8566
+ FROM walk w
8567
+ INNER JOIN media m ON m."parentId" = w.id AND m.deleted = false
8568
+ )
8569
+ SELECT root_id AS "rootId", COALESCE(SUM(size) FILTER (WHERE kind = 'file'), 0)::bigint AS "totalSize"
8570
+ FROM walk
8571
+ GROUP BY root_id
8572
+ `,
8573
+ [folderIds]
8574
+ );
8575
+ for (const r of rows) {
8576
+ map.set(Number(r.rootId), Number(r.totalSize));
8577
+ }
8578
+ for (const id of folderIds) {
8579
+ if (!map.has(id)) map.set(id, 0);
8580
+ }
8581
+ } catch (err) {
8582
+ logCrudServerError("media folder size aggregate failed", {
8583
+ message: err instanceof Error ? err.message : String(err)
8584
+ });
8585
+ }
8586
+ return map;
8587
+ }
8538
8588
  var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
8539
8589
  "date",
8540
8590
  "datetime",
@@ -8599,6 +8649,16 @@ function mergeDeletedFalseWhere(repo, where) {
8599
8649
  }
8600
8650
  return Object.keys(where).length > 0 ? { ...where, ...d } : d;
8601
8651
  }
8652
+ function normalizeProductSku(value) {
8653
+ if (value == null) return null;
8654
+ const s = String(value).trim();
8655
+ return s === "" ? null : s;
8656
+ }
8657
+ async function assertProductSkuUnique(repo, sku, excludeId) {
8658
+ const where = excludeId != null ? { sku, deleted: false, id: Not(excludeId) } : { sku, deleted: false };
8659
+ const row = await repo.findOne({ where });
8660
+ return row == null;
8661
+ }
8602
8662
  function buildSoftDeletePayload(meta, deletedBy) {
8603
8663
  const payload = { deleted: true };
8604
8664
  if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
@@ -8821,19 +8881,36 @@ function createCrudHandler(dataSource, entityMap, options) {
8821
8881
  qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
8822
8882
  }
8823
8883
  if (typeFilter) {
8824
- qb.andWhere(
8825
- new Brackets((sq) => {
8826
- sq.where("m.kind = :folderKind", { folderKind: "folder" }).orWhere("m.mimeType LIKE :mtp", {
8827
- mtp: `${typeFilter}/%`
8828
- });
8829
- })
8830
- );
8884
+ if (typeFilter === "folder") {
8885
+ qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
8886
+ } else if (typeFilter === "file") {
8887
+ qb.andWhere("m.kind = :fileKind", { fileKind: "file" });
8888
+ } else if (typeFilter === "image") {
8889
+ qb.andWhere("m.mimeType LIKE :imageMimeType", { imageMimeType: "image/%" });
8890
+ } else if (typeFilter === "video") {
8891
+ qb.andWhere("m.mimeType LIKE :videoMimeType", { videoMimeType: "video/%" });
8892
+ } else if (typeFilter === "audio") {
8893
+ qb.andWhere("m.mimeType LIKE :audioMimeType", { audioMimeType: "audio/%" });
8894
+ } else if (typeFilter === "Document") {
8895
+ qb.andWhere("m.mimeType LIKE :documentMimeType", { documentMimeType: "application/pdf" });
8896
+ } else if (typeFilter === "application") {
8897
+ qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
8898
+ }
8831
8899
  }
8832
8900
  const allowedSort = ["filename", "createdAt", "id"];
8833
8901
  const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
8834
8902
  const so = sortOrder === "DESC" ? "DESC" : "ASC";
8835
- qb.orderBy("CASE WHEN m.kind = :fk THEN 0 ELSE 1 END", "ASC").addOrderBy(`m.${sf}`, so).setParameter("fk", "folder").skip(skip).take(limit);
8836
- const [data2, total2] = await qb.getManyAndCount();
8903
+ qb.orderBy(`m.${sf}`, so).skip(skip).take(limit);
8904
+ const [rows, total2] = await qb.getManyAndCount();
8905
+ const mediaRows = rows;
8906
+ const folderIds = mediaRows.filter((m) => m.kind === "folder").map((m) => m.id);
8907
+ let data2 = rows;
8908
+ if (folderIds.length > 0) {
8909
+ const sizeMap = await aggregateMediaFolderFileSizes(dataSource, folderIds);
8910
+ data2 = mediaRows.map(
8911
+ (m) => m.kind === "folder" ? { ...m, size: sizeMap.get(m.id) ?? 0 } : m
8912
+ );
8913
+ }
8837
8914
  return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
8838
8915
  }
8839
8916
  const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
@@ -8944,6 +9021,20 @@ function createCrudHandler(dataSource, entityMap, options) {
8944
9021
  });
8945
9022
  return json({ error: "Invalid request payload" }, { status: 400 });
8946
9023
  }
9024
+ if (resource === "products") {
9025
+ if ("sku" in persistBody) {
9026
+ const skuNorm = normalizeProductSku(persistBody.sku);
9027
+ if (skuNorm) {
9028
+ const ok = await assertProductSkuUnique(repo, skuNorm);
9029
+ if (!ok) {
9030
+ return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
9031
+ }
9032
+ persistBody.sku = skuNorm;
9033
+ } else {
9034
+ persistBody.sku = null;
9035
+ }
9036
+ }
9037
+ }
8947
9038
  sanitizeBodyForEntity(repo, persistBody);
8948
9039
  let created;
8949
9040
  try {
@@ -9138,7 +9229,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9138
9229
  relations: ["order", "order.contact", "contact"]
9139
9230
  });
9140
9231
  if (!payment) return json({ message: "Not found" }, { status: 404 });
9141
- return json(payment);
9232
+ const p = payment;
9233
+ const order = p.order;
9234
+ const orderContact = order?.contact;
9235
+ const contact = p.contact;
9236
+ const customer = orderContact ?? contact;
9237
+ return json({
9238
+ ...p,
9239
+ order: order ? {
9240
+ id: order.id,
9241
+ orderNumber: order.orderNumber,
9242
+ contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null
9243
+ } : null,
9244
+ contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
9245
+ });
9142
9246
  }
9143
9247
  if (resource === "blogs") {
9144
9248
  const blog = await repo.findOne({
@@ -9253,6 +9357,23 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9253
9357
  delete u.parentId;
9254
9358
  delete u.kind;
9255
9359
  }
9360
+ if (resource === "products") {
9361
+ const currentRow = await repo.findOne({
9362
+ where: { id: numericId, deleted: false }
9363
+ });
9364
+ if (!currentRow) return json({ message: "Not found" }, { status: 404 });
9365
+ const merged = { ...currentRow, ...updatePayload };
9366
+ const effSku = normalizeProductSku(merged.sku);
9367
+ if (effSku) {
9368
+ const ok = await assertProductSkuUnique(repo, effSku, numericId);
9369
+ if (!ok) {
9370
+ return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
9371
+ }
9372
+ }
9373
+ if ("sku" in updatePayload) {
9374
+ updatePayload.sku = effSku;
9375
+ }
9376
+ }
9256
9377
  if (Object.keys(updatePayload).length > 0) {
9257
9378
  sanitizeBodyForEntity(repo, updatePayload);
9258
9379
  await repo.update(numericId, updatePayload);
@@ -9282,6 +9403,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9282
9403
  where: { id: numericId, deleted: false }
9283
9404
  });
9284
9405
  if (!existing) return json({ message: "Not found" }, { status: 404 });
9406
+ if (resource === "contacts") {
9407
+ const result2 = await repo.delete(numericId);
9408
+ if (result2.affected === 0) return json({ message: "Not found" }, { status: 404 });
9409
+ return json({ message: "Deleted successfully" }, { status: 200 });
9410
+ }
9285
9411
  let deletedBy = null;
9286
9412
  if (getDeletedByUserId) {
9287
9413
  try {
@@ -12091,7 +12217,7 @@ var DEFAULT_ADMIN_NAV = [
12091
12217
  ];
12092
12218
 
12093
12219
  // src/index.ts
12094
- console.log("\u{1F525} USING LOCAL CMS CORE (index.ts loaded) \u{1F525}");
12220
+ console.log("\u{1F525} USING LOCAL CMS (index.ts loaded) \u{1F525}");
12095
12221
  export {
12096
12222
  ADMIN_GROUP_NAME,
12097
12223
  Address,