@infuro/cms-core 1.0.4 → 1.0.5
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 +3205 -1349
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +44 -2
- package/dist/admin.d.ts +44 -2
- package/dist/admin.js +3219 -1359
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +296 -4
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +297 -5
- package/dist/api.js.map +1 -1
- package/dist/{index-DP3LK1XN.d.cts → index-BPnATEXW.d.cts} +3 -0
- package/dist/{index-DP3LK1XN.d.ts → index-BPnATEXW.d.ts} +3 -0
- package/dist/index.cjs +1521 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +270 -13
- package/dist/index.d.ts +270 -13
- package/dist/index.js +1512 -107
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/dist/api.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { A as AnalyticsHandlerConfig, a as AuthHandlersConfig, B as BlogBySlugConfig, C as ChangePasswordConfig, b as CmsApiHandlerConfig, c as CmsGetter, d as CrudHandlerOptions, D as DashboardStatsConfig, E as EntityMap, F as ForgotPasswordConfig, e as FormBySlugConfig, I as InviteAcceptConfig, f as SetPasswordConfig, g as SettingsApiConfig, U as UploadHandlerConfig, h as UserAuthApiConfig, i as UserAvatarConfig, j as UserProfileConfig, k as UsersApiConfig, l as createAnalyticsHandlers, m as createBlogBySlugHandler, n as createChangePasswordHandler, o as createCmsApiHandler, p as createCrudByIdHandler, q as createCrudHandler, r as createDashboardStatsHandler, s as createForgotPasswordHandler, t as createFormBySlugHandler, u as createInviteAcceptHandler, v as createSetPasswordHandler, w as createSettingsApiHandlers, x as createUploadHandler, y as createUserAuthApiRouter, z as createUserAvatarHandler, G as createUserProfileHandler, H as createUsersApiHandlers } from './index-
|
|
1
|
+
export { A as AnalyticsHandlerConfig, a as AuthHandlersConfig, B as BlogBySlugConfig, C as ChangePasswordConfig, b as CmsApiHandlerConfig, c as CmsGetter, d as CrudHandlerOptions, D as DashboardStatsConfig, E as EntityMap, F as ForgotPasswordConfig, e as FormBySlugConfig, I as InviteAcceptConfig, f as SetPasswordConfig, g as SettingsApiConfig, U as UploadHandlerConfig, h as UserAuthApiConfig, i as UserAvatarConfig, j as UserProfileConfig, k as UsersApiConfig, l as createAnalyticsHandlers, m as createBlogBySlugHandler, n as createChangePasswordHandler, o as createCmsApiHandler, p as createCrudByIdHandler, q as createCrudHandler, r as createDashboardStatsHandler, s as createForgotPasswordHandler, t as createFormBySlugHandler, u as createInviteAcceptHandler, v as createSetPasswordHandler, w as createSettingsApiHandlers, x as createUploadHandler, y as createUserAuthApiRouter, z as createUserAvatarHandler, G as createUserProfileHandler, H as createUsersApiHandlers } from './index-BPnATEXW.js';
|
|
2
2
|
import 'typeorm';
|
package/dist/api.js
CHANGED
|
@@ -111,7 +111,7 @@ This link expires in 1 hour.`
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
// src/api/crud.ts
|
|
114
|
-
import { ILike, Like } from "typeorm";
|
|
114
|
+
import { ILike, Like, MoreThan } from "typeorm";
|
|
115
115
|
var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
|
|
116
116
|
"date",
|
|
117
117
|
"datetime",
|
|
@@ -156,14 +156,156 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
156
156
|
if (!resource || !entity) {
|
|
157
157
|
return json({ error: "Invalid resource" }, { status: 400 });
|
|
158
158
|
}
|
|
159
|
-
const repo = dataSource.getRepository(entity);
|
|
160
159
|
const { searchParams } = new URL(req.url);
|
|
161
160
|
const page = Number(searchParams.get("page")) || 1;
|
|
162
|
-
const limit = Number(searchParams.get("limit")) || 10;
|
|
161
|
+
const limit = Math.min(Number(searchParams.get("limit")) || 10, 100);
|
|
163
162
|
const skip = (page - 1) * limit;
|
|
164
|
-
const
|
|
163
|
+
const sortFieldRaw = searchParams.get("sortField") || "createdAt";
|
|
165
164
|
const sortOrder = searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
|
|
166
165
|
const search = searchParams.get("search");
|
|
166
|
+
if (resource === "orders") {
|
|
167
|
+
const repo2 = dataSource.getRepository(entity);
|
|
168
|
+
const allowedSort = ["id", "orderNumber", "contactId", "status", "total", "currency", "createdAt", "updatedAt"];
|
|
169
|
+
const sortField = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "createdAt";
|
|
170
|
+
const sortOrderOrders = searchParams.get("sortOrder") === "asc" ? "ASC" : "DESC";
|
|
171
|
+
const statusFilter = searchParams.get("status")?.trim();
|
|
172
|
+
const dateFrom = searchParams.get("dateFrom")?.trim();
|
|
173
|
+
const dateTo = searchParams.get("dateTo")?.trim();
|
|
174
|
+
const paymentRef = searchParams.get("paymentRef")?.trim();
|
|
175
|
+
let orderIdsFromPayment = null;
|
|
176
|
+
if (paymentRef && entityMap["payments"]) {
|
|
177
|
+
const paymentRepo = dataSource.getRepository(entityMap["payments"]);
|
|
178
|
+
const payments = await paymentRepo.createQueryBuilder("p").select("p.orderId").where("p.externalReference = :ref", { ref: paymentRef }).orWhere("p.metadata->>'razorpayPaymentId' = :ref", { ref: paymentRef }).getRawMany();
|
|
179
|
+
orderIdsFromPayment = payments.map((r) => r.orderId);
|
|
180
|
+
if (orderIdsFromPayment.length === 0) {
|
|
181
|
+
return json({ total: 0, page, limit, totalPages: 0, data: [] });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const qb = repo2.createQueryBuilder("order").leftJoinAndSelect("order.contact", "contact").leftJoinAndSelect("order.items", "items").leftJoinAndSelect("items.product", "product").leftJoinAndSelect("product.collection", "collection").orderBy(`order.${sortField}`, sortOrderOrders).skip(skip).take(limit);
|
|
185
|
+
if (search && typeof search === "string" && search.trim()) {
|
|
186
|
+
const term = `%${search.trim()}%`;
|
|
187
|
+
qb.andWhere(
|
|
188
|
+
"(order.orderNumber ILIKE :term OR contact.name ILIKE :term OR contact.email ILIKE :term)",
|
|
189
|
+
{ term }
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (statusFilter) qb.andWhere("order.status = :status", { status: statusFilter });
|
|
193
|
+
if (dateFrom) qb.andWhere("order.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
194
|
+
if (dateTo) qb.andWhere("order.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
195
|
+
if (orderIdsFromPayment && orderIdsFromPayment.length) qb.andWhere("order.id IN (:...orderIds)", { orderIds: orderIdsFromPayment });
|
|
196
|
+
const [rows, total2] = await qb.getManyAndCount();
|
|
197
|
+
const data2 = rows.map((order) => {
|
|
198
|
+
const contact = order.contact;
|
|
199
|
+
const items = order.items ?? [];
|
|
200
|
+
const itemsSummary = items.map((i) => {
|
|
201
|
+
const label = i.product?.collection?.name ?? i.product?.name ?? "Product";
|
|
202
|
+
return `${label} \xD7 ${i.quantity}`;
|
|
203
|
+
}).join(", ") || "\u2014";
|
|
204
|
+
return {
|
|
205
|
+
...order,
|
|
206
|
+
contact: contact ? { id: contact.id, name: contact.name, email: contact.email, phone: contact.phone } : null,
|
|
207
|
+
itemsSummary
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
211
|
+
}
|
|
212
|
+
if (resource === "payments") {
|
|
213
|
+
const repo2 = dataSource.getRepository(entity);
|
|
214
|
+
const allowedSort = ["id", "orderId", "amount", "currency", "status", "method", "paidAt", "createdAt", "updatedAt"];
|
|
215
|
+
const sortField = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "createdAt";
|
|
216
|
+
const sortOrderPayments = searchParams.get("sortOrder") === "asc" ? "ASC" : "DESC";
|
|
217
|
+
const statusFilter = searchParams.get("status")?.trim();
|
|
218
|
+
const dateFrom = searchParams.get("dateFrom")?.trim();
|
|
219
|
+
const dateTo = searchParams.get("dateTo")?.trim();
|
|
220
|
+
const methodFilter = searchParams.get("method")?.trim();
|
|
221
|
+
const orderNumberParam = searchParams.get("orderNumber")?.trim();
|
|
222
|
+
const qb = repo2.createQueryBuilder("payment").leftJoinAndSelect("payment.order", "ord").leftJoinAndSelect("ord.contact", "orderContact").leftJoinAndSelect("payment.contact", "contact").orderBy(`payment.${sortField}`, sortOrderPayments).skip(skip).take(limit);
|
|
223
|
+
if (search && typeof search === "string" && search.trim()) {
|
|
224
|
+
const term = `%${search.trim()}%`;
|
|
225
|
+
qb.andWhere(
|
|
226
|
+
"(orderContact.name ILIKE :term OR orderContact.email ILIKE :term OR contact.name ILIKE :term OR contact.email ILIKE :term)",
|
|
227
|
+
{ term }
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (statusFilter) qb.andWhere("payment.status = :status", { status: statusFilter });
|
|
231
|
+
if (dateFrom) qb.andWhere("payment.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
232
|
+
if (dateTo) qb.andWhere("payment.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
233
|
+
if (methodFilter) qb.andWhere("payment.method = :method", { method: methodFilter });
|
|
234
|
+
if (orderNumberParam) qb.andWhere("ord.orderNumber ILIKE :orderNumber", { orderNumber: `%${orderNumberParam}%` });
|
|
235
|
+
const [rows, total2] = await qb.getManyAndCount();
|
|
236
|
+
const data2 = rows.map((payment) => {
|
|
237
|
+
const order = payment.order;
|
|
238
|
+
const orderContact = order?.contact;
|
|
239
|
+
const contact = payment.contact;
|
|
240
|
+
const customer = orderContact ?? contact;
|
|
241
|
+
return {
|
|
242
|
+
...payment,
|
|
243
|
+
order: order ? { id: order.id, orderNumber: order.orderNumber, contact: orderContact ? { name: orderContact.name, email: orderContact.email } : null } : null,
|
|
244
|
+
contact: customer ? { id: customer.id, name: customer.name, email: customer.email } : null
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
248
|
+
}
|
|
249
|
+
if (resource === "products") {
|
|
250
|
+
const repo2 = dataSource.getRepository(entity);
|
|
251
|
+
const statusFilter = searchParams.get("status")?.trim();
|
|
252
|
+
const inventory = searchParams.get("inventory")?.trim();
|
|
253
|
+
const productWhere = {};
|
|
254
|
+
if (statusFilter) productWhere.status = statusFilter;
|
|
255
|
+
if (inventory === "in_stock") productWhere.quantity = MoreThan(0);
|
|
256
|
+
if (inventory === "out_of_stock") productWhere.quantity = 0;
|
|
257
|
+
if (search && typeof search === "string" && search.trim()) {
|
|
258
|
+
productWhere.name = ILike(`%${search.trim()}%`);
|
|
259
|
+
}
|
|
260
|
+
const [data2, total2] = await repo2.findAndCount({
|
|
261
|
+
where: Object.keys(productWhere).length ? productWhere : void 0,
|
|
262
|
+
skip,
|
|
263
|
+
take: limit,
|
|
264
|
+
order: { [sortFieldRaw]: sortOrder }
|
|
265
|
+
});
|
|
266
|
+
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
267
|
+
}
|
|
268
|
+
if (resource === "contacts") {
|
|
269
|
+
const repo2 = dataSource.getRepository(entity);
|
|
270
|
+
const allowedSort = ["id", "name", "email", "createdAt", "type"];
|
|
271
|
+
const sortField = allowedSort.includes(sortFieldRaw) ? sortFieldRaw : "createdAt";
|
|
272
|
+
const sortOrderContacts = searchParams.get("sortOrder") === "asc" ? "ASC" : "DESC";
|
|
273
|
+
const typeFilter2 = searchParams.get("type")?.trim();
|
|
274
|
+
const orderIdParam = searchParams.get("orderId")?.trim();
|
|
275
|
+
const includeSummary = searchParams.get("includeSummary") === "1";
|
|
276
|
+
const qb = repo2.createQueryBuilder("contact").orderBy(`contact.${sortField}`, sortOrderContacts).skip(skip).take(limit);
|
|
277
|
+
if (search && typeof search === "string" && search.trim()) {
|
|
278
|
+
const term = `%${search.trim()}%`;
|
|
279
|
+
qb.andWhere("(contact.name ILIKE :term OR contact.email ILIKE :term OR contact.phone ILIKE :term)", { term });
|
|
280
|
+
}
|
|
281
|
+
if (typeFilter2) qb.andWhere("contact.type = :type", { type: typeFilter2 });
|
|
282
|
+
if (orderIdParam) {
|
|
283
|
+
const orderId = Number(orderIdParam);
|
|
284
|
+
if (!Number.isNaN(orderId)) {
|
|
285
|
+
qb.andWhere('contact.id IN (SELECT "contactId" FROM orders WHERE id = :orderId)', { orderId });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (includeSummary && entityMap["orders"] && entityMap["payments"]) {
|
|
289
|
+
qb.loadRelationCountAndMap("contact._orderCount", "contact.orders");
|
|
290
|
+
const [rows, total3] = await qb.getManyAndCount();
|
|
291
|
+
const contactIds = rows.map((c) => c.id);
|
|
292
|
+
const paymentRepo = dataSource.getRepository(entityMap["payments"]);
|
|
293
|
+
const paidByContact = await paymentRepo.createQueryBuilder("p").select("p.contactId", "contactId").addSelect("COALESCE(SUM(CAST(p.amount AS DECIMAL)), 0)", "total").where("p.contactId IN (:...ids)", { ids: contactIds.length ? contactIds : [0] }).andWhere("p.status = :status", { status: "completed" }).groupBy("p.contactId").getRawMany();
|
|
294
|
+
const totalPaidMap = new Map(paidByContact.map((r) => [r.contactId, Number(r.total)]));
|
|
295
|
+
const data3 = rows.map((c) => {
|
|
296
|
+
const { _orderCount, ...rest } = c;
|
|
297
|
+
return {
|
|
298
|
+
...rest,
|
|
299
|
+
orderCount: _orderCount ?? 0,
|
|
300
|
+
totalPaid: totalPaidMap.get(rest.id) ?? 0
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
return json({ total: total3, page, limit, totalPages: Math.ceil(total3 / limit), data: data3 });
|
|
304
|
+
}
|
|
305
|
+
const [data2, total2] = await qb.getManyAndCount();
|
|
306
|
+
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
307
|
+
}
|
|
308
|
+
const repo = dataSource.getRepository(entity);
|
|
167
309
|
const typeFilter = searchParams.get("type");
|
|
168
310
|
let where = {};
|
|
169
311
|
if (resource === "media") {
|
|
@@ -177,7 +319,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
177
319
|
const [data, total] = await repo.findAndCount({
|
|
178
320
|
skip,
|
|
179
321
|
take: limit,
|
|
180
|
-
order: { [
|
|
322
|
+
order: { [sortFieldRaw]: sortOrder },
|
|
181
323
|
where
|
|
182
324
|
});
|
|
183
325
|
return json({ total, page, limit, totalPages: Math.ceil(total / limit), data });
|
|
@@ -197,6 +339,107 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
197
339
|
sanitizeBodyForEntity(repo, body);
|
|
198
340
|
const created = await repo.save(repo.create(body));
|
|
199
341
|
return json(created, { status: 201 });
|
|
342
|
+
},
|
|
343
|
+
async GET_METADATA(req, resource) {
|
|
344
|
+
const authError = await requireAuth(req);
|
|
345
|
+
if (authError) return authError;
|
|
346
|
+
const entity = entityMap[resource];
|
|
347
|
+
if (!resource || !entity) {
|
|
348
|
+
return json({ error: "Invalid resource" }, { status: 400 });
|
|
349
|
+
}
|
|
350
|
+
const repo = dataSource.getRepository(entity);
|
|
351
|
+
const meta = repo.metadata;
|
|
352
|
+
const uniqueFromIndices = /* @__PURE__ */ new Set();
|
|
353
|
+
for (const idx of meta.indices) {
|
|
354
|
+
if (idx.isUnique && idx.columns.length === 1) {
|
|
355
|
+
uniqueFromIndices.add(idx.columns[0].propertyName);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
for (const uniq of meta.uniques) {
|
|
359
|
+
if (uniq.columns.length === 1) {
|
|
360
|
+
uniqueFromIndices.add(uniq.columns[0].propertyName);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const columns = meta.columns.map((col) => ({
|
|
364
|
+
name: col.propertyName,
|
|
365
|
+
type: typeof col.type === "string" ? col.type : col.type?.name ?? "unknown",
|
|
366
|
+
nullable: col.isNullable,
|
|
367
|
+
isUnique: uniqueFromIndices.has(col.propertyName),
|
|
368
|
+
isPrimary: col.isPrimary,
|
|
369
|
+
default: col.default
|
|
370
|
+
}));
|
|
371
|
+
const uniqueColumns = [...uniqueFromIndices];
|
|
372
|
+
return json({ columns, uniqueColumns });
|
|
373
|
+
},
|
|
374
|
+
async BULK_POST(req, resource) {
|
|
375
|
+
const authError = await requireAuth(req);
|
|
376
|
+
if (authError) return authError;
|
|
377
|
+
const entity = entityMap[resource];
|
|
378
|
+
if (!resource || !entity) {
|
|
379
|
+
return json({ error: "Invalid resource" }, { status: 400 });
|
|
380
|
+
}
|
|
381
|
+
const body = await req.json();
|
|
382
|
+
const { records, upsertKey = "id" } = body;
|
|
383
|
+
if (!Array.isArray(records) || records.length === 0) {
|
|
384
|
+
return json({ error: "Records array is required" }, { status: 400 });
|
|
385
|
+
}
|
|
386
|
+
const repo = dataSource.getRepository(entity);
|
|
387
|
+
for (const record of records) {
|
|
388
|
+
sanitizeBodyForEntity(repo, record);
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const result = await repo.upsert(records, {
|
|
392
|
+
conflictPaths: [upsertKey],
|
|
393
|
+
skipUpdateIfNoValuesChanged: true
|
|
394
|
+
});
|
|
395
|
+
return json({
|
|
396
|
+
success: true,
|
|
397
|
+
imported: result.identifiers.length,
|
|
398
|
+
identifiers: result.identifiers
|
|
399
|
+
});
|
|
400
|
+
} catch (error) {
|
|
401
|
+
const message = error instanceof Error ? error.message : "Bulk import failed";
|
|
402
|
+
return json({ error: message }, { status: 400 });
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
async GET_EXPORT(req, resource) {
|
|
406
|
+
const authError = await requireAuth(req);
|
|
407
|
+
if (authError) return authError;
|
|
408
|
+
const entity = entityMap[resource];
|
|
409
|
+
if (!resource || !entity) {
|
|
410
|
+
return json({ error: "Invalid resource" }, { status: 400 });
|
|
411
|
+
}
|
|
412
|
+
const { searchParams } = new URL(req.url);
|
|
413
|
+
const format = searchParams.get("format") || "csv";
|
|
414
|
+
const repo = dataSource.getRepository(entity);
|
|
415
|
+
const meta = repo.metadata;
|
|
416
|
+
const hasDeleted = meta.columns.some((c) => c.propertyName === "deleted");
|
|
417
|
+
const where = hasDeleted ? { deleted: false } : {};
|
|
418
|
+
const data = await repo.find({ where });
|
|
419
|
+
const excludeCols = /* @__PURE__ */ new Set(["deletedAt", "deletedBy", "deleted"]);
|
|
420
|
+
const columns = meta.columns.filter((c) => !excludeCols.has(c.propertyName)).map((c) => c.propertyName);
|
|
421
|
+
if (format === "json") {
|
|
422
|
+
return json(data);
|
|
423
|
+
}
|
|
424
|
+
const escapeCSV = (val) => {
|
|
425
|
+
if (val === null || val === void 0) return "";
|
|
426
|
+
const str = typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
427
|
+
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
|
|
428
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
429
|
+
}
|
|
430
|
+
return str;
|
|
431
|
+
};
|
|
432
|
+
const header = columns.join(",");
|
|
433
|
+
const rows = data.map(
|
|
434
|
+
(row) => columns.map((col) => escapeCSV(row[col])).join(",")
|
|
435
|
+
);
|
|
436
|
+
const csv = [header, ...rows].join("\n");
|
|
437
|
+
return new Response(csv, {
|
|
438
|
+
headers: {
|
|
439
|
+
"Content-Type": "text/csv; charset=utf-8",
|
|
440
|
+
"Content-Disposition": `attachment; filename="${resource}.csv"`
|
|
441
|
+
}
|
|
442
|
+
});
|
|
200
443
|
}
|
|
201
444
|
};
|
|
202
445
|
}
|
|
@@ -209,6 +452,44 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
209
452
|
const entity = entityMap[resource];
|
|
210
453
|
if (!entity) return json({ error: "Invalid resource" }, { status: 400 });
|
|
211
454
|
const repo = dataSource.getRepository(entity);
|
|
455
|
+
if (resource === "orders") {
|
|
456
|
+
const order = await repo.findOne({
|
|
457
|
+
where: { id: Number(id) },
|
|
458
|
+
relations: ["contact", "billingAddress", "shippingAddress", "items", "items.product", "items.product.collection", "payments"]
|
|
459
|
+
});
|
|
460
|
+
if (!order) return json({ message: "Not found" }, { status: 404 });
|
|
461
|
+
return json(order);
|
|
462
|
+
}
|
|
463
|
+
if (resource === "contacts") {
|
|
464
|
+
const contact = await repo.findOne({
|
|
465
|
+
where: { id: Number(id) },
|
|
466
|
+
relations: ["form_submissions", "form_submissions.form", "orders", "payments", "addresses"]
|
|
467
|
+
});
|
|
468
|
+
if (!contact) return json({ message: "Not found" }, { status: 404 });
|
|
469
|
+
const orders = contact.orders ?? [];
|
|
470
|
+
const payments = contact.payments ?? [];
|
|
471
|
+
const totalPaid = payments.filter((p) => p.status === "completed").reduce((sum, p) => sum + Number(p.amount ?? 0), 0);
|
|
472
|
+
const lastOrderAt = orders.length > 0 ? orders.reduce((latest, o) => {
|
|
473
|
+
const t = o.createdAt ? new Date(o.createdAt).getTime() : 0;
|
|
474
|
+
return t > latest ? t : latest;
|
|
475
|
+
}, 0) : null;
|
|
476
|
+
return json({
|
|
477
|
+
...contact,
|
|
478
|
+
summary: {
|
|
479
|
+
totalOrders: orders.length,
|
|
480
|
+
totalPaid,
|
|
481
|
+
lastOrderAt: lastOrderAt ? new Date(lastOrderAt).toISOString() : null
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (resource === "payments") {
|
|
486
|
+
const payment = await repo.findOne({
|
|
487
|
+
where: { id: Number(id) },
|
|
488
|
+
relations: ["order", "order.contact", "contact"]
|
|
489
|
+
});
|
|
490
|
+
if (!payment) return json({ message: "Not found" }, { status: 404 });
|
|
491
|
+
return json(payment);
|
|
492
|
+
}
|
|
212
493
|
const item = await repo.findOne({ where: { id: Number(id) } });
|
|
213
494
|
return item ? json(item) : json({ message: "Not found" }, { status: 404 });
|
|
214
495
|
},
|
|
@@ -1074,6 +1355,17 @@ function createCmsApiHandler(config) {
|
|
|
1074
1355
|
if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
1075
1356
|
const resource = resolveResource(path[0]);
|
|
1076
1357
|
if (!crudResources.includes(resource)) return config.json({ error: "Invalid resource" }, { status: 400 });
|
|
1358
|
+
if (path.length === 2) {
|
|
1359
|
+
if (path[1] === "metadata" && method === "GET") {
|
|
1360
|
+
return crud.GET_METADATA(req, resource);
|
|
1361
|
+
}
|
|
1362
|
+
if (path[1] === "bulk" && method === "POST") {
|
|
1363
|
+
return crud.BULK_POST(req, resource);
|
|
1364
|
+
}
|
|
1365
|
+
if (path[1] === "export" && method === "GET") {
|
|
1366
|
+
return crud.GET_EXPORT(req, resource);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1077
1369
|
if (path.length === 1) {
|
|
1078
1370
|
if (method === "GET") return crud.GET(req, resource);
|
|
1079
1371
|
if (method === "POST") return crud.POST(req, resource);
|