@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/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
  }
@@ -5323,15 +5336,36 @@ function createChatHandlers(config) {
5323
5336
  const email = body?.email?.trim();
5324
5337
  if (!name || !email) return json({ error: "name and email required" }, { status: 400 });
5325
5338
  const repo = contactRepo();
5326
- let contact = await repo.findOne({ where: { email, deleted: false } });
5327
- if (!contact) {
5328
- const created = repo.create({ name, email, phone: body.phone?.trim() || null });
5329
- contact = await repo.save(created);
5339
+ const phone = body.phone?.trim() || null;
5340
+ const existing = await repo.findOne({ where: { email } });
5341
+ let contact;
5342
+ if (!existing) {
5343
+ const createdAt = /* @__PURE__ */ new Date();
5344
+ contact = await repo.save(
5345
+ repo.create({ name, email, phone, createdAt, updatedAt: createdAt })
5346
+ );
5347
+ } else {
5348
+ const row = existing;
5349
+ if (row.deleted) {
5350
+ await repo.update(row.id, {
5351
+ deleted: false,
5352
+ deletedAt: null,
5353
+ deletedBy: null,
5354
+ name,
5355
+ phone
5356
+ });
5357
+ const refreshed = await repo.findOne({ where: { id: row.id } });
5358
+ if (!refreshed) return json({ error: "Failed to identify", detail: "contact missing after reactivate" }, { status: 500 });
5359
+ contact = refreshed;
5360
+ } else {
5361
+ contact = existing;
5362
+ }
5330
5363
  }
5331
5364
  const convRepoInst = convRepo();
5332
- const conv = await convRepoInst.save(convRepoInst.create({ contactId: contact.id }));
5365
+ const contactId = contact.id;
5366
+ const conv = await convRepoInst.save(convRepoInst.create({ contactId }));
5333
5367
  return json({
5334
- contactId: contact.id,
5368
+ contactId,
5335
5369
  conversationId: conv.id
5336
5370
  });
5337
5371
  } catch (err) {
@@ -8509,7 +8543,7 @@ function getNextAuthOptions(config) {
8509
8543
  }
8510
8544
 
8511
8545
  // src/api/crud.ts
8512
- import { Brackets, ILike as ILike2, MoreThan as MoreThan2 } from "typeorm";
8546
+ import { ILike as ILike2, MoreThan as MoreThan2, Not } from "typeorm";
8513
8547
  var CRUD_LOG = "[cms-crud]";
8514
8548
  function logCrudClientError(op, detail) {
8515
8549
  console.warn(CRUD_LOG, op, detail);
@@ -8517,6 +8551,40 @@ function logCrudClientError(op, detail) {
8517
8551
  function logCrudServerError(op, detail) {
8518
8552
  console.error(CRUD_LOG, op, detail);
8519
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
+ }
8520
8588
  var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
8521
8589
  "date",
8522
8590
  "datetime",
@@ -8581,6 +8649,16 @@ function mergeDeletedFalseWhere(repo, where) {
8581
8649
  }
8582
8650
  return Object.keys(where).length > 0 ? { ...where, ...d } : d;
8583
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
+ }
8584
8662
  function buildSoftDeletePayload(meta, deletedBy) {
8585
8663
  const payload = { deleted: true };
8586
8664
  if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
@@ -8803,19 +8881,36 @@ function createCrudHandler(dataSource, entityMap, options) {
8803
8881
  qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
8804
8882
  }
8805
8883
  if (typeFilter) {
8806
- qb.andWhere(
8807
- new Brackets((sq) => {
8808
- sq.where("m.kind = :folderKind", { folderKind: "folder" }).orWhere("m.mimeType LIKE :mtp", {
8809
- mtp: `${typeFilter}/%`
8810
- });
8811
- })
8812
- );
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
+ }
8813
8899
  }
8814
8900
  const allowedSort = ["filename", "createdAt", "id"];
8815
8901
  const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
8816
8902
  const so = sortOrder === "DESC" ? "DESC" : "ASC";
8817
- qb.orderBy("CASE WHEN m.kind = :fk THEN 0 ELSE 1 END", "ASC").addOrderBy(`m.${sf}`, so).setParameter("fk", "folder").skip(skip).take(limit);
8818
- 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
+ }
8819
8914
  return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
8820
8915
  }
8821
8916
  const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
@@ -8926,6 +9021,20 @@ function createCrudHandler(dataSource, entityMap, options) {
8926
9021
  });
8927
9022
  return json({ error: "Invalid request payload" }, { status: 400 });
8928
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
+ }
8929
9038
  sanitizeBodyForEntity(repo, persistBody);
8930
9039
  let created;
8931
9040
  try {
@@ -9120,7 +9229,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9120
9229
  relations: ["order", "order.contact", "contact"]
9121
9230
  });
9122
9231
  if (!payment) return json({ message: "Not found" }, { status: 404 });
9123
- 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
+ });
9124
9246
  }
9125
9247
  if (resource === "blogs") {
9126
9248
  const blog = await repo.findOne({
@@ -9235,6 +9357,23 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9235
9357
  delete u.parentId;
9236
9358
  delete u.kind;
9237
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
+ }
9238
9377
  if (Object.keys(updatePayload).length > 0) {
9239
9378
  sanitizeBodyForEntity(repo, updatePayload);
9240
9379
  await repo.update(numericId, updatePayload);
@@ -9264,6 +9403,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9264
9403
  where: { id: numericId, deleted: false }
9265
9404
  });
9266
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
+ }
9267
9411
  let deletedBy = null;
9268
9412
  if (getDeletedByUserId) {
9269
9413
  try {
@@ -12073,7 +12217,7 @@ var DEFAULT_ADMIN_NAV = [
12073
12217
  ];
12074
12218
 
12075
12219
  // src/index.ts
12076
- 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}");
12077
12221
  export {
12078
12222
  ADMIN_GROUP_NAME,
12079
12223
  Address,