docs-combiner 0.4.0 → 1.0.0
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/dist/agentApi/agentControlClaim.js +26 -0
- package/dist/agentApi/approachMatrixView.js +19 -0
- package/dist/agentApi/capabilitiesMeta.js +78 -0
- package/dist/agentApi/catalogUrlPresets.js +26 -0
- package/dist/agentApi/constants.js +11 -0
- package/dist/agentApi/creativeSelections.js +25 -0
- package/dist/agentApi/escalation.js +221 -0
- package/dist/agentApi/generatedPairsGuard.js +85 -0
- package/dist/agentApi/httpHelpers.js +77 -0
- package/dist/agentApi/imagesReadiness.js +60 -0
- package/dist/agentApi/jobStatus.js +113 -0
- package/dist/agentApi/productImageDrive.js +39 -0
- package/dist/agentApi/regenerateImages.js +125 -0
- package/dist/agentApi/rendererTypes.js +3 -0
- package/dist/agentApi/routes.js +440 -0
- package/dist/agentApi/sessionSnapshot.js +82 -0
- package/dist/agentApi/sessionTypes.js +2 -0
- package/dist/agentApi/spec.js +1045 -0
- package/dist/agentApi/types.js +2 -0
- package/dist/agentApi/validation.js +32 -0
- package/dist/campaignsCsv.js +420 -0
- package/dist/contentPairs.js +62 -0
- package/dist/flexcardBalance.js +88 -0
- package/dist/integrations/driveApi.js +420 -0
- package/dist/integrations/httpTimeout.js +116 -0
- package/dist/integrations/openrouter.js +532 -0
- package/dist/main.js +316 -1214
- package/dist/models.js +17 -0
- package/dist/offerSettings.js +19 -0
- package/dist/preload.js +9 -0
- package/dist/promptOverrides.js +437 -0
- package/dist/prompts.js +1243 -0
- package/dist/renderer.js +19 -19
- package/dist/renderer.js.map +1 -1
- package/dist/server/app.js +378 -0
- package/dist/server/auth.js +74 -0
- package/dist/server/contentJobs.js +367 -0
- package/dist/server/driveCatalog.js +122 -0
- package/dist/server/driveProxy.js +220 -0
- package/dist/server/flexcardMeta.js +29 -0
- package/dist/server/googleAuth.js +84 -0
- package/dist/server/imageAssets.js +87 -0
- package/dist/server/imageJobs.js +776 -0
- package/dist/server/index.js +38 -0
- package/dist/server/jobEvents.js +116 -0
- package/dist/server/jobs.js +358 -0
- package/dist/server/openRouterMeta.js +58 -0
- package/dist/server/spec.js +544 -0
- package/dist/server/storage.js +133 -0
- package/dist/server/telegram.js +53 -0
- package/dist/server/workspaceSnapshot.js +273 -0
- package/package.json +15 -3
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.createServerApp = createServerApp;
|
|
16
|
+
const express_1 = __importDefault(require("express"));
|
|
17
|
+
const auth_1 = require("./auth");
|
|
18
|
+
const contentJobs_1 = require("./contentJobs");
|
|
19
|
+
const driveCatalog_1 = require("./driveCatalog");
|
|
20
|
+
const driveProxy_1 = require("./driveProxy");
|
|
21
|
+
const flexcardMeta_1 = require("./flexcardMeta");
|
|
22
|
+
const googleAuth_1 = require("./googleAuth");
|
|
23
|
+
const imageJobs_1 = require("./imageJobs");
|
|
24
|
+
const jobs_1 = require("./jobs");
|
|
25
|
+
const openRouterMeta_1 = require("./openRouterMeta");
|
|
26
|
+
const spec_1 = require("./spec");
|
|
27
|
+
const storage_1 = require("./storage");
|
|
28
|
+
const telegram_1 = require("./telegram");
|
|
29
|
+
const workspaceSnapshot_1 = require("./workspaceSnapshot");
|
|
30
|
+
function isPlainJsonObject(value) {
|
|
31
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
function stringQueryParam(value) {
|
|
34
|
+
return typeof value === 'string' ? value.trim() || undefined : undefined;
|
|
35
|
+
}
|
|
36
|
+
function extractDriveFolderId(value) {
|
|
37
|
+
if (!value)
|
|
38
|
+
return undefined;
|
|
39
|
+
const foldersMatch = value.match(/\/folders\/([a-zA-Z0-9_-]+)/);
|
|
40
|
+
if (foldersMatch === null || foldersMatch === void 0 ? void 0 : foldersMatch[1])
|
|
41
|
+
return foldersMatch[1];
|
|
42
|
+
const idParamMatch = value.match(/[?&]id=([a-zA-Z0-9_-]+)/);
|
|
43
|
+
if (idParamMatch === null || idParamMatch === void 0 ? void 0 : idParamMatch[1])
|
|
44
|
+
return idParamMatch[1];
|
|
45
|
+
return value.trim();
|
|
46
|
+
}
|
|
47
|
+
function logServer(message, meta) {
|
|
48
|
+
const suffix = meta ? ` ${JSON.stringify(meta)}` : '';
|
|
49
|
+
console.log(`[docs-combiner-server] ${message}${suffix}`);
|
|
50
|
+
}
|
|
51
|
+
function sendProxyResult(res, result) {
|
|
52
|
+
const status = typeof (result === null || result === void 0 ? void 0 : result.status) === 'number' ? result.status : 200;
|
|
53
|
+
const value = result === null || result === void 0 ? void 0 : result.value;
|
|
54
|
+
if (value === null || value === undefined) {
|
|
55
|
+
res.status(status).end();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
res.status(status).json(value);
|
|
59
|
+
}
|
|
60
|
+
function createServerApp(options) {
|
|
61
|
+
var _a, _b, _c;
|
|
62
|
+
const store = (_a = options.store) !== null && _a !== void 0 ? _a : new storage_1.ServerStore();
|
|
63
|
+
const app = (0, express_1.default)();
|
|
64
|
+
const startedAt = (_b = options.startedAt) !== null && _b !== void 0 ? _b : new Date();
|
|
65
|
+
const baseUrl = options.baseUrl || 'https://combiner.site';
|
|
66
|
+
const jobRegistry = (_c = options.jobRegistry) !== null && _c !== void 0 ? _c : jobs_1.defaultServerJobRegistry;
|
|
67
|
+
app.disable('x-powered-by');
|
|
68
|
+
app.use(express_1.default.json({ limit: '25mb' }));
|
|
69
|
+
app.use('/api', (req, res, next) => {
|
|
70
|
+
const started = Date.now();
|
|
71
|
+
res.on('finish', () => {
|
|
72
|
+
logServer('http', {
|
|
73
|
+
method: req.method,
|
|
74
|
+
path: req.originalUrl.split('?')[0],
|
|
75
|
+
status: res.statusCode,
|
|
76
|
+
durationMs: Date.now() - started,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
next();
|
|
80
|
+
});
|
|
81
|
+
app.get('/api/health', (_req, res) => {
|
|
82
|
+
res.json({
|
|
83
|
+
ok: true,
|
|
84
|
+
service: 'docs-combiner-server',
|
|
85
|
+
version: options.version,
|
|
86
|
+
startedAt: startedAt.toISOString(),
|
|
87
|
+
uptimeSeconds: Math.floor((Date.now() - startedAt.getTime()) / 1000),
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
const requireAuth = (0, auth_1.createBearerAuthMiddleware)(store);
|
|
91
|
+
app.use('/api', (req, res, next) => {
|
|
92
|
+
if (req.path === '/health')
|
|
93
|
+
return next();
|
|
94
|
+
return requireAuth(req, res, next);
|
|
95
|
+
});
|
|
96
|
+
app.get('/api/spec', (_req, res) => {
|
|
97
|
+
res.json((0, spec_1.getServerApiSpec)(baseUrl, options.version));
|
|
98
|
+
});
|
|
99
|
+
app.get('/api/secrets/status', (_req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
try {
|
|
101
|
+
const config = yield store.readConfig();
|
|
102
|
+
res.json({ ok: true, secrets: (0, storage_1.buildSecretsStatus)(config) });
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
next(err);
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
108
|
+
app.put('/api/secrets', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
try {
|
|
110
|
+
if (!isPlainJsonObject(req.body)) {
|
|
111
|
+
res.status(400).json({ ok: false, error: 'JSON object payload is required.' });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const result = yield store.patchSecrets(req.body);
|
|
115
|
+
const config = yield store.readConfig();
|
|
116
|
+
res.json({
|
|
117
|
+
ok: true,
|
|
118
|
+
updated: result.updated,
|
|
119
|
+
ignored: result.ignored,
|
|
120
|
+
secrets: (0, storage_1.buildSecretsStatus)(config),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
next(err);
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
app.get('/api/settings', (_req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
try {
|
|
129
|
+
res.json({ ok: true, settings: yield store.readSettings() });
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
next(err);
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
135
|
+
app.put('/api/settings', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
try {
|
|
137
|
+
if (!isPlainJsonObject(req.body)) {
|
|
138
|
+
res.status(400).json({ ok: false, error: 'JSON object payload is required.' });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const settings = Object.assign(Object.assign({}, req.body), { updatedAt: new Date().toISOString() });
|
|
142
|
+
yield store.writeSettings(settings);
|
|
143
|
+
res.json({ ok: true, settings });
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
next(err);
|
|
147
|
+
}
|
|
148
|
+
}));
|
|
149
|
+
app.get('/api/catalog', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
var _a, _b;
|
|
151
|
+
try {
|
|
152
|
+
const settings = yield store.readSettings();
|
|
153
|
+
const offersRootFolderId = extractDriveFolderId(stringQueryParam(req.query.offersRootFolderId) || stringQueryParam(req.query.offersRootUrl)) || ((_a = settings.driveRoots) === null || _a === void 0 ? void 0 : _a.offersRootFolderId);
|
|
154
|
+
const campaignsRootFolderId = extractDriveFolderId(stringQueryParam(req.query.campaignsRootFolderId) || stringQueryParam(req.query.campaignsRootUrl)) || ((_b = settings.driveRoots) === null || _b === void 0 ? void 0 : _b.campaignsRootFolderId);
|
|
155
|
+
res.json(yield (0, driveCatalog_1.getDriveCatalog)(store, { offersRootFolderId, campaignsRootFolderId }));
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
next(err);
|
|
159
|
+
}
|
|
160
|
+
}));
|
|
161
|
+
app.get('/api/workspaces/:workspaceId', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
162
|
+
try {
|
|
163
|
+
res.json(yield (0, workspaceSnapshot_1.getWorkspaceSnapshot)(store, String(req.params.workspaceId || '')));
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
next(err);
|
|
167
|
+
}
|
|
168
|
+
}));
|
|
169
|
+
app.post('/api/google/refresh', (_req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
170
|
+
try {
|
|
171
|
+
const accessToken = yield (0, googleAuth_1.refreshServerGoogleAccessToken)(store);
|
|
172
|
+
if (!accessToken) {
|
|
173
|
+
res.status(400).json({
|
|
174
|
+
ok: false,
|
|
175
|
+
error: 'Google OAuth secrets are missing or refresh failed on the server.',
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const config = yield store.readConfig();
|
|
180
|
+
res.json({
|
|
181
|
+
ok: true,
|
|
182
|
+
secrets: (0, storage_1.buildSecretsStatus)(config),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
next(err);
|
|
187
|
+
}
|
|
188
|
+
}));
|
|
189
|
+
app.post('/api/drive/files/list', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
190
|
+
try {
|
|
191
|
+
sendProxyResult(res, yield (0, driveProxy_1.proxyDriveList)(store, req.body));
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
next(err);
|
|
195
|
+
}
|
|
196
|
+
}));
|
|
197
|
+
app.get('/api/drive/files/:fileId/metadata', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
198
|
+
try {
|
|
199
|
+
sendProxyResult(res, yield (0, driveProxy_1.proxyDriveMetadata)(store, String(req.params.fileId || ''), req.query));
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
next(err);
|
|
203
|
+
}
|
|
204
|
+
}));
|
|
205
|
+
app.get('/api/drive/files/:fileId/download', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
206
|
+
try {
|
|
207
|
+
sendProxyResult(res, yield (0, driveProxy_1.proxyDriveDownload)(store, String(req.params.fileId || '')));
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
next(err);
|
|
211
|
+
}
|
|
212
|
+
}));
|
|
213
|
+
app.post('/api/drive/folders', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
214
|
+
try {
|
|
215
|
+
sendProxyResult(res, yield (0, driveProxy_1.proxyDriveCreateFolder)(store, req.body));
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
next(err);
|
|
219
|
+
}
|
|
220
|
+
}));
|
|
221
|
+
app.post('/api/drive/files/multipart', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
222
|
+
try {
|
|
223
|
+
sendProxyResult(res, yield (0, driveProxy_1.proxyDriveMultipartUpload)(store, req.body));
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
next(err);
|
|
227
|
+
}
|
|
228
|
+
}));
|
|
229
|
+
app.patch('/api/drive/files/:fileId/media', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
230
|
+
try {
|
|
231
|
+
sendProxyResult(res, yield (0, driveProxy_1.proxyDriveMediaUpdate)(store, String(req.params.fileId || ''), req.body));
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
next(err);
|
|
235
|
+
}
|
|
236
|
+
}));
|
|
237
|
+
app.delete('/api/drive/files/:fileId', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
238
|
+
try {
|
|
239
|
+
sendProxyResult(res, yield (0, driveProxy_1.proxyDriveDelete)(store, String(req.params.fileId || ''), req.query));
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
next(err);
|
|
243
|
+
}
|
|
244
|
+
}));
|
|
245
|
+
app.post('/api/drive/files/:fileId/permissions', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
246
|
+
try {
|
|
247
|
+
sendProxyResult(res, yield (0, driveProxy_1.proxyDrivePermission)(store, String(req.params.fileId || ''), req.body));
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
next(err);
|
|
251
|
+
}
|
|
252
|
+
}));
|
|
253
|
+
app.get('/api/openrouter/models', (_req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
254
|
+
try {
|
|
255
|
+
res.json(yield (0, openRouterMeta_1.getOpenRouterModels)(store));
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
next(err);
|
|
259
|
+
}
|
|
260
|
+
}));
|
|
261
|
+
app.get('/api/openrouter/balance', (_req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
262
|
+
try {
|
|
263
|
+
res.json(yield (0, openRouterMeta_1.getOpenRouterBalance)(store));
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
next(err);
|
|
267
|
+
}
|
|
268
|
+
}));
|
|
269
|
+
app.get('/api/flexcard/balance', (_req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
270
|
+
try {
|
|
271
|
+
res.json(yield (0, flexcardMeta_1.getFlexCardBalance)(store));
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
next(err);
|
|
275
|
+
}
|
|
276
|
+
}));
|
|
277
|
+
app.post('/api/telegram/send', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
278
|
+
try {
|
|
279
|
+
if (!isPlainJsonObject(req.body)) {
|
|
280
|
+
res.status(400).json({ ok: false, error: 'JSON object payload is required.' });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
res.json(yield (0, telegram_1.sendServerTelegramMessage)(store, typeof req.body.text === 'string' ? req.body.text : ''));
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
next(err);
|
|
287
|
+
}
|
|
288
|
+
}));
|
|
289
|
+
app.post('/api/jobs/images', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
290
|
+
try {
|
|
291
|
+
if (!isPlainJsonObject(req.body)) {
|
|
292
|
+
res.status(400).json({ ok: false, error: 'JSON object payload is required.' });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const slotCount = Array.isArray(req.body.slots) ? req.body.slots.length : req.body.count;
|
|
296
|
+
logServer('image-job create request', {
|
|
297
|
+
workspaceId: typeof req.body.workspaceId === 'string' ? req.body.workspaceId : undefined,
|
|
298
|
+
slotCount,
|
|
299
|
+
});
|
|
300
|
+
res.status(202).json({
|
|
301
|
+
ok: true,
|
|
302
|
+
job: (0, imageJobs_1.createServerImageJob)(req.body, jobRegistry, options.startImageJobs === false ? undefined : store),
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
next(err);
|
|
307
|
+
}
|
|
308
|
+
}));
|
|
309
|
+
app.post('/api/jobs/content', (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
310
|
+
try {
|
|
311
|
+
if (!isPlainJsonObject(req.body)) {
|
|
312
|
+
res.status(400).json({ ok: false, error: 'JSON object payload is required.' });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const marketCount = Array.isArray(req.body.markets) ? req.body.markets.length : 0;
|
|
316
|
+
logServer('content-job create request', {
|
|
317
|
+
workspaceId: typeof req.body.workspaceId === 'string' ? req.body.workspaceId : undefined,
|
|
318
|
+
marketCount,
|
|
319
|
+
});
|
|
320
|
+
res.status(202).json({
|
|
321
|
+
ok: true,
|
|
322
|
+
job: (0, contentJobs_1.createServerContentJob)(req.body, jobRegistry, options.startContentJobs === false ? undefined : store),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
next(err);
|
|
327
|
+
}
|
|
328
|
+
}));
|
|
329
|
+
app.post('/api/jobs/status', (req, res) => {
|
|
330
|
+
if (!isPlainJsonObject(req.body)) {
|
|
331
|
+
res.status(400).json({ ok: false, error: 'JSON object payload is required.' });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const jobIds = Array.isArray(req.body.jobIds)
|
|
335
|
+
? req.body.jobIds.map(value => String(value || '').trim()).filter(Boolean)
|
|
336
|
+
: [];
|
|
337
|
+
if (jobIds.length === 0) {
|
|
338
|
+
res.status(400).json({ ok: false, error: 'jobIds array is required.' });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const uniqueJobIds = [...new Set(jobIds)].slice(0, 100);
|
|
342
|
+
const jobs = uniqueJobIds.map(jobId => jobRegistry.getJob(jobId)).filter(Boolean);
|
|
343
|
+
const foundIds = new Set(jobs.map(job => job.id));
|
|
344
|
+
res.json({
|
|
345
|
+
ok: true,
|
|
346
|
+
jobs,
|
|
347
|
+
missing: uniqueJobIds.filter(jobId => !foundIds.has(jobId)),
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
app.get('/api/jobs/:jobId', (req, res) => {
|
|
351
|
+
const job = jobRegistry.getJob(String(req.params.jobId || '').trim());
|
|
352
|
+
if (!job) {
|
|
353
|
+
res.status(404).json({ ok: false, error: 'Job not found.' });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
res.json({ ok: true, job });
|
|
357
|
+
});
|
|
358
|
+
app.delete('/api/jobs/:jobId', (req, res) => {
|
|
359
|
+
const job = jobRegistry.cancelJob(String(req.params.jobId || '').trim());
|
|
360
|
+
if (!job) {
|
|
361
|
+
res.status(404).json({ ok: false, error: 'Job not found.' });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
res.json({ ok: true, job });
|
|
365
|
+
});
|
|
366
|
+
app.use('/api', (_req, res) => {
|
|
367
|
+
res.status(404).json({ ok: false, error: 'Not found' });
|
|
368
|
+
});
|
|
369
|
+
app.use((err, _req, res, _next) => {
|
|
370
|
+
// Do not include stack traces: the server will run on a public VPS.
|
|
371
|
+
const status = Number.isInteger(err === null || err === void 0 ? void 0 : err.status) && err.status >= 400 && err.status < 600
|
|
372
|
+
? err.status
|
|
373
|
+
: 500;
|
|
374
|
+
logServer('error', { status, message: (err === null || err === void 0 ? void 0 : err.message) || 'Internal server error' });
|
|
375
|
+
res.status(status).json({ ok: false, error: (err === null || err === void 0 ? void 0 : err.message) || 'Internal server error' });
|
|
376
|
+
});
|
|
377
|
+
return app;
|
|
378
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.extractBearerToken = extractBearerToken;
|
|
13
|
+
exports.timingSafeStringEqual = timingSafeStringEqual;
|
|
14
|
+
exports.resolveApiBearerToken = resolveApiBearerToken;
|
|
15
|
+
exports.verifyBearerToken = verifyBearerToken;
|
|
16
|
+
exports.createBearerAuthMiddleware = createBearerAuthMiddleware;
|
|
17
|
+
function extractBearerToken(headerValue) {
|
|
18
|
+
var _a;
|
|
19
|
+
if (typeof headerValue !== 'string')
|
|
20
|
+
return null;
|
|
21
|
+
const match = headerValue.match(/^Bearer\s+(.+)$/i);
|
|
22
|
+
return ((_a = match === null || match === void 0 ? void 0 : match[1]) === null || _a === void 0 ? void 0 : _a.trim()) || null;
|
|
23
|
+
}
|
|
24
|
+
function timingSafeStringEqual(a, b) {
|
|
25
|
+
if (a.length !== b.length)
|
|
26
|
+
return false;
|
|
27
|
+
let diff = 0;
|
|
28
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
29
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
30
|
+
}
|
|
31
|
+
return diff === 0;
|
|
32
|
+
}
|
|
33
|
+
function resolveApiBearerToken(store_1) {
|
|
34
|
+
return __awaiter(this, arguments, void 0, function* (store, env = process.env) {
|
|
35
|
+
var _a, _b;
|
|
36
|
+
const envToken = (_a = env.DOCS_COMBINER_SERVER_TOKEN) === null || _a === void 0 ? void 0 : _a.trim();
|
|
37
|
+
if (envToken)
|
|
38
|
+
return envToken;
|
|
39
|
+
const config = yield store.readConfig();
|
|
40
|
+
return ((_b = config.apiBearerToken) === null || _b === void 0 ? void 0 : _b.trim()) || null;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function verifyBearerToken(store, actualToken) {
|
|
44
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
45
|
+
const expected = yield resolveApiBearerToken(store);
|
|
46
|
+
if (!expected) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
status: 503,
|
|
50
|
+
error: 'Server bearer token is not configured. Set DOCS_COMBINER_SERVER_TOKEN or apiBearerToken in config.json.',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const actual = actualToken === null || actualToken === void 0 ? void 0 : actualToken.trim();
|
|
54
|
+
if (!actual || !timingSafeStringEqual(actual, expected)) {
|
|
55
|
+
return { ok: false, status: 401, error: 'Unauthorized' };
|
|
56
|
+
}
|
|
57
|
+
return { ok: true };
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function createBearerAuthMiddleware(store) {
|
|
61
|
+
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
try {
|
|
63
|
+
const result = yield verifyBearerToken(store, extractBearerToken(req.header('authorization')));
|
|
64
|
+
if (!result.ok) {
|
|
65
|
+
res.status(result.status).json({ ok: false, error: result.error });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
next();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
next(err);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|