@infuro/cms-core 1.0.23 → 1.0.25
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 +5161 -3307
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +52 -11
- package/dist/admin.d.ts +52 -11
- package/dist/admin.js +5095 -3238
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +595 -60
- 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 +603 -68
- package/dist/api.js.map +1 -1
- package/dist/auth.cjs +9 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.js +9 -1
- package/dist/auth.js.map +1 -1
- package/dist/cli.cjs +7 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/{index-BGAh4fPQ.d.cts → index--vbixpxE.d.cts} +17 -2
- package/dist/{index-Cnwh7B3r.d.ts → index-DMJgi-fy.d.ts} +17 -2
- package/dist/index.cjs +683 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +684 -76
- package/dist/index.js.map +1 -1
- package/package.json +130 -129
package/dist/index.cjs
CHANGED
|
@@ -38,10 +38,52 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
// src/plugins/erp/erp-log.ts
|
|
42
|
+
function logErp(event, detail) {
|
|
43
|
+
if (detail && Object.keys(detail).length) console.info(ERP_LOG, event, detail);
|
|
44
|
+
else console.info(ERP_LOG, event);
|
|
45
|
+
}
|
|
46
|
+
function warnErp(event, detail) {
|
|
47
|
+
console.warn(ERP_LOG, event, detail);
|
|
48
|
+
}
|
|
49
|
+
function errorErp(event, detail) {
|
|
50
|
+
console.error(ERP_LOG, event, detail);
|
|
51
|
+
}
|
|
52
|
+
function erpSafeWebhookUrl(url) {
|
|
53
|
+
try {
|
|
54
|
+
const u = new URL(url);
|
|
55
|
+
return `${u.origin}${u.pathname}`;
|
|
56
|
+
} catch {
|
|
57
|
+
return "(invalid webhook URL)";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
var ERP_LOG;
|
|
61
|
+
var init_erp_log = __esm({
|
|
62
|
+
"src/plugins/erp/erp-log.ts"() {
|
|
63
|
+
"use strict";
|
|
64
|
+
ERP_LOG = "[webcore:erp]";
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
41
68
|
// src/plugins/erp/erp-queue.ts
|
|
69
|
+
function queuePayloadSummary(payload) {
|
|
70
|
+
if (payload.kind === "order") {
|
|
71
|
+
const o = payload.order;
|
|
72
|
+
return {
|
|
73
|
+
kind: payload.kind,
|
|
74
|
+
platformOrderId: o.platformOrderId ?? o.platformOrderNumber,
|
|
75
|
+
itemCount: Array.isArray(o.items) ? o.items.length : 0
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { kind: payload.kind };
|
|
79
|
+
}
|
|
42
80
|
async function queueErp(cms, payload) {
|
|
43
81
|
const queue = cms.getPlugin("queue");
|
|
44
|
-
if (!queue)
|
|
82
|
+
if (!queue) {
|
|
83
|
+
warnErp("queue:add_skipped", { reason: "queue_plugin_missing", ...queuePayloadSummary(payload) });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
logErp("queue:add", { job: ERP_QUEUE_NAME, ...queuePayloadSummary(payload) });
|
|
45
87
|
await queue.add(ERP_QUEUE_NAME, payload);
|
|
46
88
|
}
|
|
47
89
|
function registerErpQueueProcessor(cms) {
|
|
@@ -49,18 +91,31 @@ function registerErpQueueProcessor(cms) {
|
|
|
49
91
|
if (!queue) return;
|
|
50
92
|
queue.registerProcessor(ERP_QUEUE_NAME, async (data) => {
|
|
51
93
|
const erp = cms.getPlugin("erp");
|
|
52
|
-
if (!erp)
|
|
94
|
+
if (!erp) {
|
|
95
|
+
warnErp("queue:processor_skip", { reason: "erp_plugin_missing" });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
53
98
|
const payload = data;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
99
|
+
logErp("queue:job_start", queuePayloadSummary(payload));
|
|
100
|
+
try {
|
|
101
|
+
if (payload.kind === "lead") {
|
|
102
|
+
await erp.submission.submitContact(payload.contact);
|
|
103
|
+
} else if (payload.kind === "formOpportunity") {
|
|
104
|
+
await erp.submission.submitFormOpportunity(payload.contact);
|
|
105
|
+
} else if (payload.kind === "createContact") {
|
|
106
|
+
await erp.submission.submitCreateContact(payload.contact);
|
|
107
|
+
} else if (payload.kind === "order") {
|
|
108
|
+
await erp.submission.submitOrder(payload.order);
|
|
109
|
+
} else if (payload.kind === "productUpsert") {
|
|
110
|
+
await erp.submission.submitProductUpsert(payload.product);
|
|
111
|
+
}
|
|
112
|
+
logErp("queue:job_done", queuePayloadSummary(payload));
|
|
113
|
+
} catch (e) {
|
|
114
|
+
errorErp("queue:job_failed", {
|
|
115
|
+
...queuePayloadSummary(payload),
|
|
116
|
+
message: e instanceof Error ? e.message : String(e)
|
|
117
|
+
});
|
|
118
|
+
throw e;
|
|
64
119
|
}
|
|
65
120
|
});
|
|
66
121
|
}
|
|
@@ -68,6 +123,7 @@ var ERP_QUEUE_NAME;
|
|
|
68
123
|
var init_erp_queue = __esm({
|
|
69
124
|
"src/plugins/erp/erp-queue.ts"() {
|
|
70
125
|
"use strict";
|
|
126
|
+
init_erp_log();
|
|
71
127
|
ERP_QUEUE_NAME = "erp";
|
|
72
128
|
}
|
|
73
129
|
});
|
|
@@ -119,21 +175,36 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
119
175
|
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
120
176
|
for (const row of cfgRows) {
|
|
121
177
|
const r = row;
|
|
122
|
-
if (r.key === "enabled" && r.value === "false")
|
|
178
|
+
if (r.key === "enabled" && r.value === "false") {
|
|
179
|
+
logErp("paid-order:skip", { orderId, reason: "erp_config_disabled" });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (!cms.getPlugin("erp")) {
|
|
184
|
+
logErp("paid-order:skip", { orderId, reason: "erp_plugin_missing" });
|
|
185
|
+
return;
|
|
123
186
|
}
|
|
124
|
-
if (!cms.getPlugin("erp")) return;
|
|
125
187
|
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
126
188
|
const ord = await orderRepo.findOne({
|
|
127
189
|
where: { id: orderId },
|
|
128
190
|
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
129
191
|
});
|
|
130
|
-
if (!ord)
|
|
192
|
+
if (!ord) {
|
|
193
|
+
logErp("paid-order:skip", { orderId, reason: "order_not_found" });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
131
196
|
const o = ord;
|
|
132
197
|
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
133
|
-
if (!okKind)
|
|
198
|
+
if (!okKind) {
|
|
199
|
+
logErp("paid-order:skip", { orderId, reason: "order_kind_not_sale", orderKind: o.orderKind });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
134
202
|
const rawPayments = o.payments ?? [];
|
|
135
203
|
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
136
|
-
if (!completedPayments.length)
|
|
204
|
+
if (!completedPayments.length) {
|
|
205
|
+
logErp("paid-order:skip", { orderId, reason: "no_completed_payments" });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
137
208
|
const rawItems = o.items ?? [];
|
|
138
209
|
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
139
210
|
const p = it.product;
|
|
@@ -152,7 +223,10 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
152
223
|
type: itemType
|
|
153
224
|
};
|
|
154
225
|
});
|
|
155
|
-
if (!lines.length)
|
|
226
|
+
if (!lines.length) {
|
|
227
|
+
logErp("paid-order:skip", { orderId, reason: "no_line_items_with_product" });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
156
230
|
const contact = o.contact;
|
|
157
231
|
const orderTotalMajor = Number(o.total);
|
|
158
232
|
const paymentDtos = completedPayments.length === 1 && Number.isFinite(orderTotalMajor) ? [paymentRowToWebhookDto(completedPayments[0], orderTotalMajor)] : completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
@@ -174,13 +248,28 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
174
248
|
payments: paymentDtos,
|
|
175
249
|
metadata: { ...baseMeta, source: "storefront" }
|
|
176
250
|
};
|
|
251
|
+
logErp("paid-order:payload_built", {
|
|
252
|
+
orderId,
|
|
253
|
+
platformOrderId: orderDto.platformOrderId,
|
|
254
|
+
status: orderDto.status,
|
|
255
|
+
itemCount: lines.length,
|
|
256
|
+
skus: lines.map((l) => l.sku),
|
|
257
|
+
paymentCount: paymentDtos.length,
|
|
258
|
+
paymentIds: paymentDtos.map((p) => p.id),
|
|
259
|
+
total: orderTotalMajor
|
|
260
|
+
});
|
|
177
261
|
await queueErp(cms, { kind: "order", order: orderDto });
|
|
178
|
-
} catch {
|
|
262
|
+
} catch (e) {
|
|
263
|
+
errorErp("paid-order:enqueue_failed", {
|
|
264
|
+
orderId,
|
|
265
|
+
message: e instanceof Error ? e.message : String(e)
|
|
266
|
+
});
|
|
179
267
|
}
|
|
180
268
|
}
|
|
181
269
|
var init_paid_order_erp = __esm({
|
|
182
270
|
"src/plugins/erp/paid-order-erp.ts"() {
|
|
183
271
|
"use strict";
|
|
272
|
+
init_erp_log();
|
|
184
273
|
init_erp_queue();
|
|
185
274
|
}
|
|
186
275
|
});
|
|
@@ -816,6 +905,7 @@ async function createCmsApp(options) {
|
|
|
816
905
|
}
|
|
817
906
|
|
|
818
907
|
// src/plugins/erp/erp-submission.ts
|
|
908
|
+
init_erp_log();
|
|
819
909
|
var ERPSubmissionService = class {
|
|
820
910
|
webhookUrl;
|
|
821
911
|
webhookJwt;
|
|
@@ -895,7 +985,29 @@ var ERPSubmissionService = class {
|
|
|
895
985
|
};
|
|
896
986
|
return this.postWebhookJson(envelope);
|
|
897
987
|
}
|
|
988
|
+
summarizeWebhookBody(body) {
|
|
989
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
990
|
+
return { bodyKind: typeof body };
|
|
991
|
+
}
|
|
992
|
+
const o = body;
|
|
993
|
+
const out = { event_type: o.event_type, timestamp: o.timestamp };
|
|
994
|
+
const data = o.data;
|
|
995
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
996
|
+
const d = data;
|
|
997
|
+
out.dataKeys = Object.keys(d);
|
|
998
|
+
out.platformOrderId = d.platformOrderId ?? d.platformOrderNumber;
|
|
999
|
+
out.itemCount = Array.isArray(d.items) ? d.items.length : void 0;
|
|
1000
|
+
}
|
|
1001
|
+
return out;
|
|
1002
|
+
}
|
|
898
1003
|
async postWebhookJson(body) {
|
|
1004
|
+
const safeUrl = erpSafeWebhookUrl(this.webhookUrl);
|
|
1005
|
+
const bodyJson = JSON.stringify(body);
|
|
1006
|
+
logErp("webhook:post_start", {
|
|
1007
|
+
url: safeUrl,
|
|
1008
|
+
bodyBytes: bodyJson.length,
|
|
1009
|
+
...this.summarizeWebhookBody(body)
|
|
1010
|
+
});
|
|
899
1011
|
try {
|
|
900
1012
|
const res = await fetch(this.webhookUrl, {
|
|
901
1013
|
method: "POST",
|
|
@@ -903,13 +1015,19 @@ var ERPSubmissionService = class {
|
|
|
903
1015
|
"Content-Type": "application/json",
|
|
904
1016
|
"X-External-Token": this.webhookJwt
|
|
905
1017
|
},
|
|
906
|
-
body:
|
|
1018
|
+
body: bodyJson
|
|
907
1019
|
});
|
|
908
|
-
if (res.ok) return { success: true, status: res.status };
|
|
909
1020
|
const text = await res.text();
|
|
1021
|
+
const preview = text.length > 500 ? `${text.slice(0, 500)}\u2026` : text;
|
|
1022
|
+
if (res.ok) {
|
|
1023
|
+
logErp("webhook:post_ok", { status: res.status, responsePreview: preview || "(empty body)" });
|
|
1024
|
+
return { success: true, status: res.status };
|
|
1025
|
+
}
|
|
1026
|
+
warnErp("webhook:post_http_error", { status: res.status, responsePreview: preview });
|
|
910
1027
|
return { success: false, error: `${res.status} ${text.slice(0, 500)}`, status: res.status };
|
|
911
1028
|
} catch (e) {
|
|
912
1029
|
const message = e instanceof Error ? e.message : "ERP webhook request failed";
|
|
1030
|
+
errorErp("webhook:post_fetch_failed", { url: safeUrl, message });
|
|
913
1031
|
return { success: false, error: message };
|
|
914
1032
|
}
|
|
915
1033
|
}
|
|
@@ -1023,7 +1141,20 @@ var ERPSubmissionService = class {
|
|
|
1023
1141
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1024
1142
|
data: orderDto
|
|
1025
1143
|
};
|
|
1026
|
-
|
|
1144
|
+
logErp("submitOrder:envelope_ready", {
|
|
1145
|
+
event_type: envelope.event_type,
|
|
1146
|
+
timestamp: envelope.timestamp,
|
|
1147
|
+
platformOrderId: orderDto.platformOrderId ?? orderDto.platformOrderNumber,
|
|
1148
|
+
itemCount: Array.isArray(orderDto.items) ? orderDto.items.length : 0,
|
|
1149
|
+
paymentCount: Array.isArray(orderDto.payments) ? orderDto.payments.length : 0
|
|
1150
|
+
});
|
|
1151
|
+
const result = await this.postWebhookJson(envelope);
|
|
1152
|
+
if (result.success) {
|
|
1153
|
+
logErp("submitOrder:complete", { ok: true, status: result.status });
|
|
1154
|
+
} else {
|
|
1155
|
+
warnErp("submitOrder:complete", { ok: false, status: result.status, error: result.error });
|
|
1156
|
+
}
|
|
1157
|
+
return result;
|
|
1027
1158
|
}
|
|
1028
1159
|
extractContactData(formData, formFields) {
|
|
1029
1160
|
const contactData = {
|
|
@@ -4160,11 +4291,11 @@ async function readBufferFromPublicUrl(url) {
|
|
|
4160
4291
|
throw new Error("Unsupported media URL");
|
|
4161
4292
|
}
|
|
4162
4293
|
function sanitizeZipPath(entryName) {
|
|
4163
|
-
const
|
|
4164
|
-
for (const seg of
|
|
4294
|
+
const norm3 = entryName.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
4295
|
+
for (const seg of norm3) {
|
|
4165
4296
|
if (seg === ".." || seg === ".") return null;
|
|
4166
4297
|
}
|
|
4167
|
-
return
|
|
4298
|
+
return norm3;
|
|
4168
4299
|
}
|
|
4169
4300
|
function shouldSkipEntry(parts) {
|
|
4170
4301
|
if (parts[0] === "__MACOSX") return true;
|
|
@@ -5184,18 +5315,37 @@ function createUsersApiHandlers(config) {
|
|
|
5184
5315
|
try {
|
|
5185
5316
|
const body = await req.json();
|
|
5186
5317
|
if (!body?.name || !body?.email) return json({ error: "Name and email are required" }, { status: 400 });
|
|
5187
|
-
const
|
|
5188
|
-
|
|
5318
|
+
const email = body.email;
|
|
5319
|
+
const existing = await userRepo().findOne({ where: { email } });
|
|
5320
|
+
if (existing && !existing.deleted) {
|
|
5321
|
+
return json({ error: "User with this email already exists" }, { status: 400 });
|
|
5322
|
+
}
|
|
5189
5323
|
const groupRepo = dataSource.getRepository(entityMap.user_groups);
|
|
5190
5324
|
const customerG = await groupRepo.findOne({ where: { name: "Customer", deleted: false } });
|
|
5191
5325
|
const gid = body.groupId ?? null;
|
|
5192
5326
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
5193
5327
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
5194
5328
|
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
5195
|
-
const newUser = await
|
|
5329
|
+
const newUser = existing?.deleted ? await (async () => {
|
|
5330
|
+
await userRepo().update(existing.id, {
|
|
5331
|
+
deleted: false,
|
|
5332
|
+
deletedAt: null,
|
|
5333
|
+
deletedBy: null,
|
|
5334
|
+
name: body.name,
|
|
5335
|
+
email,
|
|
5336
|
+
password: null,
|
|
5337
|
+
blocked,
|
|
5338
|
+
groupId: gid,
|
|
5339
|
+
adminAccess,
|
|
5340
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
5341
|
+
});
|
|
5342
|
+
const row = await userRepo().findOne({ where: { id: existing.id } });
|
|
5343
|
+
if (!row) throw new Error("user missing after restore");
|
|
5344
|
+
return row;
|
|
5345
|
+
})() : await userRepo().save(
|
|
5196
5346
|
userRepo().create({
|
|
5197
5347
|
name: body.name,
|
|
5198
|
-
email
|
|
5348
|
+
email,
|
|
5199
5349
|
password: null,
|
|
5200
5350
|
blocked,
|
|
5201
5351
|
groupId: gid,
|
|
@@ -5348,21 +5498,110 @@ function createUserAvatarHandler(config) {
|
|
|
5348
5498
|
}
|
|
5349
5499
|
};
|
|
5350
5500
|
}
|
|
5501
|
+
var PROFILE_EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
5351
5502
|
function createUserProfileHandler(config) {
|
|
5352
|
-
const { dataSource, entityMap, json, getSession } = config;
|
|
5353
|
-
|
|
5503
|
+
const { dataSource, entityMap, json, getSession, onProfileUpdated } = config;
|
|
5504
|
+
async function loadCurrentUser() {
|
|
5354
5505
|
const session = await getSession();
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5506
|
+
const su = session?.user;
|
|
5507
|
+
if (!su?.email && su?.id == null) {
|
|
5508
|
+
return { ok: false, response: json({ error: "Unauthorized" }, { status: 401 }) };
|
|
5509
|
+
}
|
|
5510
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
5511
|
+
let user = null;
|
|
5512
|
+
const uidRaw = su.id != null ? String(su.id).trim() : "";
|
|
5513
|
+
const uid = uidRaw && /^\d+$/.test(uidRaw) ? parseInt(uidRaw, 10) : NaN;
|
|
5514
|
+
if (Number.isFinite(uid) && uid > 0) {
|
|
5515
|
+
user = await userRepo.findOne({
|
|
5516
|
+
where: { id: uid, deleted: false },
|
|
5517
|
+
select: ["id", "name", "email", "phone", "createdAt"]
|
|
5518
|
+
});
|
|
5519
|
+
}
|
|
5520
|
+
if (!user && su.email) {
|
|
5521
|
+
const em = String(su.email).trim().toLowerCase();
|
|
5522
|
+
if (em) {
|
|
5523
|
+
user = await userRepo.findOne({
|
|
5524
|
+
where: { email: em, deleted: false },
|
|
5525
|
+
select: ["id", "name", "email", "phone", "createdAt"]
|
|
5526
|
+
});
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5529
|
+
if (!user) return { ok: false, response: json({ error: "Not found" }, { status: 404 }) };
|
|
5530
|
+
return { ok: true, user };
|
|
5531
|
+
}
|
|
5532
|
+
return {
|
|
5533
|
+
async GET(_req) {
|
|
5534
|
+
try {
|
|
5535
|
+
const r = await loadCurrentUser();
|
|
5536
|
+
if (!r.ok) return r.response;
|
|
5537
|
+
const u = r.user;
|
|
5538
|
+
return json({
|
|
5539
|
+
id: u.id,
|
|
5540
|
+
name: u.name ?? "",
|
|
5541
|
+
email: u.email ?? "",
|
|
5542
|
+
phone: u.phone ?? null,
|
|
5543
|
+
createdAt: u.createdAt instanceof Date ? u.createdAt.toISOString() : u.createdAt ?? void 0
|
|
5544
|
+
});
|
|
5545
|
+
} catch {
|
|
5546
|
+
return json({ error: "Internal server error" }, { status: 500 });
|
|
5547
|
+
}
|
|
5548
|
+
},
|
|
5549
|
+
async PUT(req) {
|
|
5550
|
+
try {
|
|
5551
|
+
const r = await loadCurrentUser();
|
|
5552
|
+
if (!r.ok) return r.response;
|
|
5553
|
+
const current = r.user;
|
|
5554
|
+
let body;
|
|
5555
|
+
try {
|
|
5556
|
+
body = await req.json();
|
|
5557
|
+
} catch {
|
|
5558
|
+
return json({ error: "Invalid JSON" }, { status: 400 });
|
|
5559
|
+
}
|
|
5560
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
5561
|
+
if (!name) return json({ error: "Name is required" }, { status: 400 });
|
|
5562
|
+
const emailRaw = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
|
|
5563
|
+
if (!emailRaw || !PROFILE_EMAIL_RE.test(emailRaw)) {
|
|
5564
|
+
return json({ error: "Valid email is required" }, { status: 400 });
|
|
5565
|
+
}
|
|
5566
|
+
const phone = body.phone === null || body.phone === void 0 ? null : typeof body.phone === "string" ? body.phone.trim() || null : null;
|
|
5567
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
5568
|
+
if (emailRaw !== String(current.email ?? "").toLowerCase()) {
|
|
5569
|
+
const taken = await userRepo.findOne({
|
|
5570
|
+
where: { email: emailRaw, deleted: false },
|
|
5571
|
+
select: ["id"]
|
|
5572
|
+
});
|
|
5573
|
+
if (taken && taken.id !== current.id) {
|
|
5574
|
+
return json({ error: "Email is already in use" }, { status: 409 });
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
await userRepo.update(
|
|
5578
|
+
{ id: current.id },
|
|
5579
|
+
{
|
|
5580
|
+
name,
|
|
5581
|
+
email: emailRaw,
|
|
5582
|
+
phone,
|
|
5583
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
5584
|
+
}
|
|
5585
|
+
);
|
|
5586
|
+
const updated = await userRepo.findOne({
|
|
5587
|
+
where: { id: current.id },
|
|
5588
|
+
select: ["id", "name", "email", "phone"]
|
|
5589
|
+
});
|
|
5590
|
+
if (!updated) return json({ error: "Not found" }, { status: 404 });
|
|
5591
|
+
const row = updated;
|
|
5592
|
+
if (onProfileUpdated) {
|
|
5593
|
+
try {
|
|
5594
|
+
await onProfileUpdated(req, row);
|
|
5595
|
+
} catch {
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
return json({
|
|
5599
|
+
message: "Profile updated successfully",
|
|
5600
|
+
user: { id: row.id, name: row.name, email: row.email, phone: row.phone }
|
|
5601
|
+
});
|
|
5602
|
+
} catch {
|
|
5603
|
+
return json({ error: "Internal server error" }, { status: 500 });
|
|
5604
|
+
}
|
|
5366
5605
|
}
|
|
5367
5606
|
};
|
|
5368
5607
|
}
|
|
@@ -8687,7 +8926,7 @@ function getNextAuthOptions(config) {
|
|
|
8687
8926
|
}
|
|
8688
8927
|
},
|
|
8689
8928
|
callbacks: {
|
|
8690
|
-
async jwt({ token, user }) {
|
|
8929
|
+
async jwt({ token, user, trigger, session }) {
|
|
8691
8930
|
if (user) {
|
|
8692
8931
|
const u = user;
|
|
8693
8932
|
token.id = u.id;
|
|
@@ -8696,11 +8935,19 @@ function getNextAuthOptions(config) {
|
|
|
8696
8935
|
token.entityPerms = u.entityPerms;
|
|
8697
8936
|
token.adminAccess = u.adminAccess;
|
|
8698
8937
|
}
|
|
8938
|
+
if (trigger === "update" && session && typeof session === "object") {
|
|
8939
|
+
const s = session;
|
|
8940
|
+
const t = token;
|
|
8941
|
+
if (typeof s.name === "string") t.name = s.name;
|
|
8942
|
+
if (typeof s.email === "string") t.email = s.email;
|
|
8943
|
+
}
|
|
8699
8944
|
return token;
|
|
8700
8945
|
},
|
|
8701
8946
|
async session({ session, token }) {
|
|
8702
8947
|
if (session.user) {
|
|
8703
8948
|
const t = token;
|
|
8949
|
+
if (typeof t.name === "string") session.user.name = t.name;
|
|
8950
|
+
if (typeof t.email === "string") session.user.email = t.email;
|
|
8704
8951
|
session.user.id = t.id;
|
|
8705
8952
|
session.user.groupId = t.groupId;
|
|
8706
8953
|
session.user.isRBACAdmin = t.isRBACAdmin;
|
|
@@ -8716,6 +8963,66 @@ function getNextAuthOptions(config) {
|
|
|
8716
8963
|
|
|
8717
8964
|
// src/api/crud.ts
|
|
8718
8965
|
var import_typeorm46 = require("typeorm");
|
|
8966
|
+
|
|
8967
|
+
// src/lib/address-geo-validation.ts
|
|
8968
|
+
var import_country_state_city = require("country-state-city");
|
|
8969
|
+
function norm2(s) {
|
|
8970
|
+
return typeof s === "string" ? s.trim() : "";
|
|
8971
|
+
}
|
|
8972
|
+
function resolveCountry(input) {
|
|
8973
|
+
const t = input.trim();
|
|
8974
|
+
if (!t) return void 0;
|
|
8975
|
+
if (t.length === 2) {
|
|
8976
|
+
const byCode = import_country_state_city.Country.getCountryByCode(t.toUpperCase());
|
|
8977
|
+
if (byCode) return byCode;
|
|
8978
|
+
}
|
|
8979
|
+
const lower = t.toLowerCase();
|
|
8980
|
+
return import_country_state_city.Country.getAllCountries().find((c) => c.name.toLowerCase() === lower);
|
|
8981
|
+
}
|
|
8982
|
+
function resolveState(countryIso, input) {
|
|
8983
|
+
const t = input.trim();
|
|
8984
|
+
if (!t || !countryIso) return void 0;
|
|
8985
|
+
const states = import_country_state_city.State.getStatesOfCountry(countryIso);
|
|
8986
|
+
const lower = t.toLowerCase();
|
|
8987
|
+
return states.find((s) => s.isoCode.toLowerCase() === t.toLowerCase() || s.name.toLowerCase() === lower);
|
|
8988
|
+
}
|
|
8989
|
+
function resolveCity(countryIso, stateIso, input) {
|
|
8990
|
+
const t = input.trim();
|
|
8991
|
+
if (!t || !countryIso || !stateIso) return void 0;
|
|
8992
|
+
const lower = t.toLowerCase();
|
|
8993
|
+
const cities = import_country_state_city.City.getCitiesOfState(countryIso, stateIso);
|
|
8994
|
+
return cities.find((c) => c.name.toLowerCase() === lower);
|
|
8995
|
+
}
|
|
8996
|
+
function assertValidAddressHierarchy(country, state, city) {
|
|
8997
|
+
const c = resolveCountry(country);
|
|
8998
|
+
if (!c) return { ok: false, error: "Invalid or unknown country." };
|
|
8999
|
+
const st = resolveState(c.isoCode, state);
|
|
9000
|
+
if (!st) return { ok: false, error: "State or province does not match the selected country." };
|
|
9001
|
+
const ct = resolveCity(c.isoCode, st.isoCode, city);
|
|
9002
|
+
if (!ct) return { ok: false, error: "City does not match the selected state." };
|
|
9003
|
+
return { ok: true, country: c.name, state: st.name, city: ct.name };
|
|
9004
|
+
}
|
|
9005
|
+
function validateAndNormalizeAddressRow(row) {
|
|
9006
|
+
const line1 = norm2(row.line1);
|
|
9007
|
+
const postalCode = norm2(row.postalCode);
|
|
9008
|
+
const countryIn = norm2(row.country);
|
|
9009
|
+
const stateIn = norm2(row.state);
|
|
9010
|
+
const cityIn = norm2(row.city);
|
|
9011
|
+
if (!line1) return "Street address (line 1) is required.";
|
|
9012
|
+
if (!postalCode) return "Postal code is required.";
|
|
9013
|
+
if (!countryIn || !stateIn || !cityIn) return "Country, state, and city are required.";
|
|
9014
|
+
const geo = assertValidAddressHierarchy(countryIn, stateIn, cityIn);
|
|
9015
|
+
if (!geo.ok) return geo.error;
|
|
9016
|
+
row.line1 = line1;
|
|
9017
|
+
row.line2 = norm2(row.line2) || null;
|
|
9018
|
+
row.postalCode = postalCode;
|
|
9019
|
+
row.country = geo.country;
|
|
9020
|
+
row.state = geo.state;
|
|
9021
|
+
row.city = geo.city;
|
|
9022
|
+
return null;
|
|
9023
|
+
}
|
|
9024
|
+
|
|
9025
|
+
// src/api/crud.ts
|
|
8719
9026
|
var CRUD_LOG = "[cms-crud]";
|
|
8720
9027
|
function logCrudClientError(op, detail) {
|
|
8721
9028
|
console.warn(CRUD_LOG, op, detail);
|
|
@@ -8812,6 +9119,156 @@ function buildSearchWhereClause(repo, search) {
|
|
|
8812
9119
|
function entityHasSoftDelete(repo) {
|
|
8813
9120
|
return repo.metadata.columns.some((c) => c.propertyName === "deleted");
|
|
8814
9121
|
}
|
|
9122
|
+
var LIST_QUERY_RESERVED = /* @__PURE__ */ new Set(["page", "limit", "sortField", "sortOrder", "search"]);
|
|
9123
|
+
function dayStartUtc(isoDay) {
|
|
9124
|
+
return /* @__PURE__ */ new Date(isoDay + "T00:00:00.000Z");
|
|
9125
|
+
}
|
|
9126
|
+
function dayEndUtc(isoDay) {
|
|
9127
|
+
return /* @__PURE__ */ new Date(isoDay + "T23:59:59.999Z");
|
|
9128
|
+
}
|
|
9129
|
+
function columnTypeLabel(col) {
|
|
9130
|
+
const t = col.type;
|
|
9131
|
+
if (typeof t === "string") return t.toLowerCase();
|
|
9132
|
+
if (typeof t === "function") return t.name?.toLowerCase?.() ?? "";
|
|
9133
|
+
if (t && typeof t === "object" && "name" in t && typeof t.name === "string") {
|
|
9134
|
+
return String(t.name).toLowerCase();
|
|
9135
|
+
}
|
|
9136
|
+
return "";
|
|
9137
|
+
}
|
|
9138
|
+
function isListDateColumn(col) {
|
|
9139
|
+
const tl = columnTypeLabel(col);
|
|
9140
|
+
return DATE_COLUMN_TYPES.has(tl) || col.type === Date || TIMESTAMP_PROP_NAMES.has(col.propertyName);
|
|
9141
|
+
}
|
|
9142
|
+
function isListNumericColumn(col) {
|
|
9143
|
+
const tl = columnTypeLabel(col);
|
|
9144
|
+
if (col.type === Number) return true;
|
|
9145
|
+
const patterns = [
|
|
9146
|
+
"int",
|
|
9147
|
+
"integer",
|
|
9148
|
+
"int2",
|
|
9149
|
+
"int4",
|
|
9150
|
+
"int8",
|
|
9151
|
+
"smallint",
|
|
9152
|
+
"bigint",
|
|
9153
|
+
"float",
|
|
9154
|
+
"double",
|
|
9155
|
+
"decimal",
|
|
9156
|
+
"numeric",
|
|
9157
|
+
"real",
|
|
9158
|
+
"tinyint",
|
|
9159
|
+
"mediumint"
|
|
9160
|
+
];
|
|
9161
|
+
if (patterns.some((x) => tl.includes(x))) return true;
|
|
9162
|
+
if (tl.includes("unsigned") && (tl.includes("int") || tl.includes("bigint") || tl.includes("small"))) return true;
|
|
9163
|
+
if (!tl && /Id$/i.test(col.propertyName) && !TIMESTAMP_PROP_NAMES.has(col.propertyName)) return true;
|
|
9164
|
+
return false;
|
|
9165
|
+
}
|
|
9166
|
+
function isListBooleanColumn(col) {
|
|
9167
|
+
const tl = columnTypeLabel(col);
|
|
9168
|
+
return tl === "boolean" || tl === "bool" || col.type === Boolean;
|
|
9169
|
+
}
|
|
9170
|
+
function isListStringColumn(col) {
|
|
9171
|
+
const tl = columnTypeLabel(col);
|
|
9172
|
+
if (isListDateColumn(col) || isListNumericColumn(col) || isListBooleanColumn(col)) return false;
|
|
9173
|
+
if (["varchar", "character varying", "text", "citext", "uuid", "char", "character", "enum"].some(
|
|
9174
|
+
(x) => tl.includes(x)
|
|
9175
|
+
)) {
|
|
9176
|
+
return true;
|
|
9177
|
+
}
|
|
9178
|
+
if (col.type === String) return true;
|
|
9179
|
+
return false;
|
|
9180
|
+
}
|
|
9181
|
+
function mergeListWhereAnd(where, patch) {
|
|
9182
|
+
if (Object.keys(patch).length === 0) return where;
|
|
9183
|
+
if (Array.isArray(where)) {
|
|
9184
|
+
if (where.length === 0) return [patch];
|
|
9185
|
+
return where.map((w) => ({ ...w, ...patch }));
|
|
9186
|
+
}
|
|
9187
|
+
if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
9188
|
+
return { ...where, ...patch };
|
|
9189
|
+
}
|
|
9190
|
+
return patch;
|
|
9191
|
+
}
|
|
9192
|
+
function buildListFilterAndFromSearchParams(repo, searchParams) {
|
|
9193
|
+
const and = {};
|
|
9194
|
+
const columnNames = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
9195
|
+
for (const col of repo.metadata.columns) {
|
|
9196
|
+
const name = col.propertyName;
|
|
9197
|
+
if (!columnNames.has(name)) continue;
|
|
9198
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
9199
|
+
if (!isListDateColumn(col)) continue;
|
|
9200
|
+
const from = searchParams.get(`${name}From`)?.trim();
|
|
9201
|
+
const to = searchParams.get(`${name}To`)?.trim();
|
|
9202
|
+
if (!from && !to) continue;
|
|
9203
|
+
if (from && to) {
|
|
9204
|
+
and[name] = (0, import_typeorm46.Between)(dayStartUtc(from), dayEndUtc(to));
|
|
9205
|
+
} else if (from) {
|
|
9206
|
+
and[name] = (0, import_typeorm46.MoreThanOrEqual)(dayStartUtc(from));
|
|
9207
|
+
} else if (to) {
|
|
9208
|
+
and[name] = (0, import_typeorm46.LessThanOrEqual)(dayEndUtc(to));
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
for (const col of repo.metadata.columns) {
|
|
9212
|
+
const name = col.propertyName;
|
|
9213
|
+
if (!columnNames.has(name)) continue;
|
|
9214
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
9215
|
+
if (!isListNumericColumn(col)) continue;
|
|
9216
|
+
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
9217
|
+
const minRaw = searchParams.get(`${name}Min`)?.trim();
|
|
9218
|
+
const maxRaw = searchParams.get(`${name}Max`)?.trim();
|
|
9219
|
+
if (!minRaw && !maxRaw) continue;
|
|
9220
|
+
const parseNum = (s) => {
|
|
9221
|
+
const n = Number(s);
|
|
9222
|
+
return Number.isFinite(n) ? n : null;
|
|
9223
|
+
};
|
|
9224
|
+
const nMin = minRaw ? parseNum(minRaw) : null;
|
|
9225
|
+
const nMax = maxRaw ? parseNum(maxRaw) : null;
|
|
9226
|
+
if (nMin != null && nMax != null) {
|
|
9227
|
+
and[name] = (0, import_typeorm46.Between)(nMin, nMax);
|
|
9228
|
+
} else if (nMin != null) {
|
|
9229
|
+
and[name] = (0, import_typeorm46.MoreThanOrEqual)(nMin);
|
|
9230
|
+
} else if (nMax != null) {
|
|
9231
|
+
and[name] = (0, import_typeorm46.LessThanOrEqual)(nMax);
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9234
|
+
for (const col of repo.metadata.columns) {
|
|
9235
|
+
const name = col.propertyName;
|
|
9236
|
+
if (!columnNames.has(name)) continue;
|
|
9237
|
+
if (LIST_QUERY_RESERVED.has(name)) continue;
|
|
9238
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
9239
|
+
if (!isListStringColumn(col)) continue;
|
|
9240
|
+
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
9241
|
+
const raw = searchParams.get(name)?.trim();
|
|
9242
|
+
if (!raw) continue;
|
|
9243
|
+
and[name] = (0, import_typeorm46.ILike)(`%${raw}%`);
|
|
9244
|
+
}
|
|
9245
|
+
return and;
|
|
9246
|
+
}
|
|
9247
|
+
function buildExactListParamWhere(repo, searchParams) {
|
|
9248
|
+
const extraWhere = {};
|
|
9249
|
+
const columnNames = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
9250
|
+
for (const col of repo.metadata.columns) {
|
|
9251
|
+
const name = col.propertyName;
|
|
9252
|
+
if (!columnNames.has(name)) continue;
|
|
9253
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
9254
|
+
if (!isListNumericColumn(col)) continue;
|
|
9255
|
+
const v = searchParams.get(name)?.trim();
|
|
9256
|
+
if (v == null || v === "") continue;
|
|
9257
|
+
const n = Number(v);
|
|
9258
|
+
if (!Number.isFinite(n)) continue;
|
|
9259
|
+
extraWhere[name] = n;
|
|
9260
|
+
}
|
|
9261
|
+
for (const col of repo.metadata.columns) {
|
|
9262
|
+
if (String(col.type) !== "boolean") continue;
|
|
9263
|
+
const name = col.propertyName;
|
|
9264
|
+
if (!columnNames.has(name)) continue;
|
|
9265
|
+
const raw = searchParams.get(name)?.trim();
|
|
9266
|
+
if (raw === "true" || raw === "false") {
|
|
9267
|
+
extraWhere[name] = raw === "true";
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
9270
|
+
return extraWhere;
|
|
9271
|
+
}
|
|
8815
9272
|
function mergeDeletedFalseWhere(repo, where) {
|
|
8816
9273
|
if (!entityHasSoftDelete(repo)) return where;
|
|
8817
9274
|
const d = { deleted: false };
|
|
@@ -8821,6 +9278,12 @@ function mergeDeletedFalseWhere(repo, where) {
|
|
|
8821
9278
|
}
|
|
8822
9279
|
return Object.keys(where).length > 0 ? { ...where, ...d } : d;
|
|
8823
9280
|
}
|
|
9281
|
+
function pickDefaultListSortField(columnNames, columns) {
|
|
9282
|
+
for (const candidate of ["createdAt", "updatedAt", "id", "name", "sortOrder", "title"]) {
|
|
9283
|
+
if (columnNames.has(candidate)) return candidate;
|
|
9284
|
+
}
|
|
9285
|
+
return columns[0]?.propertyName ?? "id";
|
|
9286
|
+
}
|
|
8824
9287
|
function normalizeProductSku(value) {
|
|
8825
9288
|
if (value == null) return null;
|
|
8826
9289
|
const s = String(value).trim();
|
|
@@ -8923,6 +9386,18 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8923
9386
|
if (statusFilter) qb.andWhere("order.status = :status", { status: statusFilter });
|
|
8924
9387
|
if (dateFrom) qb.andWhere("order.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
8925
9388
|
if (dateTo) qb.andWhere("order.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
9389
|
+
const totalMin = searchParams.get("totalMin")?.trim();
|
|
9390
|
+
const totalMax = searchParams.get("totalMax")?.trim();
|
|
9391
|
+
if (totalMin) {
|
|
9392
|
+
const n = Number(totalMin);
|
|
9393
|
+
if (Number.isFinite(n)) qb.andWhere("order.total >= :totalMin", { totalMin: n });
|
|
9394
|
+
}
|
|
9395
|
+
if (totalMax) {
|
|
9396
|
+
const n = Number(totalMax);
|
|
9397
|
+
if (Number.isFinite(n)) qb.andWhere("order.total <= :totalMax", { totalMax: n });
|
|
9398
|
+
}
|
|
9399
|
+
const currency = searchParams.get("currency")?.trim();
|
|
9400
|
+
if (currency) qb.andWhere("order.currency ILIKE :orderCurrency", { orderCurrency: `%${currency}%` });
|
|
8926
9401
|
if (orderIdsFromPayment && orderIdsFromPayment.length) qb.andWhere("order.id IN (:...orderIds)", { orderIds: orderIdsFromPayment });
|
|
8927
9402
|
const [rows, total2] = await qb.getManyAndCount();
|
|
8928
9403
|
const data2 = rows.map((order) => {
|
|
@@ -8961,8 +9436,30 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8961
9436
|
if (statusFilter) qb.andWhere("payment.status = :status", { status: statusFilter });
|
|
8962
9437
|
if (dateFrom) qb.andWhere("payment.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
8963
9438
|
if (dateTo) qb.andWhere("payment.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
9439
|
+
const paidAtFrom = searchParams.get("paidAtFrom")?.trim();
|
|
9440
|
+
const paidAtTo = searchParams.get("paidAtTo")?.trim();
|
|
9441
|
+
if (paidAtFrom) {
|
|
9442
|
+
qb.andWhere("payment.paidAt >= :paidAtFrom", { paidAtFrom: /* @__PURE__ */ new Date(paidAtFrom + "T00:00:00.000Z") });
|
|
9443
|
+
}
|
|
9444
|
+
if (paidAtTo) {
|
|
9445
|
+
qb.andWhere("payment.paidAt <= :paidAtTo", { paidAtTo: /* @__PURE__ */ new Date(paidAtTo + "T23:59:59.999Z") });
|
|
9446
|
+
}
|
|
8964
9447
|
if (methodFilter) qb.andWhere("payment.method = :method", { method: methodFilter });
|
|
8965
9448
|
if (orderNumberParam) qb.andWhere("ord.orderNumber ILIKE :orderNumber", { orderNumber: `%${orderNumberParam}%` });
|
|
9449
|
+
const amountMin = searchParams.get("amountMin")?.trim();
|
|
9450
|
+
const amountMax = searchParams.get("amountMax")?.trim();
|
|
9451
|
+
if (amountMin) {
|
|
9452
|
+
const n = Number(amountMin);
|
|
9453
|
+
if (Number.isFinite(n)) qb.andWhere("payment.amount >= :amountMin", { amountMin: n });
|
|
9454
|
+
}
|
|
9455
|
+
if (amountMax) {
|
|
9456
|
+
const n = Number(amountMax);
|
|
9457
|
+
if (Number.isFinite(n)) qb.andWhere("payment.amount <= :amountMax", { amountMax: n });
|
|
9458
|
+
}
|
|
9459
|
+
const extRef = searchParams.get("externalReference")?.trim();
|
|
9460
|
+
if (extRef) {
|
|
9461
|
+
qb.andWhere("payment.externalReference ILIKE :extRef", { extRef: `%${extRef}%` });
|
|
9462
|
+
}
|
|
8966
9463
|
const [rows, total2] = await qb.getManyAndCount();
|
|
8967
9464
|
const data2 = rows.map((payment) => {
|
|
8968
9465
|
const order = payment.order;
|
|
@@ -8981,18 +9478,36 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8981
9478
|
const repo2 = dataSource.getRepository(entity);
|
|
8982
9479
|
const statusFilter = searchParams.get("status")?.trim();
|
|
8983
9480
|
const inventory = searchParams.get("inventory")?.trim();
|
|
8984
|
-
const productWhere = {
|
|
9481
|
+
const productWhere = {
|
|
9482
|
+
deleted: false,
|
|
9483
|
+
...buildListFilterAndFromSearchParams(repo2, searchParams)
|
|
9484
|
+
};
|
|
8985
9485
|
if (statusFilter) productWhere.status = statusFilter;
|
|
8986
9486
|
if (inventory === "in_stock") productWhere.quantity = (0, import_typeorm46.MoreThan)(0);
|
|
8987
9487
|
if (inventory === "out_of_stock") productWhere.quantity = 0;
|
|
9488
|
+
for (const key of ["brandId", "categoryId", "collectionId"]) {
|
|
9489
|
+
const raw = searchParams.get(key)?.trim();
|
|
9490
|
+
if (raw) {
|
|
9491
|
+
const n = Number(raw);
|
|
9492
|
+
if (Number.isFinite(n)) productWhere[key] = n;
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
const featuredRaw = searchParams.get("featured")?.trim();
|
|
9496
|
+
if (featuredRaw === "true" || featuredRaw === "false") {
|
|
9497
|
+
productWhere.featured = featuredRaw === "true";
|
|
9498
|
+
}
|
|
8988
9499
|
if (search && typeof search === "string" && search.trim()) {
|
|
8989
9500
|
productWhere.name = (0, import_typeorm46.ILike)(`%${search.trim()}%`);
|
|
8990
9501
|
}
|
|
9502
|
+
const productColumnNames = new Set(repo2.metadata.columns.map((c) => c.propertyName));
|
|
9503
|
+
const defaultProductSort = pickDefaultListSortField(productColumnNames, repo2.metadata.columns);
|
|
9504
|
+
const sortParam2 = (searchParams.get("sortField") ?? "").trim();
|
|
9505
|
+
const productSortField = sortParam2 && productColumnNames.has(sortParam2) ? sortParam2 : defaultProductSort;
|
|
8991
9506
|
const [data2, total2] = await repo2.findAndCount({
|
|
8992
9507
|
where: Object.keys(productWhere).length ? productWhere : void 0,
|
|
8993
9508
|
skip,
|
|
8994
9509
|
take: limit,
|
|
8995
|
-
order: { [
|
|
9510
|
+
order: { [productSortField]: sortOrder }
|
|
8996
9511
|
});
|
|
8997
9512
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
8998
9513
|
}
|
|
@@ -9085,27 +9600,22 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
9085
9600
|
}
|
|
9086
9601
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
9087
9602
|
}
|
|
9088
|
-
const
|
|
9603
|
+
const defaultSortField = pickDefaultListSortField(columnNames, repo.metadata.columns);
|
|
9604
|
+
const sortParam = (searchParams.get("sortField") ?? "").trim();
|
|
9605
|
+
const sortField = sortParam && columnNames.has(sortParam) ? sortParam : defaultSortField;
|
|
9089
9606
|
let where = {};
|
|
9090
9607
|
if (search) {
|
|
9091
9608
|
where = buildSearchWhereClause(repo, search);
|
|
9092
9609
|
}
|
|
9093
|
-
|
|
9094
|
-
const
|
|
9095
|
-
|
|
9096
|
-
const v = searchParams.get(key);
|
|
9097
|
-
if (v != null && v !== "" && columnNames.has(key)) {
|
|
9098
|
-
const n = Number(v);
|
|
9099
|
-
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
9100
|
-
}
|
|
9101
|
-
}
|
|
9102
|
-
if (Object.keys(extraWhere).length > 0) {
|
|
9610
|
+
where = mergeListWhereAnd(where, buildListFilterAndFromSearchParams(repo, searchParams));
|
|
9611
|
+
const exactParamWhere = buildExactListParamWhere(repo, searchParams);
|
|
9612
|
+
if (Object.keys(exactParamWhere).length > 0) {
|
|
9103
9613
|
if (Array.isArray(where)) {
|
|
9104
|
-
where = where.map((w) => ({ ...w, ...
|
|
9614
|
+
where = where.map((w) => ({ ...w, ...exactParamWhere }));
|
|
9105
9615
|
} else if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
9106
|
-
where = { ...where, ...
|
|
9616
|
+
where = { ...where, ...exactParamWhere };
|
|
9107
9617
|
} else {
|
|
9108
|
-
where =
|
|
9618
|
+
where = exactParamWhere;
|
|
9109
9619
|
}
|
|
9110
9620
|
}
|
|
9111
9621
|
where = mergeDeletedFalseWhere(repo, where);
|
|
@@ -9185,6 +9695,10 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
9185
9695
|
}
|
|
9186
9696
|
const repo = dataSource.getRepository(entity);
|
|
9187
9697
|
const persistBody = resource === "media" ? body : pickColumnUpdates(repo, body);
|
|
9698
|
+
if (resource === "contacts" && "type" in persistBody) {
|
|
9699
|
+
const t = persistBody.type;
|
|
9700
|
+
if (t === "" || t === "none" || t == null) persistBody.type = null;
|
|
9701
|
+
}
|
|
9188
9702
|
if (resource !== "media" && Object.keys(persistBody).length === 0) {
|
|
9189
9703
|
logCrudClientError("POST create", {
|
|
9190
9704
|
reason: "no_scalar_columns_after_pick",
|
|
@@ -9207,6 +9721,17 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
9207
9721
|
}
|
|
9208
9722
|
}
|
|
9209
9723
|
}
|
|
9724
|
+
if (resource === "addresses") {
|
|
9725
|
+
const cid = Number(persistBody.contactId);
|
|
9726
|
+
if (!Number.isFinite(cid)) {
|
|
9727
|
+
return json({ error: "Valid contactId is required." }, { status: 400 });
|
|
9728
|
+
}
|
|
9729
|
+
if (persistBody.tag === "") persistBody.tag = null;
|
|
9730
|
+
const addrErr = validateAndNormalizeAddressRow(persistBody);
|
|
9731
|
+
if (addrErr) {
|
|
9732
|
+
return json({ error: addrErr }, { status: 400 });
|
|
9733
|
+
}
|
|
9734
|
+
}
|
|
9210
9735
|
sanitizeBodyForEntity(repo, persistBody);
|
|
9211
9736
|
let created;
|
|
9212
9737
|
try {
|
|
@@ -9524,10 +10049,60 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
9524
10049
|
if (!cur) return json({ message: "Not found" }, { status: 404 });
|
|
9525
10050
|
}
|
|
9526
10051
|
const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
|
|
10052
|
+
if (resource === "contacts" && "type" in updatePayload) {
|
|
10053
|
+
const t = updatePayload.type;
|
|
10054
|
+
if (t === "" || t === "none" || t == null) updatePayload.type = null;
|
|
10055
|
+
}
|
|
9527
10056
|
if (resource === "media") {
|
|
9528
10057
|
const u = updatePayload;
|
|
9529
|
-
delete u.parentId;
|
|
9530
10058
|
delete u.kind;
|
|
10059
|
+
if (rawBody && typeof rawBody === "object" && "parentId" in rawBody) {
|
|
10060
|
+
let pid = null;
|
|
10061
|
+
const p = rawBody.parentId;
|
|
10062
|
+
if (p != null && p !== "") {
|
|
10063
|
+
const n = Number(p);
|
|
10064
|
+
if (!Number.isFinite(n)) {
|
|
10065
|
+
return json({ error: "Invalid parentId" }, { status: 400 });
|
|
10066
|
+
}
|
|
10067
|
+
pid = n;
|
|
10068
|
+
}
|
|
10069
|
+
if (pid != null) {
|
|
10070
|
+
const parent = await repo.findOne({
|
|
10071
|
+
where: { id: pid, deleted: false }
|
|
10072
|
+
});
|
|
10073
|
+
if (!parent || parent.kind !== "folder") {
|
|
10074
|
+
return json({ error: "parent must be a folder" }, { status: 400 });
|
|
10075
|
+
}
|
|
10076
|
+
}
|
|
10077
|
+
const row = await repo.findOne({
|
|
10078
|
+
where: { id: numericId, deleted: false }
|
|
10079
|
+
});
|
|
10080
|
+
if (!row) return json({ message: "Not found" }, { status: 404 });
|
|
10081
|
+
if (pid === numericId) {
|
|
10082
|
+
return json({ error: "Invalid parentId" }, { status: 400 });
|
|
10083
|
+
}
|
|
10084
|
+
if (row.kind === "folder" && pid != null) {
|
|
10085
|
+
let walk = pid;
|
|
10086
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10087
|
+
while (walk != null) {
|
|
10088
|
+
if (walk === numericId) {
|
|
10089
|
+
return json(
|
|
10090
|
+
{ error: "Cannot move a folder into itself or a descendant folder" },
|
|
10091
|
+
{ status: 400 }
|
|
10092
|
+
);
|
|
10093
|
+
}
|
|
10094
|
+
if (seen.has(walk)) break;
|
|
10095
|
+
seen.add(walk);
|
|
10096
|
+
const anc = await repo.findOne({
|
|
10097
|
+
where: { id: walk, deleted: false }
|
|
10098
|
+
});
|
|
10099
|
+
walk = anc ? anc.parentId ?? null : null;
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
u.parentId = pid;
|
|
10103
|
+
} else {
|
|
10104
|
+
delete u.parentId;
|
|
10105
|
+
}
|
|
9531
10106
|
}
|
|
9532
10107
|
if (resource === "products") {
|
|
9533
10108
|
const currentRow = await repo.findOne({
|
|
@@ -9546,6 +10121,26 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
9546
10121
|
updatePayload.sku = effSku;
|
|
9547
10122
|
}
|
|
9548
10123
|
}
|
|
10124
|
+
if (resource === "addresses" && Object.keys(updatePayload).length > 0) {
|
|
10125
|
+
const currentRow = await repo.findOne({
|
|
10126
|
+
where: { id: numericId }
|
|
10127
|
+
});
|
|
10128
|
+
if (!currentRow) return json({ message: "Not found" }, { status: 404 });
|
|
10129
|
+
const merged = {
|
|
10130
|
+
...currentRow,
|
|
10131
|
+
...updatePayload
|
|
10132
|
+
};
|
|
10133
|
+
if (merged.tag === "") merged.tag = null;
|
|
10134
|
+
const addrErr = validateAndNormalizeAddressRow(merged);
|
|
10135
|
+
if (addrErr) {
|
|
10136
|
+
return json({ error: addrErr }, { status: 400 });
|
|
10137
|
+
}
|
|
10138
|
+
for (const k of Object.keys(updatePayload)) {
|
|
10139
|
+
if (k in merged) {
|
|
10140
|
+
updatePayload[k] = merged[k];
|
|
10141
|
+
}
|
|
10142
|
+
}
|
|
10143
|
+
}
|
|
9549
10144
|
if (Object.keys(updatePayload).length > 0) {
|
|
9550
10145
|
sanitizeBodyForEntity(repo, updatePayload);
|
|
9551
10146
|
await repo.update(numericId, updatePayload);
|
|
@@ -10717,7 +11312,7 @@ function createCmsApiHandler(config) {
|
|
|
10717
11312
|
} : usersApi;
|
|
10718
11313
|
const usersHandlers = usersApiMerged ? createUsersApiHandlers(mergePerm(usersApiMerged) ?? usersApiMerged) : null;
|
|
10719
11314
|
const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
|
|
10720
|
-
const
|
|
11315
|
+
const profileHandlers = userProfile ? createUserProfileHandler(userProfile) : null;
|
|
10721
11316
|
const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
|
|
10722
11317
|
const smsMessageTemplateHandlers = createSmsMessageTemplateHandlers({
|
|
10723
11318
|
dataSource,
|
|
@@ -10816,7 +11411,10 @@ function createCmsApiHandler(config) {
|
|
|
10816
11411
|
}
|
|
10817
11412
|
if (path.length === 2) {
|
|
10818
11413
|
if (path[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
|
|
10819
|
-
if (path[1] === "profile" &&
|
|
11414
|
+
if (path[1] === "profile" && profileHandlers) {
|
|
11415
|
+
if (m === "GET") return profileHandlers.GET(req);
|
|
11416
|
+
if (m === "PUT") return profileHandlers.PUT(req);
|
|
11417
|
+
}
|
|
10820
11418
|
const id = path[1];
|
|
10821
11419
|
if (m === "GET") return usersHandlers.getById(req, id);
|
|
10822
11420
|
if (m === "PUT" || m === "PATCH") return usersHandlers.update(req, id);
|
|
@@ -11547,18 +12145,19 @@ function createStorefrontApiHandler(config) {
|
|
|
11547
12145
|
const contactOrErr = await getContactForAddresses();
|
|
11548
12146
|
if (contactOrErr instanceof Response) return contactOrErr;
|
|
11549
12147
|
const b = await req.json().catch(() => ({}));
|
|
11550
|
-
const
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
);
|
|
12148
|
+
const row = {
|
|
12149
|
+
contactId: contactOrErr.contactId,
|
|
12150
|
+
tag: typeof b.tag === "string" ? b.tag.trim() || null : null,
|
|
12151
|
+
line1: typeof b.line1 === "string" ? b.line1 : "",
|
|
12152
|
+
line2: typeof b.line2 === "string" ? b.line2.trim() || null : null,
|
|
12153
|
+
city: typeof b.city === "string" ? b.city : "",
|
|
12154
|
+
state: typeof b.state === "string" ? b.state : "",
|
|
12155
|
+
postalCode: typeof b.postalCode === "string" ? b.postalCode : "",
|
|
12156
|
+
country: typeof b.country === "string" ? b.country : ""
|
|
12157
|
+
};
|
|
12158
|
+
const addrErr = validateAndNormalizeAddressRow(row);
|
|
12159
|
+
if (addrErr) return json({ error: addrErr }, { status: 400 });
|
|
12160
|
+
const created = await addressRepo().save(addressRepo().create(row));
|
|
11562
12161
|
return json(serializeAddress2(created));
|
|
11563
12162
|
}
|
|
11564
12163
|
if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
|
|
@@ -11577,7 +12176,16 @@ function createStorefrontApiHandler(config) {
|
|
|
11577
12176
|
if (b.state !== void 0) updates.state = typeof b.state === "string" ? b.state.trim() || null : null;
|
|
11578
12177
|
if (b.postalCode !== void 0) updates.postalCode = typeof b.postalCode === "string" ? b.postalCode.trim() || null : null;
|
|
11579
12178
|
if (b.country !== void 0) updates.country = typeof b.country === "string" ? b.country.trim() || null : null;
|
|
11580
|
-
if (Object.keys(updates).length)
|
|
12179
|
+
if (Object.keys(updates).length) {
|
|
12180
|
+
const merged = { ...existing, ...updates };
|
|
12181
|
+
if (merged.tag === "") merged.tag = null;
|
|
12182
|
+
const addrErr = validateAndNormalizeAddressRow(merged);
|
|
12183
|
+
if (addrErr) return json({ error: addrErr }, { status: 400 });
|
|
12184
|
+
for (const k of Object.keys(updates)) {
|
|
12185
|
+
if (k in merged) updates[k] = merged[k];
|
|
12186
|
+
}
|
|
12187
|
+
await addressRepo().update(id, updates);
|
|
12188
|
+
}
|
|
11581
12189
|
const updated = await addressRepo().findOne({ where: { id } });
|
|
11582
12190
|
return json(serializeAddress2(updated));
|
|
11583
12191
|
}
|