@infuro/cms-core 1.0.41 → 1.0.43

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.
@@ -5,8 +5,8 @@ import { queueEmail, queueVendorOnboardEmails, registerEmailQueueProcessor } fro
5
5
  import { getSmsTemplateDefault, SMS_MESSAGE_TEMPLATE_DEFAULTS } from './chunk-MQBT33IV.js';
6
6
  import { EVENT_ORDER_MESSAGE_TEMPLATE_DEFAULTS, EVENT_ORDER_TEMPLATE_KEY, EVENT_ORDER_TEMPLATE_VARIABLES } from './chunk-KFFB6EMZ.js';
7
7
  import { JOB_RUNNER_QUEUE } from './chunk-GO7PPYNU.js';
8
- import { canManageRoles, sessionHasEntityAccess } from './chunk-UXMDSNIG.js';
9
- import { permissionRowsToRecord, logRbac, isSuperAdmin, vendorPortalFlagsFromUser, VENDOR_SCOPED_STORE_ENTITIES, isVendorPortalUser, getPermissionableEntityKeys, VENDOR_STORE_RBAC_ENTITIES, isSuperAdminGroupName, VENDOR_RBAC_ENTITIES, VENDOR_VIEWER_ROLE_NAME, DEFAULT_VENDOR_ROLE_DEFS, VENDOR_OWNER_GROUP_NAME, resolveVendorScopeFromSessionUser, canManageVendorRoles, isPlatformAdministrator, canManageVendorTeam, resolveDisabledMultiVendorScope, logEntityAccessDecision, explainSessionEntityAccess } from './chunk-P7QCLE5W.js';
8
+ import { canManageRoles, sessionHasEntityAccess } from './chunk-BLR6H5GL.js';
9
+ import { permissionRowsToRecord, logRbac, isSuperAdmin, vendorPortalFlagsFromUser, VENDOR_SCOPED_STORE_ENTITIES, isVendorPortalUser, getPermissionableEntityKeys, VENDOR_STORE_RBAC_ENTITIES, isSuperAdminGroupName, VENDOR_RBAC_ENTITIES, VENDOR_VIEWER_ROLE_NAME, DEFAULT_VENDOR_ROLE_DEFS, VENDOR_OWNER_GROUP_NAME, resolveVendorScopeFromSessionUser, canManageVendorRoles, isPlatformAdministrator, canManageVendorTeam, resolveDisabledMultiVendorScope, logEntityAccessDecision, explainSessionEntityAccess } from './chunk-DN65KDIA.js';
10
10
  import { queueErp } from './chunk-SYBOCAWB.js';
11
11
  import { isErpIntegrationEnabled } from './chunk-JC6DLWTE.js';
12
12
  import { __name } from './chunk-SHUYVCID.js';
@@ -94,6 +94,79 @@ async function loadUserVendorContext(dataSource, userId, preferredVendorId) {
94
94
  }
95
95
  __name(loadUserVendorContext, "loadUserVendorContext");
96
96
 
97
+ // src/lib/vendor-catalog-create-flags.ts
98
+ var ALL_OFF = {
99
+ categories: false,
100
+ collections: false,
101
+ brands: false
102
+ };
103
+ var cachedFlags = null;
104
+ function invalidateVendorCatalogCreateFlagsCache() {
105
+ cachedFlags = null;
106
+ }
107
+ __name(invalidateVendorCatalogCreateFlagsCache, "invalidateVendorCatalogCreateFlagsCache");
108
+ function parseTrue(value) {
109
+ return value === "true";
110
+ }
111
+ __name(parseTrue, "parseTrue");
112
+ async function isMultiVendorEnabled(dataSource) {
113
+ try {
114
+ const rows = await dataSource.query(`
115
+ SELECT value FROM configs
116
+ WHERE settings = 'multi_vendor' AND key = 'enabled' AND deleted = false
117
+ LIMIT 1
118
+ `);
119
+ return rows.length === 0 || rows[0].value !== "false";
120
+ } catch {
121
+ return true;
122
+ }
123
+ }
124
+ __name(isMultiVendorEnabled, "isMultiVendorEnabled");
125
+ async function getVendorCatalogCreateFlags(dataSource) {
126
+ if (cachedFlags !== null) return cachedFlags;
127
+ const multiVendorEnabled = await isMultiVendorEnabled(dataSource);
128
+ if (!multiVendorEnabled) {
129
+ cachedFlags = {
130
+ ...ALL_OFF
131
+ };
132
+ return cachedFlags;
133
+ }
134
+ try {
135
+ const rows = await dataSource.query(`
136
+ SELECT key, value FROM configs
137
+ WHERE settings = 'multi_vendor'
138
+ AND key IN (
139
+ 'vendorCanCreateCategories',
140
+ 'vendorCanCreateCollections',
141
+ 'vendorCanCreateBrands'
142
+ )
143
+ AND deleted = false
144
+ `);
145
+ const map = new Map(rows.map((r) => [
146
+ r.key,
147
+ r.value
148
+ ]));
149
+ cachedFlags = {
150
+ categories: parseTrue(map.get("vendorCanCreateCategories")),
151
+ collections: parseTrue(map.get("vendorCanCreateCollections")),
152
+ brands: parseTrue(map.get("vendorCanCreateBrands"))
153
+ };
154
+ } catch {
155
+ cachedFlags = {
156
+ ...ALL_OFF
157
+ };
158
+ }
159
+ return cachedFlags;
160
+ }
161
+ __name(getVendorCatalogCreateFlags, "getVendorCatalogCreateFlags");
162
+ function vendorCanCreateResource(resource, flags) {
163
+ if (resource === "product_categories") return flags.categories;
164
+ if (resource === "collections") return flags.collections;
165
+ if (resource === "brands") return flags.brands;
166
+ return false;
167
+ }
168
+ __name(vendorCanCreateResource, "vendorCanCreateResource");
169
+
97
170
  // src/lib/hydrate-vendor-session-user.ts
98
171
  async function hydrateVendorSessionUser(dataSource, user) {
99
172
  if (!user?.email) return user;
@@ -196,6 +269,7 @@ __name(hydrateVendorSessionUser, "hydrateVendorSessionUser");
196
269
  var cachedMultiVendorEnabled = null;
197
270
  function invalidateMultiVendorCache() {
198
271
  cachedMultiVendorEnabled = null;
272
+ invalidateVendorCatalogCreateFlagsCache();
199
273
  }
200
274
  __name(invalidateMultiVendorCache, "invalidateMultiVendorCache");
201
275
  async function checkMultiVendorEnabled(dataSource) {
@@ -234,6 +308,279 @@ async function checkEventsEnabled(dataSource) {
234
308
  }
235
309
  __name(checkEventsEnabled, "checkEventsEnabled");
236
310
 
311
+ // src/lib/product-approval.ts
312
+ var PRODUCT_APPROVAL_STATUSES = [
313
+ "pending",
314
+ "approved",
315
+ "rejected"
316
+ ];
317
+ var APPROVAL_SET = new Set(PRODUCT_APPROVAL_STATUSES);
318
+ var PRODUCT_STATUSES = /* @__PURE__ */ new Set([
319
+ "draft",
320
+ "available",
321
+ "reserved",
322
+ "sold"
323
+ ]);
324
+ var cachedRequireApproval = null;
325
+ function invalidateRequireProductApprovalCache() {
326
+ cachedRequireApproval = null;
327
+ }
328
+ __name(invalidateRequireProductApprovalCache, "invalidateRequireProductApprovalCache");
329
+ function parseTrue2(value) {
330
+ return value === "true";
331
+ }
332
+ __name(parseTrue2, "parseTrue");
333
+ async function getRequireProductApproval(dataSource) {
334
+ if (cachedRequireApproval !== null) return cachedRequireApproval;
335
+ const multiVendorEnabled = await checkMultiVendorEnabled(dataSource);
336
+ if (!multiVendorEnabled) {
337
+ cachedRequireApproval = false;
338
+ return false;
339
+ }
340
+ try {
341
+ const rows = await dataSource.query(`
342
+ SELECT value FROM configs
343
+ WHERE settings = 'multi_vendor' AND key = 'requireProductApproval' AND deleted = false
344
+ LIMIT 1
345
+ `);
346
+ cachedRequireApproval = rows.length > 0 && parseTrue2(rows[0].value);
347
+ } catch {
348
+ cachedRequireApproval = false;
349
+ }
350
+ return cachedRequireApproval;
351
+ }
352
+ __name(getRequireProductApproval, "getRequireProductApproval");
353
+ function applyVendorProductCreateApproval(persistBody, opts) {
354
+ if (!opts.isVendor || !opts.requireApproval) return;
355
+ persistBody.approvalStatus = "pending";
356
+ if (persistBody.status === "available") persistBody.status = "draft";
357
+ persistBody.rejectionReason = null;
358
+ persistBody.rejectedAt = null;
359
+ persistBody.rejectedBy = null;
360
+ }
361
+ __name(applyVendorProductCreateApproval, "applyVendorProductCreateApproval");
362
+ function assertProductApprovalUpdate(opts) {
363
+ const fromApproval = opts.fromApproval == null || opts.fromApproval === "" ? null : String(opts.fromApproval);
364
+ const toApproval = opts.toApproval == null || opts.toApproval === "" ? null : String(opts.toApproval);
365
+ const fromStatus = String(opts.fromStatus || "draft");
366
+ const toStatus = String(opts.toStatus || fromStatus);
367
+ if (opts.statusChanged && !PRODUCT_STATUSES.has(toStatus)) {
368
+ return {
369
+ ok: false,
370
+ error: `Invalid product status: ${toStatus}`
371
+ };
372
+ }
373
+ if (opts.approvalChanged && toApproval != null && !APPROVAL_SET.has(toApproval)) {
374
+ return {
375
+ ok: false,
376
+ error: `Invalid approval status: ${toApproval}`
377
+ };
378
+ }
379
+ if (!opts.requireApproval) {
380
+ if (opts.isVendor && opts.approvalChanged) {
381
+ return {
382
+ ok: false,
383
+ error: "Vendors cannot change approval status"
384
+ };
385
+ }
386
+ return {
387
+ ok: true
388
+ };
389
+ }
390
+ if (opts.isVendor) {
391
+ if (opts.approvalChanged) {
392
+ return {
393
+ ok: false,
394
+ error: "Vendors cannot change approval status"
395
+ };
396
+ }
397
+ if (opts.statusChanged && toStatus === "available" && fromApproval !== "approved") {
398
+ return {
399
+ ok: false,
400
+ error: "Product must be approved before it can be available"
401
+ };
402
+ }
403
+ if (fromApproval === "rejected" && opts.statusChanged && toStatus === "available") {
404
+ return {
405
+ ok: false,
406
+ error: "Rejected product cannot be made available"
407
+ };
408
+ }
409
+ return {
410
+ ok: true
411
+ };
412
+ }
413
+ if (opts.approvalChanged && toApproval === "rejected") {
414
+ const reason = String(opts.rejectionReason ?? "").trim();
415
+ if (!reason) return {
416
+ ok: false,
417
+ error: "Rejection reason is required"
418
+ };
419
+ }
420
+ return {
421
+ ok: true
422
+ };
423
+ }
424
+ __name(assertProductApprovalUpdate, "assertProductApprovalUpdate");
425
+ function applyApprovalStatusSideEffects(updatePayload, opts) {
426
+ const to = opts.toApproval == null || opts.toApproval === "" ? null : String(opts.toApproval);
427
+ if (to == null) return;
428
+ if (to === "approved") {
429
+ updatePayload.approvalStatus = "approved";
430
+ updatePayload.status = "available";
431
+ updatePayload.rejectionReason = null;
432
+ updatePayload.rejectedAt = null;
433
+ updatePayload.rejectedBy = null;
434
+ return;
435
+ }
436
+ if (to === "rejected") {
437
+ updatePayload.approvalStatus = "rejected";
438
+ updatePayload.rejectionReason = String(opts.rejectionReason ?? "").trim();
439
+ updatePayload.rejectedAt = /* @__PURE__ */ new Date();
440
+ updatePayload.rejectedBy = opts.rejectedBy != null && Number.isFinite(opts.rejectedBy) ? opts.rejectedBy : null;
441
+ const nextStatus = "status" in updatePayload ? String(updatePayload.status) : opts.currentStatus;
442
+ if (nextStatus === "available") updatePayload.status = "draft";
443
+ return;
444
+ }
445
+ if (to === "pending") {
446
+ updatePayload.approvalStatus = "pending";
447
+ updatePayload.rejectionReason = null;
448
+ updatePayload.rejectedAt = null;
449
+ updatePayload.rejectedBy = null;
450
+ const nextStatus = "status" in updatePayload ? String(updatePayload.status) : opts.currentStatus;
451
+ if (nextStatus === "available") updatePayload.status = "draft";
452
+ }
453
+ }
454
+ __name(applyApprovalStatusSideEffects, "applyApprovalStatusSideEffects");
455
+
456
+ // src/lib/event-approval.ts
457
+ var EVENT_APPROVAL_STATUSES = [
458
+ "pending",
459
+ "approved",
460
+ "rejected"
461
+ ];
462
+ var APPROVAL_SET2 = new Set(EVENT_APPROVAL_STATUSES);
463
+ var cachedRequireApproval2 = null;
464
+ function invalidateRequireEventApprovalCache() {
465
+ cachedRequireApproval2 = null;
466
+ }
467
+ __name(invalidateRequireEventApprovalCache, "invalidateRequireEventApprovalCache");
468
+ function parseTrue3(value) {
469
+ return value === "true";
470
+ }
471
+ __name(parseTrue3, "parseTrue");
472
+ async function getRequireEventApproval(dataSource) {
473
+ if (cachedRequireApproval2 !== null) return cachedRequireApproval2;
474
+ const [eventsOn, multiVendorOn] = await Promise.all([
475
+ checkEventsEnabled(dataSource),
476
+ checkMultiVendorEnabled(dataSource)
477
+ ]);
478
+ if (!eventsOn || !multiVendorOn) {
479
+ cachedRequireApproval2 = false;
480
+ return false;
481
+ }
482
+ try {
483
+ const rows = await dataSource.query(`
484
+ SELECT value FROM configs
485
+ WHERE settings = 'events' AND key = 'requireEventApproval' AND deleted = false
486
+ LIMIT 1
487
+ `);
488
+ cachedRequireApproval2 = rows.length > 0 && parseTrue3(rows[0].value);
489
+ } catch {
490
+ cachedRequireApproval2 = false;
491
+ }
492
+ return cachedRequireApproval2;
493
+ }
494
+ __name(getRequireEventApproval, "getRequireEventApproval");
495
+ function applyVendorEventCreateApproval(persistBody, opts) {
496
+ if (!opts.isVendor || !opts.requireApproval) return;
497
+ persistBody.approvalStatus = "pending";
498
+ persistBody.isActive = false;
499
+ persistBody.rejectionReason = null;
500
+ persistBody.rejectedAt = null;
501
+ persistBody.rejectedBy = null;
502
+ }
503
+ __name(applyVendorEventCreateApproval, "applyVendorEventCreateApproval");
504
+ function assertEventApprovalUpdate(opts) {
505
+ const fromApproval = opts.fromApproval == null || opts.fromApproval === "" ? null : String(opts.fromApproval);
506
+ const toApproval = opts.toApproval == null || opts.toApproval === "" ? null : String(opts.toApproval);
507
+ if (opts.approvalChanged && toApproval != null && !APPROVAL_SET2.has(toApproval)) {
508
+ return {
509
+ ok: false,
510
+ error: `Invalid approval status: ${toApproval}`
511
+ };
512
+ }
513
+ if (!opts.requireApproval) {
514
+ if (opts.isVendor && opts.approvalChanged) {
515
+ return {
516
+ ok: false,
517
+ error: "Vendors cannot change approval status"
518
+ };
519
+ }
520
+ return {
521
+ ok: true
522
+ };
523
+ }
524
+ if (opts.isVendor) {
525
+ if (opts.approvalChanged) {
526
+ return {
527
+ ok: false,
528
+ error: "Vendors cannot change approval status"
529
+ };
530
+ }
531
+ if (opts.activeChanged && opts.toActive && fromApproval !== "approved") {
532
+ return {
533
+ ok: false,
534
+ error: "Event must be approved before it can be activated"
535
+ };
536
+ }
537
+ return {
538
+ ok: true
539
+ };
540
+ }
541
+ if (opts.approvalChanged && toApproval === "rejected") {
542
+ const reason = String(opts.rejectionReason ?? "").trim();
543
+ if (!reason) return {
544
+ ok: false,
545
+ error: "Rejection reason is required"
546
+ };
547
+ }
548
+ return {
549
+ ok: true
550
+ };
551
+ }
552
+ __name(assertEventApprovalUpdate, "assertEventApprovalUpdate");
553
+ function applyEventApprovalStatusSideEffects(updatePayload, opts) {
554
+ const to = opts.toApproval == null || opts.toApproval === "" ? null : String(opts.toApproval);
555
+ if (to == null) return;
556
+ if (to === "approved") {
557
+ updatePayload.approvalStatus = "approved";
558
+ updatePayload.isActive = true;
559
+ updatePayload.rejectionReason = null;
560
+ updatePayload.rejectedAt = null;
561
+ updatePayload.rejectedBy = null;
562
+ return;
563
+ }
564
+ if (to === "rejected") {
565
+ updatePayload.approvalStatus = "rejected";
566
+ updatePayload.rejectionReason = String(opts.rejectionReason ?? "").trim();
567
+ updatePayload.rejectedAt = /* @__PURE__ */ new Date();
568
+ updatePayload.rejectedBy = opts.rejectedBy != null && Number.isFinite(opts.rejectedBy) ? opts.rejectedBy : null;
569
+ const nextActive = "isActive" in updatePayload ? Boolean(updatePayload.isActive) : opts.currentActive;
570
+ if (nextActive) updatePayload.isActive = false;
571
+ return;
572
+ }
573
+ if (to === "pending") {
574
+ updatePayload.approvalStatus = "pending";
575
+ updatePayload.rejectionReason = null;
576
+ updatePayload.rejectedAt = null;
577
+ updatePayload.rejectedBy = null;
578
+ const nextActive = "isActive" in updatePayload ? Boolean(updatePayload.isActive) : opts.currentActive;
579
+ if (nextActive) updatePayload.isActive = false;
580
+ }
581
+ }
582
+ __name(applyEventApprovalStatusSideEffects, "applyEventApprovalStatusSideEffects");
583
+
237
584
  // src/lib/default-vendor-id.ts
238
585
  function getDefaultVendorId() {
239
586
  const raw = process.env.DEFAULT_VENDOR_ID;
@@ -269,17 +616,20 @@ async function assertCatalogCategoryId(dataSource, categoriesEntity, categoryId,
269
616
  const row = await repo.findOne({
270
617
  where: {
271
618
  id,
272
- deleted: false,
273
- isCatalog: true
619
+ deleted: false
274
620
  }
275
621
  });
276
622
  if (!row) {
277
623
  return "Invalid catalog category. Choose one of the platform product category types.";
278
624
  }
279
- return null;
625
+ if (row.isCatalog === true) return null;
626
+ if (options?.allowVendorId != null && Number(row.vendorId) === options.allowVendorId && row.isCatalog !== true) {
627
+ return null;
628
+ }
629
+ return "Invalid catalog category. Choose one of the platform product category types.";
280
630
  }
281
631
  __name(assertCatalogCategoryId, "assertCatalogCategoryId");
282
- async function assertVendorOwnedCollectionForProduct(dataSource, collectionsEntity, collectionId, categoryId, vendorId) {
632
+ async function assertCollectionForVendorProduct(dataSource, collectionsEntity, collectionId, categoryId, vendorId, options) {
283
633
  const cid = Number(collectionId);
284
634
  if (!Number.isFinite(cid) || cid <= 0) return null;
285
635
  if (!collectionsEntity) return "Collections are not configured";
@@ -290,11 +640,13 @@ async function assertVendorOwnedCollectionForProduct(dataSource, collectionsEnti
290
640
  deleted: false
291
641
  }
292
642
  });
293
- if (!row || row.isCatalog === true) {
294
- return "Invalid collection. Choose one of your collections in this category.";
643
+ if (!row) {
644
+ return "Invalid collection. Choose a valid collection in this category.";
295
645
  }
296
- if (Number(row.vendorId) !== vendorId) {
297
- return "Invalid collection. Choose one of your collections in this category.";
646
+ const isCatalog = row.isCatalog === true;
647
+ const isOwn = Number(row.vendorId) === vendorId && !isCatalog;
648
+ if (!isCatalog && !(options?.allowVendorOwned && isOwn)) {
649
+ return "Invalid collection. Choose one of the platform collections (or your own if allowed).";
298
650
  }
299
651
  const catId = Number(categoryId);
300
652
  if (Number.isFinite(catId) && catId > 0 && Number(row.categoryId) !== catId) {
@@ -302,7 +654,7 @@ async function assertVendorOwnedCollectionForProduct(dataSource, collectionsEnti
302
654
  }
303
655
  return null;
304
656
  }
305
- __name(assertVendorOwnedCollectionForProduct, "assertVendorOwnedCollectionForProduct");
657
+ __name(assertCollectionForVendorProduct, "assertCollectionForVendorProduct");
306
658
 
307
659
  // src/lib/catalog-collection.ts
308
660
  function isCatalogCollectionRow(row) {
@@ -316,7 +668,24 @@ function applyPlatformCatalogCollectionFields(persistBody) {
316
668
  }
317
669
  __name(applyPlatformCatalogCollectionFields, "applyPlatformCatalogCollectionFields");
318
670
 
671
+ // src/lib/catalog-brand.ts
672
+ function isCatalogBrandRow(row) {
673
+ return row?.isCatalog === true;
674
+ }
675
+ __name(isCatalogBrandRow, "isCatalogBrandRow");
676
+ function applyPlatformCatalogBrandFields(persistBody) {
677
+ persistBody.isCatalog = true;
678
+ delete persistBody.vendorId;
679
+ persistBody.vendorId = getDefaultVendorId();
680
+ }
681
+ __name(applyPlatformCatalogBrandFields, "applyPlatformCatalogBrandFields");
682
+
319
683
  // src/api/vendor-scope-crud.ts
684
+ var CATALOG_LIST_RESOURCES = /* @__PURE__ */ new Set([
685
+ "product_categories",
686
+ "collections",
687
+ "brands"
688
+ ]);
320
689
  function repoHasVendorIdColumn(repo) {
321
690
  return repo.metadata.columns.some((c) => c.propertyName === "vendorId");
322
691
  }
@@ -325,7 +694,43 @@ function resourceUsesVendorScope(resource) {
325
694
  return resource === "vendors" || VENDOR_SCOPED_STORE_ENTITIES.has(resource);
326
695
  }
327
696
  __name(resourceUsesVendorScope, "resourceUsesVendorScope");
328
- function mergeVendorScopeIntoWhere(where, scope, resource) {
697
+ function mergeWhereClause(where, extra) {
698
+ if (Array.isArray(where)) {
699
+ return where.map((w) => ({
700
+ ...w,
701
+ ...extra
702
+ }));
703
+ }
704
+ if (where && typeof where === "object" && Object.keys(where).length > 0) {
705
+ return {
706
+ ...where,
707
+ ...extra
708
+ };
709
+ }
710
+ return extra;
711
+ }
712
+ __name(mergeWhereClause, "mergeWhereClause");
713
+ function mergeCatalogOrOwnWhere(where, vendorId) {
714
+ const catalog = mergeWhereClause(where, {
715
+ isCatalog: true
716
+ });
717
+ const own = mergeWhereClause(where, {
718
+ vendorId,
719
+ isCatalog: false
720
+ });
721
+ const catalogArr = Array.isArray(catalog) ? catalog : [
722
+ catalog
723
+ ];
724
+ const ownArr = Array.isArray(own) ? own : [
725
+ own
726
+ ];
727
+ return [
728
+ ...catalogArr,
729
+ ...ownArr
730
+ ];
731
+ }
732
+ __name(mergeCatalogOrOwnWhere, "mergeCatalogOrOwnWhere");
733
+ function mergeVendorScopeIntoWhere(where, scope, resource, flags) {
329
734
  if (scope.type === "all") return where;
330
735
  if (scope.type === "deny") {
331
736
  if (resource === "vendors") {
@@ -342,38 +747,20 @@ function mergeVendorScopeIntoWhere(where, scope, resource) {
342
747
  id: In(scope.vendorIds)
343
748
  });
344
749
  }
345
- if (resource === "product_categories" && scope.type === "vendor") {
750
+ if (CATALOG_LIST_RESOURCES.has(resource) && scope.type === "vendor") {
751
+ const canCreate = flags ? vendorCanCreateResource(resource, flags) : false;
752
+ if (canCreate) {
753
+ return mergeCatalogOrOwnWhere(where, scope.vendorId);
754
+ }
346
755
  return mergeWhereClause(where, {
347
756
  isCatalog: true
348
757
  });
349
758
  }
350
- if (resource === "collections" && scope.type === "vendor") {
351
- return mergeWhereClause(where, {
352
- vendorId: scope.vendorId,
353
- isCatalog: false
354
- });
355
- }
356
759
  return mergeWhereClause(where, {
357
760
  vendorId: scope.vendorId
358
761
  });
359
762
  }
360
763
  __name(mergeVendorScopeIntoWhere, "mergeVendorScopeIntoWhere");
361
- function mergeWhereClause(where, extra) {
362
- if (Array.isArray(where)) {
363
- return where.map((w) => ({
364
- ...w,
365
- ...extra
366
- }));
367
- }
368
- if (where && typeof where === "object" && Object.keys(where).length > 0) {
369
- return {
370
- ...where,
371
- ...extra
372
- };
373
- }
374
- return extra;
375
- }
376
- __name(mergeWhereClause, "mergeWhereClause");
377
764
  function applyVendorScopeToQueryBuilder(qb, alias, scope) {
378
765
  if (scope.type === "all") return;
379
766
  if (scope.type === "deny") {
@@ -385,7 +772,7 @@ function applyVendorScopeToQueryBuilder(qb, alias, scope) {
385
772
  });
386
773
  }
387
774
  __name(applyVendorScopeToQueryBuilder, "applyVendorScopeToQueryBuilder");
388
- function rowMatchesVendorScope(row, scope, resource) {
775
+ function rowMatchesVendorScope(row, scope, resource, flags) {
389
776
  if (!row) return false;
390
777
  if (scope.type === "all") return true;
391
778
  if (scope.type === "deny") return false;
@@ -393,27 +780,32 @@ function rowMatchesVendorScope(row, scope, resource) {
393
780
  const id = Number(row.id);
394
781
  return scope.vendorIds.includes(id);
395
782
  }
396
- if (resource === "product_categories" && scope.type === "vendor") {
397
- return isCatalogCategoryRow(row);
398
- }
399
- if (resource === "collections" && scope.type === "vendor") {
400
- return Number(row.vendorId) === scope.vendorId && !isCatalogCollectionRow(row);
783
+ if (CATALOG_LIST_RESOURCES.has(resource) && scope.type === "vendor") {
784
+ const isCatalog = resource === "product_categories" ? isCatalogCategoryRow(row) : resource === "collections" ? isCatalogCollectionRow(row) : isCatalogBrandRow(row);
785
+ if (isCatalog) return true;
786
+ const canCreate = flags ? vendorCanCreateResource(resource, flags) : false;
787
+ return canCreate && Number(row.vendorId) === scope.vendorId && !isCatalog;
401
788
  }
402
789
  return Number(row.vendorId) === scope.vendorId;
403
790
  }
404
791
  __name(rowMatchesVendorScope, "rowMatchesVendorScope");
405
- function vendorCannotMutateCatalogCategories(resource, scope) {
406
- return resource === "product_categories" && scope.type === "vendor";
407
- }
408
- __name(vendorCannotMutateCatalogCategories, "vendorCannotMutateCatalogCategories");
409
- function vendorCannotMutateCatalogCollections(resource, scope, body) {
410
- if (resource !== "collections" || scope.type !== "vendor") return false;
792
+ function vendorCannotMutateCatalogResource(resource, scope, flags, body) {
793
+ if (scope.type !== "vendor" || !CATALOG_LIST_RESOURCES.has(resource)) return false;
794
+ if (!vendorCanCreateResource(resource, flags)) return true;
411
795
  return body?.isCatalog === true;
412
796
  }
413
- __name(vendorCannotMutateCatalogCollections, "vendorCannotMutateCatalogCollections");
414
- function vendorScopeRowAccess(row, scope, resource) {
797
+ __name(vendorCannotMutateCatalogResource, "vendorCannotMutateCatalogResource");
798
+ function vendorCannotMutateExistingCatalogRow(resource, scope, row) {
799
+ if (scope.type !== "vendor" || !CATALOG_LIST_RESOURCES.has(resource) || !row) return false;
800
+ if (resource === "product_categories") return isCatalogCategoryRow(row);
801
+ if (resource === "collections") return isCatalogCollectionRow(row);
802
+ if (resource === "brands") return isCatalogBrandRow(row);
803
+ return false;
804
+ }
805
+ __name(vendorCannotMutateExistingCatalogRow, "vendorCannotMutateExistingCatalogRow");
806
+ function vendorScopeRowAccess(row, scope, resource, flags) {
415
807
  if (!row) return "not_found";
416
- if (rowMatchesVendorScope(row, scope, resource)) return "ok";
808
+ if (rowMatchesVendorScope(row, scope, resource, flags)) return "ok";
417
809
  return scope.type === "vendor" ? "forbidden" : "not_found";
418
810
  }
419
811
  __name(vendorScopeRowAccess, "vendorScopeRowAccess");
@@ -445,14 +837,7 @@ function requireVendorIdForScopedCreate(resource, persistBody, scope, repo, cont
445
837
  ok: true
446
838
  };
447
839
  }
448
- if (resource === "collections" && persistBody.isCatalog === true) {
449
- delete persistBody.vendorId;
450
- persistBody.vendorId = getDefaultVendorId();
451
- return {
452
- ok: true
453
- };
454
- }
455
- if (resource === "product_categories" && persistBody.isCatalog === true) {
840
+ if ((resource === "collections" || resource === "product_categories" || resource === "brands") && persistBody.isCatalog === true) {
456
841
  delete persistBody.vendorId;
457
842
  persistBody.vendorId = getDefaultVendorId();
458
843
  return {
@@ -464,7 +849,7 @@ function requireVendorIdForScopedCreate(resource, persistBody, scope, repo, cont
464
849
  };
465
850
  enforceVendorIdOnCreateBody(persistBody, scope, repo);
466
851
  if (scope.type === "vendor") {
467
- if (resource === "collections") {
852
+ if (resource === "collections" || resource === "product_categories" || resource === "brands") {
468
853
  persistBody.isCatalog = false;
469
854
  }
470
855
  return {
@@ -3411,6 +3796,7 @@ function createCrudHandler(dataSource, entityMap, options) {
3411
3796
  const scope = await resolveScope();
3412
3797
  const repo2 = dataSource.getRepository(entity);
3413
3798
  const statusFilter = searchParams.get("status")?.trim();
3799
+ const approvalStatusFilter = searchParams.get("approvalStatus")?.trim();
3414
3800
  const inventory = searchParams.get("inventory")?.trim();
3415
3801
  let productWhere = {
3416
3802
  deleted: false,
@@ -3418,6 +3804,7 @@ function createCrudHandler(dataSource, entityMap, options) {
3418
3804
  };
3419
3805
  productWhere = mergeVendorScopeIntoWhere(productWhere, scope, resource);
3420
3806
  if (statusFilter) productWhere.status = statusFilter;
3807
+ if (approvalStatusFilter) productWhere.approvalStatus = approvalStatusFilter;
3421
3808
  if (inventory === "in_stock") productWhere.quantity = MoreThan(0);
3422
3809
  if (inventory === "out_of_stock") productWhere.quantity = 0;
3423
3810
  for (const key of [
@@ -3718,6 +4105,7 @@ function createCrudHandler(dataSource, entityMap, options) {
3718
4105
  where = mergeDeletedFalseWhere(repo, where);
3719
4106
  if (resourceUsesVendorScope(resource)) {
3720
4107
  const scope = await resolveScope();
4108
+ const catalogFlags = await getVendorCatalogCreateFlags(dataSource);
3721
4109
  if (scope.type === "deny") {
3722
4110
  where = resource === "vendors" ? {
3723
4111
  id: -1
@@ -3726,7 +4114,7 @@ function createCrudHandler(dataSource, entityMap, options) {
3726
4114
  };
3727
4115
  } else if (resource === "collections") {
3728
4116
  if (scope.type === "vendor") {
3729
- where = mergeVendorScopeIntoWhere(where, scope, resource);
4117
+ where = mergeVendorScopeIntoWhere(where, scope, resource, catalogFlags);
3730
4118
  } else if (searchParams.get("isCatalog") !== "true") {
3731
4119
  where = mergeListWhereAnd(where, {
3732
4120
  isCatalog: false
@@ -3734,14 +4122,22 @@ function createCrudHandler(dataSource, entityMap, options) {
3734
4122
  }
3735
4123
  } else if (resource === "product_categories") {
3736
4124
  if (scope.type === "vendor") {
3737
- where = mergeVendorScopeIntoWhere(where, scope, resource);
4125
+ where = mergeVendorScopeIntoWhere(where, scope, resource, catalogFlags);
4126
+ } else if (searchParams.get("isCatalog") !== "false") {
4127
+ where = mergeListWhereAnd(where, {
4128
+ isCatalog: true
4129
+ });
4130
+ }
4131
+ } else if (resource === "brands") {
4132
+ if (scope.type === "vendor") {
4133
+ where = mergeVendorScopeIntoWhere(where, scope, resource, catalogFlags);
3738
4134
  } else if (searchParams.get("isCatalog") !== "false") {
3739
4135
  where = mergeListWhereAnd(where, {
3740
4136
  isCatalog: true
3741
4137
  });
3742
4138
  }
3743
4139
  } else {
3744
- where = mergeVendorScopeIntoWhere(where, scope, resource);
4140
+ where = mergeVendorScopeIntoWhere(where, scope, resource, catalogFlags);
3745
4141
  }
3746
4142
  }
3747
4143
  let data;
@@ -3811,16 +4207,11 @@ function createCrudHandler(dataSource, entityMap, options) {
3811
4207
  });
3812
4208
  }
3813
4209
  const body = rawPostBody;
3814
- if (vendorCannotMutateCatalogCategories(resource, scopePost)) {
4210
+ const catalogFlagsPost = await getVendorCatalogCreateFlags(dataSource);
4211
+ if (vendorCannotMutateCatalogResource(resource, scopePost, catalogFlagsPost, body)) {
4212
+ const label = resource === "product_categories" ? "Categories" : resource === "collections" ? "Collections" : resource === "brands" ? "Brands" : "This resource";
3815
4213
  return json({
3816
- error: "Categories are managed by the platform administrator"
3817
- }, {
3818
- status: 403
3819
- });
3820
- }
3821
- if (vendorCannotMutateCatalogCollections(resource, scopePost, body)) {
3822
- return json({
3823
- error: "Collections are managed by the platform administrator"
4214
+ error: `${label} are managed by the platform administrator`
3824
4215
  }, {
3825
4216
  status: 403
3826
4217
  });
@@ -4205,8 +4596,15 @@ function createCrudHandler(dataSource, entityMap, options) {
4205
4596
  }
4206
4597
  if (resource === "products") {
4207
4598
  const scopeForProduct = await resolveScope();
4599
+ const productFlags = await getVendorCatalogCreateFlags(dataSource);
4600
+ const requireApproval = await getRequireProductApproval(dataSource);
4601
+ applyVendorProductCreateApproval(persistBody, {
4602
+ isVendor: scopeForProduct.type === "vendor",
4603
+ requireApproval
4604
+ });
4208
4605
  const categoryErr = await assertCatalogCategoryId(dataSource, entityMap.product_categories, persistBody.categoryId, {
4209
- required: scopeForProduct.type === "vendor"
4606
+ required: scopeForProduct.type === "vendor",
4607
+ allowVendorId: scopeForProduct.type === "vendor" && productFlags.categories ? scopeForProduct.vendorId : void 0
4210
4608
  });
4211
4609
  if (categoryErr) return json({
4212
4610
  error: categoryErr
@@ -4215,7 +4613,9 @@ function createCrudHandler(dataSource, entityMap, options) {
4215
4613
  });
4216
4614
  if (scopeForProduct.type === "vendor") {
4217
4615
  if (entityMap.collections && persistBody.collectionId != null && persistBody.collectionId !== "") {
4218
- const collectionErr = await assertVendorOwnedCollectionForProduct(dataSource, entityMap.collections, persistBody.collectionId, persistBody.categoryId, scopeForProduct.vendorId);
4616
+ const collectionErr = await assertCollectionForVendorProduct(dataSource, entityMap.collections, persistBody.collectionId, persistBody.categoryId, scopeForProduct.vendorId, {
4617
+ allowVendorOwned: productFlags.collections
4618
+ });
4219
4619
  if (collectionErr) return json({
4220
4620
  error: collectionErr
4221
4621
  }, {
@@ -4224,6 +4624,14 @@ function createCrudHandler(dataSource, entityMap, options) {
4224
4624
  }
4225
4625
  }
4226
4626
  }
4627
+ if (resource === "events") {
4628
+ const scopeForEvent = await resolveScope();
4629
+ const requireEventApproval = await getRequireEventApproval(dataSource);
4630
+ applyVendorEventCreateApproval(persistBody, {
4631
+ isVendor: scopeForEvent.type === "vendor",
4632
+ requireApproval: requireEventApproval
4633
+ });
4634
+ }
4227
4635
  if (resource === "collections" && scopePost.type === "vendor") {
4228
4636
  const categoryErr = await assertCatalogCategoryId(dataSource, entityMap.product_categories, persistBody.categoryId, {
4229
4637
  required: true
@@ -4428,7 +4836,27 @@ function createCrudHandler(dataSource, entityMap, options) {
4428
4836
  }
4429
4837
  }
4430
4838
  }
4431
- if (!((resource === "collections" || resource === "product_categories") && persistBody.isCatalog === true)) {
4839
+ if (resource === "brands" && scopeCreate.type === "all") {
4840
+ applyPlatformCatalogBrandFields(persistBody);
4841
+ const slug = String(persistBody.slug ?? "").trim();
4842
+ if (slug) {
4843
+ const dup = await repo.findOne({
4844
+ where: {
4845
+ slug,
4846
+ isCatalog: true,
4847
+ deleted: false
4848
+ }
4849
+ });
4850
+ if (dup) {
4851
+ return json({
4852
+ error: "A catalog brand with this slug already exists"
4853
+ }, {
4854
+ status: 400
4855
+ });
4856
+ }
4857
+ }
4858
+ }
4859
+ if (!((resource === "collections" || resource === "product_categories" || resource === "brands") && persistBody.isCatalog === true)) {
4432
4860
  await tryAssignSingleVendorOnAdminCreate(resource, scopeCreate, persistBody);
4433
4861
  }
4434
4862
  const vendorIdCheck = requireVendorIdForScopedCreate(resource, persistBody, scopeCreate, repo, await vendorCreateContext(body));
@@ -4908,13 +5336,14 @@ function createCrudHandler(dataSource, entityMap, options) {
4908
5336
  }
4909
5337
  __name(createCrudHandler, "createCrudHandler");
4910
5338
  function createCrudByIdHandler(dataSource, entityMap, options) {
4911
- const { requireAuth, json, requireEntityPermission: reqPerm, getCms, getDeletedByUserId, getVendorScope } = options;
5339
+ const { requireAuth, json, requireEntityPermission: reqPerm, getCms, getDeletedByUserId, getVendorScope, getHydratedSessionUser } = options;
4912
5340
  const syncContactRowToErp = makeContactErpSync(dataSource, entityMap, getCms);
4913
5341
  const resolveScopeById = getVendorScope ?? (async () => ({
4914
5342
  type: "all"
4915
5343
  }));
4916
- function vendorScopeAccessJson(row, scope, resource) {
4917
- const access = vendorScopeRowAccess(row, scope, resource);
5344
+ async function vendorScopeAccessJson(row, scope, resource) {
5345
+ const flags = await getVendorCatalogCreateFlags(dataSource);
5346
+ const access = vendorScopeRowAccess(row, scope, resource, flags);
4918
5347
  if (access === "ok") return null;
4919
5348
  if (access === "forbidden") return json({
4920
5349
  error: "Forbidden"
@@ -4994,7 +5423,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
4994
5423
  "rules"
4995
5424
  ]
4996
5425
  });
4997
- const discountDenied = vendorScopeAccessJson(discount, scope2, resource);
5426
+ const discountDenied = await vendorScopeAccessJson(discount, scope2, resource);
4998
5427
  if (discountDenied) return discountDenied;
4999
5428
  const flatRules = discount.rules ?? [];
5000
5429
  const nestedRules = nestDiscountRules2(flatRules);
@@ -5052,7 +5481,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5052
5481
  "payments"
5053
5482
  ]
5054
5483
  });
5055
- const orderDenied = vendorScopeAccessJson(order, scope2, resource);
5484
+ const orderDenied = await vendorScopeAccessJson(order, scope2, resource);
5056
5485
  if (orderDenied) return orderDenied;
5057
5486
  const relatedOrders = await repo.find({
5058
5487
  where: {
@@ -5116,7 +5545,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5116
5545
  "contact"
5117
5546
  ]
5118
5547
  });
5119
- const paymentDenied = vendorScopeAccessJson(payment, scope2, resource);
5548
+ const paymentDenied = await vendorScopeAccessJson(payment, scope2, resource);
5120
5549
  if (paymentDenied) return paymentDenied;
5121
5550
  const p = payment;
5122
5551
  const order = p.order;
@@ -5155,7 +5584,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5155
5584
  "attributes.attribute"
5156
5585
  ]
5157
5586
  });
5158
- const productDenied = vendorScopeAccessJson(product, scope2, resource);
5587
+ const productDenied = await vendorScopeAccessJson(product, scope2, resource);
5159
5588
  if (productDenied) return productDenied;
5160
5589
  return product ? json(hydrateProductDisplayName(product)) : json({
5161
5590
  message: "Not found"
@@ -5173,7 +5602,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5173
5602
  "contact"
5174
5603
  ]
5175
5604
  });
5176
- const vcDenied = vendorScopeAccessJson(row, scope2, resource);
5605
+ const vcDenied = await vendorScopeAccessJson(row, scope2, resource);
5177
5606
  if (vcDenied) return vcDenied;
5178
5607
  const contact = row.contact;
5179
5608
  return json({
@@ -5196,7 +5625,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5196
5625
  "items.product"
5197
5626
  ]
5198
5627
  });
5199
- const comboDenied = vendorScopeAccessJson(combo, scope2, resource);
5628
+ const comboDenied = await vendorScopeAccessJson(combo, scope2, resource);
5200
5629
  if (comboDenied) return comboDenied;
5201
5630
  return combo ? json(combo) : json({
5202
5631
  message: "Not found"
@@ -5233,7 +5662,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5233
5662
  where: idWhere
5234
5663
  });
5235
5664
  if (resourceUsesVendorScope(resource)) {
5236
- const itemDenied = vendorScopeAccessJson(item, scope, resource);
5665
+ const itemDenied = await vendorScopeAccessJson(item, scope, resource);
5237
5666
  if (itemDenied) return itemDenied;
5238
5667
  } else if (!item) {
5239
5668
  return json({
@@ -5262,16 +5691,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5262
5691
  }
5263
5692
  const rawBody = await req.json();
5264
5693
  const scopePutEarly = await resolveScopeById();
5265
- if (vendorCannotMutateCatalogCategories(resource, scopePutEarly)) {
5694
+ const catalogFlagsPut = await getVendorCatalogCreateFlags(dataSource);
5695
+ if (vendorCannotMutateCatalogResource(resource, scopePutEarly, catalogFlagsPut, rawBody ?? void 0)) {
5696
+ const label = resource === "product_categories" ? "Categories" : resource === "collections" ? "Collections" : resource === "brands" ? "Brands" : "This resource";
5266
5697
  return json({
5267
- error: "Categories are managed by the platform administrator"
5268
- }, {
5269
- status: 403
5270
- });
5271
- }
5272
- if (vendorCannotMutateCatalogCollections(resource, scopePutEarly, rawBody ?? void 0)) {
5273
- return json({
5274
- error: "Collections are managed by the platform administrator"
5698
+ error: `${label} are managed by the platform administrator`
5275
5699
  }, {
5276
5700
  status: 403
5277
5701
  });
@@ -5637,7 +6061,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5637
6061
  id: numericId
5638
6062
  }
5639
6063
  });
5640
- const discountPutDenied = vendorScopeAccessJson(existing, scopePut2, resource);
6064
+ const discountPutDenied = await vendorScopeAccessJson(existing, scopePut2, resource);
5641
6065
  if (discountPutDenied) return discountPutDenied;
5642
6066
  const updatePayload2 = pickColumnUpdates(repo, rawBody);
5643
6067
  delete updatePayload2.vendorId;
@@ -5695,8 +6119,16 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5695
6119
  const existingScope = await repo.findOne({
5696
6120
  where: idWhereCheck
5697
6121
  });
5698
- const scopePutDenied = vendorScopeAccessJson(existingScope, scopePut, resource);
6122
+ const scopePutDenied = await vendorScopeAccessJson(existingScope, scopePut, resource);
5699
6123
  if (scopePutDenied) return scopePutDenied;
6124
+ if (vendorCannotMutateExistingCatalogRow(resource, scopePut, existingScope)) {
6125
+ const label = resource === "product_categories" ? "Categories" : resource === "collections" ? "Collections" : "Brands";
6126
+ return json({
6127
+ error: `${label} are managed by the platform administrator`
6128
+ }, {
6129
+ status: 403
6130
+ });
6131
+ }
5700
6132
  }
5701
6133
  const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
5702
6134
  if (resourceUsesVendorScope(resource)) {
@@ -5843,7 +6275,8 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5843
6275
  });
5844
6276
  const mergedCategoryId = "categoryId" in updatePayload ? updatePayload.categoryId : currentRow.categoryId;
5845
6277
  const categoryErr = await assertCatalogCategoryId(dataSource, entityMap.product_categories, "categoryId" in updatePayload ? updatePayload.categoryId : mergedCategoryId, {
5846
- required: scopePut.type === "vendor"
6278
+ required: scopePut.type === "vendor",
6279
+ allowVendorId: scopePut.type === "vendor" && catalogFlagsPut.categories ? scopePut.vendorId : void 0
5847
6280
  });
5848
6281
  if (categoryErr) return json({
5849
6282
  error: categoryErr
@@ -5852,7 +6285,9 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5852
6285
  });
5853
6286
  if (scopePut.type === "vendor") {
5854
6287
  if (entityMap.collections && "collectionId" in updatePayload && updatePayload.collectionId != null) {
5855
- const collectionErr = await assertVendorOwnedCollectionForProduct(dataSource, entityMap.collections, updatePayload.collectionId, mergedCategoryId, scopePut.vendorId);
6288
+ const collectionErr = await assertCollectionForVendorProduct(dataSource, entityMap.collections, updatePayload.collectionId, mergedCategoryId, scopePut.vendorId, {
6289
+ allowVendorOwned: catalogFlagsPut.collections
6290
+ });
5856
6291
  if (collectionErr) return json({
5857
6292
  error: collectionErr
5858
6293
  }, {
@@ -5860,6 +6295,88 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5860
6295
  });
5861
6296
  }
5862
6297
  }
6298
+ if ("status" in updatePayload || "approvalStatus" in updatePayload || "rejectionReason" in updatePayload) {
6299
+ const fromStatus = String(currentRow.status ?? "draft");
6300
+ const toStatus = String("status" in updatePayload ? updatePayload.status ?? fromStatus : fromStatus);
6301
+ const fromApproval = currentRow.approvalStatus;
6302
+ const toApproval = "approvalStatus" in updatePayload ? updatePayload.approvalStatus : fromApproval;
6303
+ const requireApproval = await getRequireProductApproval(dataSource);
6304
+ const reasonFromBody = "rejectionReason" in updatePayload ? updatePayload.rejectionReason : currentRow.rejectionReason;
6305
+ const reasonStr = typeof reasonFromBody === "string" ? reasonFromBody : reasonFromBody == null ? null : String(reasonFromBody);
6306
+ const transition = assertProductApprovalUpdate({
6307
+ fromApproval,
6308
+ toApproval,
6309
+ fromStatus,
6310
+ toStatus,
6311
+ isVendor: scopePut.type === "vendor",
6312
+ requireApproval,
6313
+ approvalChanged: "approvalStatus" in updatePayload,
6314
+ statusChanged: "status" in updatePayload,
6315
+ rejectionReason: reasonStr
6316
+ });
6317
+ if (!transition.ok) return json({
6318
+ error: transition.error
6319
+ }, {
6320
+ status: 400
6321
+ });
6322
+ if ("approvalStatus" in updatePayload && scopePut.type !== "vendor") {
6323
+ const session = await getHydratedSessionUser?.() ?? null;
6324
+ const rejectedByRaw = session?.id != null ? Number(session.id) : null;
6325
+ applyApprovalStatusSideEffects(updatePayload, {
6326
+ toApproval,
6327
+ currentStatus: fromStatus,
6328
+ rejectionReason: reasonStr,
6329
+ rejectedBy: Number.isFinite(rejectedByRaw) ? rejectedByRaw : null
6330
+ });
6331
+ }
6332
+ }
6333
+ }
6334
+ if (resource === "events") {
6335
+ const currentEvent = await repo.findOne({
6336
+ where: {
6337
+ id: numericId,
6338
+ deleted: false
6339
+ }
6340
+ });
6341
+ if (!currentEvent) return json({
6342
+ message: "Not found"
6343
+ }, {
6344
+ status: 404
6345
+ });
6346
+ if ("isActive" in updatePayload || "approvalStatus" in updatePayload || "rejectionReason" in updatePayload) {
6347
+ const fromActive = Boolean(currentEvent.isActive);
6348
+ const toActive = "isActive" in updatePayload ? Boolean(updatePayload.isActive) : fromActive;
6349
+ const fromApproval = currentEvent.approvalStatus;
6350
+ const toApproval = "approvalStatus" in updatePayload ? updatePayload.approvalStatus : fromApproval;
6351
+ const requireEventApproval = await getRequireEventApproval(dataSource);
6352
+ const reasonFromBody = "rejectionReason" in updatePayload ? updatePayload.rejectionReason : currentEvent.rejectionReason;
6353
+ const reasonStr = typeof reasonFromBody === "string" ? reasonFromBody : reasonFromBody == null ? null : String(reasonFromBody);
6354
+ const transition = assertEventApprovalUpdate({
6355
+ fromApproval,
6356
+ toApproval,
6357
+ toActive,
6358
+ isVendor: scopePut.type === "vendor",
6359
+ requireApproval: requireEventApproval,
6360
+ approvalChanged: "approvalStatus" in updatePayload,
6361
+ activeChanged: "isActive" in updatePayload,
6362
+ rejectionReason: reasonStr
6363
+ });
6364
+ if (!transition.ok) return json({
6365
+ error: transition.error
6366
+ }, {
6367
+ status: 400
6368
+ });
6369
+ if ("approvalStatus" in updatePayload && scopePut.type !== "vendor") {
6370
+ const session = await getHydratedSessionUser?.() ?? null;
6371
+ const rejectedByRaw = session?.id != null ? Number(session.id) : null;
6372
+ applyEventApprovalStatusSideEffects(updatePayload, {
6373
+ toApproval,
6374
+ currentActive: fromActive,
6375
+ rejectionReason: reasonStr,
6376
+ rejectedBy: Number.isFinite(rejectedByRaw) ? rejectedByRaw : null
6377
+ });
6378
+ }
6379
+ }
5863
6380
  }
5864
6381
  if (resource === "product_categories" && scopePut.type === "all") {
5865
6382
  const existingCatalogCat = await repo.findOne({
@@ -5917,6 +6434,34 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
5917
6434
  }
5918
6435
  }
5919
6436
  }
6437
+ if (resource === "brands" && scopePut.type === "all") {
6438
+ const existingCatalogBrand = await repo.findOne({
6439
+ where: {
6440
+ id: numericId,
6441
+ deleted: false
6442
+ }
6443
+ });
6444
+ if (existingCatalogBrand && existingCatalogBrand.isCatalog === true) {
6445
+ applyPlatformCatalogBrandFields(updatePayload);
6446
+ const slug = String(updatePayload.slug ?? existingCatalogBrand.slug ?? "").trim();
6447
+ if (slug && "slug" in updatePayload) {
6448
+ const dup = await repo.findOne({
6449
+ where: {
6450
+ slug,
6451
+ isCatalog: true,
6452
+ deleted: false
6453
+ }
6454
+ });
6455
+ if (dup && Number(dup.id) !== numericId) {
6456
+ return json({
6457
+ error: "A catalog brand with this slug already exists"
6458
+ }, {
6459
+ status: 400
6460
+ });
6461
+ }
6462
+ }
6463
+ }
6464
+ }
5920
6465
  if (resource === "addresses" && Object.keys(updatePayload).length > 0) {
5921
6466
  const currentRow = await repo.findOne({
5922
6467
  where: {
@@ -6072,9 +6617,11 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6072
6617
  const authError = await authz(req, resource, "delete");
6073
6618
  if (authError) return authError;
6074
6619
  const scopeDeleteEarly = await resolveScopeById();
6075
- if (vendorCannotMutateCatalogCategories(resource, scopeDeleteEarly)) {
6620
+ const catalogFlagsDelete = await getVendorCatalogCreateFlags(dataSource);
6621
+ if (vendorCannotMutateCatalogResource(resource, scopeDeleteEarly, catalogFlagsDelete)) {
6622
+ const label = resource === "product_categories" ? "Categories" : resource === "collections" ? "Collections" : resource === "brands" ? "Brands" : "This resource";
6076
6623
  return json({
6077
- error: "Categories are managed by the platform administrator"
6624
+ error: `${label} are managed by the platform administrator`
6078
6625
  }, {
6079
6626
  status: 403
6080
6627
  });
@@ -6103,7 +6650,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6103
6650
  }
6104
6651
  });
6105
6652
  if (resourceUsesVendorScope(resource)) {
6106
- const deleteDenied = vendorScopeAccessJson(existing, scopeDelete, resource);
6653
+ const deleteDenied = await vendorScopeAccessJson(existing, scopeDelete, resource);
6107
6654
  if (deleteDenied) return deleteDenied;
6108
6655
  } else if (!existing) {
6109
6656
  return json({
@@ -6161,7 +6708,7 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6161
6708
  id: numericId
6162
6709
  }
6163
6710
  });
6164
- const hardDeleteDenied = vendorScopeAccessJson(existingHard, scopeDelete, resource);
6711
+ const hardDeleteDenied = await vendorScopeAccessJson(existingHard, scopeDelete, resource);
6165
6712
  if (hardDeleteDenied) return hardDeleteDenied;
6166
6713
  }
6167
6714
  const result = await repo.delete(numericId);
@@ -8078,6 +8625,12 @@ function createUploadHandler(config) {
8078
8625
  parentId
8079
8626
  });
8080
8627
  } catch (err) {
8628
+ console.error("[upload] File upload failed", {
8629
+ error: err instanceof Error ? err.message : String(err),
8630
+ stack: err instanceof Error ? err.stack : void 0,
8631
+ name: err instanceof Error ? err.name : void 0,
8632
+ metadata: err && typeof err === "object" && "$metadata" in err ? err.$metadata : void 0
8633
+ });
8081
8634
  return json({
8082
8635
  error: "File upload failed"
8083
8636
  }, {
@@ -8783,41 +9336,68 @@ function createUsersApiHandlers(config) {
8783
9336
  const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10));
8784
9337
  const limit = Math.min(100, parseInt(url.searchParams.get("limit") || "10", 10));
8785
9338
  const skip = (page - 1) * limit;
8786
- const sortField = url.searchParams.get("sortField") || "createdAt";
9339
+ const sortFieldRaw = url.searchParams.get("sortField") || "createdAt";
9340
+ const allowedSort = /* @__PURE__ */ new Set([
9341
+ "createdAt",
9342
+ "updatedAt",
9343
+ "name",
9344
+ "email",
9345
+ "id",
9346
+ "blocked"
9347
+ ]);
9348
+ const sortField = allowedSort.has(sortFieldRaw) ? sortFieldRaw : "createdAt";
8787
9349
  const sortOrder = url.searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
8788
- const search = url.searchParams.get("search");
8789
- const where = search ? [
8790
- {
8791
- name: ILike(`%${search}%`),
8792
- deleted: false
8793
- },
8794
- {
8795
- email: ILike(`%${search}%`),
8796
- deleted: false
8797
- }
8798
- ] : {
9350
+ const search = url.searchParams.get("search")?.trim() || "";
9351
+ const groupName = url.searchParams.get("groupName")?.trim() || "";
9352
+ const qb = userRepo().createQueryBuilder("u").leftJoinAndSelect("u.group", "g").where("u.deleted = :deleted", {
8799
9353
  deleted: false
8800
- };
8801
- const [data, total] = await userRepo().findAndCount({
8802
- skip,
8803
- take: limit,
8804
- order: {
8805
- [sortField]: sortOrder
8806
- },
8807
- where,
8808
- relations: [
8809
- "group"
8810
- ],
8811
- select: [
8812
- "id",
8813
- "name",
8814
- "email",
8815
- "blocked",
8816
- "createdAt",
8817
- "updatedAt",
8818
- "groupId"
8819
- ]
8820
- });
9354
+ }).orderBy(`u.${sortField}`, sortOrder).skip(skip).take(limit).select([
9355
+ "u.id",
9356
+ "u.name",
9357
+ "u.email",
9358
+ "u.blocked",
9359
+ "u.createdAt",
9360
+ "u.updatedAt",
9361
+ "u.groupId",
9362
+ "g.id",
9363
+ "g.name"
9364
+ ]);
9365
+ if (search) {
9366
+ qb.andWhere("(u.name ILIKE :search OR u.email ILIKE :search)", {
9367
+ search: `%${search}%`
9368
+ });
9369
+ }
9370
+ if (groupName) {
9371
+ if (!entityMap.user_groups) {
9372
+ return json({
9373
+ total: 0,
9374
+ page,
9375
+ limit,
9376
+ totalPages: 0,
9377
+ data: []
9378
+ });
9379
+ }
9380
+ const groupRepo = dataSource.getRepository(entityMap.user_groups);
9381
+ const group = await groupRepo.findOne({
9382
+ where: {
9383
+ name: groupName,
9384
+ deleted: false
9385
+ }
9386
+ });
9387
+ if (!group) {
9388
+ return json({
9389
+ total: 0,
9390
+ page,
9391
+ limit,
9392
+ totalPages: 0,
9393
+ data: []
9394
+ });
9395
+ }
9396
+ qb.andWhere("u.groupId = :groupId", {
9397
+ groupId: Number(group.id)
9398
+ });
9399
+ }
9400
+ const [data, total] = await qb.getManyAndCount();
8821
9401
  return json({
8822
9402
  total,
8823
9403
  page,
@@ -17034,6 +17614,7 @@ var Brand = class {
17034
17614
  __name(this, "Brand");
17035
17615
  }
17036
17616
  id;
17617
+ /** Null for platform catalog brands (`isCatalog`). */
17037
17618
  vendorId;
17038
17619
  name;
17039
17620
  slug;
@@ -17041,6 +17622,8 @@ var Brand = class {
17041
17622
  metadata;
17042
17623
  description;
17043
17624
  active;
17625
+ /** Platform-wide catalog brand — vendors pick these on products unless allowed to create their own. */
17626
+ isCatalog;
17044
17627
  sortOrder;
17045
17628
  createdAt;
17046
17629
  updatedAt;
@@ -17060,8 +17643,10 @@ _ts_decorate30([
17060
17643
  _ts_metadata30("design:type", Number)
17061
17644
  ], Brand.prototype, "id", void 0);
17062
17645
  _ts_decorate30([
17063
- Column("int"),
17064
- _ts_metadata30("design:type", Number)
17646
+ Column("int", {
17647
+ nullable: true
17648
+ }),
17649
+ _ts_metadata30("design:type", Object)
17065
17650
  ], Brand.prototype, "vendorId", void 0);
17066
17651
  _ts_decorate30([
17067
17652
  Column("varchar"),
@@ -17095,6 +17680,12 @@ _ts_decorate30([
17095
17680
  }),
17096
17681
  _ts_metadata30("design:type", Boolean)
17097
17682
  ], Brand.prototype, "active", void 0);
17683
+ _ts_decorate30([
17684
+ Column("boolean", {
17685
+ default: false
17686
+ }),
17687
+ _ts_metadata30("design:type", Boolean)
17688
+ ], Brand.prototype, "isCatalog", void 0);
17098
17689
  _ts_decorate30([
17099
17690
  Column("int", {
17100
17691
  default: 0
@@ -17154,12 +17745,13 @@ _ts_decorate30([
17154
17745
  ], Brand.prototype, "seoId", void 0);
17155
17746
  _ts_decorate30([
17156
17747
  ManyToOne(() => Vendor, {
17157
- onDelete: "RESTRICT"
17748
+ onDelete: "RESTRICT",
17749
+ nullable: true
17158
17750
  }),
17159
17751
  JoinColumn({
17160
17752
  name: "vendorId"
17161
17753
  }),
17162
- _ts_metadata30("design:type", typeof Vendor === "undefined" ? Object : Vendor)
17754
+ _ts_metadata30("design:type", Object)
17163
17755
  ], Brand.prototype, "vendor", void 0);
17164
17756
  _ts_decorate30([
17165
17757
  ManyToOne(() => Seo, {
@@ -17443,6 +18035,14 @@ var Product = class {
17443
18035
  currencyPrices;
17444
18036
  quantity;
17445
18037
  status;
18038
+ /**
18039
+ * Separate from catalog status. Used when multi_vendor.requireProductApproval is on.
18040
+ * `approved` also forces `status = available` (live).
18041
+ */
18042
+ approvalStatus;
18043
+ rejectionReason;
18044
+ rejectedAt;
18045
+ rejectedBy;
17446
18046
  featured;
17447
18047
  metadata;
17448
18048
  createdAt;
@@ -17566,6 +18166,31 @@ _ts_decorate32([
17566
18166
  }),
17567
18167
  _ts_metadata32("design:type", String)
17568
18168
  ], Product.prototype, "status", void 0);
18169
+ _ts_decorate32([
18170
+ Column("varchar", {
18171
+ nullable: true
18172
+ }),
18173
+ _ts_metadata32("design:type", Object)
18174
+ ], Product.prototype, "approvalStatus", void 0);
18175
+ _ts_decorate32([
18176
+ Column("text", {
18177
+ nullable: true
18178
+ }),
18179
+ _ts_metadata32("design:type", Object)
18180
+ ], Product.prototype, "rejectionReason", void 0);
18181
+ _ts_decorate32([
18182
+ Column({
18183
+ type: "timestamptz",
18184
+ nullable: true
18185
+ }),
18186
+ _ts_metadata32("design:type", Object)
18187
+ ], Product.prototype, "rejectedAt", void 0);
18188
+ _ts_decorate32([
18189
+ Column("int", {
18190
+ nullable: true
18191
+ }),
18192
+ _ts_metadata32("design:type", Object)
18193
+ ], Product.prototype, "rejectedBy", void 0);
17569
18194
  _ts_decorate32([
17570
18195
  Column("boolean", {
17571
18196
  default: false
@@ -20654,6 +21279,14 @@ var Event = class {
20654
21279
  expectedSpeakers;
20655
21280
  expectedParticipants;
20656
21281
  isActive;
21282
+ /**
21283
+ * Separate from isActive. Used when events.requireEventApproval is on.
21284
+ * `approved` also forces `isActive = true` (live).
21285
+ */
21286
+ approvalStatus;
21287
+ rejectionReason;
21288
+ rejectedAt;
21289
+ rejectedBy;
20657
21290
  comingSoon;
20658
21291
  bannerImageUrl;
20659
21292
  logoUrl;
@@ -20748,6 +21381,31 @@ _ts_decorate60([
20748
21381
  }),
20749
21382
  _ts_metadata60("design:type", Boolean)
20750
21383
  ], Event.prototype, "isActive", void 0);
21384
+ _ts_decorate60([
21385
+ Column("varchar", {
21386
+ nullable: true
21387
+ }),
21388
+ _ts_metadata60("design:type", Object)
21389
+ ], Event.prototype, "approvalStatus", void 0);
21390
+ _ts_decorate60([
21391
+ Column("text", {
21392
+ nullable: true
21393
+ }),
21394
+ _ts_metadata60("design:type", Object)
21395
+ ], Event.prototype, "rejectionReason", void 0);
21396
+ _ts_decorate60([
21397
+ Column({
21398
+ type: "timestamptz",
21399
+ nullable: true
21400
+ }),
21401
+ _ts_metadata60("design:type", Object)
21402
+ ], Event.prototype, "rejectedAt", void 0);
21403
+ _ts_decorate60([
21404
+ Column("int", {
21405
+ nullable: true
21406
+ }),
21407
+ _ts_metadata60("design:type", Object)
21408
+ ], Event.prototype, "rejectedBy", void 0);
20751
21409
  _ts_decorate60([
20752
21410
  Column("boolean", {
20753
21411
  default: false
@@ -21308,18 +21966,20 @@ _ts_decorate63([
21308
21966
  ], Combo.prototype, "desc", void 0);
21309
21967
  _ts_decorate63([
21310
21968
  Column({
21311
- type: "integer"
21969
+ type: "integer",
21970
+ nullable: true
21312
21971
  }),
21313
- _ts_metadata63("design:type", Number)
21972
+ _ts_metadata63("design:type", Object)
21314
21973
  ], Combo.prototype, "eventId", void 0);
21315
21974
  _ts_decorate63([
21316
21975
  ManyToOne(() => Event, {
21317
- onDelete: "CASCADE"
21976
+ onDelete: "CASCADE",
21977
+ nullable: true
21318
21978
  }),
21319
21979
  JoinColumn({
21320
21980
  name: "eventId"
21321
21981
  }),
21322
- _ts_metadata63("design:type", typeof Event === "undefined" ? Object : Event)
21982
+ _ts_metadata63("design:type", Object)
21323
21983
  ], Combo.prototype, "event", void 0);
21324
21984
  _ts_decorate63([
21325
21985
  Column({
@@ -26632,16 +27292,24 @@ function createCmsApiHandler(config) {
26632
27292
  if (pe) return pe;
26633
27293
  if (group === "multi_vendor") {
26634
27294
  invalidateMultiVendorCache();
27295
+ invalidateVendorCatalogCreateFlagsCache();
27296
+ invalidateRequireProductApprovalCache();
27297
+ invalidateRequireEventApprovalCache();
26635
27298
  }
26636
27299
  if (group === "events") {
26637
27300
  invalidateEventsCache();
27301
+ invalidateRequireEventApprovalCache();
26638
27302
  }
26639
27303
  const res = await settingsHandlers.PUT(req, group);
26640
27304
  if (group === "multi_vendor") {
26641
27305
  invalidateMultiVendorCache();
27306
+ invalidateVendorCatalogCreateFlagsCache();
27307
+ invalidateRequireProductApprovalCache();
27308
+ invalidateRequireEventApprovalCache();
26642
27309
  }
26643
27310
  if (group === "events") {
26644
27311
  invalidateEventsCache();
27312
+ invalidateRequireEventApprovalCache();
26645
27313
  }
26646
27314
  return res;
26647
27315
  }
@@ -30946,4 +31614,4 @@ function createStorefrontApiHandler(config) {
30946
31614
  }
30947
31615
  __name(createStorefrontApiHandler, "createStorefrontApiHandler");
30948
31616
 
30949
- export { Address, Attendee, Attribute, BLOG_GENERATOR_AGENT_NAME, BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION, BLOG_GENERATOR_DEFAULT_VALIDATION_RULES, BLOG_GENERATOR_LLM_AGENT_SLUG, BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR, BLOG_METADATA_ENRICHER_AGENT_NAME, BLOG_METADATA_ENRICHER_DEFAULT_SYSTEM_INSTRUCTION, BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES, BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, Blog, BlogGeneratorService, Brand, CMS_ENTITY_MAP, Cart, CartItem, Category, ChatConversation, ChatMessage, Collection, Combo, ComboItem, Comment, Config, Contact, Currency, CurrencyExchange, Customer, Customer_Contacts, Discount, DiscountRules, Event, EventProduct, Form, FormField, FormSubmission, JobSchedule, JobScheduleRun, KnowledgeBaseChunk, KnowledgeBaseDocument, LlmAgent, Media, MessageTemplate, Order, OrderAddresses, OrderDiscounts, OrderItem, OrderNotificationBinding, OrderNotificationTrigger, OtpChallenge, Page, PasswordResetToken, Payment, Permission, Product, ProductAttribute, ProductCategory, ProductConfig, ProductVariant, RefundPolicy, RefundRequest, RssArticle, RssFeed, Seo, Tag, Tax, User, UserGroup, Vendor, VendorCustomer, VendorRole, VendorRolePermission, VendorUser, Wishlist, WishlistItem, ZIP_MIME_TYPES, applyRotatingVendorInvite, applyVendorCustomersContactFilter, assertCaptchaOk, assertContactAllowedForVendorOrder, buildBlogMetadataUserPrompt, buildCronFromSchedule, buildRssUserPromptFromFeeds, buildVendorInviteLink, calculateOrderRefundPreview, calculateRefundFromPolicy, checkEventsEnabled, checkMultiVendorEnabled, contactIsVendorCustomer, countRecentOtpSends, createAnalyticsHandlers, createBlogBySlugHandler, createChangePasswordHandler, createCmsApiHandler, createCmsApp, createCmsAppWithMessaging, createCrudByIdHandler, createCrudHandler, createDashboardStatsHandler, createEcommerceAnalyticsHandler, createEventOrderMessageTemplateHandlers, createForgotPasswordHandler, createFormBySlugHandler, createInviteAcceptHandler, createJobScheduleHandlers, createLlmAgentKnowledgeHandlers, createMediaZipExtractHandler, createMessageTemplateRowLoader, createOtpChallenge, createSetPasswordHandler, createSettingsApiHandlers, createSocialMediaHandlers, createStorefrontApiHandler, createUploadHandler, createUserAuthApiRouter, createUserAvatarHandler, createUserProfileHandler, createUsersApiHandlers, createVendorDashboardHandler, createVendorOnboardHandlers, customerPhoneForUser, daysBeforeEventStart, describeEventTierPolicy, ensureCustomerForUser, ensureMessagingPluginsOnCms, ensureScheduleQueueWorker, ensureVendorCustomerForOrderContact, findActiveRefundPolicyForVendor, findVendorByInviteToken, formatTierRange, generateNumericOtp, getPublicSettingsGroup, getRssArticleSummaryFromItem, hashOtpCode, hydrateVendorSessionUser, invalidateEventsCache, invalidateMultiVendorCache, isCustomerTypeContact, isZipMedia, linkUnclaimedContactToUser, llmAgentToChatAgentOptions, loadSettingsGroupFromDb, loadUserVendorContext, mergeGuardrailsIntoSystemPrompt, messagingPlugins, metaFetchUserManagedPages, metaPostPageFeed, metaPostPagePhoto, metaResolvePageAccessToken, normalizePhoneE164, normalizeRefundTiers, overlayCmsPlugins, parseBlogGeneratorAgentContent, parseBlogGeneratorModelOutput, parseBlogMetadataEnrichmentJson, parseLlmAgentValidationRules, pgBossScheduleNameForId, queueErpCreateContactIfEnabled, queueJobScheduleNow, queuePlugin, queueSms, registerJobRunnerWorker, registerMessagingQueueProcessors, registerSmsQueueProcessor, relativePathFromMediaParentId, resolveBlogCategoryIdByName, resolveSettingsEncryptionKey, resolveVendorIdForContactCheck, sanitizeMediaFolderPath, sanitizeStorageSegment, sendVendorOnboardEmails, simpleDecrypt, simpleEncrypt, syncJobScheduleToPgBoss, validateRefundTiers, validateScheduleInput, validateUserMessageAgainstAgentRules, validateUserMessageAgainstStructuredRules, verifyAndConsumeOtpChallenge, verifyOtpCodeHash, whatsappPlugin, wrapGetCmsWithMessaging };
31617
+ export { Address, Attendee, Attribute, BLOG_GENERATOR_AGENT_NAME, BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION, BLOG_GENERATOR_DEFAULT_VALIDATION_RULES, BLOG_GENERATOR_LLM_AGENT_SLUG, BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR, BLOG_METADATA_ENRICHER_AGENT_NAME, BLOG_METADATA_ENRICHER_DEFAULT_SYSTEM_INSTRUCTION, BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES, BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, Blog, BlogGeneratorService, Brand, CMS_ENTITY_MAP, Cart, CartItem, Category, ChatConversation, ChatMessage, Collection, Combo, ComboItem, Comment, Config, Contact, Currency, CurrencyExchange, Customer, Customer_Contacts, Discount, DiscountRules, Event, EventProduct, Form, FormField, FormSubmission, JobSchedule, JobScheduleRun, KnowledgeBaseChunk, KnowledgeBaseDocument, LlmAgent, Media, MessageTemplate, Order, OrderAddresses, OrderDiscounts, OrderItem, OrderNotificationBinding, OrderNotificationTrigger, OtpChallenge, Page, PasswordResetToken, Payment, Permission, Product, ProductAttribute, ProductCategory, ProductConfig, ProductVariant, RefundPolicy, RefundRequest, RssArticle, RssFeed, Seo, Tag, Tax, User, UserGroup, Vendor, VendorCustomer, VendorRole, VendorRolePermission, VendorUser, Wishlist, WishlistItem, ZIP_MIME_TYPES, applyApprovalStatusSideEffects, applyEventApprovalStatusSideEffects, applyRotatingVendorInvite, applyVendorCustomersContactFilter, applyVendorEventCreateApproval, applyVendorProductCreateApproval, assertCaptchaOk, assertContactAllowedForVendorOrder, assertEventApprovalUpdate, assertProductApprovalUpdate, buildBlogMetadataUserPrompt, buildCronFromSchedule, buildRssUserPromptFromFeeds, buildVendorInviteLink, calculateOrderRefundPreview, calculateRefundFromPolicy, checkEventsEnabled, checkMultiVendorEnabled, contactIsVendorCustomer, countRecentOtpSends, createAnalyticsHandlers, createBlogBySlugHandler, createChangePasswordHandler, createCmsApiHandler, createCmsApp, createCmsAppWithMessaging, createCrudByIdHandler, createCrudHandler, createDashboardStatsHandler, createEcommerceAnalyticsHandler, createEventOrderMessageTemplateHandlers, createForgotPasswordHandler, createFormBySlugHandler, createInviteAcceptHandler, createJobScheduleHandlers, createLlmAgentKnowledgeHandlers, createMediaZipExtractHandler, createMessageTemplateRowLoader, createOtpChallenge, createSetPasswordHandler, createSettingsApiHandlers, createSocialMediaHandlers, createStorefrontApiHandler, createUploadHandler, createUserAuthApiRouter, createUserAvatarHandler, createUserProfileHandler, createUsersApiHandlers, createVendorDashboardHandler, createVendorOnboardHandlers, customerPhoneForUser, daysBeforeEventStart, describeEventTierPolicy, ensureCustomerForUser, ensureMessagingPluginsOnCms, ensureScheduleQueueWorker, ensureVendorCustomerForOrderContact, findActiveRefundPolicyForVendor, findVendorByInviteToken, formatTierRange, generateNumericOtp, getPublicSettingsGroup, getRequireEventApproval, getRequireProductApproval, getRssArticleSummaryFromItem, getVendorCatalogCreateFlags, hashOtpCode, hydrateVendorSessionUser, invalidateEventsCache, invalidateMultiVendorCache, invalidateRequireEventApprovalCache, invalidateRequireProductApprovalCache, invalidateVendorCatalogCreateFlagsCache, isCustomerTypeContact, isZipMedia, linkUnclaimedContactToUser, llmAgentToChatAgentOptions, loadSettingsGroupFromDb, loadUserVendorContext, mergeGuardrailsIntoSystemPrompt, messagingPlugins, metaFetchUserManagedPages, metaPostPageFeed, metaPostPagePhoto, metaResolvePageAccessToken, normalizePhoneE164, normalizeRefundTiers, overlayCmsPlugins, parseBlogGeneratorAgentContent, parseBlogGeneratorModelOutput, parseBlogMetadataEnrichmentJson, parseLlmAgentValidationRules, pgBossScheduleNameForId, queueErpCreateContactIfEnabled, queueJobScheduleNow, queuePlugin, queueSms, registerJobRunnerWorker, registerMessagingQueueProcessors, registerSmsQueueProcessor, relativePathFromMediaParentId, resolveBlogCategoryIdByName, resolveSettingsEncryptionKey, resolveVendorIdForContactCheck, sanitizeMediaFolderPath, sanitizeStorageSegment, sendVendorOnboardEmails, simpleDecrypt, simpleEncrypt, syncJobScheduleToPgBoss, validateRefundTiers, validateScheduleInput, validateUserMessageAgainstAgentRules, validateUserMessageAgainstStructuredRules, verifyAndConsumeOtpChallenge, verifyOtpCodeHash, whatsappPlugin, wrapGetCmsWithMessaging };