@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.cjs CHANGED
@@ -4388,12 +4388,14 @@ function createDashboardStatsHandler(config) {
4388
4388
  const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
4389
4389
  const repo = (name) => entityMap[name] ? dataSource.getRepository(entityMap[name]) : void 0;
4390
4390
  const [contactsCount, formsCount, formSubmissionsCount, usersCount, blogsCount, recentContacts, recentSubmissions, contactTypeRows] = await Promise.all([
4391
- repo("contacts")?.count() ?? 0,
4391
+ repo("contacts")?.count({ where: { deleted: false } }) ?? 0,
4392
4392
  repo("forms")?.count({ where: { deleted: false } }) ?? 0,
4393
4393
  repo("form_submissions")?.count() ?? 0,
4394
4394
  repo("users")?.count({ where: { deleted: false } }) ?? 0,
4395
4395
  repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
4396
- repo("contacts")?.count({ where: { createdAt: (0, import_typeorm6.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
4396
+ repo("contacts")?.count({
4397
+ where: { deleted: false, createdAt: (0, import_typeorm6.MoreThanOrEqual)(sevenDaysAgo) }
4398
+ }) ?? 0,
4397
4399
  repo("form_submissions")?.count({ where: { createdAt: (0, import_typeorm6.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
4398
4400
  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() ?? []
4399
4401
  ]);
@@ -5026,11 +5028,14 @@ function createFormSubmissionHandler(config) {
5026
5028
  const contactRepo = dataSource.getRepository(entityMap.contacts);
5027
5029
  let contact = await contactRepo.findOne({ where: { email: contactData.email } });
5028
5030
  if (!contact) {
5031
+ const createdAt = /* @__PURE__ */ new Date();
5029
5032
  contact = await contactRepo.save(
5030
5033
  contactRepo.create({
5031
5034
  name: contactData.name,
5032
5035
  email: contactData.email,
5033
- phone: contactData.phone
5036
+ phone: contactData.phone,
5037
+ createdAt,
5038
+ updatedAt: createdAt
5034
5039
  })
5035
5040
  );
5036
5041
  }
@@ -5186,12 +5191,13 @@ function createUsersApiHandlers(config) {
5186
5191
  const gid = body.groupId ?? null;
5187
5192
  const isCustomer = !!(customerG && gid === customerG.id);
5188
5193
  const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
5194
+ const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
5189
5195
  const newUser = await userRepo().save(
5190
5196
  userRepo().create({
5191
5197
  name: body.name,
5192
5198
  email: body.email,
5193
5199
  password: null,
5194
- blocked: true,
5200
+ blocked,
5195
5201
  groupId: gid,
5196
5202
  adminAccess
5197
5203
  })
@@ -5206,7 +5212,14 @@ function createUsersApiHandlers(config) {
5206
5212
  inviteLink,
5207
5213
  newUser.name ?? ""
5208
5214
  );
5209
- return json({ message: "User created successfully (blocked until password is set)", user: newUser, inviteLink }, { status: 201 });
5215
+ return json(
5216
+ {
5217
+ message: blocked ? "User created successfully (blocked until password is set)" : "User created successfully",
5218
+ user: newUser,
5219
+ inviteLink
5220
+ },
5221
+ { status: 201 }
5222
+ );
5210
5223
  } catch {
5211
5224
  return json({ error: "Server Error" }, { status: 500 });
5212
5225
  }
@@ -5504,15 +5517,36 @@ function createChatHandlers(config) {
5504
5517
  const email = body?.email?.trim();
5505
5518
  if (!name || !email) return json({ error: "name and email required" }, { status: 400 });
5506
5519
  const repo = contactRepo();
5507
- let contact = await repo.findOne({ where: { email, deleted: false } });
5508
- if (!contact) {
5509
- const created = repo.create({ name, email, phone: body.phone?.trim() || null });
5510
- contact = await repo.save(created);
5520
+ const phone = body.phone?.trim() || null;
5521
+ const existing = await repo.findOne({ where: { email } });
5522
+ let contact;
5523
+ if (!existing) {
5524
+ const createdAt = /* @__PURE__ */ new Date();
5525
+ contact = await repo.save(
5526
+ repo.create({ name, email, phone, createdAt, updatedAt: createdAt })
5527
+ );
5528
+ } else {
5529
+ const row = existing;
5530
+ if (row.deleted) {
5531
+ await repo.update(row.id, {
5532
+ deleted: false,
5533
+ deletedAt: null,
5534
+ deletedBy: null,
5535
+ name,
5536
+ phone
5537
+ });
5538
+ const refreshed = await repo.findOne({ where: { id: row.id } });
5539
+ if (!refreshed) return json({ error: "Failed to identify", detail: "contact missing after reactivate" }, { status: 500 });
5540
+ contact = refreshed;
5541
+ } else {
5542
+ contact = existing;
5543
+ }
5511
5544
  }
5512
5545
  const convRepoInst = convRepo();
5513
- const conv = await convRepoInst.save(convRepoInst.create({ contactId: contact.id }));
5546
+ const contactId = contact.id;
5547
+ const conv = await convRepoInst.save(convRepoInst.create({ contactId }));
5514
5548
  return json({
5515
- contactId: contact.id,
5549
+ contactId,
5516
5550
  conversationId: conv.id
5517
5551
  });
5518
5552
  } catch (err) {
@@ -8689,6 +8723,40 @@ function logCrudClientError(op, detail) {
8689
8723
  function logCrudServerError(op, detail) {
8690
8724
  console.error(CRUD_LOG, op, detail);
8691
8725
  }
8726
+ async function aggregateMediaFolderFileSizes(dataSource, folderIds) {
8727
+ const map = /* @__PURE__ */ new Map();
8728
+ if (folderIds.length === 0) return map;
8729
+ try {
8730
+ const rows = await dataSource.query(
8731
+ `
8732
+ WITH RECURSIVE walk AS (
8733
+ SELECT m."parentId" AS root_id, m.id, m.kind, m.size
8734
+ FROM media m
8735
+ WHERE m."parentId" = ANY($1) AND m.deleted = false
8736
+ UNION ALL
8737
+ SELECT w.root_id, m.id, m.kind, m.size
8738
+ FROM walk w
8739
+ INNER JOIN media m ON m."parentId" = w.id AND m.deleted = false
8740
+ )
8741
+ SELECT root_id AS "rootId", COALESCE(SUM(size) FILTER (WHERE kind = 'file'), 0)::bigint AS "totalSize"
8742
+ FROM walk
8743
+ GROUP BY root_id
8744
+ `,
8745
+ [folderIds]
8746
+ );
8747
+ for (const r of rows) {
8748
+ map.set(Number(r.rootId), Number(r.totalSize));
8749
+ }
8750
+ for (const id of folderIds) {
8751
+ if (!map.has(id)) map.set(id, 0);
8752
+ }
8753
+ } catch (err) {
8754
+ logCrudServerError("media folder size aggregate failed", {
8755
+ message: err instanceof Error ? err.message : String(err)
8756
+ });
8757
+ }
8758
+ return map;
8759
+ }
8692
8760
  var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
8693
8761
  "date",
8694
8762
  "datetime",
@@ -8753,6 +8821,16 @@ function mergeDeletedFalseWhere(repo, where) {
8753
8821
  }
8754
8822
  return Object.keys(where).length > 0 ? { ...where, ...d } : d;
8755
8823
  }
8824
+ function normalizeProductSku(value) {
8825
+ if (value == null) return null;
8826
+ const s = String(value).trim();
8827
+ return s === "" ? null : s;
8828
+ }
8829
+ async function assertProductSkuUnique(repo, sku, excludeId) {
8830
+ const where = excludeId != null ? { sku, deleted: false, id: (0, import_typeorm46.Not)(excludeId) } : { sku, deleted: false };
8831
+ const row = await repo.findOne({ where });
8832
+ return row == null;
8833
+ }
8756
8834
  function buildSoftDeletePayload(meta, deletedBy) {
8757
8835
  const payload = { deleted: true };
8758
8836
  if (meta.columns.some((c) => c.propertyName === "deletedAt")) {
@@ -8975,19 +9053,36 @@ function createCrudHandler(dataSource, entityMap, options) {
8975
9053
  qb.andWhere("m.filename ILIKE :search", { search: `%${search.trim()}%` });
8976
9054
  }
8977
9055
  if (typeFilter) {
8978
- qb.andWhere(
8979
- new import_typeorm46.Brackets((sq) => {
8980
- sq.where("m.kind = :folderKind", { folderKind: "folder" }).orWhere("m.mimeType LIKE :mtp", {
8981
- mtp: `${typeFilter}/%`
8982
- });
8983
- })
8984
- );
9056
+ if (typeFilter === "folder") {
9057
+ qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
9058
+ } else if (typeFilter === "file") {
9059
+ qb.andWhere("m.kind = :fileKind", { fileKind: "file" });
9060
+ } else if (typeFilter === "image") {
9061
+ qb.andWhere("m.mimeType LIKE :imageMimeType", { imageMimeType: "image/%" });
9062
+ } else if (typeFilter === "video") {
9063
+ qb.andWhere("m.mimeType LIKE :videoMimeType", { videoMimeType: "video/%" });
9064
+ } else if (typeFilter === "audio") {
9065
+ qb.andWhere("m.mimeType LIKE :audioMimeType", { audioMimeType: "audio/%" });
9066
+ } else if (typeFilter === "Document") {
9067
+ qb.andWhere("m.mimeType LIKE :documentMimeType", { documentMimeType: "application/pdf" });
9068
+ } else if (typeFilter === "application") {
9069
+ qb.andWhere("m.kind = :folderKind", { folderKind: "folder" });
9070
+ }
8985
9071
  }
8986
9072
  const allowedSort = ["filename", "createdAt", "id"];
8987
9073
  const sf = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "filename";
8988
9074
  const so = sortOrder === "DESC" ? "DESC" : "ASC";
8989
- qb.orderBy("CASE WHEN m.kind = :fk THEN 0 ELSE 1 END", "ASC").addOrderBy(`m.${sf}`, so).setParameter("fk", "folder").skip(skip).take(limit);
8990
- const [data2, total2] = await qb.getManyAndCount();
9075
+ qb.orderBy(`m.${sf}`, so).skip(skip).take(limit);
9076
+ const [rows, total2] = await qb.getManyAndCount();
9077
+ const mediaRows = rows;
9078
+ const folderIds = mediaRows.filter((m) => m.kind === "folder").map((m) => m.id);
9079
+ let data2 = rows;
9080
+ if (folderIds.length > 0) {
9081
+ const sizeMap = await aggregateMediaFolderFileSizes(dataSource, folderIds);
9082
+ data2 = mediaRows.map(
9083
+ (m) => m.kind === "folder" ? { ...m, size: sizeMap.get(m.id) ?? 0 } : m
9084
+ );
9085
+ }
8991
9086
  return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
8992
9087
  }
8993
9088
  const sortField = columnNames.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
@@ -9098,6 +9193,20 @@ function createCrudHandler(dataSource, entityMap, options) {
9098
9193
  });
9099
9194
  return json({ error: "Invalid request payload" }, { status: 400 });
9100
9195
  }
9196
+ if (resource === "products") {
9197
+ if ("sku" in persistBody) {
9198
+ const skuNorm = normalizeProductSku(persistBody.sku);
9199
+ if (skuNorm) {
9200
+ const ok = await assertProductSkuUnique(repo, skuNorm);
9201
+ if (!ok) {
9202
+ return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
9203
+ }
9204
+ persistBody.sku = skuNorm;
9205
+ } else {
9206
+ persistBody.sku = null;
9207
+ }
9208
+ }
9209
+ }
9101
9210
  sanitizeBodyForEntity(repo, persistBody);
9102
9211
  let created;
9103
9212
  try {
@@ -9292,7 +9401,20 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9292
9401
  relations: ["order", "order.contact", "contact"]
9293
9402
  });
9294
9403
  if (!payment) return json({ message: "Not found" }, { status: 404 });
9295
- return json(payment);
9404
+ const p = payment;
9405
+ const order = p.order;
9406
+ const orderContact = order?.contact;
9407
+ const contact = p.contact;
9408
+ const customer = orderContact ?? contact;
9409
+ return json({
9410
+ ...p,
9411
+ order: order ? {
9412
+ id: order.id,
9413
+ orderNumber: order.orderNumber,
9414
+ contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null
9415
+ } : null,
9416
+ contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
9417
+ });
9296
9418
  }
9297
9419
  if (resource === "blogs") {
9298
9420
  const blog = await repo.findOne({
@@ -9407,6 +9529,23 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9407
9529
  delete u.parentId;
9408
9530
  delete u.kind;
9409
9531
  }
9532
+ if (resource === "products") {
9533
+ const currentRow = await repo.findOne({
9534
+ where: { id: numericId, deleted: false }
9535
+ });
9536
+ if (!currentRow) return json({ message: "Not found" }, { status: 404 });
9537
+ const merged = { ...currentRow, ...updatePayload };
9538
+ const effSku = normalizeProductSku(merged.sku);
9539
+ if (effSku) {
9540
+ const ok = await assertProductSkuUnique(repo, effSku, numericId);
9541
+ if (!ok) {
9542
+ return json({ error: "SKU already exists. Please use a unique SKU." }, { status: 400 });
9543
+ }
9544
+ }
9545
+ if ("sku" in updatePayload) {
9546
+ updatePayload.sku = effSku;
9547
+ }
9548
+ }
9410
9549
  if (Object.keys(updatePayload).length > 0) {
9411
9550
  sanitizeBodyForEntity(repo, updatePayload);
9412
9551
  await repo.update(numericId, updatePayload);
@@ -9436,6 +9575,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
9436
9575
  where: { id: numericId, deleted: false }
9437
9576
  });
9438
9577
  if (!existing) return json({ message: "Not found" }, { status: 404 });
9578
+ if (resource === "contacts") {
9579
+ const result2 = await repo.delete(numericId);
9580
+ if (result2.affected === 0) return json({ message: "Not found" }, { status: 404 });
9581
+ return json({ message: "Deleted successfully" }, { status: 200 });
9582
+ }
9439
9583
  let deletedBy = null;
9440
9584
  if (getDeletedByUserId) {
9441
9585
  try {
@@ -12245,7 +12389,7 @@ var DEFAULT_ADMIN_NAV = [
12245
12389
  ];
12246
12390
 
12247
12391
  // src/index.ts
12248
- console.log("\u{1F525} USING LOCAL CMS CORE (index.ts loaded) \u{1F525}");
12392
+ console.log("\u{1F525} USING LOCAL CMS (index.ts loaded) \u{1F525}");
12249
12393
  // Annotate the CommonJS export names for ESM import in node:
12250
12394
  0 && (module.exports = {
12251
12395
  ADMIN_GROUP_NAME,