@intranefr/superbackend 1.6.6 → 1.7.7
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/.env.example +4 -0
- package/README.md +18 -0
- package/package.json +8 -2
- package/public/js/admin-superdemos.js +396 -0
- package/public/sdk/superdemos.iife.js +614 -0
- package/public/superdemos-qa.html +324 -0
- package/sdk/superdemos/browser/src/index.js +719 -0
- package/src/cli/agent-chat.js +369 -0
- package/src/cli/agent-list.js +42 -0
- package/src/controllers/adminAgentsChat.controller.js +172 -0
- package/src/controllers/adminSuperDemos.controller.js +382 -0
- package/src/controllers/superDemosPublic.controller.js +126 -0
- package/src/middleware.js +102 -19
- package/src/models/BlogAutomationLock.js +4 -4
- package/src/models/BlogPost.js +16 -16
- package/src/models/CacheEntry.js +17 -6
- package/src/models/JsonConfig.js +2 -4
- package/src/models/RateLimitMetricBucket.js +10 -5
- package/src/models/SuperDemo.js +38 -0
- package/src/models/SuperDemoProject.js +32 -0
- package/src/models/SuperDemoStep.js +27 -0
- package/src/routes/adminAgents.routes.js +10 -0
- package/src/routes/adminMarkdowns.routes.js +3 -0
- package/src/routes/adminSuperDemos.routes.js +31 -0
- package/src/routes/superDemos.routes.js +9 -0
- package/src/services/auditLogger.js +75 -37
- package/src/services/email.service.js +18 -3
- package/src/services/superDemosAuthoringSessions.service.js +132 -0
- package/src/services/superDemosWs.service.js +164 -0
- package/src/services/terminalsWs.service.js +35 -3
- package/src/utils/rbac/rightsRegistry.js +2 -0
- package/views/admin-agents.ejs +261 -11
- package/views/admin-dashboard.ejs +78 -8
- package/views/admin-superdemos.ejs +335 -0
- package/views/admin-terminals.ejs +462 -34
- package/views/partials/admin/agents-chat.ejs +80 -0
- package/views/partials/dashboard/nav-items.ejs +1 -0
- package/views/partials/dashboard/tab-bar.ejs +6 -0
- package/cookies.txt +0 -6
- package/cookies1.txt +0 -6
- package/cookies2.txt +0 -6
- package/cookies3.txt +0 -6
- package/cookies4.txt +0 -5
- package/cookies_old.txt +0 -5
- package/cookies_old_test.txt +0 -6
- package/cookies_super.txt +0 -5
- package/cookies_super_test.txt +0 -6
- package/cookies_test.txt +0 -6
- package/test-access.js +0 -63
- package/test-iframe-fix.html +0 -63
- package/test-iframe.html +0 -14
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
const SuperDemoProject = require('../models/SuperDemoProject');
|
|
2
|
+
const SuperDemo = require('../models/SuperDemo');
|
|
3
|
+
const SuperDemoStep = require('../models/SuperDemoStep');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
generateProjectApiKeyPlaintext,
|
|
7
|
+
hashKey,
|
|
8
|
+
} = require('../services/uiComponentsCrypto.service');
|
|
9
|
+
|
|
10
|
+
const authoringSessions = require('../services/superDemosAuthoringSessions.service');
|
|
11
|
+
|
|
12
|
+
function randomLowerAlphaNum(len) {
|
|
13
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
14
|
+
let out = '';
|
|
15
|
+
for (let i = 0; i < len; i += 1) out += chars[Math.floor(Math.random() * chars.length)];
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeStylePreset(value) {
|
|
20
|
+
const allowed = new Set(['default', 'glass-dark', 'high-contrast', 'soft-purple']);
|
|
21
|
+
const v = String(value || 'default').trim().toLowerCase();
|
|
22
|
+
return allowed.has(v) ? v : 'default';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeStyleOverrides(value) {
|
|
26
|
+
const raw = String(value || '');
|
|
27
|
+
return raw.length > 20000 ? raw.slice(0, 20000) : raw;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function generateProjectId() {
|
|
31
|
+
return `sdp_${randomLowerAlphaNum(16)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function generateDemoId() {
|
|
35
|
+
return `demo_${randomLowerAlphaNum(16)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseBool(value, fallback) {
|
|
39
|
+
if (value === undefined) return fallback;
|
|
40
|
+
if (typeof value === 'boolean') return value;
|
|
41
|
+
const v = String(value).trim().toLowerCase();
|
|
42
|
+
if (v === 'true' || v === '1' || v === 'yes') return true;
|
|
43
|
+
if (v === 'false' || v === '0' || v === 'no') return false;
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function addQueryParam(urlStr, key, value) {
|
|
48
|
+
const u = new URL(urlStr);
|
|
49
|
+
u.searchParams.set(key, value);
|
|
50
|
+
return u.toString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
exports.listProjects = async (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const items = await SuperDemoProject.find({}).sort({ createdAt: -1 }).lean();
|
|
56
|
+
return res.json({ items });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('SuperDemos listProjects error:', error);
|
|
59
|
+
return res.status(500).json({ error: 'Failed to list projects' });
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
exports.createProject = async (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
const name = String(req.body?.name || '').trim();
|
|
66
|
+
const projectIdIn = req.body?.projectId !== undefined ? String(req.body.projectId).trim() : '';
|
|
67
|
+
const isPublic = parseBool(req.body?.isPublic, true);
|
|
68
|
+
|
|
69
|
+
if (!name) return res.status(400).json({ error: 'name is required' });
|
|
70
|
+
|
|
71
|
+
const projectId = projectIdIn || generateProjectId();
|
|
72
|
+
|
|
73
|
+
const doc = await SuperDemoProject.create({
|
|
74
|
+
projectId,
|
|
75
|
+
name,
|
|
76
|
+
isPublic,
|
|
77
|
+
apiKeyHash: null,
|
|
78
|
+
allowedOrigins: Array.isArray(req.body?.allowedOrigins) ? req.body.allowedOrigins : [],
|
|
79
|
+
stylePreset: normalizeStylePreset(req.body?.stylePreset),
|
|
80
|
+
styleOverrides: normalizeStyleOverrides(req.body?.styleOverrides),
|
|
81
|
+
isActive: true,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
let apiKey = null;
|
|
85
|
+
if (!isPublic) {
|
|
86
|
+
apiKey = generateProjectApiKeyPlaintext();
|
|
87
|
+
doc.apiKeyHash = hashKey(apiKey);
|
|
88
|
+
await doc.save();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return res.status(201).json({ item: doc.toObject(), apiKey });
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('SuperDemos createProject error:', error);
|
|
94
|
+
if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
|
|
95
|
+
if (error?.code === 11000) return res.status(409).json({ error: 'Project already exists' });
|
|
96
|
+
return res.status(500).json({ error: 'Failed to create project' });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
exports.updateProject = async (req, res) => {
|
|
101
|
+
try {
|
|
102
|
+
const { projectId } = req.params;
|
|
103
|
+
const doc = await SuperDemoProject.findOne({ projectId: String(projectId) });
|
|
104
|
+
if (!doc) return res.status(404).json({ error: 'Project not found' });
|
|
105
|
+
|
|
106
|
+
if (req.body?.name !== undefined) {
|
|
107
|
+
const name = String(req.body.name || '').trim();
|
|
108
|
+
if (!name) return res.status(400).json({ error: 'name is required' });
|
|
109
|
+
doc.name = name;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (req.body?.isPublic !== undefined) {
|
|
113
|
+
const nextPublic = parseBool(req.body.isPublic, doc.isPublic);
|
|
114
|
+
if (nextPublic !== doc.isPublic) {
|
|
115
|
+
doc.isPublic = nextPublic;
|
|
116
|
+
if (doc.isPublic) {
|
|
117
|
+
doc.apiKeyHash = null;
|
|
118
|
+
} else if (!doc.apiKeyHash) {
|
|
119
|
+
const apiKey = generateProjectApiKeyPlaintext();
|
|
120
|
+
doc.apiKeyHash = hashKey(apiKey);
|
|
121
|
+
await doc.save();
|
|
122
|
+
return res.json({ item: doc.toObject(), apiKey });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (req.body?.allowedOrigins !== undefined) {
|
|
128
|
+
doc.allowedOrigins = Array.isArray(req.body.allowedOrigins) ? req.body.allowedOrigins : [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (req.body?.stylePreset !== undefined) {
|
|
132
|
+
doc.stylePreset = normalizeStylePreset(req.body.stylePreset);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (req.body?.styleOverrides !== undefined) {
|
|
136
|
+
doc.styleOverrides = normalizeStyleOverrides(req.body.styleOverrides);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (req.body?.isActive !== undefined) {
|
|
140
|
+
doc.isActive = Boolean(req.body.isActive);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await doc.save();
|
|
144
|
+
return res.json({ item: doc.toObject(), apiKey: null });
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('SuperDemos updateProject error:', error);
|
|
147
|
+
if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
|
|
148
|
+
return res.status(500).json({ error: 'Failed to update project' });
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
exports.rotateProjectKey = async (req, res) => {
|
|
153
|
+
try {
|
|
154
|
+
const { projectId } = req.params;
|
|
155
|
+
const doc = await SuperDemoProject.findOne({ projectId: String(projectId) });
|
|
156
|
+
if (!doc) return res.status(404).json({ error: 'Project not found' });
|
|
157
|
+
if (doc.isPublic) return res.status(400).json({ error: 'Project is public' });
|
|
158
|
+
|
|
159
|
+
const apiKey = generateProjectApiKeyPlaintext();
|
|
160
|
+
doc.apiKeyHash = hashKey(apiKey);
|
|
161
|
+
await doc.save();
|
|
162
|
+
|
|
163
|
+
return res.json({ item: doc.toObject(), apiKey });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('SuperDemos rotateProjectKey error:', error);
|
|
166
|
+
return res.status(500).json({ error: 'Failed to rotate key' });
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
exports.listProjectDemos = async (req, res) => {
|
|
171
|
+
try {
|
|
172
|
+
const projectId = String(req.params.projectId || '').trim();
|
|
173
|
+
const project = await SuperDemoProject.findOne({ projectId }).lean();
|
|
174
|
+
if (!project) return res.status(404).json({ error: 'Project not found' });
|
|
175
|
+
|
|
176
|
+
const items = await SuperDemo.find({ projectId }).sort({ updatedAt: -1 }).lean();
|
|
177
|
+
return res.json({ items });
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('SuperDemos listProjectDemos error:', error);
|
|
180
|
+
return res.status(500).json({ error: 'Failed to list demos' });
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
exports.createDemo = async (req, res) => {
|
|
185
|
+
try {
|
|
186
|
+
const projectId = String(req.params.projectId || '').trim();
|
|
187
|
+
const project = await SuperDemoProject.findOne({ projectId }).lean();
|
|
188
|
+
if (!project) return res.status(404).json({ error: 'Project not found' });
|
|
189
|
+
|
|
190
|
+
const name = String(req.body?.name || '').trim();
|
|
191
|
+
if (!name) return res.status(400).json({ error: 'name is required' });
|
|
192
|
+
|
|
193
|
+
const demoId = generateDemoId();
|
|
194
|
+
const startUrlPattern = req.body?.startUrlPattern !== undefined ? String(req.body.startUrlPattern || '').trim() || null : null;
|
|
195
|
+
|
|
196
|
+
const doc = await SuperDemo.create({
|
|
197
|
+
demoId,
|
|
198
|
+
projectId,
|
|
199
|
+
name,
|
|
200
|
+
status: 'draft',
|
|
201
|
+
publishedVersion: 0,
|
|
202
|
+
publishedAt: null,
|
|
203
|
+
startUrlPattern,
|
|
204
|
+
isActive: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return res.status(201).json({ item: doc.toObject() });
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('SuperDemos createDemo error:', error);
|
|
210
|
+
if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
|
|
211
|
+
return res.status(500).json({ error: 'Failed to create demo' });
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
exports.getDemo = async (req, res) => {
|
|
216
|
+
try {
|
|
217
|
+
const { demoId } = req.params;
|
|
218
|
+
const item = await SuperDemo.findOne({ demoId: String(demoId) }).lean();
|
|
219
|
+
if (!item) return res.status(404).json({ error: 'Demo not found' });
|
|
220
|
+
return res.json({ item });
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('SuperDemos getDemo error:', error);
|
|
223
|
+
return res.status(500).json({ error: 'Failed to load demo' });
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
exports.updateDemo = async (req, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const { demoId } = req.params;
|
|
230
|
+
const doc = await SuperDemo.findOne({ demoId: String(demoId) });
|
|
231
|
+
if (!doc) return res.status(404).json({ error: 'Demo not found' });
|
|
232
|
+
|
|
233
|
+
if (req.body?.name !== undefined) {
|
|
234
|
+
const name = String(req.body.name || '').trim();
|
|
235
|
+
if (!name) return res.status(400).json({ error: 'name is required' });
|
|
236
|
+
doc.name = name;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (req.body?.startUrlPattern !== undefined) {
|
|
240
|
+
const p = String(req.body.startUrlPattern || '').trim();
|
|
241
|
+
doc.startUrlPattern = p ? p : null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (req.body?.isActive !== undefined) {
|
|
245
|
+
doc.isActive = Boolean(req.body.isActive);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await doc.save();
|
|
249
|
+
return res.json({ item: doc.toObject() });
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error('SuperDemos updateDemo error:', error);
|
|
252
|
+
return res.status(500).json({ error: 'Failed to update demo' });
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
exports.publishDemo = async (req, res) => {
|
|
257
|
+
try {
|
|
258
|
+
const { demoId } = req.params;
|
|
259
|
+
const doc = await SuperDemo.findOne({ demoId: String(demoId) });
|
|
260
|
+
if (!doc) return res.status(404).json({ error: 'Demo not found' });
|
|
261
|
+
|
|
262
|
+
doc.status = 'published';
|
|
263
|
+
doc.publishedVersion = Number(doc.publishedVersion || 0) + 1;
|
|
264
|
+
doc.publishedAt = new Date();
|
|
265
|
+
await doc.save();
|
|
266
|
+
|
|
267
|
+
return res.json({ item: doc.toObject() });
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error('SuperDemos publishDemo error:', error);
|
|
270
|
+
return res.status(500).json({ error: 'Failed to publish demo' });
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
exports.listSteps = async (req, res) => {
|
|
275
|
+
try {
|
|
276
|
+
const { demoId } = req.params;
|
|
277
|
+
const demo = await SuperDemo.findOne({ demoId: String(demoId) }).lean();
|
|
278
|
+
if (!demo) return res.status(404).json({ error: 'Demo not found' });
|
|
279
|
+
|
|
280
|
+
const items = await SuperDemoStep.find({ demoId: demo.demoId }).sort({ order: 1 }).lean();
|
|
281
|
+
return res.json({ items });
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error('SuperDemos listSteps error:', error);
|
|
284
|
+
return res.status(500).json({ error: 'Failed to list steps' });
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
exports.replaceSteps = async (req, res) => {
|
|
289
|
+
try {
|
|
290
|
+
const { demoId } = req.params;
|
|
291
|
+
const demo = await SuperDemo.findOne({ demoId: String(demoId) }).lean();
|
|
292
|
+
if (!demo) return res.status(404).json({ error: 'Demo not found' });
|
|
293
|
+
|
|
294
|
+
const steps = Array.isArray(req.body?.steps) ? req.body.steps : null;
|
|
295
|
+
if (!steps) return res.status(400).json({ error: 'steps array is required' });
|
|
296
|
+
|
|
297
|
+
const docs = steps.map((s, idx) => {
|
|
298
|
+
const selector = String(s?.selector || '').trim();
|
|
299
|
+
const message = String(s?.message || '').trim();
|
|
300
|
+
if (!selector) throw new Error(`steps[${idx}].selector is required`);
|
|
301
|
+
if (!message) throw new Error(`steps[${idx}].message is required`);
|
|
302
|
+
|
|
303
|
+
const placement = String(s?.placement || 'auto').trim().toLowerCase();
|
|
304
|
+
const allowedPlacements = new Set(['top', 'bottom', 'left', 'right', 'auto']);
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
demoId: demo.demoId,
|
|
308
|
+
order: idx,
|
|
309
|
+
selector,
|
|
310
|
+
selectorHints: s?.selectorHints !== undefined ? s.selectorHints : null,
|
|
311
|
+
message,
|
|
312
|
+
placement: allowedPlacements.has(placement) ? placement : 'auto',
|
|
313
|
+
waitFor: s?.waitFor !== undefined ? s.waitFor : null,
|
|
314
|
+
advance: s?.advance !== undefined ? s.advance : { type: 'manualNext' },
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
await SuperDemoStep.deleteMany({ demoId: demo.demoId });
|
|
319
|
+
if (docs.length > 0) await SuperDemoStep.insertMany(docs);
|
|
320
|
+
|
|
321
|
+
const items = await SuperDemoStep.find({ demoId: demo.demoId }).sort({ order: 1 }).lean();
|
|
322
|
+
return res.json({ items });
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error('SuperDemos replaceSteps error:', error);
|
|
325
|
+
const msg = String(error?.message || 'Failed to update steps');
|
|
326
|
+
if (msg.startsWith('steps[')) return res.status(400).json({ error: msg });
|
|
327
|
+
return res.status(500).json({ error: 'Failed to update steps' });
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
exports.createAuthoringSession = async (req, res) => {
|
|
332
|
+
try {
|
|
333
|
+
const demoId = String(req.body?.demoId || '').trim();
|
|
334
|
+
const targetUrl = String(req.body?.targetUrl || '').trim();
|
|
335
|
+
|
|
336
|
+
if (!demoId) return res.status(400).json({ error: 'demoId is required' });
|
|
337
|
+
if (!targetUrl) return res.status(400).json({ error: 'targetUrl is required' });
|
|
338
|
+
|
|
339
|
+
let demo;
|
|
340
|
+
try {
|
|
341
|
+
demo = await SuperDemo.findOne({ demoId }).lean();
|
|
342
|
+
} catch (e) {
|
|
343
|
+
return res.status(500).json({ error: 'Failed to load demo' });
|
|
344
|
+
}
|
|
345
|
+
if (!demo) return res.status(404).json({ error: 'Demo not found' });
|
|
346
|
+
|
|
347
|
+
const created = authoringSessions.createSession({ projectId: demo.projectId, demoId: demo.demoId });
|
|
348
|
+
|
|
349
|
+
let connectUrl;
|
|
350
|
+
try {
|
|
351
|
+
connectUrl = targetUrl;
|
|
352
|
+
connectUrl = addQueryParam(connectUrl, 'sd_author', '1');
|
|
353
|
+
connectUrl = addQueryParam(connectUrl, 'sd_session', created.sessionId);
|
|
354
|
+
connectUrl = addQueryParam(connectUrl, 'sd_token', created.token);
|
|
355
|
+
connectUrl = addQueryParam(connectUrl, 'sd_demoId', demo.demoId);
|
|
356
|
+
} catch {
|
|
357
|
+
return res.status(400).json({ error: 'Invalid targetUrl' });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return res.status(201).json({
|
|
361
|
+
sessionId: created.sessionId,
|
|
362
|
+
token: created.token,
|
|
363
|
+
expiresAtMs: created.expiresAtMs,
|
|
364
|
+
connectUrl,
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('SuperDemos createAuthoringSession error:', error);
|
|
368
|
+
return res.status(500).json({ error: 'Failed to create authoring session' });
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
exports.deleteAuthoringSession = async (req, res) => {
|
|
373
|
+
try {
|
|
374
|
+
const sessionId = String(req.params.sessionId || '').trim();
|
|
375
|
+
if (!sessionId) return res.status(400).json({ error: 'sessionId is required' });
|
|
376
|
+
const ok = authoringSessions.destroySession(sessionId);
|
|
377
|
+
return res.json({ success: ok });
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error('SuperDemos deleteAuthoringSession error:', error);
|
|
380
|
+
return res.status(500).json({ error: 'Failed to delete authoring session' });
|
|
381
|
+
}
|
|
382
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const SuperDemoProject = require('../models/SuperDemoProject');
|
|
2
|
+
const SuperDemo = require('../models/SuperDemo');
|
|
3
|
+
const SuperDemoStep = require('../models/SuperDemoStep');
|
|
4
|
+
|
|
5
|
+
const { verifyKey } = require('../services/uiComponentsCrypto.service');
|
|
6
|
+
|
|
7
|
+
function extractProjectKey(req) {
|
|
8
|
+
const headerToken = req.headers['x-project-key'] || req.headers['x-api-key'];
|
|
9
|
+
if (headerToken) return String(headerToken).trim();
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeUrl(v) {
|
|
14
|
+
return String(v || '').trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function loadAndAuthorizeProject(req, res, projectId) {
|
|
18
|
+
const project = await SuperDemoProject.findOne({ projectId: String(projectId), isActive: true }).lean();
|
|
19
|
+
if (!project) {
|
|
20
|
+
res.status(404).json({ error: 'Project not found' });
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!project.isPublic) {
|
|
25
|
+
const key = extractProjectKey(req);
|
|
26
|
+
const ok = project.apiKeyHash && verifyKey(key, project.apiKeyHash);
|
|
27
|
+
if (!ok) {
|
|
28
|
+
res.status(401).json({ error: 'Invalid project key' });
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return project;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exports.listPublishedDemos = async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
const { projectId } = req.params;
|
|
39
|
+
const project = await loadAndAuthorizeProject(req, res, projectId);
|
|
40
|
+
if (!project) return;
|
|
41
|
+
|
|
42
|
+
const urlFilter = normalizeUrl(req.query?.url);
|
|
43
|
+
|
|
44
|
+
const demos = await SuperDemo.find({
|
|
45
|
+
projectId: project.projectId,
|
|
46
|
+
status: 'published',
|
|
47
|
+
isActive: true,
|
|
48
|
+
})
|
|
49
|
+
.sort({ updatedAt: -1 })
|
|
50
|
+
.lean();
|
|
51
|
+
|
|
52
|
+
const items = demos
|
|
53
|
+
.filter((d) => {
|
|
54
|
+
if (!urlFilter) return true;
|
|
55
|
+
if (!d.startUrlPattern) return true;
|
|
56
|
+
return urlFilter.includes(String(d.startUrlPattern));
|
|
57
|
+
})
|
|
58
|
+
.map((d) => ({
|
|
59
|
+
demoId: d.demoId,
|
|
60
|
+
projectId: d.projectId,
|
|
61
|
+
name: d.name,
|
|
62
|
+
publishedVersion: d.publishedVersion || 0,
|
|
63
|
+
publishedAt: d.publishedAt || null,
|
|
64
|
+
startUrlPattern: d.startUrlPattern || null,
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
return res.json({
|
|
68
|
+
project: {
|
|
69
|
+
projectId: project.projectId,
|
|
70
|
+
name: project.name,
|
|
71
|
+
isPublic: project.isPublic,
|
|
72
|
+
allowedOrigins: project.allowedOrigins || [],
|
|
73
|
+
stylePreset: project.stylePreset || 'default',
|
|
74
|
+
styleOverrides: project.styleOverrides || '',
|
|
75
|
+
},
|
|
76
|
+
demos: items,
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('SuperDemos listPublishedDemos error:', error);
|
|
80
|
+
return res.status(500).json({ error: 'Failed to list demos' });
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
exports.getPublishedDemoDefinition = async (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
const { demoId } = req.params;
|
|
87
|
+
|
|
88
|
+
const demo = await SuperDemo.findOne({ demoId: String(demoId), status: 'published', isActive: true }).lean();
|
|
89
|
+
if (!demo) return res.status(404).json({ error: 'Demo not found' });
|
|
90
|
+
|
|
91
|
+
const project = await loadAndAuthorizeProject(req, res, demo.projectId);
|
|
92
|
+
if (!project) return;
|
|
93
|
+
|
|
94
|
+
const steps = await SuperDemoStep.find({ demoId: demo.demoId }).sort({ order: 1 }).lean();
|
|
95
|
+
|
|
96
|
+
return res.json({
|
|
97
|
+
project: {
|
|
98
|
+
projectId: project.projectId,
|
|
99
|
+
name: project.name,
|
|
100
|
+
isPublic: project.isPublic,
|
|
101
|
+
stylePreset: project.stylePreset || 'default',
|
|
102
|
+
styleOverrides: project.styleOverrides || '',
|
|
103
|
+
},
|
|
104
|
+
demo: {
|
|
105
|
+
demoId: demo.demoId,
|
|
106
|
+
projectId: demo.projectId,
|
|
107
|
+
name: demo.name,
|
|
108
|
+
publishedVersion: demo.publishedVersion || 0,
|
|
109
|
+
publishedAt: demo.publishedAt || null,
|
|
110
|
+
startUrlPattern: demo.startUrlPattern || null,
|
|
111
|
+
},
|
|
112
|
+
steps: steps.map((s) => ({
|
|
113
|
+
order: s.order,
|
|
114
|
+
selector: s.selector,
|
|
115
|
+
selectorHints: s.selectorHints || null,
|
|
116
|
+
message: s.message,
|
|
117
|
+
placement: s.placement,
|
|
118
|
+
waitFor: s.waitFor || null,
|
|
119
|
+
advance: s.advance || null,
|
|
120
|
+
})),
|
|
121
|
+
});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('SuperDemos getPublishedDemoDefinition error:', error);
|
|
124
|
+
return res.status(500).json({ error: 'Failed to load demo definition' });
|
|
125
|
+
}
|
|
126
|
+
};
|