@intranefr/superbackend 1.4.3

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 (188) hide show
  1. package/.commiat +4 -0
  2. package/.env.example +47 -0
  3. package/README.md +110 -0
  4. package/index.js +94 -0
  5. package/package.json +67 -0
  6. package/public/css/styles.css +139 -0
  7. package/public/js/animations.js +41 -0
  8. package/sdk/error-tracking/browser/package.json +16 -0
  9. package/sdk/error-tracking/browser/src/core.js +270 -0
  10. package/sdk/error-tracking/browser/src/embed.js +18 -0
  11. package/sdk/error-tracking/browser/src/index.js +1 -0
  12. package/server.js +5 -0
  13. package/src/admin/endpointRegistry.js +300 -0
  14. package/src/controllers/admin.controller.js +321 -0
  15. package/src/controllers/adminAssets.controller.js +530 -0
  16. package/src/controllers/adminAssetsStorage.controller.js +260 -0
  17. package/src/controllers/adminEjsVirtual.controller.js +354 -0
  18. package/src/controllers/adminFeatureFlags.controller.js +155 -0
  19. package/src/controllers/adminHeadless.controller.js +1071 -0
  20. package/src/controllers/adminI18n.controller.js +604 -0
  21. package/src/controllers/adminJsonConfigs.controller.js +97 -0
  22. package/src/controllers/adminLlm.controller.js +273 -0
  23. package/src/controllers/adminMigration.controller.js +257 -0
  24. package/src/controllers/adminSeoConfig.controller.js +515 -0
  25. package/src/controllers/adminStats.controller.js +121 -0
  26. package/src/controllers/adminUploadNamespaces.controller.js +208 -0
  27. package/src/controllers/assets.controller.js +248 -0
  28. package/src/controllers/auth.controller.js +93 -0
  29. package/src/controllers/billing.controller.js +223 -0
  30. package/src/controllers/featureFlags.controller.js +35 -0
  31. package/src/controllers/forms.controller.js +217 -0
  32. package/src/controllers/globalSettings.controller.js +252 -0
  33. package/src/controllers/headlessCrud.controller.js +126 -0
  34. package/src/controllers/i18n.controller.js +12 -0
  35. package/src/controllers/invite.controller.js +249 -0
  36. package/src/controllers/jsonConfigs.controller.js +19 -0
  37. package/src/controllers/metrics.controller.js +149 -0
  38. package/src/controllers/notificationAdmin.controller.js +264 -0
  39. package/src/controllers/notifications.controller.js +131 -0
  40. package/src/controllers/org.controller.js +357 -0
  41. package/src/controllers/orgAdmin.controller.js +491 -0
  42. package/src/controllers/stripeAdmin.controller.js +410 -0
  43. package/src/controllers/user.controller.js +361 -0
  44. package/src/controllers/userAdmin.controller.js +277 -0
  45. package/src/controllers/waitingList.controller.js +167 -0
  46. package/src/controllers/webhook.controller.js +200 -0
  47. package/src/middleware/auth.js +66 -0
  48. package/src/middleware/errorCapture.js +170 -0
  49. package/src/middleware/headlessApiTokenAuth.js +57 -0
  50. package/src/middleware/org.js +108 -0
  51. package/src/middleware.js +901 -0
  52. package/src/models/ActionEvent.js +31 -0
  53. package/src/models/ActivityLog.js +41 -0
  54. package/src/models/Asset.js +84 -0
  55. package/src/models/AuditEvent.js +93 -0
  56. package/src/models/EmailLog.js +28 -0
  57. package/src/models/ErrorAggregate.js +72 -0
  58. package/src/models/FormSubmission.js +41 -0
  59. package/src/models/GlobalSetting.js +38 -0
  60. package/src/models/HeadlessApiToken.js +24 -0
  61. package/src/models/HeadlessModelDefinition.js +41 -0
  62. package/src/models/I18nEntry.js +77 -0
  63. package/src/models/I18nLocale.js +33 -0
  64. package/src/models/Invite.js +70 -0
  65. package/src/models/JsonConfig.js +46 -0
  66. package/src/models/Notification.js +60 -0
  67. package/src/models/Organization.js +57 -0
  68. package/src/models/OrganizationMember.js +43 -0
  69. package/src/models/StripeCatalogItem.js +77 -0
  70. package/src/models/StripeWebhookEvent.js +57 -0
  71. package/src/models/User.js +89 -0
  72. package/src/models/VirtualEjsFile.js +60 -0
  73. package/src/models/VirtualEjsFileVersion.js +43 -0
  74. package/src/models/VirtualEjsGroupChange.js +32 -0
  75. package/src/models/WaitingList.js +41 -0
  76. package/src/models/Webhook.js +63 -0
  77. package/src/models/Workflow.js +29 -0
  78. package/src/models/WorkflowExecution.js +12 -0
  79. package/src/routes/admin.routes.js +26 -0
  80. package/src/routes/adminAssets.routes.js +28 -0
  81. package/src/routes/adminAssetsStorage.routes.js +13 -0
  82. package/src/routes/adminAudit.routes.js +196 -0
  83. package/src/routes/adminEjsVirtual.routes.js +17 -0
  84. package/src/routes/adminErrors.routes.js +164 -0
  85. package/src/routes/adminFeatureFlags.routes.js +12 -0
  86. package/src/routes/adminHeadless.routes.js +38 -0
  87. package/src/routes/adminI18n.routes.js +22 -0
  88. package/src/routes/adminJsonConfigs.routes.js +15 -0
  89. package/src/routes/adminLlm.routes.js +12 -0
  90. package/src/routes/adminMigration.routes.js +81 -0
  91. package/src/routes/adminSeoConfig.routes.js +20 -0
  92. package/src/routes/adminUploadNamespaces.routes.js +13 -0
  93. package/src/routes/assets.routes.js +21 -0
  94. package/src/routes/auth.routes.js +12 -0
  95. package/src/routes/billing.routes.js +11 -0
  96. package/src/routes/errorTracking.routes.js +31 -0
  97. package/src/routes/featureFlags.routes.js +9 -0
  98. package/src/routes/forms.routes.js +9 -0
  99. package/src/routes/formsAdmin.routes.js +13 -0
  100. package/src/routes/globalSettings.routes.js +18 -0
  101. package/src/routes/headless.routes.js +15 -0
  102. package/src/routes/i18n.routes.js +8 -0
  103. package/src/routes/invite.routes.js +9 -0
  104. package/src/routes/jsonConfigs.routes.js +8 -0
  105. package/src/routes/log.routes.js +111 -0
  106. package/src/routes/metrics.routes.js +9 -0
  107. package/src/routes/notificationAdmin.routes.js +15 -0
  108. package/src/routes/notifications.routes.js +12 -0
  109. package/src/routes/org.routes.js +31 -0
  110. package/src/routes/orgAdmin.routes.js +20 -0
  111. package/src/routes/publicAssets.routes.js +7 -0
  112. package/src/routes/stripeAdmin.routes.js +20 -0
  113. package/src/routes/user.routes.js +22 -0
  114. package/src/routes/userAdmin.routes.js +15 -0
  115. package/src/routes/waitingList.routes.js +13 -0
  116. package/src/routes/waitingListAdmin.routes.js +9 -0
  117. package/src/routes/webhook.routes.js +32 -0
  118. package/src/routes/workflowWebhook.routes.js +54 -0
  119. package/src/routes/workflows.routes.js +110 -0
  120. package/src/services/assets.service.js +110 -0
  121. package/src/services/audit.service.js +62 -0
  122. package/src/services/auditLogger.js +165 -0
  123. package/src/services/ejsVirtual.service.js +614 -0
  124. package/src/services/email.service.js +351 -0
  125. package/src/services/errorLogger.js +221 -0
  126. package/src/services/featureFlags.service.js +202 -0
  127. package/src/services/forms.service.js +214 -0
  128. package/src/services/globalSettings.service.js +49 -0
  129. package/src/services/headlessApiTokens.service.js +158 -0
  130. package/src/services/headlessCrypto.service.js +31 -0
  131. package/src/services/headlessModels.service.js +356 -0
  132. package/src/services/i18n.service.js +314 -0
  133. package/src/services/i18nInferredKeys.service.js +337 -0
  134. package/src/services/jsonConfigs.service.js +392 -0
  135. package/src/services/llm.service.js +749 -0
  136. package/src/services/migration.service.js +581 -0
  137. package/src/services/migrationAssets/fsLocal.js +58 -0
  138. package/src/services/migrationAssets/index.js +134 -0
  139. package/src/services/migrationAssets/s3.js +75 -0
  140. package/src/services/migrationAssets/sftp.js +92 -0
  141. package/src/services/notification.service.js +212 -0
  142. package/src/services/objectStorage.service.js +514 -0
  143. package/src/services/seoConfig.service.js +402 -0
  144. package/src/services/storage.js +150 -0
  145. package/src/services/stripe.service.js +185 -0
  146. package/src/services/stripeHelper.service.js +264 -0
  147. package/src/services/uploadNamespaces.service.js +326 -0
  148. package/src/services/webhook.service.js +157 -0
  149. package/src/services/workflow.service.js +271 -0
  150. package/src/utils/asyncHandler.js +5 -0
  151. package/src/utils/encryption.js +80 -0
  152. package/src/utils/jwt.js +40 -0
  153. package/src/utils/orgRoles.js +156 -0
  154. package/src/utils/validation.js +26 -0
  155. package/src/utils/webhookRetry.js +93 -0
  156. package/views/admin-assets.ejs +444 -0
  157. package/views/admin-audit.ejs +283 -0
  158. package/views/admin-coolify-deploy.ejs +207 -0
  159. package/views/admin-dashboard-home.ejs +291 -0
  160. package/views/admin-dashboard.ejs +397 -0
  161. package/views/admin-ejs-virtual.ejs +280 -0
  162. package/views/admin-errors.ejs +368 -0
  163. package/views/admin-feature-flags.ejs +390 -0
  164. package/views/admin-forms.ejs +526 -0
  165. package/views/admin-global-settings.ejs +436 -0
  166. package/views/admin-headless.ejs +2020 -0
  167. package/views/admin-i18n-locales.ejs +221 -0
  168. package/views/admin-i18n.ejs +728 -0
  169. package/views/admin-json-configs.ejs +410 -0
  170. package/views/admin-llm.ejs +884 -0
  171. package/views/admin-metrics.ejs +274 -0
  172. package/views/admin-migration.ejs +814 -0
  173. package/views/admin-notifications.ejs +430 -0
  174. package/views/admin-organizations.ejs +984 -0
  175. package/views/admin-seo-config.ejs +673 -0
  176. package/views/admin-stripe-pricing.ejs +558 -0
  177. package/views/admin-test.ejs +342 -0
  178. package/views/admin-users.ejs +452 -0
  179. package/views/admin-waiting-list.ejs +547 -0
  180. package/views/admin-webhooks.ejs +329 -0
  181. package/views/admin-workflows.ejs +310 -0
  182. package/views/partials/admin-assets-script.ejs +2022 -0
  183. package/views/partials/admin-test-sidebar.ejs +14 -0
  184. package/views/partials/dashboard/nav-items.ejs +66 -0
  185. package/views/partials/dashboard/palette.ejs +63 -0
  186. package/views/partials/dashboard/sidebar.ejs +21 -0
  187. package/views/partials/dashboard/tab-bar.ejs +26 -0
  188. package/views/partials/footer.ejs +3 -0
@@ -0,0 +1,410 @@
1
+ const mongoose = require('mongoose');
2
+ const StripeCatalogItem = require('../models/StripeCatalogItem');
3
+ const stripeHelper = require('../services/stripeHelper.service');
4
+ const { createAuditEvent, getBasicAuthActor } = require('../services/audit.service');
5
+
6
+ const DEFAULT_LIMIT = 50;
7
+ const MAX_LIMIT = 500;
8
+
9
+ function parseLimit(value) {
10
+ const parsed = parseInt(value, 10);
11
+ if (!Number.isFinite(parsed)) return DEFAULT_LIMIT;
12
+ return Math.min(MAX_LIMIT, Math.max(1, parsed));
13
+ }
14
+
15
+ function parseOffset(value) {
16
+ const parsed = parseInt(value, 10);
17
+ if (!Number.isFinite(parsed)) return 0;
18
+ return Math.max(0, parsed);
19
+ }
20
+
21
+ exports.getStripeStatus = async (req, res) => {
22
+ try {
23
+ const configured = await stripeHelper.isStripeConfigured();
24
+ const catalogCount = await StripeCatalogItem.countDocuments({});
25
+ const activeCount = await StripeCatalogItem.countDocuments({ active: true });
26
+
27
+ return res.json({
28
+ configured,
29
+ catalogCount,
30
+ activeCount,
31
+ envPriceIdCreator: process.env.STRIPE_PRICE_ID_CREATOR ? '(set)' : '(not set)',
32
+ envPriceIdPro: process.env.STRIPE_PRICE_ID_PRO ? '(set)' : '(not set)'
33
+ });
34
+ } catch (error) {
35
+ console.error('Stripe status error:', error);
36
+ return res.status(500).json({ error: 'Failed to get Stripe status' });
37
+ }
38
+ };
39
+
40
+ exports.listCatalog = async (req, res) => {
41
+ try {
42
+ const { active, billingType, planKey, limit, offset } = req.query;
43
+
44
+ const parsedLimit = parseLimit(limit);
45
+ const parsedOffset = parseOffset(offset);
46
+
47
+ const query = {};
48
+ if (active === 'true') query.active = true;
49
+ if (active === 'false') query.active = false;
50
+ if (billingType) query.billingType = String(billingType);
51
+ if (planKey) query.planKey = String(planKey);
52
+
53
+ const items = await StripeCatalogItem.find(query)
54
+ .sort({ createdAt: -1 })
55
+ .limit(parsedLimit)
56
+ .skip(parsedOffset)
57
+ .lean();
58
+
59
+ const total = await StripeCatalogItem.countDocuments(query);
60
+
61
+ return res.json({
62
+ items,
63
+ pagination: {
64
+ total,
65
+ limit: parsedLimit,
66
+ offset: parsedOffset
67
+ }
68
+ });
69
+ } catch (error) {
70
+ console.error('Catalog list error:', error);
71
+ return res.status(500).json({ error: 'Failed to list catalog' });
72
+ }
73
+ };
74
+
75
+ exports.getCatalogItem = async (req, res) => {
76
+ try {
77
+ const { id } = req.params;
78
+
79
+ if (!id || !mongoose.Types.ObjectId.isValid(String(id))) {
80
+ return res.status(400).json({ error: 'Invalid catalog item ID' });
81
+ }
82
+
83
+ const item = await StripeCatalogItem.findById(id).lean();
84
+ if (!item) {
85
+ return res.status(404).json({ error: 'Catalog item not found' });
86
+ }
87
+
88
+ return res.json({ item });
89
+ } catch (error) {
90
+ console.error('Catalog get error:', error);
91
+ return res.status(500).json({ error: 'Failed to get catalog item' });
92
+ }
93
+ };
94
+
95
+ exports.upsertCatalogItem = async (req, res) => {
96
+ try {
97
+ if (!(await stripeHelper.isStripeConfigured())) {
98
+ return res.status(400).json({ error: 'Stripe is not configured' });
99
+ }
100
+
101
+ const {
102
+ productName,
103
+ productDescription,
104
+ planKey,
105
+ displayName,
106
+ billingType,
107
+ currency,
108
+ unitAmount,
109
+ interval,
110
+ intervalCount,
111
+ metadata,
112
+ existingProductId
113
+ } = req.body;
114
+
115
+ if (!productName || !planKey || !displayName || !billingType || unitAmount === undefined) {
116
+ return res.status(400).json({
117
+ error: 'productName, planKey, displayName, billingType, and unitAmount are required'
118
+ });
119
+ }
120
+
121
+ if (!['subscription', 'one_time'].includes(billingType)) {
122
+ return res.status(400).json({ error: 'billingType must be subscription or one_time' });
123
+ }
124
+
125
+ if (billingType === 'subscription' && !interval) {
126
+ return res.status(400).json({ error: 'interval is required for subscription billing' });
127
+ }
128
+
129
+ const actor = getBasicAuthActor(req);
130
+
131
+ const result = await stripeHelper.upsertStripeProductAndPrice({
132
+ productName: String(productName),
133
+ productDescription: productDescription ? String(productDescription) : '',
134
+ planKey: String(planKey).trim(),
135
+ displayName: String(displayName).trim(),
136
+ billingType: String(billingType),
137
+ currency: currency ? String(currency).toLowerCase() : 'usd',
138
+ unitAmount: parseInt(unitAmount, 10),
139
+ interval: interval ? String(interval) : null,
140
+ intervalCount: intervalCount ? parseInt(intervalCount, 10) : 1,
141
+ metadata: metadata || {},
142
+ existingProductId: existingProductId || null,
143
+ adminId: actor.actorId
144
+ });
145
+
146
+ await createAuditEvent({
147
+ ...actor,
148
+ action: 'admin.stripe.catalog.upsert',
149
+ entityType: 'StripeCatalogItem',
150
+ entityId: String(result.catalogItem._id),
151
+ before: null,
152
+ after: result.catalogItem,
153
+ meta: { stripeProductId: result.product.id, stripePriceId: result.price.id }
154
+ });
155
+
156
+ return res.status(201).json({
157
+ message: 'Catalog item created/updated',
158
+ catalogItem: result.catalogItem,
159
+ stripeProduct: { id: result.product.id, name: result.product.name },
160
+ stripePrice: { id: result.price.id, unitAmount: result.price.unit_amount }
161
+ });
162
+ } catch (error) {
163
+ console.error('Catalog upsert error:', error);
164
+ return res.status(500).json({ error: error.message || 'Failed to upsert catalog item' });
165
+ }
166
+ };
167
+
168
+ exports.importStripePrice = async (req, res) => {
169
+ try {
170
+ if (!(await stripeHelper.isStripeConfigured())) {
171
+ return res.status(400).json({ error: 'Stripe is not configured' });
172
+ }
173
+
174
+ const { stripePriceId, planKey, displayName } = req.body;
175
+
176
+ if (!stripePriceId || !planKey) {
177
+ return res.status(400).json({ error: 'stripePriceId and planKey are required' });
178
+ }
179
+
180
+ const actor = getBasicAuthActor(req);
181
+
182
+ const result = await stripeHelper.importStripePrice(
183
+ String(stripePriceId),
184
+ String(planKey).trim(),
185
+ displayName ? String(displayName).trim() : null,
186
+ actor.actorId
187
+ );
188
+
189
+ await createAuditEvent({
190
+ ...actor,
191
+ action: 'admin.stripe.catalog.import',
192
+ entityType: 'StripeCatalogItem',
193
+ entityId: String(result.catalogItem._id),
194
+ before: null,
195
+ after: result.catalogItem,
196
+ meta: { stripePriceId }
197
+ });
198
+
199
+ return res.status(201).json({
200
+ message: 'Price imported successfully',
201
+ catalogItem: result.catalogItem
202
+ });
203
+ } catch (error) {
204
+ console.error('Import price error:', error);
205
+ return res.status(500).json({ error: error.message || 'Failed to import price' });
206
+ }
207
+ };
208
+
209
+ exports.deactivateCatalogItem = async (req, res) => {
210
+ try {
211
+ const { id } = req.params;
212
+
213
+ if (!id || !mongoose.Types.ObjectId.isValid(String(id))) {
214
+ return res.status(400).json({ error: 'Invalid catalog item ID' });
215
+ }
216
+
217
+ const item = await StripeCatalogItem.findById(id);
218
+ if (!item) {
219
+ return res.status(404).json({ error: 'Catalog item not found' });
220
+ }
221
+
222
+ const before = item.toObject();
223
+ const actor = getBasicAuthActor(req);
224
+
225
+ item.active = false;
226
+ item.updatedByAdminId = actor.actorId;
227
+ await item.save();
228
+
229
+ await createAuditEvent({
230
+ ...actor,
231
+ action: 'admin.stripe.catalog.deactivate',
232
+ entityType: 'StripeCatalogItem',
233
+ entityId: String(item._id),
234
+ before,
235
+ after: item.toObject(),
236
+ meta: null
237
+ });
238
+
239
+ return res.json({ message: 'Catalog item deactivated', item });
240
+ } catch (error) {
241
+ console.error('Deactivate error:', error);
242
+ return res.status(500).json({ error: 'Failed to deactivate catalog item' });
243
+ }
244
+ };
245
+
246
+ exports.activateCatalogItem = async (req, res) => {
247
+ try {
248
+ const { id } = req.params;
249
+
250
+ if (!id || !mongoose.Types.ObjectId.isValid(String(id))) {
251
+ return res.status(400).json({ error: 'Invalid catalog item ID' });
252
+ }
253
+
254
+ const item = await StripeCatalogItem.findById(id);
255
+ if (!item) {
256
+ return res.status(404).json({ error: 'Catalog item not found' });
257
+ }
258
+
259
+ const before = item.toObject();
260
+ const actor = getBasicAuthActor(req);
261
+
262
+ item.active = true;
263
+ item.updatedByAdminId = actor.actorId;
264
+ await item.save();
265
+
266
+ await createAuditEvent({
267
+ ...actor,
268
+ action: 'admin.stripe.catalog.activate',
269
+ entityType: 'StripeCatalogItem',
270
+ entityId: String(item._id),
271
+ before,
272
+ after: item.toObject(),
273
+ meta: null
274
+ });
275
+
276
+ return res.json({ message: 'Catalog item activated', item });
277
+ } catch (error) {
278
+ console.error('Activate error:', error);
279
+ return res.status(500).json({ error: 'Failed to activate catalog item' });
280
+ }
281
+ };
282
+
283
+ exports.deleteCatalogItem = async (req, res) => {
284
+ try {
285
+ const { id } = req.params;
286
+
287
+ if (!id || !mongoose.Types.ObjectId.isValid(String(id))) {
288
+ return res.status(400).json({ error: 'Invalid catalog item ID' });
289
+ }
290
+
291
+ const item = await StripeCatalogItem.findById(id);
292
+ if (!item) {
293
+ return res.status(404).json({ error: 'Catalog item not found' });
294
+ }
295
+
296
+ const before = item.toObject();
297
+ const actor = getBasicAuthActor(req);
298
+
299
+ await StripeCatalogItem.deleteOne({ _id: id });
300
+
301
+ await createAuditEvent({
302
+ ...actor,
303
+ action: 'admin.stripe.catalog.delete',
304
+ entityType: 'StripeCatalogItem',
305
+ entityId: String(id),
306
+ before,
307
+ after: null,
308
+ meta: null
309
+ });
310
+
311
+ return res.json({ message: 'Catalog item deleted' });
312
+ } catch (error) {
313
+ console.error('Delete error:', error);
314
+ return res.status(500).json({ error: 'Failed to delete catalog item' });
315
+ }
316
+ };
317
+
318
+ exports.listStripeProducts = async (req, res) => {
319
+ try {
320
+ if (!(await stripeHelper.isStripeConfigured())) {
321
+ return res.status(400).json({ error: 'Stripe is not configured' });
322
+ }
323
+
324
+ const stripe = await stripeHelper.getStripeClient();
325
+ const { limit = 20, starting_after } = req.query;
326
+
327
+ const params = {
328
+ limit: Math.min(100, Math.max(1, parseInt(limit, 10) || 20)),
329
+ active: true
330
+ };
331
+ if (starting_after) params.starting_after = starting_after;
332
+
333
+ const products = await stripe.products.list(params);
334
+
335
+ return res.json({
336
+ products: products.data,
337
+ hasMore: products.has_more
338
+ });
339
+ } catch (error) {
340
+ console.error('List Stripe products error:', error);
341
+ return res.status(500).json({ error: error.message || 'Failed to list Stripe products' });
342
+ }
343
+ };
344
+
345
+ exports.listStripePrices = async (req, res) => {
346
+ try {
347
+ if (!(await stripeHelper.isStripeConfigured())) {
348
+ return res.status(400).json({ error: 'Stripe is not configured' });
349
+ }
350
+
351
+ const stripe = await stripeHelper.getStripeClient();
352
+ const { product, limit = 20, starting_after } = req.query;
353
+
354
+ const params = {
355
+ limit: Math.min(100, Math.max(1, parseInt(limit, 10) || 20)),
356
+ active: true,
357
+ expand: ['data.product']
358
+ };
359
+ if (product) params.product = product;
360
+ if (starting_after) params.starting_after = starting_after;
361
+
362
+ const prices = await stripe.prices.list(params);
363
+
364
+ const catalogPriceIds = await StripeCatalogItem.find({}).select('stripePriceId').lean();
365
+ const mappedIds = new Set(catalogPriceIds.map(c => c.stripePriceId));
366
+
367
+ const pricesWithMapping = prices.data.map(p => ({
368
+ ...p,
369
+ _isMapped: mappedIds.has(p.id)
370
+ }));
371
+
372
+ return res.json({
373
+ prices: pricesWithMapping,
374
+ hasMore: prices.has_more
375
+ });
376
+ } catch (error) {
377
+ console.error('List Stripe prices error:', error);
378
+ return res.status(500).json({ error: error.message || 'Failed to list Stripe prices' });
379
+ }
380
+ };
381
+
382
+ exports.syncEnvFromCatalog = async (req, res) => {
383
+ try {
384
+ const items = await StripeCatalogItem.find({ active: true })
385
+ .sort({ createdAt: -1 })
386
+ .lean();
387
+
388
+ const applied = [];
389
+
390
+ for (const item of items) {
391
+ const envVar = String(item.planKey || '').trim();
392
+ if (!envVar) continue;
393
+
394
+ process.env[envVar] = item.stripePriceId;
395
+ applied.push({
396
+ envVar,
397
+ stripePriceId: item.stripePriceId,
398
+ });
399
+ }
400
+
401
+ return res.json({
402
+ applied,
403
+ totalActive: items.length,
404
+ });
405
+ } catch (error) {
406
+ console.error('Stripe env sync error:', error);
407
+ return res.status(500).json({ error: error.message || 'Failed to sync env from catalog' });
408
+ }
409
+ };
410
+