@infuro/cms-core 1.0.46 → 1.0.50

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.
Files changed (65) hide show
  1. package/README.md +30 -16
  2. package/dist/admin.cjs +4568 -2716
  3. package/dist/admin.d.cts +18 -4
  4. package/dist/admin.d.ts +18 -4
  5. package/dist/admin.js +3475 -1625
  6. package/dist/api.cjs +38 -39
  7. package/dist/api.d.cts +9 -3
  8. package/dist/api.d.ts +9 -3
  9. package/dist/api.js +6 -7
  10. package/dist/auth.cjs +84 -51
  11. package/dist/auth.d.cts +105 -4
  12. package/dist/auth.d.ts +105 -4
  13. package/dist/auth.js +4 -3
  14. package/dist/chunk-2IAXTKQN.cjs +19 -0
  15. package/dist/chunk-3EOM4V2M.cjs +452 -0
  16. package/dist/{chunk-X6UQFV4X.cjs → chunk-77NUXO6A.cjs} +14 -12
  17. package/dist/{chunk-KFFB6EMZ.js → chunk-AYERBA7I.js} +3 -7
  18. package/dist/chunk-EIZKW2PF.js +13 -0
  19. package/dist/{chunk-CRYKVJTO.js → chunk-IPDHT2UV.js} +1420 -3
  20. package/dist/{chunk-UDVLFVEC.cjs → chunk-JCMOOPNX.cjs} +3820 -2907
  21. package/dist/{chunk-X47PBV4I.js → chunk-JE22VP6S.js} +1 -0
  22. package/dist/{chunk-DN65KDIA.js → chunk-JIWUVQ6B.js} +14 -13
  23. package/dist/{chunk-HT5DI3XW.cjs → chunk-KOTXDSQB.cjs} +1 -0
  24. package/dist/{chunk-UKC3HYXI.js → chunk-LT2WOPKA.js} +3733 -2813
  25. package/dist/chunk-RK5ETF2I.js +434 -0
  26. package/dist/{chunk-SPQFNIYJ.cjs → chunk-TJ2MIQUT.cjs} +3 -7
  27. package/dist/{chunk-TCSGFAWB.cjs → chunk-UUWAUHUW.cjs} +1451 -3
  28. package/dist/{chunk-57O3HZPG.js → chunk-WRKV6MHB.js} +415 -3
  29. package/dist/{chunk-FBKDMRQL.cjs → chunk-YV5PK4JW.cjs} +427 -7
  30. package/dist/cli.cjs +13 -22
  31. package/dist/cli.js +13 -22
  32. package/dist/{emit-order-notification-trigger-IVLWKKJL.js → emit-order-notification-trigger-6XX7LSC4.js} +1 -1
  33. package/dist/{emit-order-notification-trigger-D2N3KFHL.cjs → emit-order-notification-trigger-NASG7ZEO.cjs} +3 -3
  34. package/dist/{event-order-defaults-EMWBIGL3.cjs → event-order-defaults-DU7V3YND.cjs} +9 -9
  35. package/dist/{event-order-defaults-SDRNAZHC.js → event-order-defaults-H4RT7NMR.js} +1 -1
  36. package/dist/hooks.cjs +43 -0
  37. package/dist/hooks.d.cts +10 -1
  38. package/dist/hooks.d.ts +10 -1
  39. package/dist/hooks.js +39 -1
  40. package/dist/index.cjs +479 -339
  41. package/dist/index.d.cts +181 -20
  42. package/dist/index.d.ts +181 -20
  43. package/dist/index.js +115 -13
  44. package/dist/migrations/1782200000000-VendorTeamProfiles.ts +233 -0
  45. package/dist/migrations/1782300000000-AddTypeToOrderNotificationBindings.ts +75 -0
  46. package/dist/migrations/1782400000000-CreateUserDeviceTokens.ts +36 -0
  47. package/dist/migrations/1782500000000-AddPushToOrderNotificationBindingsChannelEnum.ts +15 -0
  48. package/dist/{order-notification-dispatcher-ZJZB2HUN.js → order-notification-dispatcher-3TA4ETYJ.js} +1 -2
  49. package/dist/{order-notification-dispatcher-SBQAMM5V.cjs → order-notification-dispatcher-ZEAZ5SHV.cjs} +7 -4
  50. package/dist/{rbac-debug-db-2SGNLANF.js → rbac-debug-db-6EMIVMGF.js} +1 -1
  51. package/dist/{rbac-debug-db-4Z6A425H.cjs → rbac-debug-db-6PWTLBEL.cjs} +2 -2
  52. package/dist/theme.cjs +6 -1
  53. package/dist/theme.d.cts +4 -1
  54. package/dist/theme.d.ts +4 -1
  55. package/dist/theme.js +3 -1
  56. package/package.json +1 -1
  57. package/src/admin/admin.css +7 -0
  58. package/dist/chunk-7PMHZRF3.cjs +0 -872
  59. package/dist/chunk-CQHXW4TR.js +0 -107
  60. package/dist/chunk-GUSHM5HN.js +0 -862
  61. package/dist/chunk-NBY4NVTY.cjs +0 -182
  62. package/dist/chunk-SF2XKMCI.js +0 -171
  63. package/dist/chunk-XMRMZ6NQ.cjs +0 -109
  64. package/dist/send-order-placed-emails-B5ZVJT7T.cjs +0 -15
  65. package/dist/send-order-placed-emails-WPI37KRZ.js +0 -6
@@ -0,0 +1,434 @@
1
+ import { explainSessionEntityAccess, isVendorPortalUser, logRbac } from './chunk-JIWUVQ6B.js';
2
+ import { __name } from './chunk-SHUYVCID.js';
3
+ import { IsNull } from 'typeorm';
4
+
5
+ async function linkUnclaimedContactToUser(dataSource, contactsEntity, userId, email) {
6
+ const repo = dataSource.getRepository(contactsEntity);
7
+ const found = await repo.findOne({
8
+ where: {
9
+ email,
10
+ userId: IsNull(),
11
+ deleted: false
12
+ }
13
+ });
14
+ if (found) await repo.update(found.id, {
15
+ userId
16
+ });
17
+ }
18
+ __name(linkUnclaimedContactToUser, "linkUnclaimedContactToUser");
19
+
20
+ // src/lib/ensure-customer-for-user.ts
21
+ function normalizeEmail(email) {
22
+ return email.trim().toLowerCase();
23
+ }
24
+ __name(normalizeEmail, "normalizeEmail");
25
+ function pgErrorCode(err) {
26
+ if (!err || typeof err !== "object") return void 0;
27
+ const driver = err.driverError;
28
+ return driver?.code ?? err.code;
29
+ }
30
+ __name(pgErrorCode, "pgErrorCode");
31
+ function normalizeCustomerPhone(phone) {
32
+ const p = typeof phone === "string" ? phone.trim() : "";
33
+ return p || null;
34
+ }
35
+ __name(normalizeCustomerPhone, "normalizeCustomerPhone");
36
+ function isSyntheticCustomerPhone(phone) {
37
+ const p = String(phone ?? "").trim();
38
+ if (!p) return true;
39
+ if (p.startsWith("e-") || p.startsWith("u-")) return true;
40
+ if (p.includes("@")) return true;
41
+ return false;
42
+ }
43
+ __name(isSyntheticCustomerPhone, "isSyntheticCustomerPhone");
44
+ function customerPhoneForUser(_userId, phone) {
45
+ return normalizeCustomerPhone(phone);
46
+ }
47
+ __name(customerPhoneForUser, "customerPhoneForUser");
48
+ function customerPhoneForEmail(_email, phone) {
49
+ return normalizeCustomerPhone(phone);
50
+ }
51
+ __name(customerPhoneForEmail, "customerPhoneForEmail");
52
+ function resolveNextPhone(inputPhone, existingPhone) {
53
+ if (inputPhone) return inputPhone;
54
+ const existing = normalizeCustomerPhone(existingPhone);
55
+ if (!existing || isSyntheticCustomerPhone(existing)) return null;
56
+ return existing;
57
+ }
58
+ __name(resolveNextPhone, "resolveNextPhone");
59
+ async function ensureCustomerRecord(dsOrEm, customerEntity, input) {
60
+ const repo = dsOrEm.getRepository(customerEntity);
61
+ const email = normalizeEmail(input.email);
62
+ if (!email) return null;
63
+ const name = String(input.name ?? "").trim() || email.split("@")[0] || "Customer";
64
+ const userId = input.userId != null && Number.isFinite(Number(input.userId)) && Number(input.userId) > 0 ? Number(input.userId) : null;
65
+ const phone = normalizeCustomerPhone(input.phone);
66
+ let row = await repo.findOne({
67
+ where: {
68
+ email,
69
+ deleted: false
70
+ }
71
+ });
72
+ if (!row) {
73
+ row = await repo.findOne({
74
+ where: {
75
+ email
76
+ }
77
+ });
78
+ }
79
+ if (row) {
80
+ const existingUserId = row.userId;
81
+ if (userId != null && existingUserId != null && existingUserId !== userId) {
82
+ return null;
83
+ }
84
+ const nextPhone = resolveNextPhone(phone, row.phone);
85
+ const patch = {
86
+ name,
87
+ phone: nextPhone,
88
+ deleted: false,
89
+ deletedAt: null,
90
+ deletedBy: null,
91
+ updatedAt: /* @__PURE__ */ new Date()
92
+ };
93
+ if (userId != null && existingUserId == null) {
94
+ patch.userId = userId;
95
+ }
96
+ await repo.update(row.id, patch);
97
+ return {
98
+ id: row.id
99
+ };
100
+ }
101
+ try {
102
+ const created = await repo.save(repo.create({
103
+ userId,
104
+ name,
105
+ email,
106
+ phone,
107
+ deleted: false
108
+ }));
109
+ return {
110
+ id: created.id
111
+ };
112
+ } catch (err) {
113
+ const code = pgErrorCode(err);
114
+ if (code === "25P02") throw err;
115
+ row = await repo.findOne({
116
+ where: {
117
+ email
118
+ }
119
+ });
120
+ if (row) {
121
+ const existingUserId = row.userId;
122
+ if (userId != null && existingUserId != null && existingUserId !== userId) return null;
123
+ await repo.update(row.id, {
124
+ name,
125
+ phone: resolveNextPhone(phone, row.phone),
126
+ ...userId != null && existingUserId == null ? {
127
+ userId
128
+ } : {},
129
+ deleted: false,
130
+ deletedAt: null,
131
+ deletedBy: null,
132
+ updatedAt: /* @__PURE__ */ new Date()
133
+ });
134
+ return {
135
+ id: row.id
136
+ };
137
+ }
138
+ if (code === "23505") return null;
139
+ throw err;
140
+ }
141
+ }
142
+ __name(ensureCustomerRecord, "ensureCustomerRecord");
143
+ async function restoreCustomerRow(repo, row, user, name, email, phone) {
144
+ const id = row.id;
145
+ await repo.update(id, {
146
+ userId: user.id,
147
+ name,
148
+ email,
149
+ phone,
150
+ deleted: false,
151
+ deletedAt: null,
152
+ deletedBy: null,
153
+ updatedAt: /* @__PURE__ */ new Date()
154
+ });
155
+ return {
156
+ id
157
+ };
158
+ }
159
+ __name(restoreCustomerRow, "restoreCustomerRow");
160
+ async function ensureCustomerForUser(dsOrEm, customerEntity, user, overrides) {
161
+ const repo = dsOrEm.getRepository(customerEntity);
162
+ const email = normalizeEmail(user.email);
163
+ const name = String(user.name ?? "").trim() || email.split("@")[0] || "User";
164
+ const phone = normalizeCustomerPhone(overrides?.phone ?? user.phone);
165
+ let row = await repo.findOne({
166
+ where: {
167
+ userId: user.id,
168
+ deleted: false
169
+ }
170
+ });
171
+ if (row) {
172
+ const nextPhone = resolveNextPhone(phone, row.phone);
173
+ await repo.update(row.id, {
174
+ name,
175
+ email,
176
+ phone: nextPhone,
177
+ updatedAt: /* @__PURE__ */ new Date()
178
+ });
179
+ return {
180
+ id: row.id
181
+ };
182
+ }
183
+ row = await repo.findOne({
184
+ where: {
185
+ email,
186
+ deleted: false
187
+ }
188
+ });
189
+ if (row) {
190
+ const existingUserId = row.userId;
191
+ if (existingUserId != null && existingUserId !== user.id) {
192
+ return null;
193
+ }
194
+ const nextPhone = resolveNextPhone(phone, row.phone);
195
+ await repo.update(row.id, {
196
+ userId: user.id,
197
+ name,
198
+ phone: nextPhone,
199
+ updatedAt: /* @__PURE__ */ new Date()
200
+ });
201
+ return {
202
+ id: row.id
203
+ };
204
+ }
205
+ const deletedByEmail = await repo.findOne({
206
+ where: {
207
+ email
208
+ }
209
+ });
210
+ if (deletedByEmail) {
211
+ const existingUserId = deletedByEmail.userId;
212
+ if (existingUserId != null && existingUserId !== user.id) return null;
213
+ return restoreCustomerRow(repo, deletedByEmail, user, name, email, resolveNextPhone(phone, deletedByEmail.phone));
214
+ }
215
+ if (phone) {
216
+ const deletedByPhone = await repo.findOne({
217
+ where: {
218
+ phone
219
+ }
220
+ });
221
+ if (deletedByPhone) {
222
+ const existingUserId = deletedByPhone.userId;
223
+ if (existingUserId != null && existingUserId !== user.id) {
224
+ return ensureCustomerForUser(dsOrEm, customerEntity, user, {
225
+ phone: null
226
+ });
227
+ }
228
+ return restoreCustomerRow(repo, deletedByPhone, user, name, email, phone);
229
+ }
230
+ }
231
+ try {
232
+ const created = await repo.save(repo.create({
233
+ userId: user.id,
234
+ name,
235
+ email,
236
+ phone,
237
+ deleted: false
238
+ }));
239
+ return {
240
+ id: created.id
241
+ };
242
+ } catch (err) {
243
+ const code = pgErrorCode(err);
244
+ if (code === "25P02") throw err;
245
+ row = await repo.findOne({
246
+ where: {
247
+ userId: user.id
248
+ }
249
+ });
250
+ if (row) {
251
+ return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
252
+ }
253
+ row = await repo.findOne({
254
+ where: {
255
+ email
256
+ }
257
+ });
258
+ if (row && (row.userId ?? user.id) === user.id) {
259
+ return restoreCustomerRow(repo, row, user, name, email, resolveNextPhone(phone, row.phone));
260
+ }
261
+ if (code === "23505") return null;
262
+ throw err;
263
+ }
264
+ }
265
+ __name(ensureCustomerForUser, "ensureCustomerForUser");
266
+
267
+ // src/auth/helpers.ts
268
+ var RBAC_ADMIN_ONLY_ENTITIES = /* @__PURE__ */ new Set([
269
+ "users",
270
+ "user_groups",
271
+ "permissions"
272
+ ]);
273
+ function sessionHasEntityAccess(user, entity, action) {
274
+ return explainSessionEntityAccess(user, entity, action).allowed;
275
+ }
276
+ __name(sessionHasEntityAccess, "sessionHasEntityAccess");
277
+ function canManageRoles(user) {
278
+ return !!(user?.email && user.isRBACAdmin);
279
+ }
280
+ __name(canManageRoles, "canManageRoles");
281
+ var OPEN_ENDPOINTS = [
282
+ {
283
+ "/api/contacts": [
284
+ "POST"
285
+ ]
286
+ },
287
+ {
288
+ "/api/form-submissions": [
289
+ "POST"
290
+ ]
291
+ },
292
+ {
293
+ "/api/blogs": [
294
+ "GET"
295
+ ]
296
+ }
297
+ ];
298
+ var PERMISSION_REQUIRED_ENDPOINTS = {};
299
+ function isOpenEndpoint(pathname) {
300
+ return OPEN_ENDPOINTS.some((endpoint) => pathname.startsWith(Object.keys(endpoint)[0]));
301
+ }
302
+ __name(isOpenEndpoint, "isOpenEndpoint");
303
+ function getRequiredPermission(pathname) {
304
+ return null;
305
+ }
306
+ __name(getRequiredPermission, "getRequiredPermission");
307
+ function isPublicMethod(pathname, method) {
308
+ for (const endpoint of OPEN_ENDPOINTS) {
309
+ const key = Object.keys(endpoint)[0];
310
+ if (pathname.startsWith(key) && endpoint[key].includes(method)) return true;
311
+ }
312
+ return false;
313
+ }
314
+ __name(isPublicMethod, "isPublicMethod");
315
+ function createAuthHelpers(getSession, NextResponse) {
316
+ return createAuthHelpersFromGetSession(getSession, NextResponse);
317
+ }
318
+ __name(createAuthHelpers, "createAuthHelpers");
319
+ function createCmsAuthBundle(getSession, NextResponse) {
320
+ const auth = createAuthHelpers(getSession, NextResponse);
321
+ return {
322
+ requireAuth: auth.requireAuth,
323
+ requireAdminAccess: auth.requireAdminAccess,
324
+ requireEntityPermission: auth.requireEntityPermission,
325
+ getSessionUser: auth.getAuthenticatedUser,
326
+ getAuthenticatedUser: auth.getAuthenticatedUser
327
+ };
328
+ }
329
+ __name(createCmsAuthBundle, "createCmsAuthBundle");
330
+ function createAuthHelpersFromGetSession(getSession, NextResponse) {
331
+ return {
332
+ async requireAuth(_req) {
333
+ const session = await getSession();
334
+ if (!session?.user?.email) {
335
+ return NextResponse.json({
336
+ error: "Unauthorized"
337
+ }, {
338
+ status: 401
339
+ });
340
+ }
341
+ return null;
342
+ },
343
+ async requirePermission(_req, _permission) {
344
+ const session = await getSession();
345
+ if (!session?.user?.email) {
346
+ return NextResponse.json({
347
+ error: "Unauthorized"
348
+ }, {
349
+ status: 401
350
+ });
351
+ }
352
+ return null;
353
+ },
354
+ async requireEntityPermission(_req, entity, action) {
355
+ const session = await getSession();
356
+ if (!session?.user?.email) {
357
+ return NextResponse.json({
358
+ error: "Unauthorized"
359
+ }, {
360
+ status: 401
361
+ });
362
+ }
363
+ const u = session.user;
364
+ const allowed = sessionHasEntityAccess(u, entity, action);
365
+ const { reason } = explainSessionEntityAccess(u, entity, action);
366
+ logRbac("authHelpers.requireEntityPermission", {
367
+ entity,
368
+ action,
369
+ allowed,
370
+ reason,
371
+ email: u.email,
372
+ groupName: u.groupName,
373
+ vendorIds: u.vendorIds,
374
+ isVendorPortal: u.isVendorPortal,
375
+ isVendorOwner: u.isVendorOwner
376
+ });
377
+ if (allowed) return null;
378
+ return NextResponse.json({
379
+ error: "Forbidden",
380
+ entity,
381
+ action,
382
+ reason
383
+ }, {
384
+ status: 403
385
+ });
386
+ },
387
+ async requireAdminAccess(_req) {
388
+ const session = await getSession();
389
+ if (!session?.user?.email) {
390
+ return NextResponse.json({
391
+ error: "Unauthorized"
392
+ }, {
393
+ status: 401
394
+ });
395
+ }
396
+ const u = session.user;
397
+ if (u.isRBACAdmin) return null;
398
+ if (isVendorPortalUser(u)) return null;
399
+ if (u.adminAccess === true) return null;
400
+ if (u.adminAccess === false) {
401
+ logRbac("requireAdminAccess denied (adminAccess:false)", {
402
+ email: u.email,
403
+ groupName: u.groupName,
404
+ isVendorPortal: u.isVendorPortal
405
+ });
406
+ return NextResponse.json({
407
+ error: "Forbidden",
408
+ reason: "admin_access"
409
+ }, {
410
+ status: 403
411
+ });
412
+ }
413
+ logRbac("requireAdminAccess denied (no matching rule)", {
414
+ email: u.email,
415
+ adminAccess: u.adminAccess,
416
+ groupName: u.groupName,
417
+ isVendorPortal: u.isVendorPortal
418
+ });
419
+ return NextResponse.json({
420
+ error: "Forbidden",
421
+ reason: "admin_access"
422
+ }, {
423
+ status: 403
424
+ });
425
+ },
426
+ async getAuthenticatedUser() {
427
+ const session = await getSession();
428
+ return session?.user ?? null;
429
+ }
430
+ };
431
+ }
432
+ __name(createAuthHelpersFromGetSession, "createAuthHelpersFromGetSession");
433
+
434
+ export { OPEN_ENDPOINTS, PERMISSION_REQUIRED_ENDPOINTS, RBAC_ADMIN_ONLY_ENTITIES, canManageRoles, createAuthHelpers, createCmsAuthBundle, customerPhoneForEmail, customerPhoneForUser, ensureCustomerForUser, ensureCustomerRecord, getRequiredPermission, isOpenEndpoint, isPublicMethod, isSyntheticCustomerPhone, linkUnclaimedContactToUser, normalizeCustomerPhone, sessionHasEntityAccess };
@@ -6,6 +6,7 @@ var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
6
6
  var EVENT_ORDER_TEMPLATE_KEY = "eventOrderPlaced";
7
7
  var EVENT_ORDER_TEMPLATE_VARIABLES = [
8
8
  "eventName",
9
+ "vendorName",
9
10
  "eventSlug",
10
11
  "venue",
11
12
  "startDate",
@@ -17,7 +18,6 @@ var EVENT_ORDER_TEMPLATE_VARIABLES = [
17
18
  "customerPhone",
18
19
  "productNames",
19
20
  "trackUrl",
20
- "qrImageUrl",
21
21
  "orderDetails",
22
22
  "orderDetailsHtml"
23
23
  ];
@@ -33,6 +33,7 @@ var EVENT_ORDER_WHATSAPP_PARAM_KEYS = [
33
33
  ];
34
34
  var EVENT_ORDER_TEMPLATE_VARIABLE_HINTS = {
35
35
  eventName: "Event title",
36
+ vendorName: "Vendor / store name",
36
37
  eventSlug: "Event URL slug",
37
38
  venue: "Venue name",
38
39
  startDate: "Formatted event start",
@@ -43,8 +44,7 @@ var EVENT_ORDER_TEMPLATE_VARIABLE_HINTS = {
43
44
  customerEmail: "Buyer email",
44
45
  customerPhone: "Buyer phone",
45
46
  productNames: "Comma-separated ticket names",
46
- trackUrl: "Public order tracking link (QR destination)",
47
- qrImageUrl: 'QR code image URL for email (use in <img src="\u2026">)',
47
+ trackUrl: "Public order tracking link",
48
48
  orderDetails: "Plain-text order summary with line items",
49
49
  orderDetailsHtml: "HTML order summary block",
50
50
  trackUrlWhatsApp: "Same as trackUrl \u2014 include in Meta template as last param"
@@ -60,10 +60,6 @@ var EVENT_ORDER_MESSAGE_TEMPLATE_DEFAULTS = [
60
60
  {{orderDetailsHtml}}
61
61
  <p><strong>Venue:</strong> {{venue}}<br/>
62
62
  <strong>Date:</strong> {{startDate}}</p>
63
- <p>Show this QR code at the venue or scan to open your order:</p>
64
- <p style="text-align:center;margin:20px 0">
65
- <img src="{{qrImageUrl}}" alt="Order QR" width="180" height="180" style="border:1px solid #e5e7eb;border-radius:8px" />
66
- </p>
67
63
  <p style="text-align:center"><a href="{{trackUrl}}">View order online</a></p>
68
64
  <p>We look forward to seeing you there.</p>`
69
65
  },