@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.
- package/.commiat +4 -0
- package/.env.example +47 -0
- package/README.md +110 -0
- package/index.js +94 -0
- package/package.json +67 -0
- package/public/css/styles.css +139 -0
- package/public/js/animations.js +41 -0
- package/sdk/error-tracking/browser/package.json +16 -0
- package/sdk/error-tracking/browser/src/core.js +270 -0
- package/sdk/error-tracking/browser/src/embed.js +18 -0
- package/sdk/error-tracking/browser/src/index.js +1 -0
- package/server.js +5 -0
- package/src/admin/endpointRegistry.js +300 -0
- package/src/controllers/admin.controller.js +321 -0
- package/src/controllers/adminAssets.controller.js +530 -0
- package/src/controllers/adminAssetsStorage.controller.js +260 -0
- package/src/controllers/adminEjsVirtual.controller.js +354 -0
- package/src/controllers/adminFeatureFlags.controller.js +155 -0
- package/src/controllers/adminHeadless.controller.js +1071 -0
- package/src/controllers/adminI18n.controller.js +604 -0
- package/src/controllers/adminJsonConfigs.controller.js +97 -0
- package/src/controllers/adminLlm.controller.js +273 -0
- package/src/controllers/adminMigration.controller.js +257 -0
- package/src/controllers/adminSeoConfig.controller.js +515 -0
- package/src/controllers/adminStats.controller.js +121 -0
- package/src/controllers/adminUploadNamespaces.controller.js +208 -0
- package/src/controllers/assets.controller.js +248 -0
- package/src/controllers/auth.controller.js +93 -0
- package/src/controllers/billing.controller.js +223 -0
- package/src/controllers/featureFlags.controller.js +35 -0
- package/src/controllers/forms.controller.js +217 -0
- package/src/controllers/globalSettings.controller.js +252 -0
- package/src/controllers/headlessCrud.controller.js +126 -0
- package/src/controllers/i18n.controller.js +12 -0
- package/src/controllers/invite.controller.js +249 -0
- package/src/controllers/jsonConfigs.controller.js +19 -0
- package/src/controllers/metrics.controller.js +149 -0
- package/src/controllers/notificationAdmin.controller.js +264 -0
- package/src/controllers/notifications.controller.js +131 -0
- package/src/controllers/org.controller.js +357 -0
- package/src/controllers/orgAdmin.controller.js +491 -0
- package/src/controllers/stripeAdmin.controller.js +410 -0
- package/src/controllers/user.controller.js +361 -0
- package/src/controllers/userAdmin.controller.js +277 -0
- package/src/controllers/waitingList.controller.js +167 -0
- package/src/controllers/webhook.controller.js +200 -0
- package/src/middleware/auth.js +66 -0
- package/src/middleware/errorCapture.js +170 -0
- package/src/middleware/headlessApiTokenAuth.js +57 -0
- package/src/middleware/org.js +108 -0
- package/src/middleware.js +901 -0
- package/src/models/ActionEvent.js +31 -0
- package/src/models/ActivityLog.js +41 -0
- package/src/models/Asset.js +84 -0
- package/src/models/AuditEvent.js +93 -0
- package/src/models/EmailLog.js +28 -0
- package/src/models/ErrorAggregate.js +72 -0
- package/src/models/FormSubmission.js +41 -0
- package/src/models/GlobalSetting.js +38 -0
- package/src/models/HeadlessApiToken.js +24 -0
- package/src/models/HeadlessModelDefinition.js +41 -0
- package/src/models/I18nEntry.js +77 -0
- package/src/models/I18nLocale.js +33 -0
- package/src/models/Invite.js +70 -0
- package/src/models/JsonConfig.js +46 -0
- package/src/models/Notification.js +60 -0
- package/src/models/Organization.js +57 -0
- package/src/models/OrganizationMember.js +43 -0
- package/src/models/StripeCatalogItem.js +77 -0
- package/src/models/StripeWebhookEvent.js +57 -0
- package/src/models/User.js +89 -0
- package/src/models/VirtualEjsFile.js +60 -0
- package/src/models/VirtualEjsFileVersion.js +43 -0
- package/src/models/VirtualEjsGroupChange.js +32 -0
- package/src/models/WaitingList.js +41 -0
- package/src/models/Webhook.js +63 -0
- package/src/models/Workflow.js +29 -0
- package/src/models/WorkflowExecution.js +12 -0
- package/src/routes/admin.routes.js +26 -0
- package/src/routes/adminAssets.routes.js +28 -0
- package/src/routes/adminAssetsStorage.routes.js +13 -0
- package/src/routes/adminAudit.routes.js +196 -0
- package/src/routes/adminEjsVirtual.routes.js +17 -0
- package/src/routes/adminErrors.routes.js +164 -0
- package/src/routes/adminFeatureFlags.routes.js +12 -0
- package/src/routes/adminHeadless.routes.js +38 -0
- package/src/routes/adminI18n.routes.js +22 -0
- package/src/routes/adminJsonConfigs.routes.js +15 -0
- package/src/routes/adminLlm.routes.js +12 -0
- package/src/routes/adminMigration.routes.js +81 -0
- package/src/routes/adminSeoConfig.routes.js +20 -0
- package/src/routes/adminUploadNamespaces.routes.js +13 -0
- package/src/routes/assets.routes.js +21 -0
- package/src/routes/auth.routes.js +12 -0
- package/src/routes/billing.routes.js +11 -0
- package/src/routes/errorTracking.routes.js +31 -0
- package/src/routes/featureFlags.routes.js +9 -0
- package/src/routes/forms.routes.js +9 -0
- package/src/routes/formsAdmin.routes.js +13 -0
- package/src/routes/globalSettings.routes.js +18 -0
- package/src/routes/headless.routes.js +15 -0
- package/src/routes/i18n.routes.js +8 -0
- package/src/routes/invite.routes.js +9 -0
- package/src/routes/jsonConfigs.routes.js +8 -0
- package/src/routes/log.routes.js +111 -0
- package/src/routes/metrics.routes.js +9 -0
- package/src/routes/notificationAdmin.routes.js +15 -0
- package/src/routes/notifications.routes.js +12 -0
- package/src/routes/org.routes.js +31 -0
- package/src/routes/orgAdmin.routes.js +20 -0
- package/src/routes/publicAssets.routes.js +7 -0
- package/src/routes/stripeAdmin.routes.js +20 -0
- package/src/routes/user.routes.js +22 -0
- package/src/routes/userAdmin.routes.js +15 -0
- package/src/routes/waitingList.routes.js +13 -0
- package/src/routes/waitingListAdmin.routes.js +9 -0
- package/src/routes/webhook.routes.js +32 -0
- package/src/routes/workflowWebhook.routes.js +54 -0
- package/src/routes/workflows.routes.js +110 -0
- package/src/services/assets.service.js +110 -0
- package/src/services/audit.service.js +62 -0
- package/src/services/auditLogger.js +165 -0
- package/src/services/ejsVirtual.service.js +614 -0
- package/src/services/email.service.js +351 -0
- package/src/services/errorLogger.js +221 -0
- package/src/services/featureFlags.service.js +202 -0
- package/src/services/forms.service.js +214 -0
- package/src/services/globalSettings.service.js +49 -0
- package/src/services/headlessApiTokens.service.js +158 -0
- package/src/services/headlessCrypto.service.js +31 -0
- package/src/services/headlessModels.service.js +356 -0
- package/src/services/i18n.service.js +314 -0
- package/src/services/i18nInferredKeys.service.js +337 -0
- package/src/services/jsonConfigs.service.js +392 -0
- package/src/services/llm.service.js +749 -0
- package/src/services/migration.service.js +581 -0
- package/src/services/migrationAssets/fsLocal.js +58 -0
- package/src/services/migrationAssets/index.js +134 -0
- package/src/services/migrationAssets/s3.js +75 -0
- package/src/services/migrationAssets/sftp.js +92 -0
- package/src/services/notification.service.js +212 -0
- package/src/services/objectStorage.service.js +514 -0
- package/src/services/seoConfig.service.js +402 -0
- package/src/services/storage.js +150 -0
- package/src/services/stripe.service.js +185 -0
- package/src/services/stripeHelper.service.js +264 -0
- package/src/services/uploadNamespaces.service.js +326 -0
- package/src/services/webhook.service.js +157 -0
- package/src/services/workflow.service.js +271 -0
- package/src/utils/asyncHandler.js +5 -0
- package/src/utils/encryption.js +80 -0
- package/src/utils/jwt.js +40 -0
- package/src/utils/orgRoles.js +156 -0
- package/src/utils/validation.js +26 -0
- package/src/utils/webhookRetry.js +93 -0
- package/views/admin-assets.ejs +444 -0
- package/views/admin-audit.ejs +283 -0
- package/views/admin-coolify-deploy.ejs +207 -0
- package/views/admin-dashboard-home.ejs +291 -0
- package/views/admin-dashboard.ejs +397 -0
- package/views/admin-ejs-virtual.ejs +280 -0
- package/views/admin-errors.ejs +368 -0
- package/views/admin-feature-flags.ejs +390 -0
- package/views/admin-forms.ejs +526 -0
- package/views/admin-global-settings.ejs +436 -0
- package/views/admin-headless.ejs +2020 -0
- package/views/admin-i18n-locales.ejs +221 -0
- package/views/admin-i18n.ejs +728 -0
- package/views/admin-json-configs.ejs +410 -0
- package/views/admin-llm.ejs +884 -0
- package/views/admin-metrics.ejs +274 -0
- package/views/admin-migration.ejs +814 -0
- package/views/admin-notifications.ejs +430 -0
- package/views/admin-organizations.ejs +984 -0
- package/views/admin-seo-config.ejs +673 -0
- package/views/admin-stripe-pricing.ejs +558 -0
- package/views/admin-test.ejs +342 -0
- package/views/admin-users.ejs +452 -0
- package/views/admin-waiting-list.ejs +547 -0
- package/views/admin-webhooks.ejs +329 -0
- package/views/admin-workflows.ejs +310 -0
- package/views/partials/admin-assets-script.ejs +2022 -0
- package/views/partials/admin-test-sidebar.ejs +14 -0
- package/views/partials/dashboard/nav-items.ejs +66 -0
- package/views/partials/dashboard/palette.ejs +63 -0
- package/views/partials/dashboard/sidebar.ejs +21 -0
- package/views/partials/dashboard/tab-bar.ejs +26 -0
- 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
|
+
|