@stemy/backend 5.2.1 → 6.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/commands/clear-command.d.ts +6 -6
- package/commands/fixtures-command.d.ts +9 -9
- package/commands/index.d.ts +2 -2
- package/common-types.d.ts +325 -326
- package/esm2020/services/assets.mjs +2 -2
- package/esm2022/commands/clear-command.mjs +15 -0
- package/esm2022/commands/fixtures-command.mjs +26 -0
- package/esm2022/commands/index.mjs +7 -0
- package/esm2022/common-types.mjs +26 -0
- package/esm2022/fixtures/index.mjs +5 -0
- package/esm2022/fixtures/ttl.fixture.mjs +23 -0
- package/esm2022/public_api.mjs +378 -0
- package/esm2022/requests/asset-image-params.mjs +60 -0
- package/esm2022/rest-controllers/assets.controller.mjs +177 -0
- package/esm2022/rest-controllers/auth.controller.mjs +57 -0
- package/esm2022/rest-controllers/gallery.controller.mjs +27 -0
- package/esm2022/rest-controllers/progresses.controller.mjs +38 -0
- package/esm2022/rest-controllers/terminal-styles.mjs +67 -0
- package/esm2022/rest-controllers/terminal.controller.mjs +132 -0
- package/esm2022/rest-middlewares/container.middleware.mjs +22 -0
- package/esm2022/rest-middlewares/error-handler.middleware.mjs +79 -0
- package/esm2022/rest-middlewares/language.middleware.mjs +21 -0
- package/esm2022/rest-middlewares/request-ended.middleware.mjs +26 -0
- package/esm2022/rest-middlewares/request-started.middleware.mjs +25 -0
- package/esm2022/services/asset-processor.mjs +90 -0
- package/esm2022/services/asset-resolver.mjs +36 -0
- package/esm2022/services/assets.mjs +154 -0
- package/esm2022/services/backend-provider.mjs +83 -0
- package/esm2022/services/cache-processor.mjs +16 -0
- package/esm2022/services/cache.mjs +70 -0
- package/esm2022/services/configuration.mjs +68 -0
- package/esm2022/services/drivers/asset-grid.driver.mjs +27 -0
- package/esm2022/services/drivers/asset-local.driver.mjs +41 -0
- package/esm2022/services/endpoint-provider.mjs +13 -0
- package/esm2022/services/entities/asset.mjs +61 -0
- package/esm2022/services/entities/base-entity.mjs +32 -0
- package/esm2022/services/entities/lazy-asset.mjs +87 -0
- package/esm2022/services/entities/progress.mjs +182 -0
- package/esm2022/services/entities/temp-asset.mjs +53 -0
- package/esm2022/services/fixtures.mjs +38 -0
- package/esm2022/services/gallery-cache.mjs +29 -0
- package/esm2022/services/gallery-image.mjs +42 -0
- package/esm2022/services/gallery.mjs +124 -0
- package/esm2022/services/id-generator.mjs +49 -0
- package/esm2022/services/job-manager.mjs +198 -0
- package/esm2022/services/lazy-assets.mjs +68 -0
- package/esm2022/services/logger.mjs +26 -0
- package/esm2022/services/mail-sender.mjs +42 -0
- package/esm2022/services/memory-cache.mjs +61 -0
- package/esm2022/services/mongo-connector.mjs +36 -0
- package/esm2022/services/open-api.mjs +124 -0
- package/esm2022/services/progresses.mjs +93 -0
- package/esm2022/services/template-renderer.mjs +71 -0
- package/esm2022/services/terminal-manager.mjs +88 -0
- package/esm2022/services/token-generator.mjs +37 -0
- package/esm2022/services/translation-provider.mjs +39 -0
- package/esm2022/services/translator.mjs +67 -0
- package/esm2022/services/user-manager.mjs +27 -0
- package/esm2022/socket-controllers/progress.controller.mjs +63 -0
- package/esm2022/socket-controllers/terminal.controller.mjs +61 -0
- package/esm2022/socket-controllers/terminal.mjs +89 -0
- package/esm2022/socket-middlewares/compression.middleware.mjs +14 -0
- package/esm2022/static.mjs +23 -0
- package/esm2022/stemy-backend.mjs +5 -0
- package/esm2022/utilities/base-doc.mjs +33 -0
- package/esm2022/utilities/decorators.mjs +51 -0
- package/esm2022/utilities/di-container.mjs +88 -0
- package/esm2022/utilities/empty-job.mjs +13 -0
- package/esm2022/utilities/lazy-asset-generator.mjs +38 -0
- package/esm2022/utilities/mongoose.mjs +216 -0
- package/esm2022/utilities/tree.mjs +119 -0
- package/esm2022/utils.mjs +726 -0
- package/esm2022/validators.mjs +46 -0
- package/fesm2015/stemy-backend.mjs +1 -1
- package/fesm2015/stemy-backend.mjs.map +1 -1
- package/fesm2020/stemy-backend.mjs +1 -1
- package/fesm2020/stemy-backend.mjs.map +1 -1
- package/fesm2022/stemy-backend.mjs +4690 -0
- package/fesm2022/stemy-backend.mjs.map +1 -0
- package/fixtures/index.d.ts +2 -2
- package/fixtures/ttl.fixture.d.ts +7 -7
- package/index.d.ts +5 -5
- package/package.json +43 -49
- package/public_api.d.ts +44 -44
- package/requests/asset-image-params.d.ts +12 -12
- package/rest-controllers/assets.controller.d.ts +25 -27
- package/rest-controllers/auth.controller.d.ts +14 -14
- package/rest-controllers/gallery.controller.d.ts +6 -7
- package/rest-controllers/progresses.controller.d.ts +9 -9
- package/rest-controllers/terminal-styles.d.ts +2 -2
- package/rest-controllers/terminal.controller.d.ts +10 -10
- package/rest-middlewares/container.middleware.d.ts +8 -8
- package/rest-middlewares/error-handler.middleware.d.ts +13 -13
- package/rest-middlewares/language.middleware.d.ts +8 -8
- package/rest-middlewares/request-ended.middleware.d.ts +8 -8
- package/rest-middlewares/request-started.middleware.d.ts +8 -8
- package/services/asset-processor.d.ts +10 -11
- package/services/asset-resolver.d.ts +10 -10
- package/services/assets.d.ts +24 -26
- package/services/backend-provider.d.ts +19 -20
- package/services/cache-processor.d.ts +4 -4
- package/services/cache.d.ts +23 -23
- package/services/configuration.d.ts +15 -15
- package/services/drivers/asset-grid.driver.d.ts +11 -11
- package/services/drivers/asset-local.driver.d.ts +10 -11
- package/services/endpoint-provider.d.ts +4 -4
- package/services/entities/asset.d.ts +19 -21
- package/services/entities/base-entity.d.ts +13 -13
- package/services/entities/lazy-asset.d.ts +25 -25
- package/services/entities/progress.d.ts +49 -49
- package/services/entities/temp-asset.d.ts +20 -22
- package/services/fixtures.d.ts +8 -8
- package/services/gallery-cache.d.ts +8 -9
- package/services/gallery-image.d.ts +10 -11
- package/services/gallery.d.ts +13 -13
- package/services/id-generator.d.ts +11 -11
- package/services/job-manager.d.ts +35 -35
- package/services/lazy-assets.d.ts +22 -22
- package/services/logger.d.ts +8 -8
- package/services/mail-sender.d.ts +20 -20
- package/services/memory-cache.d.ts +10 -10
- package/services/mongo-connector.d.ts +12 -12
- package/services/open-api.d.ts +13 -13
- package/services/progresses.d.ts +20 -20
- package/services/template-renderer.d.ts +14 -14
- package/services/terminal-manager.d.ts +15 -15
- package/services/token-generator.d.ts +6 -6
- package/services/translation-provider.d.ts +9 -9
- package/services/translator.d.ts +15 -15
- package/services/user-manager.d.ts +6 -6
- package/socket-controllers/progress.controller.d.ts +10 -10
- package/socket-controllers/terminal.controller.d.ts +13 -13
- package/socket-controllers/terminal.d.ts +19 -20
- package/socket-middlewares/compression.middleware.d.ts +4 -4
- package/static.d.ts +2 -2
- package/utilities/base-doc.d.ts +40 -40
- package/utilities/decorators.d.ts +10 -10
- package/utilities/di-container.d.ts +43 -43
- package/utilities/empty-job.d.ts +4 -4
- package/utilities/lazy-asset-generator.d.ts +15 -15
- package/utilities/mongoose.d.ts +36 -36
- package/utilities/tree.d.ts +15 -15
- package/utils.d.ts +120 -123
- package/validators.d.ts +7 -7
|
@@ -0,0 +1,4690 @@
|
|
|
1
|
+
import { dirname, basename, join, resolve } from 'path';
|
|
2
|
+
import webToken from 'jsonwebtoken';
|
|
3
|
+
import { injectable, scoped, Lifecycle, singleton, injectAll, inject, isFactoryProvider, container } from 'tsyringe';
|
|
4
|
+
import { HttpError, getMetadataArgsStorage, Authorized, Post, UploadedFile, Body, Get, Param, QueryParam, Res, QueryParams, Controller, UnauthorizedError, CurrentUser, Header, BadRequestError, Middleware, createParamDecorator, useContainer, useExpressServer } from 'routing-controllers';
|
|
5
|
+
import { OnMessage, ConnectedSocket, MessageBody, SocketController, Middleware as Middleware$1, SocketControllers } from 'socket-controllers';
|
|
6
|
+
import { __decorate, __param, __metadata } from 'tslib';
|
|
7
|
+
import fontKit_ from 'fontkit';
|
|
8
|
+
import sharp_ from 'sharp';
|
|
9
|
+
import { ObjectId as ObjectId$1 } from 'bson';
|
|
10
|
+
import axios from 'axios';
|
|
11
|
+
import { mkdir, unlink, readFile as readFile$1, writeFile as writeFile$1, lstat, readdir, access, constants, lstatSync, readFileSync, existsSync, mkdirSync, createWriteStream, createReadStream } from 'fs';
|
|
12
|
+
import { gzip, gunzip } from 'zlib';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { exec } from 'child_process';
|
|
15
|
+
import { createHash } from 'crypto';
|
|
16
|
+
import { Subscription, Observable, Subject, from, BehaviorSubject } from 'rxjs';
|
|
17
|
+
import { ObjectId } from 'mongodb';
|
|
18
|
+
import mongoose, { Types } from 'mongoose';
|
|
19
|
+
import { Readable, PassThrough } from 'stream';
|
|
20
|
+
import { fileTypeFromBuffer as fileTypeFromBuffer$1, fileTypeFromStream as fileTypeFromStream$1 } from 'file-type/core';
|
|
21
|
+
import dotenv from 'dotenv';
|
|
22
|
+
import cron from 'node-cron';
|
|
23
|
+
import { socket } from 'zeromq/v5-compat';
|
|
24
|
+
import { filter as filter$1, map, first, timeout } from 'rxjs/operators';
|
|
25
|
+
import { createServer } from 'http';
|
|
26
|
+
import { Server } from 'socket.io';
|
|
27
|
+
import cors from 'cors';
|
|
28
|
+
import express_ from 'express';
|
|
29
|
+
import bodyParser from 'body-parser';
|
|
30
|
+
import { routingControllersToSpec, OpenAPI, getStatusCode } from 'routing-controllers-openapi';
|
|
31
|
+
import { defaultMetadataStorage } from 'class-transformer/cjs/storage';
|
|
32
|
+
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
|
|
33
|
+
import { ValidatorConstraint, ValidationTypes, Min, Max, IsOptional, IsBoolean } from 'class-validator';
|
|
34
|
+
import { v4 } from 'uuid';
|
|
35
|
+
import { createTransport } from 'nodemailer';
|
|
36
|
+
import * as Handlebars from 'handlebars';
|
|
37
|
+
import { CommandsAddon, AnsiCodes } from '@stemy/terminal-commands-addon';
|
|
38
|
+
import { compare } from 'bcrypt';
|
|
39
|
+
import moment from 'moment';
|
|
40
|
+
import { GridFSBucket } from 'mongodb/lib/gridfs';
|
|
41
|
+
import { writeFile as writeFile$2, rm } from 'fs/promises';
|
|
42
|
+
import { getModelForClass } from '@typegoose/typegoose';
|
|
43
|
+
import { getValue as getValue$1, setValue } from 'mongoose/lib/utils';
|
|
44
|
+
|
|
45
|
+
// --- DI functions ---
|
|
46
|
+
const Type = Function;
|
|
47
|
+
// --- Injection tokens ---
|
|
48
|
+
const FIXTURE = Symbol.for("fixture-token");
|
|
49
|
+
const JOB = Symbol.for("job-token");
|
|
50
|
+
const TERMINAL_COMMAND = Symbol.for("terminal-command-token");
|
|
51
|
+
const EXPRESS = Symbol.for("express-token");
|
|
52
|
+
const HTTP_SERVER = Symbol.for("http-server-token");
|
|
53
|
+
const SOCKET_SERVER = Symbol.for("socket-server-token");
|
|
54
|
+
const SOCKET_CONTROLLERS = Symbol.for("socket-controllers-token");
|
|
55
|
+
const PARAMETER = Symbol.for("parameter-token");
|
|
56
|
+
const DI_CONTAINER = Symbol.for("di-container-token");
|
|
57
|
+
const OPENAPI_VALIDATION = Symbol.for("openapi-validation-token");
|
|
58
|
+
const LOCAL_DIR = Symbol.for('asset-local-dir');
|
|
59
|
+
const ASSET_DRIVER = Symbol.for('assets-driver');
|
|
60
|
+
class Parameter {
|
|
61
|
+
name;
|
|
62
|
+
defaultValue;
|
|
63
|
+
resolver;
|
|
64
|
+
constructor(name, defaultValue, resolver = null) {
|
|
65
|
+
this.name = name;
|
|
66
|
+
this.defaultValue = defaultValue;
|
|
67
|
+
this.resolver = resolver;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
var AssetProcessor_1;
|
|
72
|
+
const sharp$3 = sharp_;
|
|
73
|
+
const fontKit = fontKit_;
|
|
74
|
+
const fontTypes = [
|
|
75
|
+
"application/font-woff", "application/font-woff2", "application/x-font-opentype", "application/x-font-truetype", "application/x-font-datafork",
|
|
76
|
+
"font/woff", "font/woff2", "font/otf", "font/ttf", "font/datafork"
|
|
77
|
+
];
|
|
78
|
+
const imageTypes = ["image/jpeg", "image/jpg", "image/png", "image/svg+xml"];
|
|
79
|
+
const fontProps = [
|
|
80
|
+
"postscriptName", "fullName", "familyName", "subfamilyName",
|
|
81
|
+
"copyright", "version", "unitsPerEm", "ascent", "descent", "lineGap",
|
|
82
|
+
"underlinePosition", "underlineThickness", "italicAngle", "capHeight",
|
|
83
|
+
"xHeight", "numGlyphs", "characterSet", "availableFeatures"
|
|
84
|
+
];
|
|
85
|
+
let AssetProcessor = AssetProcessor_1 = class AssetProcessor {
|
|
86
|
+
static extractFontFormat(font) {
|
|
87
|
+
const name = font.constructor.name;
|
|
88
|
+
const tag = font["directory"].tag;
|
|
89
|
+
switch (name) {
|
|
90
|
+
case "TTFFont":
|
|
91
|
+
return tag === "OTTO" ? "opentype" : "truetype";
|
|
92
|
+
case "WOFF2Font":
|
|
93
|
+
return "woff2";
|
|
94
|
+
case "WOFFFont":
|
|
95
|
+
return "woff";
|
|
96
|
+
case "DFont":
|
|
97
|
+
return "datafork";
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
static isImage(contentType) {
|
|
102
|
+
return imageTypes.indexOf(contentType) >= 0;
|
|
103
|
+
}
|
|
104
|
+
static async copyImageMeta(buffer, metadata, fileType) {
|
|
105
|
+
if (fileType.mime === "image/svg+xml") {
|
|
106
|
+
const match = /<svg([^<>]+)>/gi.exec(buffer.toString("utf8"));
|
|
107
|
+
if (match && match.length > 1) {
|
|
108
|
+
const attrs = match[1].match(/([a-z]+)="([^"]+)"/gi);
|
|
109
|
+
attrs.forEach(attr => {
|
|
110
|
+
if (attr.length < 5)
|
|
111
|
+
return;
|
|
112
|
+
const [name, value] = attr.split("=");
|
|
113
|
+
const val = value.replace(/"/gi, "");
|
|
114
|
+
metadata[name] = isNaN(val) ? val : Number(val);
|
|
115
|
+
});
|
|
116
|
+
if (metadata.viewBox && (isNaN(metadata.width) || isNaN(metadata.height))) {
|
|
117
|
+
const parts = metadata.viewBox.split(" ");
|
|
118
|
+
metadata.width = Number(parts[0]) + Number(parts[2]);
|
|
119
|
+
metadata.height = Number(parts[1]) + Number(parts[3]);
|
|
120
|
+
}
|
|
121
|
+
if (!isNaN(metadata.width) && !isNaN(metadata.height)) {
|
|
122
|
+
metadata.svgSize = { x: metadata.width, y: metadata.height };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return buffer;
|
|
126
|
+
}
|
|
127
|
+
const output = await sharp$3(buffer).rotate().toBuffer({ resolveWithObject: true });
|
|
128
|
+
Object.assign(metadata, output.info);
|
|
129
|
+
return output.data;
|
|
130
|
+
}
|
|
131
|
+
static isFont(contentType) {
|
|
132
|
+
return fontTypes.indexOf(contentType) >= 0;
|
|
133
|
+
}
|
|
134
|
+
static copyFontMeta(buffer, metadata) {
|
|
135
|
+
const font = fontKit.create(buffer);
|
|
136
|
+
metadata.format = AssetProcessor_1.extractFontFormat(font);
|
|
137
|
+
fontProps.forEach(prop => {
|
|
138
|
+
metadata[prop] = font[prop];
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async process(buffer, metadata, fileType) {
|
|
142
|
+
if (AssetProcessor_1.isImage(fileType.mime)) {
|
|
143
|
+
buffer = await AssetProcessor_1.copyImageMeta(buffer, metadata, fileType);
|
|
144
|
+
}
|
|
145
|
+
if (AssetProcessor_1.isFont(fileType.mime)) {
|
|
146
|
+
AssetProcessor_1.copyFontMeta(buffer, metadata);
|
|
147
|
+
}
|
|
148
|
+
return buffer;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
AssetProcessor = AssetProcessor_1 = __decorate([
|
|
152
|
+
injectable(),
|
|
153
|
+
scoped(Lifecycle.ContainerScoped)
|
|
154
|
+
], AssetProcessor);
|
|
155
|
+
|
|
156
|
+
const sharp$2 = sharp_;
|
|
157
|
+
const diContainers = {
|
|
158
|
+
appContainer: null
|
|
159
|
+
};
|
|
160
|
+
function isNullOrUndefined(value) {
|
|
161
|
+
return value == null || typeof value == "undefined";
|
|
162
|
+
}
|
|
163
|
+
function isDefined(value) {
|
|
164
|
+
return !isNullOrUndefined(value);
|
|
165
|
+
}
|
|
166
|
+
function getType(obj) {
|
|
167
|
+
const regex = new RegExp("\\s([a-zA-Z]+)");
|
|
168
|
+
return Object.prototype.toString.call(obj).match(regex)[1].toLowerCase();
|
|
169
|
+
}
|
|
170
|
+
function isObject(value) {
|
|
171
|
+
return getType(value) == "object";
|
|
172
|
+
}
|
|
173
|
+
function isArray(value) {
|
|
174
|
+
return Array.isArray(value);
|
|
175
|
+
}
|
|
176
|
+
function isBuffer(value) {
|
|
177
|
+
return value instanceof Buffer;
|
|
178
|
+
}
|
|
179
|
+
function isBoolean(value) {
|
|
180
|
+
return typeof value === "boolean";
|
|
181
|
+
}
|
|
182
|
+
function isDate(value) {
|
|
183
|
+
return !!value && value[Symbol.toPrimitive] && !isNaN(value) && "undefined" !== typeof value.getDate;
|
|
184
|
+
}
|
|
185
|
+
function isPrimitive(value) {
|
|
186
|
+
const type = typeof value;
|
|
187
|
+
return value == null || (type !== "object" && type !== "function");
|
|
188
|
+
}
|
|
189
|
+
function isString(value) {
|
|
190
|
+
return typeof value === "string";
|
|
191
|
+
}
|
|
192
|
+
function isFunction(value) {
|
|
193
|
+
return typeof value === "function";
|
|
194
|
+
}
|
|
195
|
+
function isConstructor(value) {
|
|
196
|
+
return (value && typeof value === "function" && value.prototype && value.prototype.constructor) === value && value.name !== "Object";
|
|
197
|
+
}
|
|
198
|
+
function isType(value) {
|
|
199
|
+
return isConstructor(value);
|
|
200
|
+
}
|
|
201
|
+
function isInterface(obj, interFaceObject) {
|
|
202
|
+
if (!obj || typeof obj !== "object" || isArray(obj) || !isObject(interFaceObject))
|
|
203
|
+
return false;
|
|
204
|
+
const keys = Object.keys(interFaceObject);
|
|
205
|
+
for (const key of keys) {
|
|
206
|
+
let type = interFaceObject[key] || "";
|
|
207
|
+
if (type.startsWith("*")) {
|
|
208
|
+
type = type.substr(1);
|
|
209
|
+
if (obj.hasOwnProperty(key) && getType(obj[key]) !== type)
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
else if (!obj.hasOwnProperty(key) || getType(obj[key]) !== type) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
function ucFirst(value) {
|
|
219
|
+
if (!value)
|
|
220
|
+
return "";
|
|
221
|
+
return value[0].toUpperCase() + value.substr(1);
|
|
222
|
+
}
|
|
223
|
+
function lcFirst(value) {
|
|
224
|
+
if (!value)
|
|
225
|
+
return "";
|
|
226
|
+
return value[0].toLowerCase() + value.substr(1);
|
|
227
|
+
}
|
|
228
|
+
function isObjectId(id) {
|
|
229
|
+
return typeof id === "string" && id.length == 24 && !isNaN(Number("0x" + id));
|
|
230
|
+
}
|
|
231
|
+
function firstItem(value) {
|
|
232
|
+
return value[0];
|
|
233
|
+
}
|
|
234
|
+
function lastItem(value) {
|
|
235
|
+
return value[value.length - 1];
|
|
236
|
+
}
|
|
237
|
+
function regroup(value, comparator) {
|
|
238
|
+
const result = [];
|
|
239
|
+
if (!isArray(value) || value.length == 0)
|
|
240
|
+
return result;
|
|
241
|
+
value = Array.from(value);
|
|
242
|
+
result.push([value.shift()]);
|
|
243
|
+
value.forEach(item => {
|
|
244
|
+
const group = result.find(g => g.some(a => comparator(a, item)));
|
|
245
|
+
if (group) {
|
|
246
|
+
group.push(item);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
result.push([item]);
|
|
250
|
+
});
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
function uniqueItems(value) {
|
|
254
|
+
return value.filter((v, ix) => value.indexOf(v) === ix);
|
|
255
|
+
}
|
|
256
|
+
function getValue(obj, key, defaultValue, treeFallback = false) {
|
|
257
|
+
key = key || "";
|
|
258
|
+
const keys = key.split(".");
|
|
259
|
+
let curKey = "";
|
|
260
|
+
do {
|
|
261
|
+
curKey += keys.shift();
|
|
262
|
+
if (isDefined(obj) && isDefined(obj[curKey]) && (typeof obj[curKey] === "object" || !keys.length)) {
|
|
263
|
+
obj = obj[curKey];
|
|
264
|
+
curKey = "";
|
|
265
|
+
}
|
|
266
|
+
else if (!keys.length) {
|
|
267
|
+
defaultValue = typeof defaultValue == "undefined" ? key.replace(new RegExp(`${curKey}$`), `{${curKey}}`) : defaultValue;
|
|
268
|
+
obj = treeFallback ? obj || defaultValue : defaultValue;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
curKey += ".";
|
|
272
|
+
}
|
|
273
|
+
} while (keys.length);
|
|
274
|
+
return obj;
|
|
275
|
+
}
|
|
276
|
+
function groupBy(items, cb) {
|
|
277
|
+
return items.reduce((res, item) => {
|
|
278
|
+
const group = cb(item);
|
|
279
|
+
res[group] = res[group] || [];
|
|
280
|
+
res[group].push(item);
|
|
281
|
+
return res;
|
|
282
|
+
}, {});
|
|
283
|
+
}
|
|
284
|
+
function convertValue(value, type) {
|
|
285
|
+
switch (type) {
|
|
286
|
+
case "boolean":
|
|
287
|
+
value = typeof value == "string" ? value.toLowerCase() : value;
|
|
288
|
+
return (value == "no" || value == "false" || value == "0") ? false : !!value;
|
|
289
|
+
case "number":
|
|
290
|
+
const val = parseFloat(value);
|
|
291
|
+
return isNaN(val) ? 0 : val;
|
|
292
|
+
case "array":
|
|
293
|
+
try {
|
|
294
|
+
return JSON.parse(value);
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
return `${value}`.split(", ");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
const cropInterface = {
|
|
303
|
+
x: "number",
|
|
304
|
+
y: "number",
|
|
305
|
+
w: "number",
|
|
306
|
+
h: "number"
|
|
307
|
+
};
|
|
308
|
+
function toCropRegion(cropInfo) {
|
|
309
|
+
let crop = cropInfo;
|
|
310
|
+
if (isString(cropInfo)) {
|
|
311
|
+
try {
|
|
312
|
+
crop = JSON.parse(cropInfo);
|
|
313
|
+
}
|
|
314
|
+
catch (e) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (!isInterface(crop, cropInterface))
|
|
319
|
+
return null;
|
|
320
|
+
return {
|
|
321
|
+
width: Math.round(crop.w),
|
|
322
|
+
height: Math.round(crop.h),
|
|
323
|
+
top: Math.round(crop.y),
|
|
324
|
+
left: Math.round(crop.x)
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
async function toImage(src, params, meta) {
|
|
328
|
+
// Default params and meta
|
|
329
|
+
params = params || {};
|
|
330
|
+
meta = meta || {};
|
|
331
|
+
// Get default crop info
|
|
332
|
+
const crop = toCropRegion(meta.crop);
|
|
333
|
+
// Return the src if there are no params and no default crop exists
|
|
334
|
+
if (meta.extension === "svg" || (Object.keys(params).length == 0 && !crop)) {
|
|
335
|
+
return src;
|
|
336
|
+
}
|
|
337
|
+
// Parse params
|
|
338
|
+
params.rotation = isNaN(params.rotation) ? 0 : Math.round(params.rotation / 90) * 90;
|
|
339
|
+
params.canvasScaleX = isNaN(params.canvasScaleX) ? 1 : Number(params.canvasScaleX);
|
|
340
|
+
params.canvasScaleY = isNaN(params.canvasScaleY) ? 1 : Number(params.canvasScaleY);
|
|
341
|
+
params.scaleX = isNaN(params.scaleX) ? 1 : Number(params.scaleX);
|
|
342
|
+
params.scaleY = isNaN(params.scaleY) ? 1 : Number(params.scaleY);
|
|
343
|
+
params.crop = isBoolean(params.crop) ? params.crop : params.crop == "true";
|
|
344
|
+
let buffer = src instanceof Readable ? await streamToBuffer(src) : src;
|
|
345
|
+
try {
|
|
346
|
+
// Get crop info
|
|
347
|
+
const cropBefore = toCropRegion(params.cropBefore || (params.crop ? meta.cropBefore : null));
|
|
348
|
+
const cropAfter = toCropRegion(params.cropAfter || (params.crop ? meta.cropAfter : null));
|
|
349
|
+
// Get metadata
|
|
350
|
+
let img = sharp$2(buffer);
|
|
351
|
+
let { width, height } = await img.metadata();
|
|
352
|
+
// Crop before resize
|
|
353
|
+
if (cropBefore) {
|
|
354
|
+
width = cropBefore.width;
|
|
355
|
+
height = cropBefore.height;
|
|
356
|
+
img = img.extract(cropBefore);
|
|
357
|
+
}
|
|
358
|
+
else if (crop) {
|
|
359
|
+
width = crop.width;
|
|
360
|
+
height = crop.height;
|
|
361
|
+
img = img.extract(crop);
|
|
362
|
+
}
|
|
363
|
+
// Resize canvas
|
|
364
|
+
const canvasScaleX = meta?.canvasScaleX || 1;
|
|
365
|
+
const canvasScaleY = meta?.canvasScaleY || 1;
|
|
366
|
+
if (params.canvasScaleX !== canvasScaleX || params.canvasScaleY !== canvasScaleY) {
|
|
367
|
+
width = Math.round(width * params.canvasScaleX);
|
|
368
|
+
height = Math.round(height * params.canvasScaleY);
|
|
369
|
+
img = img.resize({ width, height, background: "#00000000", fit: "contain" });
|
|
370
|
+
}
|
|
371
|
+
// Resize image
|
|
372
|
+
if (params.scaleX !== 1 || params.scaleY !== 1) {
|
|
373
|
+
width = Math.round(width * params.scaleX);
|
|
374
|
+
height = Math.round(height * params.scaleY);
|
|
375
|
+
img = img.resize({ width, height, background: "#00000000", fit: "fill" });
|
|
376
|
+
}
|
|
377
|
+
// Crop after resize
|
|
378
|
+
if (cropAfter) {
|
|
379
|
+
img = img.extract(cropAfter);
|
|
380
|
+
}
|
|
381
|
+
// Rotate
|
|
382
|
+
if (params.rotation !== 0) {
|
|
383
|
+
buffer = await img.toBuffer();
|
|
384
|
+
img = sharp$2(buffer).rotate(params.rotation);
|
|
385
|
+
}
|
|
386
|
+
buffer = await img.toBuffer();
|
|
387
|
+
src = src instanceof Readable ? bufferToStream(buffer) : buffer;
|
|
388
|
+
return src;
|
|
389
|
+
}
|
|
390
|
+
catch (e) {
|
|
391
|
+
console.log("Image conversion error", e);
|
|
392
|
+
src = src instanceof Readable ? bufferToStream(buffer) : buffer;
|
|
393
|
+
return src;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function bufferToStream(buffer) {
|
|
397
|
+
const readStream = new PassThrough();
|
|
398
|
+
readStream.end(buffer);
|
|
399
|
+
return readStream;
|
|
400
|
+
}
|
|
401
|
+
function streamToBuffer(stream) {
|
|
402
|
+
return new Promise((resolve, reject) => {
|
|
403
|
+
const concat = [];
|
|
404
|
+
stream.on("data", data => {
|
|
405
|
+
concat.push(data);
|
|
406
|
+
});
|
|
407
|
+
stream.on("error", reject);
|
|
408
|
+
stream.on("end", () => resolve(Buffer.concat(concat)));
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
class ReadableStreamClone extends Readable {
|
|
412
|
+
constructor(readableStream, opts) {
|
|
413
|
+
super(opts);
|
|
414
|
+
readableStream?.on("data", chunk => {
|
|
415
|
+
this.push(chunk);
|
|
416
|
+
});
|
|
417
|
+
readableStream?.on("end", () => {
|
|
418
|
+
this.push(null);
|
|
419
|
+
});
|
|
420
|
+
readableStream?.on("error", err => {
|
|
421
|
+
this.emit("error", err);
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
_read(size) {
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function copyStream(stream, opts) {
|
|
428
|
+
return new ReadableStreamClone(stream, opts);
|
|
429
|
+
}
|
|
430
|
+
function mkdirRecursive(path, mode = null) {
|
|
431
|
+
return new Promise((resolve, reject) => {
|
|
432
|
+
mkdir(path, { mode: mode || 0o777, recursive: true }, err => {
|
|
433
|
+
if (err) {
|
|
434
|
+
reject(err);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
resolve();
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
function deleteFile(path) {
|
|
442
|
+
return new Promise((resolve, reject) => {
|
|
443
|
+
unlink(path, err => {
|
|
444
|
+
if (err) {
|
|
445
|
+
reject(err);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
resolve();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
function readFile(path) {
|
|
453
|
+
return new Promise((res, rej) => {
|
|
454
|
+
readFile$1(path, (err, data) => {
|
|
455
|
+
if (err) {
|
|
456
|
+
rej(err);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
res(data);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
async function readAndDeleteFile(path, timeout = 5000) {
|
|
464
|
+
const data = await readFile(path);
|
|
465
|
+
setTimeout(() => {
|
|
466
|
+
unlink(path, () => {
|
|
467
|
+
});
|
|
468
|
+
}, timeout);
|
|
469
|
+
return data;
|
|
470
|
+
}
|
|
471
|
+
async function writeFile(path, data) {
|
|
472
|
+
await mkdirRecursive(dirname(path));
|
|
473
|
+
return new Promise((res, rej) => {
|
|
474
|
+
writeFile$1(path, data, err => {
|
|
475
|
+
if (err) {
|
|
476
|
+
rej(err);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
res(data);
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
function valueToPromise(value) {
|
|
484
|
+
return value instanceof Promise ? value : Promise.resolve(value);
|
|
485
|
+
}
|
|
486
|
+
function promiseTimeout(timeout = 1000, error = false) {
|
|
487
|
+
return new Promise((resolve, reject) => {
|
|
488
|
+
setTimeout(() => {
|
|
489
|
+
if (error) {
|
|
490
|
+
reject(`Timeout exceeded: ${timeout}ms`);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
resolve(`Timeout: ${timeout}ms`);
|
|
494
|
+
}, timeout);
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function getConstructorName(type) {
|
|
498
|
+
return type.prototype.constructor.name;
|
|
499
|
+
}
|
|
500
|
+
function getFunctionParams(func) {
|
|
501
|
+
// Remove comments of the form /* ... */
|
|
502
|
+
// Removing comments of the form //
|
|
503
|
+
// Remove body of the function { ... }
|
|
504
|
+
// removing "=>" if func is arrow function
|
|
505
|
+
const str = func.toString()
|
|
506
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
507
|
+
.replace(/\/\/(.)*/g, "")
|
|
508
|
+
.replace(/{[\s\S]*}/, "")
|
|
509
|
+
.replace(/=>/g, "")
|
|
510
|
+
.trim();
|
|
511
|
+
// Start parameter names after first "("
|
|
512
|
+
const start = str.indexOf("(") + 1;
|
|
513
|
+
// End parameter names is just before last ")"
|
|
514
|
+
const end = str.length - 1;
|
|
515
|
+
const result = str.substring(start, end).split(", ");
|
|
516
|
+
const params = [];
|
|
517
|
+
result.forEach(element => {
|
|
518
|
+
// Removing any default value
|
|
519
|
+
element = element.replace(/=[\s\S]*/g, "").trim();
|
|
520
|
+
if (element.length > 0)
|
|
521
|
+
params.push(element);
|
|
522
|
+
});
|
|
523
|
+
return params;
|
|
524
|
+
}
|
|
525
|
+
function getFileName(path, withExtension = false) {
|
|
526
|
+
const name = basename(path || "");
|
|
527
|
+
return withExtension ? name : name.split(".").slice(0, -1).join(".");
|
|
528
|
+
}
|
|
529
|
+
function getExtension(path) {
|
|
530
|
+
const name = basename(path || "");
|
|
531
|
+
return name.split(".").pop();
|
|
532
|
+
}
|
|
533
|
+
function createIdString() {
|
|
534
|
+
return new ObjectId().toHexString();
|
|
535
|
+
}
|
|
536
|
+
function idToString(value) {
|
|
537
|
+
if (Array.isArray(value)) {
|
|
538
|
+
return value.map(idToString);
|
|
539
|
+
}
|
|
540
|
+
return value instanceof ObjectId || value instanceof mongoose.Types.ObjectId
|
|
541
|
+
? value.toHexString()
|
|
542
|
+
: (isString(value) ? value : value || null);
|
|
543
|
+
}
|
|
544
|
+
function createTransformer(transform) {
|
|
545
|
+
return (doc, ret, options) => {
|
|
546
|
+
ret.id = idToString(ret.id) || ret.id;
|
|
547
|
+
if (doc._id) {
|
|
548
|
+
ret._id = idToString(doc._id);
|
|
549
|
+
ret.id = ret.id || ret._id;
|
|
550
|
+
}
|
|
551
|
+
delete ret.__v;
|
|
552
|
+
return isFunction(transform) ? transform(doc, ret, options) || ret : ret;
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function broadcast(socketServer, cb) {
|
|
556
|
+
socketServer.sockets.sockets.forEach((client) => {
|
|
557
|
+
cb(client);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
function rand(min, max) {
|
|
561
|
+
return Math.round(random(min, max));
|
|
562
|
+
}
|
|
563
|
+
function random(min, max) {
|
|
564
|
+
return min + Math.random() * (max - min);
|
|
565
|
+
}
|
|
566
|
+
function multiSubscription(...subscriptions) {
|
|
567
|
+
return new Subscription(() => {
|
|
568
|
+
subscriptions.forEach(s => {
|
|
569
|
+
s.unsubscribe();
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
function observableFromFunction(callbackFunc) {
|
|
574
|
+
let subject;
|
|
575
|
+
return new Observable((subscriber) => {
|
|
576
|
+
if (!subject) {
|
|
577
|
+
subject = new Subject();
|
|
578
|
+
try {
|
|
579
|
+
subject = from(callbackFunc());
|
|
580
|
+
}
|
|
581
|
+
catch (err) {
|
|
582
|
+
subject.error(err);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return subject.subscribe(subscriber);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
function padLeft(value, count = 3, padWith = "0") {
|
|
589
|
+
return `${value}`.padStart(count, padWith);
|
|
590
|
+
}
|
|
591
|
+
function padRight(value, count = 3, padWith = "0") {
|
|
592
|
+
return `${value}`.padEnd(count, padWith);
|
|
593
|
+
}
|
|
594
|
+
function camelCaseToDash(str) {
|
|
595
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
596
|
+
}
|
|
597
|
+
function gzipPromised(data, opts) {
|
|
598
|
+
return new Promise((resolve, reject) => {
|
|
599
|
+
gzip(data, opts, (err, result) => {
|
|
600
|
+
if (err) {
|
|
601
|
+
reject(err);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
resolve(result.toString("base64"));
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
function gunzipPromised(data, opts) {
|
|
609
|
+
return new Promise((resolve, reject) => {
|
|
610
|
+
gunzip(Buffer.from(data, "base64"), opts, (err, result) => {
|
|
611
|
+
if (err) {
|
|
612
|
+
reject(err);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
resolve(result.toString("utf8"));
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
function deleteFromBucket(bucket, id) {
|
|
620
|
+
const fileId = id instanceof ObjectId ? id : new ObjectId(id);
|
|
621
|
+
return new Promise(((resolve, reject) => {
|
|
622
|
+
if (!id) {
|
|
623
|
+
// We don't care about empty id
|
|
624
|
+
resolve(null);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
bucket.delete(fileId, error => {
|
|
628
|
+
let err = error;
|
|
629
|
+
if (error) {
|
|
630
|
+
err = error.message || error || "";
|
|
631
|
+
if (!isString(err) || !err.startsWith("FileNotFound")) {
|
|
632
|
+
reject(err);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
resolve(fileId.toHexString());
|
|
637
|
+
});
|
|
638
|
+
}));
|
|
639
|
+
}
|
|
640
|
+
const defaultPredicate = () => true;
|
|
641
|
+
function copyRecursive(target, source, predicate, copies) {
|
|
642
|
+
if (isPrimitive(source) || isDate(source) || isFunction(source))
|
|
643
|
+
return source;
|
|
644
|
+
if (copies.has(source))
|
|
645
|
+
return copies.get(source);
|
|
646
|
+
if (isArray(source)) {
|
|
647
|
+
target = isArray(target) ? Array.from(target) : [];
|
|
648
|
+
source.forEach((item, index) => {
|
|
649
|
+
if (!predicate(item, index, target, source))
|
|
650
|
+
return;
|
|
651
|
+
if (target.length > index)
|
|
652
|
+
target[index] = copyRecursive(target[index], item, predicate, copies);
|
|
653
|
+
else
|
|
654
|
+
target.push(copyRecursive(null, item, predicate, copies));
|
|
655
|
+
});
|
|
656
|
+
copies.set(source, target);
|
|
657
|
+
return target;
|
|
658
|
+
}
|
|
659
|
+
if (isBuffer(source))
|
|
660
|
+
return Buffer.from(source);
|
|
661
|
+
// If object defines __shouldCopy as false, then don't copy it
|
|
662
|
+
if (source.__shouldCopy === false)
|
|
663
|
+
return source;
|
|
664
|
+
// Copy object
|
|
665
|
+
const shouldCopy = isFunction(source.__shouldCopy) ? source.__shouldCopy : () => true;
|
|
666
|
+
if (isConstructor(source.constructor)) {
|
|
667
|
+
if (!target) {
|
|
668
|
+
try {
|
|
669
|
+
target = new source.constructor();
|
|
670
|
+
}
|
|
671
|
+
catch (e) {
|
|
672
|
+
const proto = source.constructor.prototype || source.prototype;
|
|
673
|
+
target = Object.create(proto);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
target = Object.assign({}, target || {});
|
|
679
|
+
}
|
|
680
|
+
// Set to copies to prevent circular references
|
|
681
|
+
copies.set(source, target);
|
|
682
|
+
// Copy map entries
|
|
683
|
+
if (target instanceof Map) {
|
|
684
|
+
if (source instanceof Map) {
|
|
685
|
+
for (let [key, value] of source.entries()) {
|
|
686
|
+
if (!predicate(value, key, target, source))
|
|
687
|
+
continue;
|
|
688
|
+
target.set(key, !shouldCopy(key, value) ? value : copyRecursive(target.get(key), value, predicate, copies));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return target;
|
|
692
|
+
}
|
|
693
|
+
// Copy object members
|
|
694
|
+
let keys = Object.keys(source);
|
|
695
|
+
keys.forEach(key => {
|
|
696
|
+
if (!predicate(source[key], key, target, source))
|
|
697
|
+
return;
|
|
698
|
+
target[key] = !shouldCopy(key, source[key]) ? source[key] : copyRecursive(target[key], source[key], predicate, copies);
|
|
699
|
+
}, target);
|
|
700
|
+
// Copy object properties
|
|
701
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
702
|
+
keys = Object.keys(descriptors).filter(k => keys.indexOf(k) < 0);
|
|
703
|
+
keys.forEach(key => {
|
|
704
|
+
Object.defineProperty(target, key, descriptors[key]);
|
|
705
|
+
});
|
|
706
|
+
return target;
|
|
707
|
+
}
|
|
708
|
+
function filter(obj, predicate) {
|
|
709
|
+
return copyRecursive(null, obj, predicate || defaultPredicate, new Map());
|
|
710
|
+
}
|
|
711
|
+
function copy(obj) {
|
|
712
|
+
return copyRecursive(null, obj, defaultPredicate, new Map());
|
|
713
|
+
}
|
|
714
|
+
function assign(target, source, predicate) {
|
|
715
|
+
return copyRecursive(target, source, predicate, new Map());
|
|
716
|
+
}
|
|
717
|
+
function md5(data) {
|
|
718
|
+
if (isObject(data)) {
|
|
719
|
+
data = JSON.stringify(data);
|
|
720
|
+
}
|
|
721
|
+
if (!isString(data)) {
|
|
722
|
+
throw `Can't md5 other that raw object or string`;
|
|
723
|
+
}
|
|
724
|
+
return createHash("md5").update(data).digest("hex");
|
|
725
|
+
}
|
|
726
|
+
function runCommand(scriptPath, expectedCode = 0) {
|
|
727
|
+
return new Promise((resolve, reject) => {
|
|
728
|
+
const cp = exec(scriptPath, (error, stdout) => {
|
|
729
|
+
if (error && expectedCode !== error.code) {
|
|
730
|
+
console.log(error);
|
|
731
|
+
reject(error);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const lines = (stdout || "").split("\n");
|
|
735
|
+
let line = null;
|
|
736
|
+
while (!line && lines.length > 0) {
|
|
737
|
+
line = lines.pop();
|
|
738
|
+
}
|
|
739
|
+
resolve(line);
|
|
740
|
+
});
|
|
741
|
+
cp.stdout.on("data", function (data) {
|
|
742
|
+
console.log(data.toString());
|
|
743
|
+
});
|
|
744
|
+
cp.stderr.on("data", function (data) {
|
|
745
|
+
console.error(data.toString());
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
var ConsoleColor;
|
|
750
|
+
(function (ConsoleColor) {
|
|
751
|
+
ConsoleColor["Reset"] = "\u001B[0m";
|
|
752
|
+
ConsoleColor["Bright"] = "\u001B[1m";
|
|
753
|
+
ConsoleColor["Dim"] = "\u001B[2m";
|
|
754
|
+
ConsoleColor["Underscore"] = "\u001B[4m";
|
|
755
|
+
ConsoleColor["Blink"] = "\u001B[5m";
|
|
756
|
+
ConsoleColor["Reverse"] = "\u001B[7m";
|
|
757
|
+
ConsoleColor["Hidden"] = "\u001B[8m";
|
|
758
|
+
ConsoleColor["FgBlack"] = "\u001B[30m";
|
|
759
|
+
ConsoleColor["FgRed"] = "\u001B[31m";
|
|
760
|
+
ConsoleColor["FgGreen"] = "\u001B[32m";
|
|
761
|
+
ConsoleColor["FgYellow"] = "\u001B[33m";
|
|
762
|
+
ConsoleColor["FgBlue"] = "\u001B[34m";
|
|
763
|
+
ConsoleColor["FgMagenta"] = "\u001B[35m";
|
|
764
|
+
ConsoleColor["FgCyan"] = "\u001B[36m";
|
|
765
|
+
ConsoleColor["FgWhite"] = "\u001B[37m";
|
|
766
|
+
ConsoleColor["FgDefault"] = "\u001B[38m";
|
|
767
|
+
ConsoleColor["BgBlack"] = "\u001B[40m";
|
|
768
|
+
ConsoleColor["BgRed"] = "\u001B[41m";
|
|
769
|
+
ConsoleColor["BgGreen"] = "\u001B[42m";
|
|
770
|
+
ConsoleColor["BgYellow"] = "\u001B[43m";
|
|
771
|
+
ConsoleColor["BgBlue"] = "\u001B[44m";
|
|
772
|
+
ConsoleColor["BgMagenta"] = "\u001B[45m";
|
|
773
|
+
ConsoleColor["BgCyan"] = "\u001B[46m";
|
|
774
|
+
ConsoleColor["BgWhite"] = "\u001B[47m";
|
|
775
|
+
ConsoleColor["BgDefault"] = "\u001B[48m";
|
|
776
|
+
})(ConsoleColor || (ConsoleColor = {}));
|
|
777
|
+
const defaultColors = {
|
|
778
|
+
keyColor: ConsoleColor.FgWhite,
|
|
779
|
+
numberColor: ConsoleColor.FgBlue,
|
|
780
|
+
stringColor: ConsoleColor.FgYellow,
|
|
781
|
+
trueColor: ConsoleColor.FgGreen,
|
|
782
|
+
falseColor: ConsoleColor.FgRed,
|
|
783
|
+
nullColor: ConsoleColor.BgMagenta
|
|
784
|
+
};
|
|
785
|
+
function colorize(input, color) {
|
|
786
|
+
return `${color}${input}${ConsoleColor.Reset}`;
|
|
787
|
+
}
|
|
788
|
+
function jsonHighlight(input, colorOptions) {
|
|
789
|
+
const colors = Object.assign({}, defaultColors, colorOptions);
|
|
790
|
+
const json = (isString(input) ? input : JSON.stringify(input, null, 2)).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
791
|
+
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+]?\d+)?)/g, (match) => {
|
|
792
|
+
let color = colors.numberColor;
|
|
793
|
+
if (/^"/.test(match)) {
|
|
794
|
+
if (/:$/.test(match)) {
|
|
795
|
+
color = colors.keyColor;
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
color = colors.stringColor;
|
|
799
|
+
match = '"' + match.substr(1, match.length - 2) + '"';
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
color = /true/.test(match)
|
|
804
|
+
? colors.trueColor
|
|
805
|
+
: /false/.test(match)
|
|
806
|
+
? colors.falseColor
|
|
807
|
+
: /null/.test(match)
|
|
808
|
+
? colors.nullColor
|
|
809
|
+
: color;
|
|
810
|
+
}
|
|
811
|
+
return `${color}${match}${ConsoleColor.Reset}`;
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
function replaceSpecialChars(str, to = "-") {
|
|
815
|
+
return `${str}`.replace(/[&\/\\#, +()$~%.@'":*?<>{}]/g, to);
|
|
816
|
+
}
|
|
817
|
+
function regexEscape(str) {
|
|
818
|
+
return `${str}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
819
|
+
}
|
|
820
|
+
function flatten(arr) {
|
|
821
|
+
return arr.reduce((flat, toFlatten) => {
|
|
822
|
+
return flat.concat(isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
|
|
823
|
+
}, []);
|
|
824
|
+
}
|
|
825
|
+
function wrapError(e, message, httpCode = 500) {
|
|
826
|
+
if (axios.isAxiosError(e)) {
|
|
827
|
+
e.message = message;
|
|
828
|
+
return e;
|
|
829
|
+
}
|
|
830
|
+
return new HttpError(httpCode, `${message}: ${e}`);
|
|
831
|
+
}
|
|
832
|
+
function getDirName() {
|
|
833
|
+
if (typeof __dirname === "undefined") {
|
|
834
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
835
|
+
return dirname(__filename);
|
|
836
|
+
}
|
|
837
|
+
return __dirname;
|
|
838
|
+
}
|
|
839
|
+
function prepareUrl(ending = "/") {
|
|
840
|
+
return url => {
|
|
841
|
+
return url ? `${url.replace(/\/+$/, "")}${ending}` : ending;
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const prepareUrlSlash = prepareUrl("/");
|
|
845
|
+
const prepareUrlEmpty = prepareUrl("");
|
|
846
|
+
function checkTextFileType(type) {
|
|
847
|
+
return type.mime.indexOf("text") >= 0 || type.mime.indexOf("xml") >= 0;
|
|
848
|
+
}
|
|
849
|
+
function fixTextFileType(type, buffer) {
|
|
850
|
+
const text = buffer.toString("utf8");
|
|
851
|
+
if (text.indexOf("<svg") >= 0) {
|
|
852
|
+
return { ext: "svg", mime: "image/svg+xml" };
|
|
853
|
+
}
|
|
854
|
+
return type;
|
|
855
|
+
}
|
|
856
|
+
async function fileTypeFromBuffer(buffer) {
|
|
857
|
+
const stream = bufferToStream(buffer);
|
|
858
|
+
const type = (await fileTypeFromBuffer$1(buffer) ?? { ext: "txt", mime: "text/plain" });
|
|
859
|
+
if (checkTextFileType(type)) {
|
|
860
|
+
return fixTextFileType(type, buffer);
|
|
861
|
+
}
|
|
862
|
+
return type;
|
|
863
|
+
}
|
|
864
|
+
async function fileTypeFromStream(stream) {
|
|
865
|
+
return (await fileTypeFromStream$1(stream) ?? { ext: "txt", mime: "text/plain" });
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
let Configuration = class Configuration {
|
|
869
|
+
paramMap;
|
|
870
|
+
paramValues;
|
|
871
|
+
constructor(params) {
|
|
872
|
+
dotenv.config();
|
|
873
|
+
this.paramMap = {};
|
|
874
|
+
this.paramValues = {};
|
|
875
|
+
(params || []).forEach(param => this.add(param));
|
|
876
|
+
}
|
|
877
|
+
add(param) {
|
|
878
|
+
const existingParam = this.paramMap[param.name] || param;
|
|
879
|
+
existingParam.defaultValue = param.defaultValue;
|
|
880
|
+
existingParam.resolver = param.resolver || existingParam.resolver;
|
|
881
|
+
this.paramMap[param.name] = existingParam;
|
|
882
|
+
}
|
|
883
|
+
resolveValue(param, alreadyResolved) {
|
|
884
|
+
const envName = param.name.replace(/\.?([A-Z|0-9]+)/g, function (x, y) {
|
|
885
|
+
return "_" + y.toLowerCase();
|
|
886
|
+
}).replace(/\./gi, "_").replace(/^_/, "").toUpperCase();
|
|
887
|
+
const envValue = process.env[envName];
|
|
888
|
+
const helper = (p) => this.resolveInternal(p, alreadyResolved);
|
|
889
|
+
if (typeof envValue !== "undefined") {
|
|
890
|
+
const value = isFunction(param.resolver)
|
|
891
|
+
? param.resolver(envValue, helper)
|
|
892
|
+
: convertValue(envValue, getType(param.defaultValue));
|
|
893
|
+
console.log(colorize(`Processing param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), colorize(envName, ConsoleColor.FgBlue), `"${envValue}"`, value);
|
|
894
|
+
return value;
|
|
895
|
+
}
|
|
896
|
+
else if (isFunction(param.resolver)) {
|
|
897
|
+
const value = param.resolver(param.defaultValue, helper);
|
|
898
|
+
console.log(colorize(`Processing default param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), param.defaultValue, value);
|
|
899
|
+
return value;
|
|
900
|
+
}
|
|
901
|
+
console.log(colorize(`Using default param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), param.defaultValue);
|
|
902
|
+
return param.defaultValue;
|
|
903
|
+
}
|
|
904
|
+
hasParam(name) {
|
|
905
|
+
return !!this.paramMap[name];
|
|
906
|
+
}
|
|
907
|
+
resolve(name) {
|
|
908
|
+
return this.resolveInternal(name, []);
|
|
909
|
+
}
|
|
910
|
+
resolveInternal(name, alreadyResolved) {
|
|
911
|
+
if (alreadyResolved.includes(name)) {
|
|
912
|
+
throw new Error(`Circular dependency detected: ${alreadyResolved.join(" -> ")} -> ${name}`);
|
|
913
|
+
}
|
|
914
|
+
alreadyResolved.push(name);
|
|
915
|
+
const param = this.paramMap[name];
|
|
916
|
+
if (!param)
|
|
917
|
+
throw new Error(`Parameter with name: '${name}' does not exists in configuration`);
|
|
918
|
+
if (!(name in this.paramValues)) {
|
|
919
|
+
this.paramValues[name] = this.resolveValue(param, alreadyResolved);
|
|
920
|
+
}
|
|
921
|
+
return this.paramValues[name];
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
Configuration = __decorate([
|
|
925
|
+
singleton(),
|
|
926
|
+
__param(0, injectAll(PARAMETER)),
|
|
927
|
+
__metadata("design:paramtypes", [Array])
|
|
928
|
+
], Configuration);
|
|
929
|
+
|
|
930
|
+
let MongoConnector = class MongoConnector {
|
|
931
|
+
configuration;
|
|
932
|
+
get connection() {
|
|
933
|
+
return this.conn;
|
|
934
|
+
}
|
|
935
|
+
get database() {
|
|
936
|
+
return this.db;
|
|
937
|
+
}
|
|
938
|
+
conn;
|
|
939
|
+
db;
|
|
940
|
+
constructor(configuration) {
|
|
941
|
+
this.configuration = configuration;
|
|
942
|
+
this.conn = null;
|
|
943
|
+
this.db = null;
|
|
944
|
+
}
|
|
945
|
+
async connect() {
|
|
946
|
+
if (this.db)
|
|
947
|
+
return this.db;
|
|
948
|
+
this.conn = (await mongoose.connect(this.configuration.resolve("mongoUri"), {
|
|
949
|
+
dbName: this.configuration.resolve("mongoDb"),
|
|
950
|
+
user: this.configuration.resolve("mongoUser"),
|
|
951
|
+
pass: this.configuration.resolve("mongoPassword")
|
|
952
|
+
})).connection;
|
|
953
|
+
this.db = this.conn.db;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
MongoConnector = __decorate([
|
|
957
|
+
singleton(),
|
|
958
|
+
__metadata("design:paramtypes", [Configuration])
|
|
959
|
+
], MongoConnector);
|
|
960
|
+
|
|
961
|
+
class BaseEntity {
|
|
962
|
+
oid;
|
|
963
|
+
data;
|
|
964
|
+
collection;
|
|
965
|
+
get id() {
|
|
966
|
+
return this.oid.toHexString();
|
|
967
|
+
}
|
|
968
|
+
deleted;
|
|
969
|
+
constructor(oid, data, collection) {
|
|
970
|
+
this.oid = oid;
|
|
971
|
+
this.data = data;
|
|
972
|
+
this.collection = collection;
|
|
973
|
+
}
|
|
974
|
+
save() {
|
|
975
|
+
return this.collection.updateOne({ _id: this.oid }, { $set: this.toJSON() }, { upsert: true });
|
|
976
|
+
}
|
|
977
|
+
async load() {
|
|
978
|
+
const res = await this.collection.findOne({ _id: this.oid });
|
|
979
|
+
this.deleted = !res;
|
|
980
|
+
this.data = res || {};
|
|
981
|
+
return this;
|
|
982
|
+
}
|
|
983
|
+
toJSON() {
|
|
984
|
+
const ret = Object.assign({}, this.data);
|
|
985
|
+
delete ret._id;
|
|
986
|
+
ret.id = this.id;
|
|
987
|
+
ret.updatedAt = new Date();
|
|
988
|
+
ret.createdAt = ret.createdAt || ret.updatedAt;
|
|
989
|
+
return ret;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
class Asset extends BaseEntity {
|
|
994
|
+
driver;
|
|
995
|
+
get filename() {
|
|
996
|
+
return this.data.filename;
|
|
997
|
+
}
|
|
998
|
+
get contentType() {
|
|
999
|
+
return this.data.contentType;
|
|
1000
|
+
}
|
|
1001
|
+
get metadata() {
|
|
1002
|
+
return this.data.metadata;
|
|
1003
|
+
}
|
|
1004
|
+
get stream() {
|
|
1005
|
+
return this.driver.openDownloadStream(this.oid);
|
|
1006
|
+
}
|
|
1007
|
+
constructor(id, data, collection, driver) {
|
|
1008
|
+
super(id, data, collection);
|
|
1009
|
+
this.driver = driver;
|
|
1010
|
+
}
|
|
1011
|
+
async unlink() {
|
|
1012
|
+
try {
|
|
1013
|
+
await this.driver.delete(this.oid);
|
|
1014
|
+
await this.collection.deleteOne({ _id: this.oid });
|
|
1015
|
+
}
|
|
1016
|
+
catch (error) {
|
|
1017
|
+
let err = error;
|
|
1018
|
+
if (error) {
|
|
1019
|
+
err = error.message || error || "";
|
|
1020
|
+
if (!isString(err) || !err.startsWith("FileNotFound")) {
|
|
1021
|
+
throw err;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return this.id;
|
|
1026
|
+
}
|
|
1027
|
+
async setMeta(metadata) {
|
|
1028
|
+
metadata = Object.assign(this.metadata, metadata || {});
|
|
1029
|
+
await this.collection.updateOne({ _id: this.oid }, { $set: { metadata } });
|
|
1030
|
+
}
|
|
1031
|
+
getBuffer() {
|
|
1032
|
+
return streamToBuffer(this.stream);
|
|
1033
|
+
}
|
|
1034
|
+
async download(metadata) {
|
|
1035
|
+
metadata = Object.assign(this.metadata, metadata || {});
|
|
1036
|
+
metadata.downloadCount = isNaN(metadata.downloadCount) || !metadata.firstDownload
|
|
1037
|
+
? 1
|
|
1038
|
+
: metadata.downloadCount + 1;
|
|
1039
|
+
metadata.firstDownload = metadata.firstDownload || new Date();
|
|
1040
|
+
metadata.lastDownload = new Date();
|
|
1041
|
+
await this.collection.updateOne({ _id: this.oid }, { $set: { metadata } });
|
|
1042
|
+
return this.stream;
|
|
1043
|
+
}
|
|
1044
|
+
async getImage(params = null) {
|
|
1045
|
+
return toImage(this.stream, params, this.metadata);
|
|
1046
|
+
}
|
|
1047
|
+
async downloadImage(params, metadata) {
|
|
1048
|
+
return toImage(await this.download(metadata), params, this.metadata);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
class TempAsset {
|
|
1053
|
+
buffer;
|
|
1054
|
+
filename;
|
|
1055
|
+
contentType;
|
|
1056
|
+
metadata;
|
|
1057
|
+
id;
|
|
1058
|
+
get stream() {
|
|
1059
|
+
return bufferToStream(this.buffer);
|
|
1060
|
+
}
|
|
1061
|
+
constructor(buffer, filename, contentType, metadata) {
|
|
1062
|
+
this.buffer = buffer;
|
|
1063
|
+
this.filename = filename;
|
|
1064
|
+
this.contentType = contentType;
|
|
1065
|
+
this.metadata = metadata;
|
|
1066
|
+
this.id = new ObjectId$1().toHexString();
|
|
1067
|
+
}
|
|
1068
|
+
async unlink() {
|
|
1069
|
+
throw new Error(`Temp asset '${this.id}' can not be removed!`);
|
|
1070
|
+
}
|
|
1071
|
+
async setMeta(meta) {
|
|
1072
|
+
Object.assign(this.metadata, meta || {});
|
|
1073
|
+
}
|
|
1074
|
+
async getBuffer() {
|
|
1075
|
+
return this.buffer;
|
|
1076
|
+
}
|
|
1077
|
+
async download(metadata) {
|
|
1078
|
+
return this.stream;
|
|
1079
|
+
}
|
|
1080
|
+
downloadImage(params, metadata) {
|
|
1081
|
+
Object.assign(this.metadata, metadata || {});
|
|
1082
|
+
return toImage(this.stream, params, this.metadata);
|
|
1083
|
+
}
|
|
1084
|
+
getImage(params) {
|
|
1085
|
+
return this.downloadImage(params);
|
|
1086
|
+
}
|
|
1087
|
+
async save() {
|
|
1088
|
+
return this;
|
|
1089
|
+
}
|
|
1090
|
+
async load() {
|
|
1091
|
+
return this;
|
|
1092
|
+
}
|
|
1093
|
+
toJSON() {
|
|
1094
|
+
return {
|
|
1095
|
+
id: this.id,
|
|
1096
|
+
filename: this.filename,
|
|
1097
|
+
contentType: this.contentType,
|
|
1098
|
+
metadata: this.metadata
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
let Assets = class Assets {
|
|
1104
|
+
connector;
|
|
1105
|
+
assetProcessor;
|
|
1106
|
+
driver;
|
|
1107
|
+
collection;
|
|
1108
|
+
constructor(connector, assetProcessor, driver) {
|
|
1109
|
+
this.connector = connector;
|
|
1110
|
+
this.assetProcessor = assetProcessor;
|
|
1111
|
+
this.driver = driver;
|
|
1112
|
+
this.collection = connector.database?.collection("assets.metadata");
|
|
1113
|
+
}
|
|
1114
|
+
async write(stream, contentType = null, metadata = null) {
|
|
1115
|
+
const uploadStream = copyStream(stream);
|
|
1116
|
+
const buffer = await streamToBuffer(stream);
|
|
1117
|
+
let fileType = { ext: "", mime: contentType };
|
|
1118
|
+
try {
|
|
1119
|
+
fileType = await fileTypeFromBuffer(buffer);
|
|
1120
|
+
}
|
|
1121
|
+
catch (e) {
|
|
1122
|
+
if (!fileType.mime) {
|
|
1123
|
+
throw new Error(`Can't determine mime type: ${e}`);
|
|
1124
|
+
}
|
|
1125
|
+
console.log(`Can't determine mime type`, e);
|
|
1126
|
+
}
|
|
1127
|
+
metadata = metadata || {};
|
|
1128
|
+
return this.upload(uploadStream, fileType, metadata);
|
|
1129
|
+
}
|
|
1130
|
+
async writeBuffer(buffer, metadata = null, contentType = null) {
|
|
1131
|
+
let fileType = { ext: "", mime: contentType };
|
|
1132
|
+
try {
|
|
1133
|
+
fileType = await fileTypeFromBuffer(buffer);
|
|
1134
|
+
}
|
|
1135
|
+
catch (e) {
|
|
1136
|
+
if (!fileType.mime) {
|
|
1137
|
+
throw `Can't determine mime type`;
|
|
1138
|
+
}
|
|
1139
|
+
console.log(`Can't determine mime type`, e);
|
|
1140
|
+
}
|
|
1141
|
+
metadata = metadata || {};
|
|
1142
|
+
buffer = await this.assetProcessor.process(buffer, metadata, fileType);
|
|
1143
|
+
return this.upload(bufferToStream(buffer), fileType, metadata);
|
|
1144
|
+
}
|
|
1145
|
+
async writeUrl(url, metadata = null) {
|
|
1146
|
+
metadata = metadata || {};
|
|
1147
|
+
metadata.filename = metadata.filename || url;
|
|
1148
|
+
metadata.url = url;
|
|
1149
|
+
metadata.uploadTime = new Date().getTime();
|
|
1150
|
+
const oneWeek = 1000 * 3600 * 24 * 7;
|
|
1151
|
+
const asset = await this.find({ "metadata.url": url, "metadata.uploadTime": { $gt: metadata.uploadTime - oneWeek } });
|
|
1152
|
+
if (asset)
|
|
1153
|
+
return asset;
|
|
1154
|
+
const buffer = (await axios({ url, responseType: "arraybuffer" })).data;
|
|
1155
|
+
return this.writeBuffer(buffer, metadata);
|
|
1156
|
+
}
|
|
1157
|
+
async download(url, contentType = null) {
|
|
1158
|
+
let buffer = (await axios({ url, responseType: "arraybuffer" })).data;
|
|
1159
|
+
let fileType = { ext: "", mime: contentType };
|
|
1160
|
+
try {
|
|
1161
|
+
fileType = await fileTypeFromBuffer(buffer);
|
|
1162
|
+
}
|
|
1163
|
+
catch (e) {
|
|
1164
|
+
if (!fileType.mime) {
|
|
1165
|
+
throw `Can't determine mime type`;
|
|
1166
|
+
}
|
|
1167
|
+
console.log(`Can't determine mime type`, e);
|
|
1168
|
+
}
|
|
1169
|
+
const metadata = {
|
|
1170
|
+
filename: url,
|
|
1171
|
+
extension: (fileType.ext || "").trim()
|
|
1172
|
+
};
|
|
1173
|
+
buffer = await this.assetProcessor.process(buffer, metadata, fileType);
|
|
1174
|
+
return new TempAsset(buffer, url, fileType.mime, metadata);
|
|
1175
|
+
}
|
|
1176
|
+
async read(id) {
|
|
1177
|
+
return !id ? null : this.find({ _id: new ObjectId$1(id) });
|
|
1178
|
+
}
|
|
1179
|
+
async find(where) {
|
|
1180
|
+
const data = await this.collection.findOne(where);
|
|
1181
|
+
return !data ? null : new Asset(data._id, data, this.collection, this.driver);
|
|
1182
|
+
}
|
|
1183
|
+
async findMany(where) {
|
|
1184
|
+
const cursor = this.collection.find(where);
|
|
1185
|
+
const items = await cursor.toArray() || [];
|
|
1186
|
+
const result = [];
|
|
1187
|
+
for (let item of items) {
|
|
1188
|
+
if (!item)
|
|
1189
|
+
continue;
|
|
1190
|
+
result.push(new Asset(item._id, item, this.collection, this.driver));
|
|
1191
|
+
}
|
|
1192
|
+
return result;
|
|
1193
|
+
}
|
|
1194
|
+
async deleteMany(where) {
|
|
1195
|
+
const assets = await this.findMany(where);
|
|
1196
|
+
return Promise.all(assets.map(a => a.unlink()));
|
|
1197
|
+
}
|
|
1198
|
+
async unlink(id) {
|
|
1199
|
+
const asset = await this.read(id);
|
|
1200
|
+
if (!asset)
|
|
1201
|
+
return null;
|
|
1202
|
+
return asset.unlink();
|
|
1203
|
+
}
|
|
1204
|
+
async upload(stream, fileType, metadata) {
|
|
1205
|
+
const contentType = fileType.mime.trim();
|
|
1206
|
+
metadata = Object.assign({
|
|
1207
|
+
downloadCount: 0,
|
|
1208
|
+
firstDownload: null,
|
|
1209
|
+
lastDownload: null
|
|
1210
|
+
}, metadata || {});
|
|
1211
|
+
metadata.filename = metadata.filename || new ObjectId$1().toHexString();
|
|
1212
|
+
metadata.extension = (fileType.ext || "").trim();
|
|
1213
|
+
return new Promise(((resolve, reject) => {
|
|
1214
|
+
const uploaderStream = this.driver.openUploadStream(metadata.filename, {
|
|
1215
|
+
chunkSizeBytes: 1048576,
|
|
1216
|
+
metadata,
|
|
1217
|
+
contentType: fileType.mime
|
|
1218
|
+
});
|
|
1219
|
+
stream.pipe(uploaderStream)
|
|
1220
|
+
.on("error", error => {
|
|
1221
|
+
reject(error.message || error);
|
|
1222
|
+
})
|
|
1223
|
+
.on("finish", () => {
|
|
1224
|
+
const asset = new Asset(uploaderStream.id, {
|
|
1225
|
+
filename: metadata.filename,
|
|
1226
|
+
contentType,
|
|
1227
|
+
metadata
|
|
1228
|
+
}, this.collection, this.driver);
|
|
1229
|
+
asset.save().then(() => {
|
|
1230
|
+
resolve(asset);
|
|
1231
|
+
}, error => {
|
|
1232
|
+
reject(error.message || error);
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
}));
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
Assets = __decorate([
|
|
1239
|
+
injectable(),
|
|
1240
|
+
scoped(Lifecycle.ContainerScoped),
|
|
1241
|
+
__param(2, inject(ASSET_DRIVER)),
|
|
1242
|
+
__metadata("design:paramtypes", [MongoConnector,
|
|
1243
|
+
AssetProcessor, Object])
|
|
1244
|
+
], Assets);
|
|
1245
|
+
|
|
1246
|
+
class LazyAsset extends BaseEntity {
|
|
1247
|
+
logger;
|
|
1248
|
+
assets;
|
|
1249
|
+
progresses;
|
|
1250
|
+
get jobName() {
|
|
1251
|
+
return this.data.jobName;
|
|
1252
|
+
}
|
|
1253
|
+
get jobParams() {
|
|
1254
|
+
return this.data.jobParams;
|
|
1255
|
+
}
|
|
1256
|
+
get jobQue() {
|
|
1257
|
+
return this.data.jobQue;
|
|
1258
|
+
}
|
|
1259
|
+
get createdAt() {
|
|
1260
|
+
return this.data.createdAt;
|
|
1261
|
+
}
|
|
1262
|
+
get updatedAt() {
|
|
1263
|
+
return this.data.updatedAt;
|
|
1264
|
+
}
|
|
1265
|
+
get progressId() {
|
|
1266
|
+
return this.data.progressId;
|
|
1267
|
+
}
|
|
1268
|
+
get assetId() {
|
|
1269
|
+
return this.data.assetId;
|
|
1270
|
+
}
|
|
1271
|
+
constructor(id, data, collection, logger, assets, progresses) {
|
|
1272
|
+
super(id, data, collection);
|
|
1273
|
+
this.logger = logger;
|
|
1274
|
+
this.assets = assets;
|
|
1275
|
+
this.progresses = progresses;
|
|
1276
|
+
}
|
|
1277
|
+
async unlink() {
|
|
1278
|
+
await this.load();
|
|
1279
|
+
if (!this.progressId) {
|
|
1280
|
+
await this.collection.deleteOne({ _id: this.oid });
|
|
1281
|
+
}
|
|
1282
|
+
return this.assets.unlink(this.assetId);
|
|
1283
|
+
}
|
|
1284
|
+
startWorking() {
|
|
1285
|
+
this.load().then(() => {
|
|
1286
|
+
if (this.deleted)
|
|
1287
|
+
return;
|
|
1288
|
+
this.progresses.get(this.progressId).then(p => {
|
|
1289
|
+
p?.cancel();
|
|
1290
|
+
});
|
|
1291
|
+
this.startWorkingOnAsset(false).then(() => {
|
|
1292
|
+
this.logger.log("lazy-assets", `Started working on lazy asset: ${this.id}`);
|
|
1293
|
+
}).catch(reason => {
|
|
1294
|
+
this.logger.log("lazy-assets", `Can't start working on lazy asset: ${this.id}\nReason: ${reason}`);
|
|
1295
|
+
});
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
async loadAsset() {
|
|
1299
|
+
await this.load();
|
|
1300
|
+
if (this.deleted)
|
|
1301
|
+
return null;
|
|
1302
|
+
if (this.assetId) {
|
|
1303
|
+
return this.assets.read(this.assetId);
|
|
1304
|
+
}
|
|
1305
|
+
if (this.progressId) {
|
|
1306
|
+
await this.progresses.waitToFinish(this.progressId);
|
|
1307
|
+
return this.loadAsset();
|
|
1308
|
+
}
|
|
1309
|
+
await this.startWorkingOnAsset(true);
|
|
1310
|
+
return this.loadAsset();
|
|
1311
|
+
}
|
|
1312
|
+
async writeAsset(asset) {
|
|
1313
|
+
this.data.updatedAt = new Date();
|
|
1314
|
+
this.data.assetId = asset.id;
|
|
1315
|
+
await asset.setMeta({ lazyId: this.id });
|
|
1316
|
+
await this.save();
|
|
1317
|
+
return asset;
|
|
1318
|
+
}
|
|
1319
|
+
async startWorkingOnAsset(fromLoad) {
|
|
1320
|
+
const oldAsset = this.data.assetId;
|
|
1321
|
+
this.data.updatedAt = new Date();
|
|
1322
|
+
this.data.progressId = (await this.progresses.create()).id;
|
|
1323
|
+
this.data.assetId = null;
|
|
1324
|
+
await this.save();
|
|
1325
|
+
await this.assets.unlink(oldAsset);
|
|
1326
|
+
const jobParams = JSON.parse(await gunzipPromised(this.data.jobParams));
|
|
1327
|
+
await this.progresses.jobMan.enqueueWithName(this.data.jobName, { ...jobParams, lazyId: this.id, fromLoad });
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
let Logger = class Logger {
|
|
1332
|
+
config;
|
|
1333
|
+
tags;
|
|
1334
|
+
ignoredTags;
|
|
1335
|
+
constructor(config) {
|
|
1336
|
+
this.config = config;
|
|
1337
|
+
this.tags = this.config.resolve("logTags");
|
|
1338
|
+
this.ignoredTags = this.config.resolve("ignoredLogTags");
|
|
1339
|
+
}
|
|
1340
|
+
log(tag, ...params) {
|
|
1341
|
+
if (this.ignoredTags.includes(tag))
|
|
1342
|
+
return;
|
|
1343
|
+
if (this.tags.length === 0 || this.tags.includes(tag)) {
|
|
1344
|
+
console.log(`[${tag}]`, ...params);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
Logger = __decorate([
|
|
1349
|
+
singleton(),
|
|
1350
|
+
__metadata("design:paramtypes", [Configuration])
|
|
1351
|
+
], Logger);
|
|
1352
|
+
|
|
1353
|
+
let JobManager = class JobManager {
|
|
1354
|
+
config;
|
|
1355
|
+
logger;
|
|
1356
|
+
container;
|
|
1357
|
+
jobTypes;
|
|
1358
|
+
jobs;
|
|
1359
|
+
messages;
|
|
1360
|
+
processing;
|
|
1361
|
+
apiPush;
|
|
1362
|
+
apiPull;
|
|
1363
|
+
workerPush;
|
|
1364
|
+
workerPull;
|
|
1365
|
+
maxTimeout;
|
|
1366
|
+
constructor(config, logger, container, jobTypes) {
|
|
1367
|
+
this.config = config;
|
|
1368
|
+
this.logger = logger;
|
|
1369
|
+
this.container = container;
|
|
1370
|
+
this.jobTypes = jobTypes || [];
|
|
1371
|
+
this.jobs = this.jobTypes.reduce((res, jobType) => {
|
|
1372
|
+
const jobName = getConstructorName(jobType);
|
|
1373
|
+
res[jobName] = (jobParams, uniqueId) => {
|
|
1374
|
+
const job = this.resolveJobInstance(jobType, jobParams, uniqueId);
|
|
1375
|
+
const messageBridge = {
|
|
1376
|
+
sendMessage: (message, params) => {
|
|
1377
|
+
params.uniqueId = uniqueId;
|
|
1378
|
+
this.workerPush.send([message, JSON.stringify(params)]);
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
messageBridge.sendMessage(`job-started`, { name: jobName });
|
|
1382
|
+
return job.process(messageBridge);
|
|
1383
|
+
};
|
|
1384
|
+
return res;
|
|
1385
|
+
}, {});
|
|
1386
|
+
this.messages = new Subject();
|
|
1387
|
+
this.processing = null;
|
|
1388
|
+
this.maxTimeout = this.config.resolve("jobTimeout");
|
|
1389
|
+
}
|
|
1390
|
+
on(message, cb) {
|
|
1391
|
+
return this.messages
|
|
1392
|
+
.pipe(filter$1(t => t.message === message))
|
|
1393
|
+
.pipe(map(t => t.params)).subscribe(cb);
|
|
1394
|
+
}
|
|
1395
|
+
async process(jobType, params = {}) {
|
|
1396
|
+
let instance = null;
|
|
1397
|
+
try {
|
|
1398
|
+
instance = this.resolveJobInstance(jobType, params);
|
|
1399
|
+
}
|
|
1400
|
+
catch (e) {
|
|
1401
|
+
const jobName = getConstructorName(jobType);
|
|
1402
|
+
throw new Error(`Can't resolve params for job: ${jobName}, with params: ${JSON.stringify(params)}. Reason: ${e}`);
|
|
1403
|
+
}
|
|
1404
|
+
return instance.process();
|
|
1405
|
+
}
|
|
1406
|
+
async enqueueWithName(name, params = {}) {
|
|
1407
|
+
return this.sendToWorkers(this.tryResolveFromName(name, params), params);
|
|
1408
|
+
}
|
|
1409
|
+
async enqueue(jobType, params = {}) {
|
|
1410
|
+
return this.sendToWorkers(this.tryResolveAndInit(jobType, params), params);
|
|
1411
|
+
}
|
|
1412
|
+
schedule(minute, hour, dayOfMonth, month, dayOfWeek, jobType, params = {}) {
|
|
1413
|
+
const expression = [minute, hour, dayOfMonth, month, dayOfWeek].map(t => {
|
|
1414
|
+
if (isObject(t)) {
|
|
1415
|
+
const range = t;
|
|
1416
|
+
return `${range.min || 0}-${range.max || 0}`;
|
|
1417
|
+
}
|
|
1418
|
+
if (isArray(t)) {
|
|
1419
|
+
return t.join(",");
|
|
1420
|
+
}
|
|
1421
|
+
return `${t}`;
|
|
1422
|
+
}).join(" ");
|
|
1423
|
+
const jobName = getConstructorName(jobType);
|
|
1424
|
+
if (!cron.validate(expression)) {
|
|
1425
|
+
this.logger.log("job-manager", `Can't schedule the task: '${jobName}' because time expression is invalid.`);
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
return cron.schedule(expression, () => {
|
|
1429
|
+
this.enqueue(jobType, params).catch(e => {
|
|
1430
|
+
this.logger.log("job-manager", `Can't enqueue job: '${jobName}' because: ${e}`);
|
|
1431
|
+
});
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
async initProcessing() {
|
|
1435
|
+
const host = this.config.resolve("zmqRemoteHost");
|
|
1436
|
+
const pushHost = `${host}:${this.config.resolve("zmqBackPort")}`;
|
|
1437
|
+
this.workerPush = socket("push");
|
|
1438
|
+
this.workerPush.connect(pushHost);
|
|
1439
|
+
this.logger.log("job-manager", `Worker producer connected to: ${pushHost}`);
|
|
1440
|
+
const pullHost = `${host}:${this.config.resolve("zmqPort")}`;
|
|
1441
|
+
this.workerPull = socket("pull");
|
|
1442
|
+
this.workerPull.connect(pullHost);
|
|
1443
|
+
this.logger.log("job-manager", `Worker consumer connected to: ${pullHost}`);
|
|
1444
|
+
this.workerPull.on("message", async (name, args, uniqId) => {
|
|
1445
|
+
try {
|
|
1446
|
+
const jobName = name.toString("utf8");
|
|
1447
|
+
const jobParams = JSON.parse(args.toString("utf8"));
|
|
1448
|
+
const uniqueId = uniqId?.toString("utf8");
|
|
1449
|
+
console.time(uniqueId);
|
|
1450
|
+
console.timeLog(uniqueId, `Started working on background job: ${colorize(jobName, ConsoleColor.FgCyan)} with args: \n${jsonHighlight(jobParams)}\n\n`);
|
|
1451
|
+
try {
|
|
1452
|
+
await Promise.race([this.jobs[jobName](jobParams, uniqueId), promiseTimeout(this.maxTimeout, true)]);
|
|
1453
|
+
console.timeLog(uniqueId, `Finished working on background job: ${colorize(jobName, ConsoleColor.FgCyan)}\n\n`);
|
|
1454
|
+
}
|
|
1455
|
+
catch (e) {
|
|
1456
|
+
console.timeLog(uniqueId, `Background job failed: ${colorize(jobName, ConsoleColor.FgRed)}\n${e}\n\n`);
|
|
1457
|
+
}
|
|
1458
|
+
console.timeEnd(uniqueId);
|
|
1459
|
+
}
|
|
1460
|
+
catch (e) {
|
|
1461
|
+
this.logger.log("job-manager", `Failed to start job: ${e.message}`);
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
startProcessing() {
|
|
1466
|
+
this.processing = this.processing || this.initProcessing();
|
|
1467
|
+
return this.processing;
|
|
1468
|
+
}
|
|
1469
|
+
tryResolve(jobType, params) {
|
|
1470
|
+
const jobName = getConstructorName(jobType);
|
|
1471
|
+
if (!this.jobs[jobName]) {
|
|
1472
|
+
throw `Can't find job with name: ${jobName} so it can't be enqueued!`;
|
|
1473
|
+
}
|
|
1474
|
+
try {
|
|
1475
|
+
this.resolveJobInstance(jobType, params);
|
|
1476
|
+
}
|
|
1477
|
+
catch (e) {
|
|
1478
|
+
throw `Can't resolve params for job: ${jobName}, with params: ${JSON.stringify(params)}. Reason: ${e}`;
|
|
1479
|
+
}
|
|
1480
|
+
return jobName;
|
|
1481
|
+
}
|
|
1482
|
+
tryResolveFromName(jobName, params) {
|
|
1483
|
+
const jobType = this.jobTypes.find(type => {
|
|
1484
|
+
return getConstructorName(type) == jobName;
|
|
1485
|
+
});
|
|
1486
|
+
if (!jobType) {
|
|
1487
|
+
throw `Can't find job type with name: ${jobName} so it can't be enqueued!`;
|
|
1488
|
+
}
|
|
1489
|
+
return this.tryResolveAndInit(jobType, params);
|
|
1490
|
+
}
|
|
1491
|
+
tryResolveAndInit(jobType, params) {
|
|
1492
|
+
if (!this.apiPush) {
|
|
1493
|
+
const port = this.config.resolve("zmqPort");
|
|
1494
|
+
this.apiPush = socket("push");
|
|
1495
|
+
this.apiPush.bind(`tcp://0.0.0.0:${port}`);
|
|
1496
|
+
this.logger.log("job-manager", `API producer bound to port: ${port}`);
|
|
1497
|
+
}
|
|
1498
|
+
if (!this.apiPull) {
|
|
1499
|
+
const backPort = this.config.resolve("zmqBackPort");
|
|
1500
|
+
this.apiPull = socket("pull");
|
|
1501
|
+
this.apiPull.bind(`tcp://0.0.0.0:${backPort}`);
|
|
1502
|
+
this.apiPull.on("message", (name, args) => {
|
|
1503
|
+
const message = name.toString("utf8");
|
|
1504
|
+
const params = JSON.parse(args?.toString("utf8") || "{}");
|
|
1505
|
+
const paramTypes = Object.keys(params).reduce((res, key) => {
|
|
1506
|
+
res[key] = getType(params[key]);
|
|
1507
|
+
return res;
|
|
1508
|
+
}, {});
|
|
1509
|
+
this.logger.log("job-manager", `Received a message from worker: "${colorize(message, ConsoleColor.FgCyan)}" with args: ${jsonHighlight(paramTypes)}\n\n`);
|
|
1510
|
+
this.messages.next({ message, params });
|
|
1511
|
+
});
|
|
1512
|
+
this.logger.log("job-manager", `API consumer bound to port: ${backPort}`);
|
|
1513
|
+
}
|
|
1514
|
+
return this.tryResolve(jobType, params);
|
|
1515
|
+
}
|
|
1516
|
+
resolveJobInstance(jobType, params, uniqueId = "") {
|
|
1517
|
+
const container = this.container.createChildContainer();
|
|
1518
|
+
Object.keys(params).map((name) => {
|
|
1519
|
+
container.register(name, { useValue: params[name] });
|
|
1520
|
+
});
|
|
1521
|
+
container.register("uniqueId", { useValue: uniqueId });
|
|
1522
|
+
container.register(jobType, jobType);
|
|
1523
|
+
return container.resolve(jobType);
|
|
1524
|
+
}
|
|
1525
|
+
async sendToWorkers(jobName, params) {
|
|
1526
|
+
const uniqueId = new ObjectId$1().toHexString();
|
|
1527
|
+
this.apiPush.send([jobName, JSON.stringify(params), uniqueId]);
|
|
1528
|
+
return uniqueId;
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
JobManager = __decorate([
|
|
1532
|
+
singleton(),
|
|
1533
|
+
__param(2, inject(DI_CONTAINER)),
|
|
1534
|
+
__param(3, injectAll(JOB)),
|
|
1535
|
+
__metadata("design:paramtypes", [Configuration,
|
|
1536
|
+
Logger, Object, Array])
|
|
1537
|
+
], JobManager);
|
|
1538
|
+
|
|
1539
|
+
class Progress extends BaseEntity {
|
|
1540
|
+
messageBridge;
|
|
1541
|
+
get current() {
|
|
1542
|
+
return this.data.current;
|
|
1543
|
+
}
|
|
1544
|
+
get max() {
|
|
1545
|
+
return this.data.max;
|
|
1546
|
+
}
|
|
1547
|
+
get message() {
|
|
1548
|
+
return this.data.message;
|
|
1549
|
+
}
|
|
1550
|
+
get error() {
|
|
1551
|
+
return this.data.error;
|
|
1552
|
+
}
|
|
1553
|
+
get canceled() {
|
|
1554
|
+
return this.data.canceled;
|
|
1555
|
+
}
|
|
1556
|
+
get percent() {
|
|
1557
|
+
return this.max > 0 ? Math.round(this.current / this.max * 100) : 0;
|
|
1558
|
+
}
|
|
1559
|
+
get remaining() {
|
|
1560
|
+
return this.max > 0 ? this.max - this.current : 0;
|
|
1561
|
+
}
|
|
1562
|
+
constructor(id, data, collection) {
|
|
1563
|
+
super(id, data, collection);
|
|
1564
|
+
}
|
|
1565
|
+
setMessageBridge(messageBridge) {
|
|
1566
|
+
this.messageBridge = messageBridge || this.messageBridge;
|
|
1567
|
+
return this;
|
|
1568
|
+
}
|
|
1569
|
+
async createSubProgress(progressValue, max, message) {
|
|
1570
|
+
if (max <= 0 && progressValue > 0) {
|
|
1571
|
+
await this.advance(progressValue);
|
|
1572
|
+
}
|
|
1573
|
+
if (message !== null) {
|
|
1574
|
+
this.data.message = message;
|
|
1575
|
+
await this.save();
|
|
1576
|
+
}
|
|
1577
|
+
return new SubProgress(this, this.current, progressValue, Math.max(max, 1));
|
|
1578
|
+
}
|
|
1579
|
+
async setMax(max) {
|
|
1580
|
+
if (isNaN(max) || max <= 0) {
|
|
1581
|
+
throw "Max progress value must be bigger than zero";
|
|
1582
|
+
}
|
|
1583
|
+
this.data.max = max;
|
|
1584
|
+
await this.save();
|
|
1585
|
+
}
|
|
1586
|
+
async setMessage(message) {
|
|
1587
|
+
this.data.message = message;
|
|
1588
|
+
await this.save();
|
|
1589
|
+
}
|
|
1590
|
+
async setError(error) {
|
|
1591
|
+
this.data.error = error;
|
|
1592
|
+
await this.save();
|
|
1593
|
+
}
|
|
1594
|
+
async advance(value = 1) {
|
|
1595
|
+
if (isNaN(value) || value <= 0) {
|
|
1596
|
+
throw new Error(`Advance value must be bigger than zero: ${this.id}`);
|
|
1597
|
+
}
|
|
1598
|
+
await this.load();
|
|
1599
|
+
if (this.deleted || this.canceled) {
|
|
1600
|
+
const status = this.deleted ? "deleted" : "canceled";
|
|
1601
|
+
throw new Error(`Can't advance ${status} progress: ${this.id}`);
|
|
1602
|
+
}
|
|
1603
|
+
this.data.current = Math.min(this.max, this.current + value);
|
|
1604
|
+
await this.save();
|
|
1605
|
+
}
|
|
1606
|
+
async cancel() {
|
|
1607
|
+
this.data.canceled = true;
|
|
1608
|
+
await this.save();
|
|
1609
|
+
}
|
|
1610
|
+
save() {
|
|
1611
|
+
if (this.messageBridge) {
|
|
1612
|
+
this.messageBridge.sendMessage(`progress-changed`, this.toJSON());
|
|
1613
|
+
}
|
|
1614
|
+
return super.save();
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
class SubProgress {
|
|
1618
|
+
parent;
|
|
1619
|
+
progressFrom;
|
|
1620
|
+
progressValue;
|
|
1621
|
+
mMax;
|
|
1622
|
+
get id() {
|
|
1623
|
+
return this.parent.id;
|
|
1624
|
+
}
|
|
1625
|
+
get current() {
|
|
1626
|
+
return this.mCurrent;
|
|
1627
|
+
}
|
|
1628
|
+
get max() {
|
|
1629
|
+
return this.mMax;
|
|
1630
|
+
}
|
|
1631
|
+
get message() {
|
|
1632
|
+
return this.parent.message;
|
|
1633
|
+
}
|
|
1634
|
+
get error() {
|
|
1635
|
+
return this.parent.error;
|
|
1636
|
+
}
|
|
1637
|
+
get percent() {
|
|
1638
|
+
return this.parent.percent;
|
|
1639
|
+
}
|
|
1640
|
+
get remaining() {
|
|
1641
|
+
return this.max - this.mCurrent;
|
|
1642
|
+
}
|
|
1643
|
+
get canceled() {
|
|
1644
|
+
return !this.parent || this.parent.canceled;
|
|
1645
|
+
}
|
|
1646
|
+
mCurrent;
|
|
1647
|
+
constructor(parent, progressFrom, progressValue, mMax = 100) {
|
|
1648
|
+
this.parent = parent;
|
|
1649
|
+
this.progressFrom = progressFrom;
|
|
1650
|
+
this.progressValue = progressValue;
|
|
1651
|
+
this.mMax = mMax;
|
|
1652
|
+
if (progressFrom < 0) {
|
|
1653
|
+
throw "Progress from must be bigger than or zero";
|
|
1654
|
+
}
|
|
1655
|
+
if (progressValue <= 0) {
|
|
1656
|
+
throw "Progress value must be bigger than zero";
|
|
1657
|
+
}
|
|
1658
|
+
this.mCurrent = 0;
|
|
1659
|
+
}
|
|
1660
|
+
setMessageBridge(messageBridge) {
|
|
1661
|
+
if (!this.parent)
|
|
1662
|
+
return this;
|
|
1663
|
+
this.parent.setMessageBridge(messageBridge);
|
|
1664
|
+
return this;
|
|
1665
|
+
}
|
|
1666
|
+
async createSubProgress(progressValue, max, message) {
|
|
1667
|
+
if (max <= 0 && progressValue > 0) {
|
|
1668
|
+
await this.advance(progressValue);
|
|
1669
|
+
}
|
|
1670
|
+
if (message !== null) {
|
|
1671
|
+
await this.setMessage(message);
|
|
1672
|
+
}
|
|
1673
|
+
return new SubProgress(this, this.current, progressValue, Math.max(max, 1));
|
|
1674
|
+
}
|
|
1675
|
+
async setMax(max) {
|
|
1676
|
+
if (isNaN(max) || max <= 0) {
|
|
1677
|
+
throw "Max progress value must be bigger than zero";
|
|
1678
|
+
}
|
|
1679
|
+
this.mMax = max;
|
|
1680
|
+
await this.save();
|
|
1681
|
+
}
|
|
1682
|
+
async setMessage(message) {
|
|
1683
|
+
if (!this.parent)
|
|
1684
|
+
return null;
|
|
1685
|
+
await this.parent.setMessage(message);
|
|
1686
|
+
}
|
|
1687
|
+
async setError(error) {
|
|
1688
|
+
if (!this.parent)
|
|
1689
|
+
return null;
|
|
1690
|
+
await this.parent.setError(error);
|
|
1691
|
+
}
|
|
1692
|
+
async advance(value = 1) {
|
|
1693
|
+
if (isNaN(value) || value <= 0) {
|
|
1694
|
+
throw "Advance value must be bigger than zero";
|
|
1695
|
+
}
|
|
1696
|
+
this.mCurrent = Math.min(this.max, this.mCurrent + value);
|
|
1697
|
+
await this.save();
|
|
1698
|
+
}
|
|
1699
|
+
async cancel() {
|
|
1700
|
+
if (!this.parent)
|
|
1701
|
+
return null;
|
|
1702
|
+
await this.parent.cancel();
|
|
1703
|
+
}
|
|
1704
|
+
async save() {
|
|
1705
|
+
const ratio = this.max > 0 ? this.mCurrent / this.max : 0;
|
|
1706
|
+
const newProgress = this.progressFrom + Math.round(this.progressValue * ratio);
|
|
1707
|
+
const current = this.parent.current;
|
|
1708
|
+
if (newProgress <= current)
|
|
1709
|
+
return null;
|
|
1710
|
+
await this.parent.advance(newProgress);
|
|
1711
|
+
}
|
|
1712
|
+
async load() {
|
|
1713
|
+
return null;
|
|
1714
|
+
}
|
|
1715
|
+
toJSON() {
|
|
1716
|
+
return this.parent.toJSON();
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
let Progresses = class Progresses {
|
|
1721
|
+
connector;
|
|
1722
|
+
jobMan;
|
|
1723
|
+
collection;
|
|
1724
|
+
progresses;
|
|
1725
|
+
constructor(connector, jobMan) {
|
|
1726
|
+
this.connector = connector;
|
|
1727
|
+
this.jobMan = jobMan;
|
|
1728
|
+
this.collection = connector.database.collection("progresses");
|
|
1729
|
+
this.progresses = {};
|
|
1730
|
+
this.jobMan.on("progress-changed", progress => {
|
|
1731
|
+
const id = progress.id;
|
|
1732
|
+
this.progresses[id] = new Progress(new ObjectId$1(id), progress, this.collection);
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
async waitToFinish(id) {
|
|
1736
|
+
return Promise.race([
|
|
1737
|
+
this.waitForProgress(id, async () => {
|
|
1738
|
+
let progress = this.progresses[id];
|
|
1739
|
+
if (!progress || progress.percent < 100) {
|
|
1740
|
+
progress = await this.get(id);
|
|
1741
|
+
}
|
|
1742
|
+
if (!progress) {
|
|
1743
|
+
throw new Error(`Progress does not exists with id: ${id}`);
|
|
1744
|
+
}
|
|
1745
|
+
return progress;
|
|
1746
|
+
}, 500),
|
|
1747
|
+
this.waitForProgress(id, async () => {
|
|
1748
|
+
return this.progresses[id] || null;
|
|
1749
|
+
}, 25)
|
|
1750
|
+
]);
|
|
1751
|
+
}
|
|
1752
|
+
async get(id) {
|
|
1753
|
+
return !id ? null : this.find({ _id: new ObjectId$1(id) });
|
|
1754
|
+
}
|
|
1755
|
+
async find(where) {
|
|
1756
|
+
const data = await this.collection.findOne(where);
|
|
1757
|
+
return !data ? null : new Progress(data._id, data, this.collection);
|
|
1758
|
+
}
|
|
1759
|
+
async create(max = 100) {
|
|
1760
|
+
if (isNaN(max) || max <= 0) {
|
|
1761
|
+
throw new Error(`Max progress value must be bigger than zero`);
|
|
1762
|
+
}
|
|
1763
|
+
const data = {
|
|
1764
|
+
current: 0,
|
|
1765
|
+
max: max,
|
|
1766
|
+
message: "",
|
|
1767
|
+
error: "",
|
|
1768
|
+
canceled: false
|
|
1769
|
+
};
|
|
1770
|
+
const res = await this.collection.insertOne(data);
|
|
1771
|
+
return new Progress(res.insertedId, data, this.collection);
|
|
1772
|
+
}
|
|
1773
|
+
async remove(id) {
|
|
1774
|
+
await this.collection.deleteOne({ _id: new ObjectId$1(id) });
|
|
1775
|
+
return id;
|
|
1776
|
+
}
|
|
1777
|
+
async waitForProgress(id, cb, delay) {
|
|
1778
|
+
let isFinished = false;
|
|
1779
|
+
let progress = null;
|
|
1780
|
+
let waitTime = 0;
|
|
1781
|
+
while (!isFinished) {
|
|
1782
|
+
progress = await cb();
|
|
1783
|
+
waitTime += delay;
|
|
1784
|
+
if (progress) {
|
|
1785
|
+
if (progress.error) {
|
|
1786
|
+
throw new Error(progress.error);
|
|
1787
|
+
}
|
|
1788
|
+
isFinished = progress.percent >= 100;
|
|
1789
|
+
}
|
|
1790
|
+
if (!isFinished) {
|
|
1791
|
+
if (waitTime >= this.jobMan.maxTimeout) {
|
|
1792
|
+
throw new Error(`Progress with id: ${id} probably never will be finished!`);
|
|
1793
|
+
}
|
|
1794
|
+
await promiseTimeout(delay);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return progress;
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
Progresses = __decorate([
|
|
1801
|
+
singleton(),
|
|
1802
|
+
__metadata("design:paramtypes", [MongoConnector, JobManager])
|
|
1803
|
+
], Progresses);
|
|
1804
|
+
|
|
1805
|
+
let LazyAssets = class LazyAssets {
|
|
1806
|
+
connector;
|
|
1807
|
+
assets;
|
|
1808
|
+
progresses;
|
|
1809
|
+
logger;
|
|
1810
|
+
jobMan;
|
|
1811
|
+
collection;
|
|
1812
|
+
constructor(connector, assets, progresses, logger, jobMan) {
|
|
1813
|
+
this.connector = connector;
|
|
1814
|
+
this.assets = assets;
|
|
1815
|
+
this.progresses = progresses;
|
|
1816
|
+
this.logger = logger;
|
|
1817
|
+
this.jobMan = jobMan;
|
|
1818
|
+
this.collection = connector.database.collection("lazyassets");
|
|
1819
|
+
}
|
|
1820
|
+
async create(jobType, jobParamsObj = {}, jobQue = "main") {
|
|
1821
|
+
const jobName = this.jobMan.tryResolve(jobType, { ...jobParamsObj, lazyId: "" });
|
|
1822
|
+
const jobParams = await gzipPromised(JSON.stringify(jobParamsObj));
|
|
1823
|
+
const data = {
|
|
1824
|
+
jobName,
|
|
1825
|
+
jobParams,
|
|
1826
|
+
jobQue,
|
|
1827
|
+
};
|
|
1828
|
+
const existingAsset = await this.find(data);
|
|
1829
|
+
if (existingAsset)
|
|
1830
|
+
return existingAsset;
|
|
1831
|
+
data.createdAt = new Date();
|
|
1832
|
+
data.updatedAt = data.createdAt;
|
|
1833
|
+
const res = await this.collection.insertOne(data);
|
|
1834
|
+
return new LazyAsset(res.insertedId, data, this.collection, this.logger, this.assets, this.progresses);
|
|
1835
|
+
}
|
|
1836
|
+
async read(id) {
|
|
1837
|
+
return !id ? null : this.find({ _id: new ObjectId$1(id) });
|
|
1838
|
+
}
|
|
1839
|
+
async find(where) {
|
|
1840
|
+
const data = await this.collection.findOne(where);
|
|
1841
|
+
return !data
|
|
1842
|
+
? null
|
|
1843
|
+
: new LazyAsset(data._id, data, this.collection, this.logger, this.assets, this.progresses);
|
|
1844
|
+
}
|
|
1845
|
+
async unlink(id) {
|
|
1846
|
+
const asset = await this.read(id);
|
|
1847
|
+
if (!asset)
|
|
1848
|
+
return null;
|
|
1849
|
+
return asset.unlink();
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
LazyAssets = __decorate([
|
|
1853
|
+
injectable(),
|
|
1854
|
+
scoped(Lifecycle.ContainerScoped),
|
|
1855
|
+
__metadata("design:paramtypes", [MongoConnector,
|
|
1856
|
+
Assets,
|
|
1857
|
+
Progresses,
|
|
1858
|
+
Logger,
|
|
1859
|
+
JobManager])
|
|
1860
|
+
], LazyAssets);
|
|
1861
|
+
|
|
1862
|
+
let AssetResolver = class AssetResolver {
|
|
1863
|
+
assets;
|
|
1864
|
+
lazyAssets;
|
|
1865
|
+
constructor(assets, lazyAssets) {
|
|
1866
|
+
this.assets = assets;
|
|
1867
|
+
this.lazyAssets = lazyAssets;
|
|
1868
|
+
}
|
|
1869
|
+
async resolve(id, lazy = false) {
|
|
1870
|
+
let asset = null;
|
|
1871
|
+
if (lazy) {
|
|
1872
|
+
const lazyAsset = await this.lazyAssets.read(id);
|
|
1873
|
+
if (!lazyAsset)
|
|
1874
|
+
return null;
|
|
1875
|
+
return lazyAsset.loadAsset();
|
|
1876
|
+
}
|
|
1877
|
+
asset = await this.assets.read(id);
|
|
1878
|
+
if (!asset) {
|
|
1879
|
+
const lazyAsset = await this.lazyAssets.read(id);
|
|
1880
|
+
if (!lazyAsset)
|
|
1881
|
+
return null;
|
|
1882
|
+
return lazyAsset.loadAsset();
|
|
1883
|
+
}
|
|
1884
|
+
return asset;
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1887
|
+
AssetResolver = __decorate([
|
|
1888
|
+
injectable(),
|
|
1889
|
+
scoped(Lifecycle.ContainerScoped),
|
|
1890
|
+
__metadata("design:paramtypes", [Assets, LazyAssets])
|
|
1891
|
+
], AssetResolver);
|
|
1892
|
+
|
|
1893
|
+
function checkValue(multi, value) {
|
|
1894
|
+
if (multi) {
|
|
1895
|
+
return Array.isArray(value) && value.every(v => {
|
|
1896
|
+
try {
|
|
1897
|
+
const id = new ObjectId$1(v);
|
|
1898
|
+
return id instanceof ObjectId$1;
|
|
1899
|
+
}
|
|
1900
|
+
catch (e) {
|
|
1901
|
+
return false;
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
if (null === value)
|
|
1906
|
+
return true;
|
|
1907
|
+
try {
|
|
1908
|
+
const id = new ObjectId$1(value);
|
|
1909
|
+
return id instanceof ObjectId$1;
|
|
1910
|
+
}
|
|
1911
|
+
catch (e) {
|
|
1912
|
+
return false;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
let IsFile = class IsFile {
|
|
1916
|
+
validate(value, validationArguments) {
|
|
1917
|
+
const [multi] = (validationArguments.constraints || []);
|
|
1918
|
+
return checkValue(multi, value);
|
|
1919
|
+
}
|
|
1920
|
+
};
|
|
1921
|
+
IsFile = __decorate([
|
|
1922
|
+
ValidatorConstraint()
|
|
1923
|
+
], IsFile);
|
|
1924
|
+
let IsObjectId = class IsObjectId {
|
|
1925
|
+
validate(value, validationArguments) {
|
|
1926
|
+
const [_, multi] = (validationArguments.constraints || []);
|
|
1927
|
+
return checkValue(multi, value);
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
IsObjectId = __decorate([
|
|
1931
|
+
ValidatorConstraint()
|
|
1932
|
+
], IsObjectId);
|
|
1933
|
+
|
|
1934
|
+
let OpenApi = class OpenApi {
|
|
1935
|
+
container;
|
|
1936
|
+
customValidation;
|
|
1937
|
+
docs;
|
|
1938
|
+
docsStr;
|
|
1939
|
+
get apiDocs() {
|
|
1940
|
+
if (!this.docs)
|
|
1941
|
+
this.docs = this.createApiDocs();
|
|
1942
|
+
return this.docs;
|
|
1943
|
+
}
|
|
1944
|
+
get apiDocsStr() {
|
|
1945
|
+
if (!this.docsStr)
|
|
1946
|
+
this.docsStr = JSON.stringify(this.apiDocs);
|
|
1947
|
+
return this.docsStr;
|
|
1948
|
+
}
|
|
1949
|
+
constructor(container, customValidation) {
|
|
1950
|
+
this.container = container;
|
|
1951
|
+
this.customValidation = customValidation;
|
|
1952
|
+
this.docs = null;
|
|
1953
|
+
}
|
|
1954
|
+
async schemaToExample(src, req) {
|
|
1955
|
+
const maybeRef = src;
|
|
1956
|
+
if (maybeRef.$ref) {
|
|
1957
|
+
const schemas = this.apiDocs.components.schemas;
|
|
1958
|
+
const schema = maybeRef.$ref
|
|
1959
|
+
.replace("#/components/schemas/", "")
|
|
1960
|
+
.replace("#/definitions/", "");
|
|
1961
|
+
return this.schemaToExample(schemas[schema], req);
|
|
1962
|
+
}
|
|
1963
|
+
let schema = src;
|
|
1964
|
+
if (schema.oneOf) {
|
|
1965
|
+
schema = Object.assign({}, schema, schema.oneOf[0]);
|
|
1966
|
+
}
|
|
1967
|
+
if (schema.type === "object") {
|
|
1968
|
+
const result = {};
|
|
1969
|
+
await Promise.all(Object.keys(schema.properties).map(async (key) => {
|
|
1970
|
+
result[key] = await this.schemaToExample(schema.properties[key], req);
|
|
1971
|
+
}));
|
|
1972
|
+
return result;
|
|
1973
|
+
}
|
|
1974
|
+
if (schema.type === "array") {
|
|
1975
|
+
return [await this.schemaToExample(schema.items, req)];
|
|
1976
|
+
}
|
|
1977
|
+
if (schema.type === "string") {
|
|
1978
|
+
if (isDefined(schema.default)) {
|
|
1979
|
+
if (isFunction(schema.default)) {
|
|
1980
|
+
return schema.default(this.container);
|
|
1981
|
+
}
|
|
1982
|
+
return schema.default;
|
|
1983
|
+
}
|
|
1984
|
+
if (schema.format == "date") {
|
|
1985
|
+
return new Date().toISOString().substr(0, 10);
|
|
1986
|
+
}
|
|
1987
|
+
if (schema.format == "date-time") {
|
|
1988
|
+
return new Date().toISOString();
|
|
1989
|
+
}
|
|
1990
|
+
if (schema.enum) {
|
|
1991
|
+
return schema.enum[0];
|
|
1992
|
+
}
|
|
1993
|
+
return "string";
|
|
1994
|
+
}
|
|
1995
|
+
if (schema.type === "number") {
|
|
1996
|
+
return schema.default ?? 0;
|
|
1997
|
+
}
|
|
1998
|
+
else if (schema.type === "boolean") {
|
|
1999
|
+
return schema.default ?? false;
|
|
2000
|
+
}
|
|
2001
|
+
else {
|
|
2002
|
+
return schema.default ?? null;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
createApiDocs() {
|
|
2006
|
+
const storage = getMetadataArgsStorage();
|
|
2007
|
+
const docs = routingControllersToSpec(storage);
|
|
2008
|
+
// docs.basePath = "/api/";
|
|
2009
|
+
docs.components = {
|
|
2010
|
+
...(docs.components || {}),
|
|
2011
|
+
schemas: validationMetadatasToSchemas({
|
|
2012
|
+
classTransformerMetadataStorage: defaultMetadataStorage,
|
|
2013
|
+
additionalConverters: {
|
|
2014
|
+
[ValidationTypes.CUSTOM_VALIDATION]: (meta, options) => {
|
|
2015
|
+
const res = isFunction(this.customValidation) ? this.customValidation(meta, options) : this.customValidation;
|
|
2016
|
+
if (isObject(res))
|
|
2017
|
+
return res;
|
|
2018
|
+
const constraints = meta.constraints || [];
|
|
2019
|
+
if (meta.constraintCls === IsFile) {
|
|
2020
|
+
return {
|
|
2021
|
+
multi: constraints[0] || false,
|
|
2022
|
+
type: "file"
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
if (meta.constraintCls === IsObjectId) {
|
|
2026
|
+
return {
|
|
2027
|
+
endpoint: constraints[0] || false,
|
|
2028
|
+
multi: constraints[1] || false,
|
|
2029
|
+
type: "list"
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
return null;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
})
|
|
2036
|
+
};
|
|
2037
|
+
return docs;
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2040
|
+
OpenApi = __decorate([
|
|
2041
|
+
singleton(),
|
|
2042
|
+
__param(0, inject(DI_CONTAINER)),
|
|
2043
|
+
__param(1, inject(OPENAPI_VALIDATION)),
|
|
2044
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
2045
|
+
], OpenApi);
|
|
2046
|
+
|
|
2047
|
+
let Fixtures = class Fixtures {
|
|
2048
|
+
container;
|
|
2049
|
+
fixtures;
|
|
2050
|
+
constructor(container) {
|
|
2051
|
+
this.container = container;
|
|
2052
|
+
}
|
|
2053
|
+
init(output) {
|
|
2054
|
+
try {
|
|
2055
|
+
return this.container.resolveAll(FIXTURE);
|
|
2056
|
+
}
|
|
2057
|
+
catch (e) {
|
|
2058
|
+
output.writeln(e.message);
|
|
2059
|
+
return [];
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
async load(output) {
|
|
2063
|
+
output = output || {
|
|
2064
|
+
write: console.log,
|
|
2065
|
+
writeln: t => console.log(t + "\n")
|
|
2066
|
+
};
|
|
2067
|
+
this.fixtures = this.fixtures || this.init(output);
|
|
2068
|
+
output.write(`Loading fixtures: ${this.fixtures.length} items`);
|
|
2069
|
+
for (let fixture of this.fixtures) {
|
|
2070
|
+
await fixture.load(output);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
Fixtures = __decorate([
|
|
2075
|
+
injectable(),
|
|
2076
|
+
scoped(Lifecycle.ContainerScoped),
|
|
2077
|
+
__param(0, inject(DI_CONTAINER)),
|
|
2078
|
+
__metadata("design:paramtypes", [Object])
|
|
2079
|
+
], Fixtures);
|
|
2080
|
+
|
|
2081
|
+
const express = express_;
|
|
2082
|
+
let BackendProvider = class BackendProvider {
|
|
2083
|
+
config;
|
|
2084
|
+
container;
|
|
2085
|
+
get io() {
|
|
2086
|
+
this.ioServer = this.ioServer || new Server(this.server, {
|
|
2087
|
+
path: "/socket",
|
|
2088
|
+
cors: {
|
|
2089
|
+
credentials: true,
|
|
2090
|
+
exposedHeaders: ["content-disposition"],
|
|
2091
|
+
origin: (origin, callback) => {
|
|
2092
|
+
callback(null, true);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
});
|
|
2096
|
+
return this.ioServer;
|
|
2097
|
+
}
|
|
2098
|
+
get express() {
|
|
2099
|
+
if (!this.expressApp) {
|
|
2100
|
+
this.expressApp = express();
|
|
2101
|
+
this.expressApp.set("trust proxy", true);
|
|
2102
|
+
this.expressApp.use(bodyParser.json({
|
|
2103
|
+
limit: this.config.resolve("jsonLimit")
|
|
2104
|
+
}));
|
|
2105
|
+
this.expressApp.options("*", cors());
|
|
2106
|
+
this.expressApp.get("/api-docs", (req, res) => {
|
|
2107
|
+
this.openApi = this.openApi || this.container.get(OpenApi);
|
|
2108
|
+
res.header("Content-Type", "application/json")
|
|
2109
|
+
.status(200)
|
|
2110
|
+
.end(this.openApi.apiDocsStr);
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
return this.expressApp;
|
|
2114
|
+
}
|
|
2115
|
+
get server() {
|
|
2116
|
+
this.httpServer = this.httpServer || createServer(this.express);
|
|
2117
|
+
return this.httpServer;
|
|
2118
|
+
}
|
|
2119
|
+
ioServer;
|
|
2120
|
+
httpServer;
|
|
2121
|
+
expressApp;
|
|
2122
|
+
openApi;
|
|
2123
|
+
constructor(config, container) {
|
|
2124
|
+
this.config = config;
|
|
2125
|
+
this.container = container;
|
|
2126
|
+
}
|
|
2127
|
+
async quickStart() {
|
|
2128
|
+
const port = this.config.resolve("appPort");
|
|
2129
|
+
const isWorker = this.config.resolve("isWorker");
|
|
2130
|
+
if (isWorker || this.config.resolve("startWorker")) {
|
|
2131
|
+
await this.container.resolve(JobManager).startProcessing();
|
|
2132
|
+
if (isWorker) {
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
if (this.config.resolve("fixtures")) {
|
|
2137
|
+
const fixtures = this.container.resolve(Fixtures);
|
|
2138
|
+
await fixtures.load();
|
|
2139
|
+
}
|
|
2140
|
+
return new Promise(resolve => {
|
|
2141
|
+
this.server.listen(port, () => resolve(`Service listening on port ${port}!`));
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
};
|
|
2145
|
+
BackendProvider = __decorate([
|
|
2146
|
+
singleton(),
|
|
2147
|
+
__param(1, inject(DI_CONTAINER)),
|
|
2148
|
+
__metadata("design:paramtypes", [Configuration, Object])
|
|
2149
|
+
], BackendProvider);
|
|
2150
|
+
|
|
2151
|
+
let CacheProcessor = class CacheProcessor {
|
|
2152
|
+
async serialize(data) {
|
|
2153
|
+
return data;
|
|
2154
|
+
}
|
|
2155
|
+
async deserialize(data) {
|
|
2156
|
+
return data;
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
CacheProcessor = __decorate([
|
|
2160
|
+
injectable(),
|
|
2161
|
+
scoped(Lifecycle.ContainerScoped)
|
|
2162
|
+
], CacheProcessor);
|
|
2163
|
+
|
|
2164
|
+
let Cache = class Cache {
|
|
2165
|
+
connector;
|
|
2166
|
+
config;
|
|
2167
|
+
cacheProcessor;
|
|
2168
|
+
collection;
|
|
2169
|
+
constructor(connector, config, cacheProcessor) {
|
|
2170
|
+
this.connector = connector;
|
|
2171
|
+
this.config = config;
|
|
2172
|
+
this.cacheProcessor = cacheProcessor;
|
|
2173
|
+
}
|
|
2174
|
+
async prepare() {
|
|
2175
|
+
if (this.collection)
|
|
2176
|
+
return;
|
|
2177
|
+
if (!this.connector.database) {
|
|
2178
|
+
throw new Error(`You can't use cache without mongo connection!`);
|
|
2179
|
+
}
|
|
2180
|
+
this.collection = this.connector.database.collection(this.config.resolve("cacheCollection"));
|
|
2181
|
+
await this.collection.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
|
2182
|
+
}
|
|
2183
|
+
async set(key, value, ttl, expirationTimestamp = null, tags = {}) {
|
|
2184
|
+
await this.prepare();
|
|
2185
|
+
const item = {
|
|
2186
|
+
_id: key,
|
|
2187
|
+
data: await this.cacheProcessor.serialize(value),
|
|
2188
|
+
tags: await this.cacheProcessor.serialize(tags),
|
|
2189
|
+
expirationTimestamp,
|
|
2190
|
+
};
|
|
2191
|
+
if (ttl) {
|
|
2192
|
+
const now = Math.round(new Date().getTime() / 1000);
|
|
2193
|
+
item.expiresAt = now + ttl;
|
|
2194
|
+
}
|
|
2195
|
+
await this.collection.updateOne({ _id: key }, { $set: item }, { upsert: true });
|
|
2196
|
+
return value;
|
|
2197
|
+
}
|
|
2198
|
+
async get(key) {
|
|
2199
|
+
await this.prepare();
|
|
2200
|
+
let item = await this.collection.findOne({ _id: key });
|
|
2201
|
+
const now = Math.round(new Date().getTime() / 1000);
|
|
2202
|
+
if (item && item.expiresAt && item.expiresAt < now) {
|
|
2203
|
+
item = null;
|
|
2204
|
+
}
|
|
2205
|
+
if (!item) {
|
|
2206
|
+
throw new Error(`Cache probably doesn't exists with key: ${key}`);
|
|
2207
|
+
}
|
|
2208
|
+
return await this.cacheProcessor.deserialize(item.data);
|
|
2209
|
+
}
|
|
2210
|
+
async getOrSet(key, valueCb, ttl, expirationTimestamp = null, tags = {}) {
|
|
2211
|
+
try {
|
|
2212
|
+
return await this.get(key);
|
|
2213
|
+
}
|
|
2214
|
+
catch (e) {
|
|
2215
|
+
return await this.set(key, await valueCb(), ttl, expirationTimestamp, tags);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
async delete(key) {
|
|
2219
|
+
await this.prepare();
|
|
2220
|
+
await this.collection.deleteOne({ _id: key });
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
Cache = __decorate([
|
|
2224
|
+
singleton(),
|
|
2225
|
+
__metadata("design:paramtypes", [MongoConnector, Configuration, CacheProcessor])
|
|
2226
|
+
], Cache);
|
|
2227
|
+
|
|
2228
|
+
let EndpointProvider = class EndpointProvider {
|
|
2229
|
+
async configure(app) {
|
|
2230
|
+
console.log(`Express app is mounted to: ${app.mountpath}`);
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
EndpointProvider = __decorate([
|
|
2234
|
+
injectable(),
|
|
2235
|
+
scoped(Lifecycle.ContainerScoped)
|
|
2236
|
+
], EndpointProvider);
|
|
2237
|
+
|
|
2238
|
+
const sharp$1 = sharp_;
|
|
2239
|
+
const bigSize = 1500;
|
|
2240
|
+
const thumbSize = 250;
|
|
2241
|
+
class GalleryImage {
|
|
2242
|
+
folder;
|
|
2243
|
+
handler;
|
|
2244
|
+
thumb;
|
|
2245
|
+
big;
|
|
2246
|
+
targetSize;
|
|
2247
|
+
constructor(folder, size, handler) {
|
|
2248
|
+
this.folder = folder;
|
|
2249
|
+
this.handler = handler;
|
|
2250
|
+
this.thumb = v4();
|
|
2251
|
+
this.big = v4();
|
|
2252
|
+
this.targetSize = !size ? { width: thumbSize, height: thumbSize } : size;
|
|
2253
|
+
}
|
|
2254
|
+
async serve(id) {
|
|
2255
|
+
const isThumb = id == this.thumb;
|
|
2256
|
+
if (await this.handler.hasResult(isThumb)) {
|
|
2257
|
+
return this.handler.serveResult(isThumb);
|
|
2258
|
+
}
|
|
2259
|
+
const original = sharp$1(await this.handler.getOriginal()).rotate();
|
|
2260
|
+
const meta = await original.metadata();
|
|
2261
|
+
const ratio = meta.width / meta.height;
|
|
2262
|
+
const sizeRatio = isThumb ? this.targetSize.width / this.targetSize.height : 1;
|
|
2263
|
+
const size = isThumb ? Math.max(this.targetSize.width, this.targetSize.height) : bigSize;
|
|
2264
|
+
const targetHeight = ratio > sizeRatio ? size : Math.round(size / ratio);
|
|
2265
|
+
const targetWidth = Math.round(targetHeight * ratio);
|
|
2266
|
+
const resized = original.resize(targetWidth, targetHeight);
|
|
2267
|
+
const buffer = await (isThumb ? resized.extract({
|
|
2268
|
+
left: Math.floor((targetWidth - this.targetSize.width) / 2),
|
|
2269
|
+
top: Math.floor((targetHeight - this.targetSize.height) / 2),
|
|
2270
|
+
width: this.targetSize.width,
|
|
2271
|
+
height: this.targetSize.height
|
|
2272
|
+
}).toBuffer() : resized.toBuffer());
|
|
2273
|
+
await this.handler.writeResult(isThumb, buffer);
|
|
2274
|
+
return this.handler.serveResult(isThumb);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
let GalleryCache = class GalleryCache {
|
|
2279
|
+
imgCache;
|
|
2280
|
+
constructor() {
|
|
2281
|
+
this.imgCache = {};
|
|
2282
|
+
}
|
|
2283
|
+
put(img) {
|
|
2284
|
+
this.imgCache[img.thumb] = img;
|
|
2285
|
+
this.imgCache[img.big] = img;
|
|
2286
|
+
}
|
|
2287
|
+
serve(id) {
|
|
2288
|
+
const img = this.imgCache[id];
|
|
2289
|
+
return !img ? null : img.serve(id);
|
|
2290
|
+
}
|
|
2291
|
+
create(folder, targetSize, handler) {
|
|
2292
|
+
const image = new GalleryImage(folder, targetSize, handler);
|
|
2293
|
+
this.put(image);
|
|
2294
|
+
return image;
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
GalleryCache = __decorate([
|
|
2298
|
+
injectable(),
|
|
2299
|
+
scoped(Lifecycle.ContainerScoped),
|
|
2300
|
+
__metadata("design:paramtypes", [])
|
|
2301
|
+
], GalleryCache);
|
|
2302
|
+
|
|
2303
|
+
const sharp = sharp_;
|
|
2304
|
+
let Gallery = class Gallery {
|
|
2305
|
+
config;
|
|
2306
|
+
galleryCache;
|
|
2307
|
+
dir;
|
|
2308
|
+
output;
|
|
2309
|
+
cache;
|
|
2310
|
+
constructor(config, galleryCache) {
|
|
2311
|
+
this.config = config;
|
|
2312
|
+
this.galleryCache = galleryCache;
|
|
2313
|
+
this.cache = {};
|
|
2314
|
+
this.dir = this.config.resolve("galleryDir");
|
|
2315
|
+
this.output = join(this.config.resolve("cacheDir"), "gallery");
|
|
2316
|
+
}
|
|
2317
|
+
async getFolder(folder, size = null) {
|
|
2318
|
+
this.cache[folder] = this.cache[folder] || new Promise(resolve => {
|
|
2319
|
+
lstat(join(this.dir, folder), (err, stats) => {
|
|
2320
|
+
if (err || !stats.isDirectory()) {
|
|
2321
|
+
resolve([]);
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
this.readRecursive(folder, "", size).then(resolve, () => resolve([]));
|
|
2325
|
+
});
|
|
2326
|
+
});
|
|
2327
|
+
return this.cache[folder];
|
|
2328
|
+
}
|
|
2329
|
+
readRecursive(path, folder, size) {
|
|
2330
|
+
return new Promise(resolve => {
|
|
2331
|
+
readdir(join(this.dir, path), (err, files) => {
|
|
2332
|
+
if (err) {
|
|
2333
|
+
resolve([]);
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
const promises = files.map(file => {
|
|
2337
|
+
return new Promise(async (resolve) => {
|
|
2338
|
+
const filePath = join(path, file);
|
|
2339
|
+
const absoluteFilePath = join(this.dir, filePath);
|
|
2340
|
+
lstat(absoluteFilePath, (err, stats) => {
|
|
2341
|
+
if (err) {
|
|
2342
|
+
resolve([]);
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
if (stats.isDirectory()) {
|
|
2346
|
+
this.readRecursive(filePath, join(folder, file), size).then(resolve);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
const sharpImg = sharp(absoluteFilePath);
|
|
2350
|
+
sharpImg.rotate().metadata().then(() => {
|
|
2351
|
+
const getResultPath = (isThumb) => {
|
|
2352
|
+
return join(this.output, filePath.replace(/.([a-z|A-Z]+)$/gi, function (ext) {
|
|
2353
|
+
const suffix = isThumb ? 'thumb' : 'big';
|
|
2354
|
+
return `-${suffix}${ext}`;
|
|
2355
|
+
}));
|
|
2356
|
+
};
|
|
2357
|
+
resolve([this.galleryCache.create(folder, size, {
|
|
2358
|
+
getOriginal: () => {
|
|
2359
|
+
return new Promise((res, rej) => {
|
|
2360
|
+
readFile$1(absoluteFilePath, (err, data) => {
|
|
2361
|
+
if (err) {
|
|
2362
|
+
rej(err);
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
res(data);
|
|
2366
|
+
});
|
|
2367
|
+
});
|
|
2368
|
+
},
|
|
2369
|
+
writeResult: (isThumb, buffer) => {
|
|
2370
|
+
return new Promise(async (res, rej) => {
|
|
2371
|
+
const resultPath = getResultPath(isThumb);
|
|
2372
|
+
await mkdirRecursive(dirname(resultPath));
|
|
2373
|
+
writeFile$1(resultPath, buffer, err => {
|
|
2374
|
+
if (err) {
|
|
2375
|
+
rej(err);
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
res();
|
|
2379
|
+
});
|
|
2380
|
+
});
|
|
2381
|
+
},
|
|
2382
|
+
hasResult: (isThumb) => {
|
|
2383
|
+
return new Promise(res => {
|
|
2384
|
+
access(getResultPath(isThumb), constants.R_OK, err => {
|
|
2385
|
+
res(!err);
|
|
2386
|
+
});
|
|
2387
|
+
});
|
|
2388
|
+
},
|
|
2389
|
+
serveResult: (isThumb) => {
|
|
2390
|
+
return new Promise((res, rej) => {
|
|
2391
|
+
readFile$1(getResultPath(isThumb), (err, data) => {
|
|
2392
|
+
if (err) {
|
|
2393
|
+
rej(err);
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
res(data);
|
|
2397
|
+
});
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
})]);
|
|
2401
|
+
}, () => resolve([]));
|
|
2402
|
+
});
|
|
2403
|
+
});
|
|
2404
|
+
});
|
|
2405
|
+
Promise.all(promises).then(folders => {
|
|
2406
|
+
resolve([].concat.apply([], folders));
|
|
2407
|
+
});
|
|
2408
|
+
});
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
Gallery = __decorate([
|
|
2413
|
+
injectable(),
|
|
2414
|
+
scoped(Lifecycle.ContainerScoped),
|
|
2415
|
+
__metadata("design:paramtypes", [Configuration, GalleryCache])
|
|
2416
|
+
], Gallery);
|
|
2417
|
+
|
|
2418
|
+
let IdGenerator = class IdGenerator {
|
|
2419
|
+
config;
|
|
2420
|
+
prefix;
|
|
2421
|
+
separator;
|
|
2422
|
+
chars;
|
|
2423
|
+
parts;
|
|
2424
|
+
constructor(config) {
|
|
2425
|
+
this.config = config;
|
|
2426
|
+
this.prefix = config.resolve("idPrefix");
|
|
2427
|
+
this.separator = config.resolve("idSeparator");
|
|
2428
|
+
this.chars = config.resolve("idChars");
|
|
2429
|
+
this.parts = config.resolve("idParts");
|
|
2430
|
+
}
|
|
2431
|
+
async generate(checkCb) {
|
|
2432
|
+
let id = null;
|
|
2433
|
+
let tries = 0;
|
|
2434
|
+
let notGood = true;
|
|
2435
|
+
while (notGood && tries < 5) {
|
|
2436
|
+
id = this.generateId();
|
|
2437
|
+
notGood = await checkCb(id);
|
|
2438
|
+
tries++;
|
|
2439
|
+
}
|
|
2440
|
+
if (notGood) {
|
|
2441
|
+
throw `Couldn't generate an unique id..`;
|
|
2442
|
+
}
|
|
2443
|
+
return id;
|
|
2444
|
+
}
|
|
2445
|
+
generateId() {
|
|
2446
|
+
return this.prefix + this.parts.map(num => {
|
|
2447
|
+
let s = "";
|
|
2448
|
+
for (let i = 0; i < num; i++) {
|
|
2449
|
+
const ix = rand(0, this.chars.length - 1);
|
|
2450
|
+
s += this.chars[ix];
|
|
2451
|
+
}
|
|
2452
|
+
return s;
|
|
2453
|
+
}).join(this.separator);
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
IdGenerator = __decorate([
|
|
2457
|
+
injectable(),
|
|
2458
|
+
scoped(Lifecycle.ContainerScoped),
|
|
2459
|
+
__metadata("design:paramtypes", [Configuration])
|
|
2460
|
+
], IdGenerator);
|
|
2461
|
+
|
|
2462
|
+
let TranslationProvider = class TranslationProvider {
|
|
2463
|
+
config;
|
|
2464
|
+
cache;
|
|
2465
|
+
constructor(config, cache) {
|
|
2466
|
+
this.config = config;
|
|
2467
|
+
this.cache = cache;
|
|
2468
|
+
}
|
|
2469
|
+
getDictionary(language) {
|
|
2470
|
+
return this.cache.getOrSet(`translations-${language}`, async () => {
|
|
2471
|
+
try {
|
|
2472
|
+
const url = this.config.resolve("translationsTemplate")
|
|
2473
|
+
.replace(`__lang__`, language)
|
|
2474
|
+
.replace(`[lang]`, language);
|
|
2475
|
+
const data = await axios.get(url).then(t => t.data);
|
|
2476
|
+
if (isObject(data[language])) {
|
|
2477
|
+
return data[language];
|
|
2478
|
+
}
|
|
2479
|
+
return data;
|
|
2480
|
+
}
|
|
2481
|
+
catch (e) {
|
|
2482
|
+
return {
|
|
2483
|
+
message: `${e}`
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
}, 5 * 60);
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
TranslationProvider = __decorate([
|
|
2490
|
+
singleton(),
|
|
2491
|
+
__metadata("design:paramtypes", [Configuration, Cache])
|
|
2492
|
+
], TranslationProvider);
|
|
2493
|
+
|
|
2494
|
+
let Translator = class Translator {
|
|
2495
|
+
translationProvider;
|
|
2496
|
+
dictionaries;
|
|
2497
|
+
constructor(translationProvider) {
|
|
2498
|
+
this.translationProvider = translationProvider;
|
|
2499
|
+
this.dictionaries = {};
|
|
2500
|
+
}
|
|
2501
|
+
async getDictionary(language) {
|
|
2502
|
+
this.dictionaries[language] = await this.translationProvider.getDictionary(language);
|
|
2503
|
+
return this.dictionaries[language];
|
|
2504
|
+
}
|
|
2505
|
+
getTranslationSync(language, key, params) {
|
|
2506
|
+
if (!isString(key) || !key.length) {
|
|
2507
|
+
throw new Error(`Parameter "key" required`);
|
|
2508
|
+
}
|
|
2509
|
+
const dictionary = this.dictionaries[language];
|
|
2510
|
+
const translation = getValue(dictionary, key, key) || key;
|
|
2511
|
+
return this.interpolate(translation, params);
|
|
2512
|
+
}
|
|
2513
|
+
getTranslation(language, key, params) {
|
|
2514
|
+
if (!isString(key) || !key.length) {
|
|
2515
|
+
throw new Error(`Parameter "key" required`);
|
|
2516
|
+
}
|
|
2517
|
+
return this.getDictionary(language).then(dictionary => {
|
|
2518
|
+
const translation = getValue(dictionary, key, key) || key;
|
|
2519
|
+
return this.interpolate(translation, params);
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
getTranslations(language, ...keys) {
|
|
2523
|
+
return new Promise(resolve => {
|
|
2524
|
+
Promise.all(keys.map(key => this.getTranslation(language, key))).then(translations => {
|
|
2525
|
+
resolve(keys.reduce((result, key, i) => {
|
|
2526
|
+
result[key] = translations[i];
|
|
2527
|
+
return result;
|
|
2528
|
+
}, {}));
|
|
2529
|
+
});
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
interpolate(expr, params) {
|
|
2533
|
+
if (typeof expr === "string") {
|
|
2534
|
+
return this.interpolateString(expr, params);
|
|
2535
|
+
}
|
|
2536
|
+
if (typeof expr === "function") {
|
|
2537
|
+
return expr(params);
|
|
2538
|
+
}
|
|
2539
|
+
return expr;
|
|
2540
|
+
}
|
|
2541
|
+
interpolateString(expr, params) {
|
|
2542
|
+
if (!expr || !params)
|
|
2543
|
+
return expr;
|
|
2544
|
+
return expr.replace(/{{\s?([^{}\s]*)\s?}}/g, (substring, b) => {
|
|
2545
|
+
const r = getValue(params, b);
|
|
2546
|
+
return isDefined(r) ? r : substring;
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
Translator = __decorate([
|
|
2551
|
+
injectable(),
|
|
2552
|
+
singleton(),
|
|
2553
|
+
__metadata("design:paramtypes", [TranslationProvider])
|
|
2554
|
+
], Translator);
|
|
2555
|
+
|
|
2556
|
+
let TemplateRenderer = class TemplateRenderer {
|
|
2557
|
+
translator;
|
|
2558
|
+
config;
|
|
2559
|
+
templates;
|
|
2560
|
+
initPromise;
|
|
2561
|
+
constructor(translator, config) {
|
|
2562
|
+
this.translator = translator;
|
|
2563
|
+
this.config = config;
|
|
2564
|
+
this.templates = {};
|
|
2565
|
+
Handlebars.registerHelper(`object`, function ({ hash }) {
|
|
2566
|
+
return hash;
|
|
2567
|
+
});
|
|
2568
|
+
Handlebars.registerHelper(`now`, function () {
|
|
2569
|
+
return new Date().getTime();
|
|
2570
|
+
});
|
|
2571
|
+
Handlebars.registerHelper(`keys`, function (obj) {
|
|
2572
|
+
return !obj ? [] : Object.keys(obj);
|
|
2573
|
+
});
|
|
2574
|
+
Handlebars.registerHelper(`translate`, function (key, params) {
|
|
2575
|
+
return translator.getTranslationSync(this.language, key, params);
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
init() {
|
|
2579
|
+
this.initPromise = this.initPromise || this.parseTemplates(this.config.resolve("templatesDir"), []);
|
|
2580
|
+
return this.initPromise;
|
|
2581
|
+
}
|
|
2582
|
+
async parseTemplates(dir, dirPath) {
|
|
2583
|
+
return new Promise(resolve => {
|
|
2584
|
+
readdir(dir, async (err, files) => {
|
|
2585
|
+
for (let file of files) {
|
|
2586
|
+
const path = join(dir, file);
|
|
2587
|
+
if (lstatSync(path).isDirectory()) {
|
|
2588
|
+
await this.parseTemplates(join(dir, file), dirPath.concat([file]));
|
|
2589
|
+
continue;
|
|
2590
|
+
}
|
|
2591
|
+
const parts = file.split(".");
|
|
2592
|
+
parts.pop();
|
|
2593
|
+
const name = parts.join(".");
|
|
2594
|
+
const fullName = dirPath.concat([name]).join("-");
|
|
2595
|
+
const content = readFileSync(path).toString("utf8");
|
|
2596
|
+
this.templates[fullName] = Handlebars.compile(content);
|
|
2597
|
+
Handlebars.registerPartial(fullName, content);
|
|
2598
|
+
}
|
|
2599
|
+
resolve();
|
|
2600
|
+
});
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
async render(template, language, context) {
|
|
2604
|
+
await this.init();
|
|
2605
|
+
await this.translator.getDictionary(language);
|
|
2606
|
+
if (!this.templates[template]) {
|
|
2607
|
+
return Promise.reject(`Template not found with name: ${template}`);
|
|
2608
|
+
}
|
|
2609
|
+
context = Object.assign({ language }, context || {});
|
|
2610
|
+
const res = this.templates[template](context);
|
|
2611
|
+
return res instanceof Error ? await Promise.reject(res) : res;
|
|
2612
|
+
}
|
|
2613
|
+
};
|
|
2614
|
+
TemplateRenderer = __decorate([
|
|
2615
|
+
singleton(),
|
|
2616
|
+
__metadata("design:paramtypes", [Translator, Configuration])
|
|
2617
|
+
], TemplateRenderer);
|
|
2618
|
+
|
|
2619
|
+
let MailSender = class MailSender {
|
|
2620
|
+
config;
|
|
2621
|
+
renderer;
|
|
2622
|
+
transporter;
|
|
2623
|
+
get translator() {
|
|
2624
|
+
return this.renderer.translator;
|
|
2625
|
+
}
|
|
2626
|
+
constructor(config, renderer) {
|
|
2627
|
+
this.config = config;
|
|
2628
|
+
this.renderer = renderer;
|
|
2629
|
+
this.transporter = createTransport({
|
|
2630
|
+
host: this.config.resolve("smtpHost"),
|
|
2631
|
+
port: this.config.resolve("smtpPort"),
|
|
2632
|
+
auth: {
|
|
2633
|
+
user: this.config.resolve("smtpUser"),
|
|
2634
|
+
pass: this.config.resolve("smtpPassword"),
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
async sendMail(language, options) {
|
|
2639
|
+
const subject = await this.translator.getTranslation(language, options.subject || "-");
|
|
2640
|
+
const html = await this.renderer.render(options.template, language, options.context);
|
|
2641
|
+
return this.transporter.sendMail({
|
|
2642
|
+
from: options.from || this.config.resolve("mailSenderAddress"),
|
|
2643
|
+
to: options.to,
|
|
2644
|
+
attachments: options.attachments,
|
|
2645
|
+
subject,
|
|
2646
|
+
html
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
};
|
|
2650
|
+
MailSender = __decorate([
|
|
2651
|
+
singleton(),
|
|
2652
|
+
__metadata("design:paramtypes", [Configuration, TemplateRenderer])
|
|
2653
|
+
], MailSender);
|
|
2654
|
+
|
|
2655
|
+
let MemoryCache = class MemoryCache {
|
|
2656
|
+
cache;
|
|
2657
|
+
cacheMap;
|
|
2658
|
+
constructor(cache) {
|
|
2659
|
+
this.cache = cache;
|
|
2660
|
+
this.cacheMap = new Map();
|
|
2661
|
+
}
|
|
2662
|
+
async set(key, value, ttl, expirationTimestamp = null, tags = {}) {
|
|
2663
|
+
const now = Math.round(new Date().getTime() / 1000);
|
|
2664
|
+
const expTimestamp = Math.min(isNaN(ttl) ? Number.MAX_SAFE_INTEGER : ttl, 3600);
|
|
2665
|
+
this.cacheMap.set(key, {
|
|
2666
|
+
_id: key,
|
|
2667
|
+
data: value,
|
|
2668
|
+
expirationTimestamp: expTimestamp,
|
|
2669
|
+
expiresAt: now + expTimestamp,
|
|
2670
|
+
});
|
|
2671
|
+
return this.cache.set(key, value, ttl, expirationTimestamp, tags);
|
|
2672
|
+
}
|
|
2673
|
+
async get(key) {
|
|
2674
|
+
let item = this.cacheMap.get(key);
|
|
2675
|
+
const now = Math.round(new Date().getTime() / 1000);
|
|
2676
|
+
let expTimestamp = 3600;
|
|
2677
|
+
if (item && item.expiresAt && item.expiresAt < now) {
|
|
2678
|
+
expTimestamp = item.expirationTimestamp;
|
|
2679
|
+
item = null;
|
|
2680
|
+
}
|
|
2681
|
+
if (!item) {
|
|
2682
|
+
const value = await this.cache.get(key);
|
|
2683
|
+
this.cacheMap.set(key, {
|
|
2684
|
+
_id: key,
|
|
2685
|
+
data: value,
|
|
2686
|
+
expirationTimestamp: expTimestamp,
|
|
2687
|
+
expiresAt: now + expTimestamp,
|
|
2688
|
+
});
|
|
2689
|
+
return value;
|
|
2690
|
+
}
|
|
2691
|
+
return item.data;
|
|
2692
|
+
}
|
|
2693
|
+
async getOrSet(key, valueCb, ttl, expirationTimestamp = null, tags = {}) {
|
|
2694
|
+
try {
|
|
2695
|
+
return await this.get(key);
|
|
2696
|
+
}
|
|
2697
|
+
catch (e) {
|
|
2698
|
+
return await this.set(key, await valueCb(), ttl, expirationTimestamp, tags);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
async delete(key) {
|
|
2702
|
+
this.cacheMap.delete(key);
|
|
2703
|
+
await this.cacheMap.delete(key);
|
|
2704
|
+
}
|
|
2705
|
+
};
|
|
2706
|
+
MemoryCache = __decorate([
|
|
2707
|
+
injectable(),
|
|
2708
|
+
scoped(Lifecycle.ContainerScoped),
|
|
2709
|
+
__metadata("design:paramtypes", [Cache])
|
|
2710
|
+
], MemoryCache);
|
|
2711
|
+
|
|
2712
|
+
let TerminalManager = class TerminalManager {
|
|
2713
|
+
logger;
|
|
2714
|
+
config;
|
|
2715
|
+
servicePassword;
|
|
2716
|
+
suggestions;
|
|
2717
|
+
commands;
|
|
2718
|
+
loggedOutCommands;
|
|
2719
|
+
loggedInCommands;
|
|
2720
|
+
constructor(logger, config, commands) {
|
|
2721
|
+
this.logger = logger;
|
|
2722
|
+
this.config = config;
|
|
2723
|
+
this.servicePassword = config.resolve("servicePassword");
|
|
2724
|
+
this.suggestions = {
|
|
2725
|
+
login: async (args) => {
|
|
2726
|
+
if (args.length > 2) {
|
|
2727
|
+
return null;
|
|
2728
|
+
}
|
|
2729
|
+
const input = `${args.at(1).label}`;
|
|
2730
|
+
return (!input) ? [] : [{
|
|
2731
|
+
id: input,
|
|
2732
|
+
label: input,
|
|
2733
|
+
masked: true
|
|
2734
|
+
}];
|
|
2735
|
+
},
|
|
2736
|
+
...commands.reduce((acc, command) => {
|
|
2737
|
+
command.name = camelCaseToDash(command.name || command.constructor.name || "");
|
|
2738
|
+
if (!command.name || !command.suggest)
|
|
2739
|
+
return acc;
|
|
2740
|
+
acc[command.name] = async (a, t) => command.suggest(a, t);
|
|
2741
|
+
return acc;
|
|
2742
|
+
}, {})
|
|
2743
|
+
};
|
|
2744
|
+
this.commands = commands.reduce((acc, command) => {
|
|
2745
|
+
if (!command.name)
|
|
2746
|
+
return acc;
|
|
2747
|
+
acc[command.name] = async (a, t) => command.execute(a, t);
|
|
2748
|
+
return acc;
|
|
2749
|
+
}, {});
|
|
2750
|
+
this.loggedOutCommands = ["login", "clear"];
|
|
2751
|
+
this.loggedInCommands = Object.keys(this.commands);
|
|
2752
|
+
this.loggedInCommands.push("logout");
|
|
2753
|
+
console.log(`Current service password is: ${colorize(this.servicePassword, ConsoleColor.FgGreen)}`);
|
|
2754
|
+
}
|
|
2755
|
+
loadAddons(terminal) {
|
|
2756
|
+
let loggedIn = false;
|
|
2757
|
+
const addon = new CommandsAddon({
|
|
2758
|
+
commands: {
|
|
2759
|
+
login: async (args, terminal) => {
|
|
2760
|
+
if (args.at(1).label === this.servicePassword) {
|
|
2761
|
+
loggedIn = true;
|
|
2762
|
+
terminal.writeln("Logged in as admin");
|
|
2763
|
+
}
|
|
2764
|
+
else {
|
|
2765
|
+
throw new Error("Invalid login");
|
|
2766
|
+
}
|
|
2767
|
+
},
|
|
2768
|
+
logout: async (args, terminal) => {
|
|
2769
|
+
loggedIn = false;
|
|
2770
|
+
terminal.writeln("Logged out");
|
|
2771
|
+
},
|
|
2772
|
+
...this.commands
|
|
2773
|
+
},
|
|
2774
|
+
suggestCommands: async () => {
|
|
2775
|
+
if (loggedIn) {
|
|
2776
|
+
return this.loggedInCommands;
|
|
2777
|
+
}
|
|
2778
|
+
return this.loggedOutCommands;
|
|
2779
|
+
},
|
|
2780
|
+
suggestions: this.suggestions
|
|
2781
|
+
});
|
|
2782
|
+
terminal.loadAddon(addon);
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
TerminalManager = __decorate([
|
|
2786
|
+
singleton(),
|
|
2787
|
+
__param(2, injectAll(TERMINAL_COMMAND)),
|
|
2788
|
+
__metadata("design:paramtypes", [Logger,
|
|
2789
|
+
Configuration, Array])
|
|
2790
|
+
], TerminalManager);
|
|
2791
|
+
|
|
2792
|
+
let TokenGenerator = class TokenGenerator {
|
|
2793
|
+
chars;
|
|
2794
|
+
constructor() {
|
|
2795
|
+
this.chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
2796
|
+
}
|
|
2797
|
+
async generate(checkCb) {
|
|
2798
|
+
let token = null;
|
|
2799
|
+
let tries = 0;
|
|
2800
|
+
let notGood = true;
|
|
2801
|
+
while (notGood && tries < 5) {
|
|
2802
|
+
token = this.generateToken();
|
|
2803
|
+
notGood = await checkCb(token);
|
|
2804
|
+
tries++;
|
|
2805
|
+
}
|
|
2806
|
+
if (notGood) {
|
|
2807
|
+
throw new Error(`Couldn't generate an unique token..`);
|
|
2808
|
+
}
|
|
2809
|
+
return token;
|
|
2810
|
+
}
|
|
2811
|
+
generateToken() {
|
|
2812
|
+
let s = "";
|
|
2813
|
+
for (let i = 0; i < 15; i++) {
|
|
2814
|
+
const ix = rand(0, this.chars.length - 1);
|
|
2815
|
+
s += this.chars[ix];
|
|
2816
|
+
}
|
|
2817
|
+
return s;
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
TokenGenerator = __decorate([
|
|
2821
|
+
singleton(),
|
|
2822
|
+
__metadata("design:paramtypes", [])
|
|
2823
|
+
], TokenGenerator);
|
|
2824
|
+
|
|
2825
|
+
const sampleUser = {
|
|
2826
|
+
id: "5a3cdf7c6a9cf0ba32feccdf",
|
|
2827
|
+
email: "admin@site.com",
|
|
2828
|
+
password: "",
|
|
2829
|
+
roles: ["admin"]
|
|
2830
|
+
};
|
|
2831
|
+
let UserManager = class UserManager {
|
|
2832
|
+
async getByCredentials(credentials) {
|
|
2833
|
+
return (sampleUser.email == credentials.email) ? sampleUser : await Promise.reject("message.login.error");
|
|
2834
|
+
}
|
|
2835
|
+
async getById(id) {
|
|
2836
|
+
return (sampleUser.id == id) ? sampleUser : null;
|
|
2837
|
+
}
|
|
2838
|
+
async serialize(user) {
|
|
2839
|
+
const res = Object.assign({}, user);
|
|
2840
|
+
delete res.password;
|
|
2841
|
+
return res;
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
UserManager = __decorate([
|
|
2845
|
+
injectable(),
|
|
2846
|
+
scoped(Lifecycle.ContainerScoped)
|
|
2847
|
+
], UserManager);
|
|
2848
|
+
|
|
2849
|
+
class AssetImageParams {
|
|
2850
|
+
rotation = 0;
|
|
2851
|
+
canvasScaleX = 1;
|
|
2852
|
+
canvasScaleY = 1;
|
|
2853
|
+
scaleX = 1;
|
|
2854
|
+
scaleY = 1;
|
|
2855
|
+
lazy = false;
|
|
2856
|
+
crop = false;
|
|
2857
|
+
cropBefore = false;
|
|
2858
|
+
cropAfter = false;
|
|
2859
|
+
}
|
|
2860
|
+
__decorate([
|
|
2861
|
+
Min(-360),
|
|
2862
|
+
Max(360),
|
|
2863
|
+
IsOptional(),
|
|
2864
|
+
__metadata("design:type", Number)
|
|
2865
|
+
], AssetImageParams.prototype, "rotation", void 0);
|
|
2866
|
+
__decorate([
|
|
2867
|
+
Min(0.0001),
|
|
2868
|
+
IsOptional(),
|
|
2869
|
+
__metadata("design:type", Number)
|
|
2870
|
+
], AssetImageParams.prototype, "canvasScaleX", void 0);
|
|
2871
|
+
__decorate([
|
|
2872
|
+
Min(0.0001),
|
|
2873
|
+
IsOptional(),
|
|
2874
|
+
__metadata("design:type", Number)
|
|
2875
|
+
], AssetImageParams.prototype, "canvasScaleY", void 0);
|
|
2876
|
+
__decorate([
|
|
2877
|
+
Min(0.0001),
|
|
2878
|
+
IsOptional(),
|
|
2879
|
+
__metadata("design:type", Number)
|
|
2880
|
+
], AssetImageParams.prototype, "scaleX", void 0);
|
|
2881
|
+
__decorate([
|
|
2882
|
+
Min(0.0001),
|
|
2883
|
+
IsOptional(),
|
|
2884
|
+
__metadata("design:type", Number)
|
|
2885
|
+
], AssetImageParams.prototype, "scaleY", void 0);
|
|
2886
|
+
__decorate([
|
|
2887
|
+
IsBoolean(),
|
|
2888
|
+
IsOptional(),
|
|
2889
|
+
__metadata("design:type", Boolean)
|
|
2890
|
+
], AssetImageParams.prototype, "lazy", void 0);
|
|
2891
|
+
__decorate([
|
|
2892
|
+
IsBoolean(),
|
|
2893
|
+
IsOptional(),
|
|
2894
|
+
__metadata("design:type", Boolean)
|
|
2895
|
+
], AssetImageParams.prototype, "crop", void 0);
|
|
2896
|
+
__decorate([
|
|
2897
|
+
IsBoolean(),
|
|
2898
|
+
IsOptional(),
|
|
2899
|
+
__metadata("design:type", Boolean)
|
|
2900
|
+
], AssetImageParams.prototype, "cropBefore", void 0);
|
|
2901
|
+
__decorate([
|
|
2902
|
+
IsBoolean(),
|
|
2903
|
+
IsOptional(),
|
|
2904
|
+
__metadata("design:type", Boolean)
|
|
2905
|
+
], AssetImageParams.prototype, "cropAfter", void 0);
|
|
2906
|
+
|
|
2907
|
+
let AssetsController = class AssetsController {
|
|
2908
|
+
assets;
|
|
2909
|
+
assetResolver;
|
|
2910
|
+
constructor(assets, assetResolver) {
|
|
2911
|
+
this.assets = assets;
|
|
2912
|
+
this.assetResolver = assetResolver;
|
|
2913
|
+
}
|
|
2914
|
+
async upload(file) {
|
|
2915
|
+
try {
|
|
2916
|
+
const contentType = file.mimetype === "application/octet-stream" ? null : file.mimetype;
|
|
2917
|
+
const asset = await this.assets.writeBuffer(file.buffer, { filename: file.filename }, contentType);
|
|
2918
|
+
return asset.toJSON();
|
|
2919
|
+
}
|
|
2920
|
+
catch (e) {
|
|
2921
|
+
const msg = e?.message || e || "Unknown error";
|
|
2922
|
+
throw new HttpError(400, `Asset can't be uploaded.\n${msg}`);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
async uploadUrl(body) {
|
|
2926
|
+
try {
|
|
2927
|
+
const asset = await this.assets.writeUrl(body.url, body);
|
|
2928
|
+
return asset.toJSON();
|
|
2929
|
+
}
|
|
2930
|
+
catch (e) {
|
|
2931
|
+
const msg = e?.message || e || "Unknown error";
|
|
2932
|
+
throw new HttpError(400, `Asset can't be uploaded.\n${msg}`);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
async getFile(id, lazy, res) {
|
|
2936
|
+
const asset = await this.getAsset("Asset", id, lazy, res);
|
|
2937
|
+
return asset.download();
|
|
2938
|
+
}
|
|
2939
|
+
async getMetadata(id, lazy, res) {
|
|
2940
|
+
const asset = await this.assetResolver.resolve(id, lazy);
|
|
2941
|
+
if (!asset) {
|
|
2942
|
+
throw new HttpError(404, `Asset with id: '${id}' not found.`);
|
|
2943
|
+
}
|
|
2944
|
+
return asset.metadata;
|
|
2945
|
+
}
|
|
2946
|
+
async getImageRotation(id, params, res, rotation = 0) {
|
|
2947
|
+
const asset = await this.getAsset("Image", id, params.lazy, res);
|
|
2948
|
+
if (rotation !== 0) {
|
|
2949
|
+
params.rotation = params.rotation || rotation;
|
|
2950
|
+
}
|
|
2951
|
+
return asset.downloadImage(params);
|
|
2952
|
+
}
|
|
2953
|
+
async getImage(id, params, res) {
|
|
2954
|
+
return this.getImageRotation(id, params, res);
|
|
2955
|
+
}
|
|
2956
|
+
async getFileByName(name, res) {
|
|
2957
|
+
const asset = await this.getAssetByName("Asset", name, res);
|
|
2958
|
+
return asset.download();
|
|
2959
|
+
}
|
|
2960
|
+
async getImageByName(name, params, res) {
|
|
2961
|
+
const asset = await this.getAssetByName("Image", name, res);
|
|
2962
|
+
return asset.downloadImage(params);
|
|
2963
|
+
}
|
|
2964
|
+
setAssetHeaders(asset, res) {
|
|
2965
|
+
const ext = asset.metadata?.extension;
|
|
2966
|
+
if (ext) {
|
|
2967
|
+
res.header("content-disposition", `inline; filename=${asset.filename}.${ext}`);
|
|
2968
|
+
}
|
|
2969
|
+
if (asset.contentType) {
|
|
2970
|
+
res.header("content-type", asset.contentType);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
async getAsset(type, id, lazy, res) {
|
|
2974
|
+
let asset = await this.assetResolver.resolve(id, lazy);
|
|
2975
|
+
if (!asset) {
|
|
2976
|
+
throw new HttpError(404, `${type} with id: '${id}' not found.`);
|
|
2977
|
+
}
|
|
2978
|
+
asset = await this.resolveFinalAsset(type, asset);
|
|
2979
|
+
this.setAssetHeaders(asset, res);
|
|
2980
|
+
return asset;
|
|
2981
|
+
}
|
|
2982
|
+
async getAssetByName(type, filename, res) {
|
|
2983
|
+
let asset = await this.assets.find({ filename });
|
|
2984
|
+
if (!asset) {
|
|
2985
|
+
throw new HttpError(404, `${type} with filename: '${filename}' not found.`);
|
|
2986
|
+
}
|
|
2987
|
+
asset = await this.resolveFinalAsset(type, asset);
|
|
2988
|
+
this.setAssetHeaders(asset, res);
|
|
2989
|
+
return asset;
|
|
2990
|
+
}
|
|
2991
|
+
async resolveFinalAsset(type, asset) {
|
|
2992
|
+
if (asset.metadata?.classified) {
|
|
2993
|
+
throw new HttpError(403, `${type} is classified, and can be only downloaded from a custom url.`);
|
|
2994
|
+
}
|
|
2995
|
+
if (type == 'Image' && asset.metadata.preview) {
|
|
2996
|
+
return this.resolveFinalAsset(type, await this.assetResolver.resolve(asset.metadata.preview));
|
|
2997
|
+
}
|
|
2998
|
+
return asset;
|
|
2999
|
+
}
|
|
3000
|
+
};
|
|
3001
|
+
__decorate([
|
|
3002
|
+
Authorized(),
|
|
3003
|
+
Post(""),
|
|
3004
|
+
__param(0, UploadedFile("file")),
|
|
3005
|
+
__metadata("design:type", Function),
|
|
3006
|
+
__metadata("design:paramtypes", [Object]),
|
|
3007
|
+
__metadata("design:returntype", Promise)
|
|
3008
|
+
], AssetsController.prototype, "upload", null);
|
|
3009
|
+
__decorate([
|
|
3010
|
+
Authorized(),
|
|
3011
|
+
Post("url"),
|
|
3012
|
+
__param(0, Body()),
|
|
3013
|
+
__metadata("design:type", Function),
|
|
3014
|
+
__metadata("design:paramtypes", [Object]),
|
|
3015
|
+
__metadata("design:returntype", Promise)
|
|
3016
|
+
], AssetsController.prototype, "uploadUrl", null);
|
|
3017
|
+
__decorate([
|
|
3018
|
+
Get("/:id"),
|
|
3019
|
+
__param(0, Param("id")),
|
|
3020
|
+
__param(1, QueryParam("lazy")),
|
|
3021
|
+
__param(2, Res()),
|
|
3022
|
+
__metadata("design:type", Function),
|
|
3023
|
+
__metadata("design:paramtypes", [String, Boolean, Object]),
|
|
3024
|
+
__metadata("design:returntype", Promise)
|
|
3025
|
+
], AssetsController.prototype, "getFile", null);
|
|
3026
|
+
__decorate([
|
|
3027
|
+
Get("/metadata/:id"),
|
|
3028
|
+
__param(0, Param("id")),
|
|
3029
|
+
__param(1, QueryParam("lazy")),
|
|
3030
|
+
__param(2, Res()),
|
|
3031
|
+
__metadata("design:type", Function),
|
|
3032
|
+
__metadata("design:paramtypes", [String, Boolean, Object]),
|
|
3033
|
+
__metadata("design:returntype", Promise)
|
|
3034
|
+
], AssetsController.prototype, "getMetadata", null);
|
|
3035
|
+
__decorate([
|
|
3036
|
+
Get("/image/:id/:rotation"),
|
|
3037
|
+
__param(0, Param("id")),
|
|
3038
|
+
__param(1, QueryParams()),
|
|
3039
|
+
__param(2, Res()),
|
|
3040
|
+
__param(3, Param("rotation")),
|
|
3041
|
+
__metadata("design:type", Function),
|
|
3042
|
+
__metadata("design:paramtypes", [String, AssetImageParams, Object, Number]),
|
|
3043
|
+
__metadata("design:returntype", Promise)
|
|
3044
|
+
], AssetsController.prototype, "getImageRotation", null);
|
|
3045
|
+
__decorate([
|
|
3046
|
+
Get("/image/:id"),
|
|
3047
|
+
__param(0, Param("id")),
|
|
3048
|
+
__param(1, QueryParams()),
|
|
3049
|
+
__param(2, Res()),
|
|
3050
|
+
__metadata("design:type", Function),
|
|
3051
|
+
__metadata("design:paramtypes", [String, AssetImageParams, Object]),
|
|
3052
|
+
__metadata("design:returntype", Promise)
|
|
3053
|
+
], AssetsController.prototype, "getImage", null);
|
|
3054
|
+
__decorate([
|
|
3055
|
+
Get("/by-name/:name"),
|
|
3056
|
+
__param(0, Param("name")),
|
|
3057
|
+
__param(1, Res()),
|
|
3058
|
+
__metadata("design:type", Function),
|
|
3059
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
3060
|
+
__metadata("design:returntype", Promise)
|
|
3061
|
+
], AssetsController.prototype, "getFileByName", null);
|
|
3062
|
+
__decorate([
|
|
3063
|
+
Get("/by-name/image/:name"),
|
|
3064
|
+
__param(0, Param("name")),
|
|
3065
|
+
__param(1, QueryParams()),
|
|
3066
|
+
__param(2, Res()),
|
|
3067
|
+
__metadata("design:type", Function),
|
|
3068
|
+
__metadata("design:paramtypes", [String, AssetImageParams, Object]),
|
|
3069
|
+
__metadata("design:returntype", Promise)
|
|
3070
|
+
], AssetsController.prototype, "getImageByName", null);
|
|
3071
|
+
AssetsController = __decorate([
|
|
3072
|
+
injectable(),
|
|
3073
|
+
Controller("/assets"),
|
|
3074
|
+
__metadata("design:paramtypes", [Assets, AssetResolver])
|
|
3075
|
+
], AssetsController);
|
|
3076
|
+
|
|
3077
|
+
let AuthController = class AuthController {
|
|
3078
|
+
config;
|
|
3079
|
+
userManager;
|
|
3080
|
+
constructor(config, userManager) {
|
|
3081
|
+
this.config = config;
|
|
3082
|
+
this.userManager = userManager;
|
|
3083
|
+
}
|
|
3084
|
+
async login(credentials, res) {
|
|
3085
|
+
let user = null;
|
|
3086
|
+
try {
|
|
3087
|
+
user = await this.userManager.getByCredentials(credentials);
|
|
3088
|
+
}
|
|
3089
|
+
catch (reason) {
|
|
3090
|
+
throw new HttpError(401, reason);
|
|
3091
|
+
}
|
|
3092
|
+
const valid = !user ? false : await compare(credentials.password, user.password);
|
|
3093
|
+
if (valid !== true)
|
|
3094
|
+
throw new UnauthorizedError(`message.login.error`);
|
|
3095
|
+
return {
|
|
3096
|
+
token: webToken.sign({ id: user._id || user.id }, this.config.resolve("jwtSecret")),
|
|
3097
|
+
user: await this.userManager.serialize(user)
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
3100
|
+
getProfile(user) {
|
|
3101
|
+
return this.userManager.serialize(user);
|
|
3102
|
+
}
|
|
3103
|
+
};
|
|
3104
|
+
__decorate([
|
|
3105
|
+
Post("/login"),
|
|
3106
|
+
__param(0, Body()),
|
|
3107
|
+
__param(1, Res()),
|
|
3108
|
+
__metadata("design:type", Function),
|
|
3109
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
3110
|
+
__metadata("design:returntype", Promise)
|
|
3111
|
+
], AuthController.prototype, "login", null);
|
|
3112
|
+
__decorate([
|
|
3113
|
+
Authorized(),
|
|
3114
|
+
Get("/user"),
|
|
3115
|
+
__param(0, CurrentUser()),
|
|
3116
|
+
__metadata("design:type", Function),
|
|
3117
|
+
__metadata("design:paramtypes", [Object]),
|
|
3118
|
+
__metadata("design:returntype", void 0)
|
|
3119
|
+
], AuthController.prototype, "getProfile", null);
|
|
3120
|
+
AuthController = __decorate([
|
|
3121
|
+
injectable(),
|
|
3122
|
+
Controller(),
|
|
3123
|
+
__metadata("design:paramtypes", [Configuration, UserManager])
|
|
3124
|
+
], AuthController);
|
|
3125
|
+
|
|
3126
|
+
let GalleryController = class GalleryController {
|
|
3127
|
+
galleryCache;
|
|
3128
|
+
constructor(galleryCache) {
|
|
3129
|
+
this.galleryCache = galleryCache;
|
|
3130
|
+
}
|
|
3131
|
+
getFile(id) {
|
|
3132
|
+
return this.galleryCache.serve(id);
|
|
3133
|
+
}
|
|
3134
|
+
};
|
|
3135
|
+
__decorate([
|
|
3136
|
+
Get("/:id"),
|
|
3137
|
+
__param(0, Param("id")),
|
|
3138
|
+
__metadata("design:type", Function),
|
|
3139
|
+
__metadata("design:paramtypes", [String]),
|
|
3140
|
+
__metadata("design:returntype", void 0)
|
|
3141
|
+
], GalleryController.prototype, "getFile", null);
|
|
3142
|
+
GalleryController = __decorate([
|
|
3143
|
+
injectable(),
|
|
3144
|
+
Controller("/gallery"),
|
|
3145
|
+
__metadata("design:paramtypes", [GalleryCache])
|
|
3146
|
+
], GalleryController);
|
|
3147
|
+
|
|
3148
|
+
let ProgressesController = class ProgressesController {
|
|
3149
|
+
progresses;
|
|
3150
|
+
config;
|
|
3151
|
+
connectionType;
|
|
3152
|
+
constructor(progresses, config) {
|
|
3153
|
+
this.progresses = progresses;
|
|
3154
|
+
this.config = config;
|
|
3155
|
+
const mainEndpoint = this.config.resolve("mainEndpoint");
|
|
3156
|
+
this.connectionType = !mainEndpoint ? "polling" : "socket";
|
|
3157
|
+
}
|
|
3158
|
+
async getProgress(id) {
|
|
3159
|
+
const progress = await this.progresses.get(id);
|
|
3160
|
+
if (!progress)
|
|
3161
|
+
return null;
|
|
3162
|
+
const json = progress.toJSON();
|
|
3163
|
+
json.connectionType = this.connectionType;
|
|
3164
|
+
return json;
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
3167
|
+
__decorate([
|
|
3168
|
+
Get("/:id"),
|
|
3169
|
+
__param(0, Param("id")),
|
|
3170
|
+
__metadata("design:type", Function),
|
|
3171
|
+
__metadata("design:paramtypes", [String]),
|
|
3172
|
+
__metadata("design:returntype", Promise)
|
|
3173
|
+
], ProgressesController.prototype, "getProgress", null);
|
|
3174
|
+
ProgressesController = __decorate([
|
|
3175
|
+
injectable(),
|
|
3176
|
+
Controller("/progresses"),
|
|
3177
|
+
__metadata("design:paramtypes", [Progresses, Configuration])
|
|
3178
|
+
], ProgressesController);
|
|
3179
|
+
|
|
3180
|
+
// Add a comment hint for webstorm to style the string as css
|
|
3181
|
+
var styles = `
|
|
3182
|
+
|
|
3183
|
+
body {
|
|
3184
|
+
margin: 0;
|
|
3185
|
+
padding: 0;
|
|
3186
|
+
background: #1e1e1e;
|
|
3187
|
+
}
|
|
3188
|
+
body * {
|
|
3189
|
+
box-sizing: border-box;
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
#terminal {
|
|
3193
|
+
margin: 40px;
|
|
3194
|
+
background: black;
|
|
3195
|
+
border: 1px solid #dedede;
|
|
3196
|
+
border-radius: 5px;
|
|
3197
|
+
overflow: hidden;
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
#terminal .xterm {
|
|
3201
|
+
margin: 10px;
|
|
3202
|
+
height: calc(100vh - 120px);
|
|
3203
|
+
border-radius: 5px;
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
#terminal .xterm-viewport::-webkit-scrollbar {
|
|
3207
|
+
width: 0.4em;
|
|
3208
|
+
background-color: #222;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
#terminal .xterm-viewport::-webkit-scrollbar-thumb {
|
|
3212
|
+
background-color: #555;
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
#terminal .window-header {
|
|
3216
|
+
display: flex;
|
|
3217
|
+
align-items: center;
|
|
3218
|
+
justify-content: flex-end;
|
|
3219
|
+
background-color: #f1f1f1;
|
|
3220
|
+
padding: 10px;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
#terminal .window-header button {
|
|
3224
|
+
background-color: transparent;
|
|
3225
|
+
border: none;
|
|
3226
|
+
font-size: 14px;
|
|
3227
|
+
font-weight: bold;
|
|
3228
|
+
color: #444;
|
|
3229
|
+
cursor: pointer;
|
|
3230
|
+
margin-left: 10px;
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
.minimize-button {
|
|
3234
|
+
content: "-";
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
.maximize-button {
|
|
3238
|
+
content: "+";
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
.close-button {
|
|
3242
|
+
content: "x";
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
`;
|
|
3246
|
+
|
|
3247
|
+
let TerminalController$1 = class TerminalController {
|
|
3248
|
+
config;
|
|
3249
|
+
serviceName;
|
|
3250
|
+
serviceUrl;
|
|
3251
|
+
constructor(config) {
|
|
3252
|
+
this.config = config;
|
|
3253
|
+
this.serviceName = config.resolve("serviceName");
|
|
3254
|
+
this.serviceUrl = config.resolve("serviceUrl");
|
|
3255
|
+
}
|
|
3256
|
+
terminal() {
|
|
3257
|
+
return this.generateClient("terminal");
|
|
3258
|
+
}
|
|
3259
|
+
console() {
|
|
3260
|
+
return this.generateClient("console");
|
|
3261
|
+
}
|
|
3262
|
+
generateClient(alias) {
|
|
3263
|
+
return `
|
|
3264
|
+
<!DOCTYPE html>
|
|
3265
|
+
<html>
|
|
3266
|
+
<head>
|
|
3267
|
+
<meta charset="utf-8">
|
|
3268
|
+
<title>${this.serviceName} ${alias}</title>
|
|
3269
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.1.0/css/xterm.min.css"/>
|
|
3270
|
+
<script type="text/javascript" src="${this.serviceUrl}/socket/socket.io.js"></script>
|
|
3271
|
+
<style>
|
|
3272
|
+
${styles}
|
|
3273
|
+
</style>
|
|
3274
|
+
</head>
|
|
3275
|
+
<body>
|
|
3276
|
+
<div id="terminal"></div>
|
|
3277
|
+
<script type="module">
|
|
3278
|
+
// Import terminal modules
|
|
3279
|
+
import xterm from 'https://cdn.jsdelivr.net/npm/xterm@5.1.0/+esm';
|
|
3280
|
+
import xtermFit from 'https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/+esm';
|
|
3281
|
+
// Initialize variables
|
|
3282
|
+
var terminal = new xterm.Terminal();
|
|
3283
|
+
var fitAddon = new xtermFit.FitAddon();
|
|
3284
|
+
var socket = io("${this.serviceUrl}", {path: "/socket"});
|
|
3285
|
+
var clear = function () {
|
|
3286
|
+
terminal.clear();
|
|
3287
|
+
terminal.reset();
|
|
3288
|
+
// Prev line
|
|
3289
|
+
terminal.write("\u001B[F");
|
|
3290
|
+
}
|
|
3291
|
+
// Initialize terminal
|
|
3292
|
+
terminal.loadAddon(fitAddon);
|
|
3293
|
+
terminal.open(document.getElementById('terminal'));
|
|
3294
|
+
terminal.onData(function (data) {
|
|
3295
|
+
socket.emit("terminal-data", data.charCodeAt(0) === 127 ? "\b" : data);
|
|
3296
|
+
});
|
|
3297
|
+
// Socket listeners
|
|
3298
|
+
socket.on("terminal-data", function (data) {
|
|
3299
|
+
if (data === "\x1bc") {
|
|
3300
|
+
clear();
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
terminal.write(data);
|
|
3304
|
+
});
|
|
3305
|
+
socket.on("terminal-upload", function (data) {
|
|
3306
|
+
var input = document.createElement("input");
|
|
3307
|
+
input.type = "file";
|
|
3308
|
+
input.accept = data.accept;
|
|
3309
|
+
input.onchange = function () {
|
|
3310
|
+
var file = input.files[0];
|
|
3311
|
+
var reader = new FileReader();
|
|
3312
|
+
reader.onload = function () {
|
|
3313
|
+
socket.emit("terminal-upload", {
|
|
3314
|
+
id: data.id,
|
|
3315
|
+
label: file.name,
|
|
3316
|
+
content: reader.result,
|
|
3317
|
+
accept: data.accept
|
|
3318
|
+
});
|
|
3319
|
+
};
|
|
3320
|
+
reader.onerror = function () {
|
|
3321
|
+
socket.emit("terminal-upload", {
|
|
3322
|
+
id: data.id,
|
|
3323
|
+
label: file.name,
|
|
3324
|
+
error: reader.error,
|
|
3325
|
+
accept: data.accept
|
|
3326
|
+
});
|
|
3327
|
+
};
|
|
3328
|
+
reader.readAsDataURL(file);
|
|
3329
|
+
};
|
|
3330
|
+
input.click();
|
|
3331
|
+
});
|
|
3332
|
+
socket.on("terminal-download", function (data) {
|
|
3333
|
+
var link = document.createElement("a");
|
|
3334
|
+
link.href = data.content;
|
|
3335
|
+
link.download = data.filename;
|
|
3336
|
+
link.click();
|
|
3337
|
+
});
|
|
3338
|
+
socket.on("connect", function () {
|
|
3339
|
+
clear();
|
|
3340
|
+
terminal.writeln("Welcome to ${this.serviceName} service's ${alias}!");
|
|
3341
|
+
socket.emit("terminal-init");
|
|
3342
|
+
});
|
|
3343
|
+
socket.on("disconnect", function () {
|
|
3344
|
+
clear();
|
|
3345
|
+
terminal.writeln("Disconnected from ${this.serviceName} service's ${alias}.");
|
|
3346
|
+
});
|
|
3347
|
+
</script>
|
|
3348
|
+
</body>
|
|
3349
|
+
</html>
|
|
3350
|
+
`;
|
|
3351
|
+
}
|
|
3352
|
+
};
|
|
3353
|
+
__decorate([
|
|
3354
|
+
Get("/terminal"),
|
|
3355
|
+
Header("Content-Type", "text/html"),
|
|
3356
|
+
__metadata("design:type", Function),
|
|
3357
|
+
__metadata("design:paramtypes", []),
|
|
3358
|
+
__metadata("design:returntype", void 0)
|
|
3359
|
+
], TerminalController$1.prototype, "terminal", null);
|
|
3360
|
+
__decorate([
|
|
3361
|
+
Get("/console"),
|
|
3362
|
+
Header("Content-Type", "text/html"),
|
|
3363
|
+
__metadata("design:type", Function),
|
|
3364
|
+
__metadata("design:paramtypes", []),
|
|
3365
|
+
__metadata("design:returntype", void 0)
|
|
3366
|
+
], TerminalController$1.prototype, "console", null);
|
|
3367
|
+
TerminalController$1 = __decorate([
|
|
3368
|
+
injectable(),
|
|
3369
|
+
Controller(),
|
|
3370
|
+
__metadata("design:paramtypes", [Configuration])
|
|
3371
|
+
], TerminalController$1);
|
|
3372
|
+
|
|
3373
|
+
let ErrorHandlerMiddleware = class ErrorHandlerMiddleware {
|
|
3374
|
+
configuration;
|
|
3375
|
+
translator;
|
|
3376
|
+
get isDev() {
|
|
3377
|
+
return this.configuration.resolve("nodeEnv") === "development";
|
|
3378
|
+
}
|
|
3379
|
+
constructor(configuration, translator) {
|
|
3380
|
+
this.configuration = configuration;
|
|
3381
|
+
this.translator = translator;
|
|
3382
|
+
}
|
|
3383
|
+
async error(error, req, res, next) {
|
|
3384
|
+
const result = await this.getResult(error, req, res);
|
|
3385
|
+
if (this.isDev) {
|
|
3386
|
+
if (axios.isAxiosError(error)) {
|
|
3387
|
+
console.log(`Axios error:`, result);
|
|
3388
|
+
console.log(error.config.data);
|
|
3389
|
+
}
|
|
3390
|
+
else {
|
|
3391
|
+
console.log(error.constructor?.name || "Error", result, res.statusCode);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
res.json(result);
|
|
3395
|
+
}
|
|
3396
|
+
async getResult(error, req, res) {
|
|
3397
|
+
const result = {};
|
|
3398
|
+
if (axios.isAxiosError(error)) {
|
|
3399
|
+
res.status(error.response.status);
|
|
3400
|
+
result.message = error.message;
|
|
3401
|
+
result.error = error.response.data;
|
|
3402
|
+
result.headers = error.response.headers;
|
|
3403
|
+
result.url = `${error.config.baseURL}${error.config.url}`;
|
|
3404
|
+
return result;
|
|
3405
|
+
}
|
|
3406
|
+
if (error instanceof BadRequestError) {
|
|
3407
|
+
res.status(400);
|
|
3408
|
+
if (error.constructor.name === "ParamRequiredError") {
|
|
3409
|
+
result.message = await this.translator.getTranslation(req.language, "message.parameter-required.error");
|
|
3410
|
+
result.param = error.message;
|
|
3411
|
+
}
|
|
3412
|
+
else {
|
|
3413
|
+
result.message = error.message || await this.translator.getTranslation(req.language, "message.form-validation.error");
|
|
3414
|
+
result.errors = error["errors"];
|
|
3415
|
+
if (error.stack && this.isDev) {
|
|
3416
|
+
result.stack = error.stack;
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
return result;
|
|
3420
|
+
}
|
|
3421
|
+
res.status(error.httpCode || 500);
|
|
3422
|
+
if (error instanceof Error) {
|
|
3423
|
+
if (error.name) {
|
|
3424
|
+
result.name = error.name;
|
|
3425
|
+
}
|
|
3426
|
+
if (error.message) {
|
|
3427
|
+
result.message = error.message;
|
|
3428
|
+
}
|
|
3429
|
+
if (error.stack && this.isDev) {
|
|
3430
|
+
result.stack = error.stack;
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
else if (typeof error === "string") {
|
|
3434
|
+
result.message = error;
|
|
3435
|
+
}
|
|
3436
|
+
return result;
|
|
3437
|
+
}
|
|
3438
|
+
};
|
|
3439
|
+
ErrorHandlerMiddleware = __decorate([
|
|
3440
|
+
injectable(),
|
|
3441
|
+
Middleware({ type: "after" }),
|
|
3442
|
+
__metadata("design:paramtypes", [Configuration, Translator])
|
|
3443
|
+
], ErrorHandlerMiddleware);
|
|
3444
|
+
|
|
3445
|
+
let ContainerMiddleware = class ContainerMiddleware {
|
|
3446
|
+
container;
|
|
3447
|
+
constructor(container) {
|
|
3448
|
+
this.container = container;
|
|
3449
|
+
}
|
|
3450
|
+
use(request, response, next) {
|
|
3451
|
+
request.container = this.container;
|
|
3452
|
+
next(null);
|
|
3453
|
+
}
|
|
3454
|
+
};
|
|
3455
|
+
ContainerMiddleware = __decorate([
|
|
3456
|
+
injectable(),
|
|
3457
|
+
Middleware({ type: "before" }),
|
|
3458
|
+
__param(0, inject(DI_CONTAINER)),
|
|
3459
|
+
__metadata("design:paramtypes", [Object])
|
|
3460
|
+
], ContainerMiddleware);
|
|
3461
|
+
|
|
3462
|
+
let LanguageMiddleware = class LanguageMiddleware {
|
|
3463
|
+
config;
|
|
3464
|
+
constructor(config) {
|
|
3465
|
+
this.config = config;
|
|
3466
|
+
}
|
|
3467
|
+
use(request, response, next) {
|
|
3468
|
+
request.language = request.query.language || this.config.resolve("defaultLanguage");
|
|
3469
|
+
next(null);
|
|
3470
|
+
}
|
|
3471
|
+
};
|
|
3472
|
+
LanguageMiddleware = __decorate([
|
|
3473
|
+
injectable(),
|
|
3474
|
+
Middleware({ type: "before" }),
|
|
3475
|
+
__metadata("design:paramtypes", [Configuration])
|
|
3476
|
+
], LanguageMiddleware);
|
|
3477
|
+
|
|
3478
|
+
let RequestEndedMiddleware = class RequestEndedMiddleware {
|
|
3479
|
+
logger;
|
|
3480
|
+
constructor(logger) {
|
|
3481
|
+
this.logger = logger;
|
|
3482
|
+
}
|
|
3483
|
+
use(request, response, next) {
|
|
3484
|
+
request.ended = moment();
|
|
3485
|
+
const diff = request.ended.diff(request.started);
|
|
3486
|
+
const duration = moment.duration(diff);
|
|
3487
|
+
this.logger.log("request-time", `Request '${request.id}' ended at: ${request.ended.format("YYYY-MM-DD HH:mm:ss")} [${request.method}] ${request.url}`);
|
|
3488
|
+
this.logger.log("request-time", `Request '${request.id}' lasted: ${duration.asMilliseconds()}ms`);
|
|
3489
|
+
next(null);
|
|
3490
|
+
}
|
|
3491
|
+
};
|
|
3492
|
+
RequestEndedMiddleware = __decorate([
|
|
3493
|
+
injectable(),
|
|
3494
|
+
Middleware({ type: "after" }),
|
|
3495
|
+
__metadata("design:paramtypes", [Logger])
|
|
3496
|
+
], RequestEndedMiddleware);
|
|
3497
|
+
|
|
3498
|
+
let RequestStartedMiddleware = class RequestStartedMiddleware {
|
|
3499
|
+
logger;
|
|
3500
|
+
constructor(logger) {
|
|
3501
|
+
this.logger = logger;
|
|
3502
|
+
}
|
|
3503
|
+
use(request, response, next) {
|
|
3504
|
+
request.id = new ObjectId$1().toHexString();
|
|
3505
|
+
request.started = moment();
|
|
3506
|
+
this.logger.log("request-time", `Request '${request.id}' started at: ${request.started.format("YYYY-MM-DD HH:mm:ss")} [${request.method}] ${request.url}`);
|
|
3507
|
+
next(null);
|
|
3508
|
+
}
|
|
3509
|
+
};
|
|
3510
|
+
RequestStartedMiddleware = __decorate([
|
|
3511
|
+
injectable(),
|
|
3512
|
+
Middleware({ type: "before" }),
|
|
3513
|
+
__metadata("design:paramtypes", [Logger])
|
|
3514
|
+
], RequestStartedMiddleware);
|
|
3515
|
+
|
|
3516
|
+
let ProgressController = class ProgressController {
|
|
3517
|
+
progresses;
|
|
3518
|
+
socketServer;
|
|
3519
|
+
constructor(progresses, socketServer) {
|
|
3520
|
+
this.progresses = progresses;
|
|
3521
|
+
this.socketServer = socketServer;
|
|
3522
|
+
}
|
|
3523
|
+
async advanceProgress(client, progressId) {
|
|
3524
|
+
const progress = await this.progresses.get(progressId);
|
|
3525
|
+
if (!progress)
|
|
3526
|
+
return;
|
|
3527
|
+
const json = progress.toJSON();
|
|
3528
|
+
broadcast(this.socketServer, c => {
|
|
3529
|
+
if (c.interestedProgresses instanceof Set && c.interestedProgresses.has(progressId)) {
|
|
3530
|
+
client.emit("background-progress-changed", json);
|
|
3531
|
+
}
|
|
3532
|
+
});
|
|
3533
|
+
console.log(`progress changed: ${client.id}, data: ${JSON.stringify(json)}`);
|
|
3534
|
+
}
|
|
3535
|
+
async setProgressInterest(client, progressId) {
|
|
3536
|
+
const progress = await this.progresses.get(progressId);
|
|
3537
|
+
if (!progress)
|
|
3538
|
+
return;
|
|
3539
|
+
const json = progress.toJSON();
|
|
3540
|
+
client.interestedProgresses = client.interestedProgresses || new Set();
|
|
3541
|
+
if (client.interestedProgresses.has(progressId))
|
|
3542
|
+
return;
|
|
3543
|
+
client.interestedProgresses.add(progressId);
|
|
3544
|
+
client.emit("background-progress-changed", json);
|
|
3545
|
+
console.log(`progress interest added: ${client.id}, data: ${JSON.stringify(json)}`);
|
|
3546
|
+
}
|
|
3547
|
+
};
|
|
3548
|
+
__decorate([
|
|
3549
|
+
OnMessage("background-progress"),
|
|
3550
|
+
__param(0, ConnectedSocket()),
|
|
3551
|
+
__param(1, MessageBody()),
|
|
3552
|
+
__metadata("design:type", Function),
|
|
3553
|
+
__metadata("design:paramtypes", [Object, String]),
|
|
3554
|
+
__metadata("design:returntype", Promise)
|
|
3555
|
+
], ProgressController.prototype, "advanceProgress", null);
|
|
3556
|
+
__decorate([
|
|
3557
|
+
OnMessage("background-progress-interest"),
|
|
3558
|
+
__param(0, ConnectedSocket()),
|
|
3559
|
+
__param(1, MessageBody()),
|
|
3560
|
+
__metadata("design:type", Function),
|
|
3561
|
+
__metadata("design:paramtypes", [Object, String]),
|
|
3562
|
+
__metadata("design:returntype", Promise)
|
|
3563
|
+
], ProgressController.prototype, "setProgressInterest", null);
|
|
3564
|
+
ProgressController = __decorate([
|
|
3565
|
+
singleton(),
|
|
3566
|
+
SocketController(),
|
|
3567
|
+
__param(1, inject(SOCKET_SERVER)),
|
|
3568
|
+
__metadata("design:paramtypes", [Progresses, Server])
|
|
3569
|
+
], ProgressController);
|
|
3570
|
+
|
|
3571
|
+
class Terminal {
|
|
3572
|
+
client;
|
|
3573
|
+
addons;
|
|
3574
|
+
files$;
|
|
3575
|
+
input$;
|
|
3576
|
+
constructor(client) {
|
|
3577
|
+
this.client = client;
|
|
3578
|
+
this.addons = [];
|
|
3579
|
+
this.files$ = new BehaviorSubject([]);
|
|
3580
|
+
this.input$ = new Subject();
|
|
3581
|
+
}
|
|
3582
|
+
onData(cb) {
|
|
3583
|
+
const sub = this.input$.pipe(map(v => {
|
|
3584
|
+
if (v === "\b") {
|
|
3585
|
+
return "\x7f";
|
|
3586
|
+
}
|
|
3587
|
+
return v;
|
|
3588
|
+
})).subscribe(cb);
|
|
3589
|
+
return {
|
|
3590
|
+
dispose: () => sub.unsubscribe()
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
write(data) {
|
|
3594
|
+
this.client.emit("terminal-data", data);
|
|
3595
|
+
}
|
|
3596
|
+
writeln(data) {
|
|
3597
|
+
this.write(data + "\n");
|
|
3598
|
+
}
|
|
3599
|
+
loadAddon(addon) {
|
|
3600
|
+
addon.activate(this);
|
|
3601
|
+
this.addons.push(addon);
|
|
3602
|
+
}
|
|
3603
|
+
dispose() {
|
|
3604
|
+
this.input$.complete();
|
|
3605
|
+
this.client = null;
|
|
3606
|
+
this.addons.forEach(a => a.dispose());
|
|
3607
|
+
}
|
|
3608
|
+
async suggestFiles(accept) {
|
|
3609
|
+
const rand = Math.round(Math.random() * 1000);
|
|
3610
|
+
const id = `${Date.now()}-${rand}`;
|
|
3611
|
+
const files = this.files$.value;
|
|
3612
|
+
return files.filter(f => f.accept === accept).concat([
|
|
3613
|
+
{
|
|
3614
|
+
id,
|
|
3615
|
+
label: "...",
|
|
3616
|
+
onAccept: async () => {
|
|
3617
|
+
this.client.emit("terminal-upload", {
|
|
3618
|
+
id,
|
|
3619
|
+
accept
|
|
3620
|
+
});
|
|
3621
|
+
const file = await this.files$
|
|
3622
|
+
.pipe(first(v => v.some(f => f.id === id)))
|
|
3623
|
+
.pipe(timeout(120000))
|
|
3624
|
+
.pipe(map(v => v.find(f => f.id === id)))
|
|
3625
|
+
.toPromise();
|
|
3626
|
+
if (file.error) {
|
|
3627
|
+
this.files$.next(this.files$.value.filter(f => f.id !== id));
|
|
3628
|
+
throw new Error(file.error);
|
|
3629
|
+
}
|
|
3630
|
+
return file;
|
|
3631
|
+
},
|
|
3632
|
+
showAlways: true,
|
|
3633
|
+
accept: accept,
|
|
3634
|
+
}
|
|
3635
|
+
]);
|
|
3636
|
+
}
|
|
3637
|
+
async downloadFile(filename, buffer) {
|
|
3638
|
+
const type = await fileTypeFromBuffer(buffer);
|
|
3639
|
+
const data = buffer.toString("base64");
|
|
3640
|
+
const content = `data:${type.mime};base64,${data}`;
|
|
3641
|
+
this.client.emit("terminal-download", {
|
|
3642
|
+
filename,
|
|
3643
|
+
content
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
addFile(upload) {
|
|
3647
|
+
if (upload.content) {
|
|
3648
|
+
upload.buffer = Buffer.from(upload.content, "base64");
|
|
3649
|
+
}
|
|
3650
|
+
this.files$.next(this.files$.value.concat(upload));
|
|
3651
|
+
}
|
|
3652
|
+
input(data) {
|
|
3653
|
+
this.input$.next(data);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
let TerminalController = class TerminalController {
|
|
3658
|
+
manager;
|
|
3659
|
+
terminals;
|
|
3660
|
+
constructor(manager) {
|
|
3661
|
+
this.manager = manager;
|
|
3662
|
+
this.terminals = {};
|
|
3663
|
+
}
|
|
3664
|
+
async terminalInit(client) {
|
|
3665
|
+
const terminal = new Terminal(client);
|
|
3666
|
+
this.manager.loadAddons(terminal);
|
|
3667
|
+
this.terminals[client.id] = terminal;
|
|
3668
|
+
client.on("disconnect", () => terminal.dispose());
|
|
3669
|
+
}
|
|
3670
|
+
async terminalData(client, data) {
|
|
3671
|
+
const terminal = this.terminals[client.id];
|
|
3672
|
+
if (!terminal)
|
|
3673
|
+
return;
|
|
3674
|
+
terminal.input(data);
|
|
3675
|
+
}
|
|
3676
|
+
async terminalUpload(client, upload) {
|
|
3677
|
+
const terminal = this.terminals[client.id];
|
|
3678
|
+
if (!terminal || !upload)
|
|
3679
|
+
return;
|
|
3680
|
+
terminal.addFile(upload);
|
|
3681
|
+
}
|
|
3682
|
+
};
|
|
3683
|
+
__decorate([
|
|
3684
|
+
OnMessage("terminal-init"),
|
|
3685
|
+
__param(0, ConnectedSocket()),
|
|
3686
|
+
__metadata("design:type", Function),
|
|
3687
|
+
__metadata("design:paramtypes", [Object]),
|
|
3688
|
+
__metadata("design:returntype", Promise)
|
|
3689
|
+
], TerminalController.prototype, "terminalInit", null);
|
|
3690
|
+
__decorate([
|
|
3691
|
+
OnMessage("terminal-data"),
|
|
3692
|
+
__param(0, ConnectedSocket()),
|
|
3693
|
+
__param(1, MessageBody()),
|
|
3694
|
+
__metadata("design:type", Function),
|
|
3695
|
+
__metadata("design:paramtypes", [Object, String]),
|
|
3696
|
+
__metadata("design:returntype", Promise)
|
|
3697
|
+
], TerminalController.prototype, "terminalData", null);
|
|
3698
|
+
__decorate([
|
|
3699
|
+
OnMessage("terminal-upload"),
|
|
3700
|
+
__param(0, ConnectedSocket()),
|
|
3701
|
+
__param(1, MessageBody()),
|
|
3702
|
+
__metadata("design:type", Function),
|
|
3703
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
3704
|
+
__metadata("design:returntype", Promise)
|
|
3705
|
+
], TerminalController.prototype, "terminalUpload", null);
|
|
3706
|
+
TerminalController = __decorate([
|
|
3707
|
+
singleton(),
|
|
3708
|
+
SocketController(),
|
|
3709
|
+
__metadata("design:paramtypes", [TerminalManager])
|
|
3710
|
+
], TerminalController);
|
|
3711
|
+
|
|
3712
|
+
let CompressionMiddleware = class CompressionMiddleware {
|
|
3713
|
+
use(socket, next) {
|
|
3714
|
+
next();
|
|
3715
|
+
}
|
|
3716
|
+
};
|
|
3717
|
+
CompressionMiddleware = __decorate([
|
|
3718
|
+
injectable(),
|
|
3719
|
+
Middleware$1({})
|
|
3720
|
+
], CompressionMiddleware);
|
|
3721
|
+
|
|
3722
|
+
class Tree {
|
|
3723
|
+
container;
|
|
3724
|
+
exists;
|
|
3725
|
+
path;
|
|
3726
|
+
map;
|
|
3727
|
+
get parentTree() {
|
|
3728
|
+
return this.container.parent.tree;
|
|
3729
|
+
}
|
|
3730
|
+
constructor(container, exists, path) {
|
|
3731
|
+
this.container = container;
|
|
3732
|
+
this.exists = exists;
|
|
3733
|
+
this.path = path;
|
|
3734
|
+
this.map = new Map();
|
|
3735
|
+
}
|
|
3736
|
+
resolveService() {
|
|
3737
|
+
return !this.exists ? null : this.container.resolve(this.path);
|
|
3738
|
+
}
|
|
3739
|
+
resolveLeaves() {
|
|
3740
|
+
let map;
|
|
3741
|
+
try {
|
|
3742
|
+
const parentTree = this.parentTree.resolvePath(this.path);
|
|
3743
|
+
map = parentTree.resolveLeaves();
|
|
3744
|
+
}
|
|
3745
|
+
catch (e) {
|
|
3746
|
+
map = new Map();
|
|
3747
|
+
}
|
|
3748
|
+
const visitor = (treeMap, path) => {
|
|
3749
|
+
treeMap.forEach((tree, key) => {
|
|
3750
|
+
const subKey = !path ? key : `${path}.${key}`;
|
|
3751
|
+
if (tree.map.size == 0) {
|
|
3752
|
+
map.set(subKey, tree);
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
visitor(tree.map, subKey);
|
|
3756
|
+
});
|
|
3757
|
+
};
|
|
3758
|
+
visitor(this.map, "");
|
|
3759
|
+
return map;
|
|
3760
|
+
}
|
|
3761
|
+
resolveServices() {
|
|
3762
|
+
const map = new Map();
|
|
3763
|
+
this.resolveLeaves().forEach((leaf, key) => {
|
|
3764
|
+
map.set(key, leaf.resolveService());
|
|
3765
|
+
});
|
|
3766
|
+
return map;
|
|
3767
|
+
}
|
|
3768
|
+
resolveAncestor(path) {
|
|
3769
|
+
if (!isString(path) || path.length == 0) {
|
|
3770
|
+
return this;
|
|
3771
|
+
}
|
|
3772
|
+
let parentTree;
|
|
3773
|
+
try {
|
|
3774
|
+
parentTree = this.parentTree.resolvePath(this.path);
|
|
3775
|
+
parentTree = parentTree.resolveAncestor(path);
|
|
3776
|
+
}
|
|
3777
|
+
catch (e) {
|
|
3778
|
+
parentTree = new Tree(this.container, false, "");
|
|
3779
|
+
}
|
|
3780
|
+
const pathParts = path.split(".");
|
|
3781
|
+
let tree = this;
|
|
3782
|
+
let previousTree = this;
|
|
3783
|
+
for (let part of pathParts) {
|
|
3784
|
+
tree = tree.map.get(part);
|
|
3785
|
+
if (!tree) {
|
|
3786
|
+
if (previousTree == this) {
|
|
3787
|
+
if (parentTree.path.length > 0) {
|
|
3788
|
+
return parentTree;
|
|
3789
|
+
}
|
|
3790
|
+
throw new Error(`Ancestor '${path}' not found in current tree: '${this.path}'`);
|
|
3791
|
+
}
|
|
3792
|
+
return previousTree;
|
|
3793
|
+
}
|
|
3794
|
+
previousTree = tree;
|
|
3795
|
+
}
|
|
3796
|
+
return parentTree.path.length > previousTree.path.length
|
|
3797
|
+
? parentTree : previousTree;
|
|
3798
|
+
}
|
|
3799
|
+
resolvePath(path, throwError = true) {
|
|
3800
|
+
const absolutePath = !this.path ? path : `${this.path}.${path}`;
|
|
3801
|
+
let tree = new Tree(this.container, false, absolutePath);
|
|
3802
|
+
try {
|
|
3803
|
+
tree = this.resolveAncestor(path);
|
|
3804
|
+
}
|
|
3805
|
+
catch (e) {
|
|
3806
|
+
if (throwError) {
|
|
3807
|
+
throw new Error(`Path '${path}' not found in current tree: '${this.path}'`);
|
|
3808
|
+
}
|
|
3809
|
+
return tree;
|
|
3810
|
+
}
|
|
3811
|
+
if (tree.path !== absolutePath) {
|
|
3812
|
+
if (throwError) {
|
|
3813
|
+
throw new Error(`Path '${path}' not found in current tree: '${this.path}'`);
|
|
3814
|
+
}
|
|
3815
|
+
return tree;
|
|
3816
|
+
}
|
|
3817
|
+
return tree;
|
|
3818
|
+
}
|
|
3819
|
+
addPath(path) {
|
|
3820
|
+
if (!isString(path) || path.length == 0) {
|
|
3821
|
+
return this;
|
|
3822
|
+
}
|
|
3823
|
+
const pathParts = path.split(".");
|
|
3824
|
+
const maxIx = pathParts.length - 1;
|
|
3825
|
+
let tree = this;
|
|
3826
|
+
path = this.path;
|
|
3827
|
+
for (let ix = 0; ix <= maxIx; ix++) {
|
|
3828
|
+
const part = pathParts[ix];
|
|
3829
|
+
if (!tree.map.has(part)) {
|
|
3830
|
+
tree.map.set(part, new Tree(this.container, false, !path ? part : `${path}.${part}`));
|
|
3831
|
+
}
|
|
3832
|
+
tree = tree.map.get(part);
|
|
3833
|
+
tree.exists = tree.exists || ix == maxIx;
|
|
3834
|
+
path = tree.path;
|
|
3835
|
+
}
|
|
3836
|
+
return this;
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
class DiContainer {
|
|
3841
|
+
container;
|
|
3842
|
+
parent;
|
|
3843
|
+
get registeredTokens() {
|
|
3844
|
+
return (this.parent?.registeredTokens || []).concat(this.tokens);
|
|
3845
|
+
}
|
|
3846
|
+
get tree() {
|
|
3847
|
+
return this.root;
|
|
3848
|
+
}
|
|
3849
|
+
tokens;
|
|
3850
|
+
tokenSet;
|
|
3851
|
+
root;
|
|
3852
|
+
constructor(container, parent = null) {
|
|
3853
|
+
this.container = container;
|
|
3854
|
+
this.parent = parent;
|
|
3855
|
+
container["wrapperContainer"] = this;
|
|
3856
|
+
this.tokens = [];
|
|
3857
|
+
this.tokenSet = new Set();
|
|
3858
|
+
this.root = new Tree(this, false, "");
|
|
3859
|
+
}
|
|
3860
|
+
beforeResolution(token, callback, options) {
|
|
3861
|
+
this.container.beforeResolution(token, callback, options);
|
|
3862
|
+
}
|
|
3863
|
+
afterResolution(token, callback, options) {
|
|
3864
|
+
this.container.afterResolution(token, callback, options);
|
|
3865
|
+
}
|
|
3866
|
+
clearInstances() {
|
|
3867
|
+
this.container.clearInstances();
|
|
3868
|
+
}
|
|
3869
|
+
createChildContainer() {
|
|
3870
|
+
return new DiContainer(this.container.createChildContainer(), this);
|
|
3871
|
+
}
|
|
3872
|
+
isRegistered(token, recursive) {
|
|
3873
|
+
return this.container.isRegistered(token, recursive);
|
|
3874
|
+
}
|
|
3875
|
+
register(token, provider, options) {
|
|
3876
|
+
if (isFactoryProvider(provider)) {
|
|
3877
|
+
this.container.register(token, {
|
|
3878
|
+
useFactory: dc => {
|
|
3879
|
+
return provider.useFactory(dc["wrapperContainer"]);
|
|
3880
|
+
}
|
|
3881
|
+
});
|
|
3882
|
+
return this.addToken(token);
|
|
3883
|
+
}
|
|
3884
|
+
this.container.register(token, provider);
|
|
3885
|
+
return this.addToken(token);
|
|
3886
|
+
}
|
|
3887
|
+
registerInstance(token, instance) {
|
|
3888
|
+
this.container.registerInstance(token, instance);
|
|
3889
|
+
return this.addToken(token);
|
|
3890
|
+
}
|
|
3891
|
+
registerSingleton(from, to) {
|
|
3892
|
+
this.container.registerSingleton(from, to);
|
|
3893
|
+
return this.addToken(from);
|
|
3894
|
+
}
|
|
3895
|
+
registerType(from, to) {
|
|
3896
|
+
this.container.registerType(from, to);
|
|
3897
|
+
return this.addToken(from);
|
|
3898
|
+
}
|
|
3899
|
+
reset() {
|
|
3900
|
+
this.tokens = [];
|
|
3901
|
+
this.tokenSet = new Set();
|
|
3902
|
+
this.container.reset();
|
|
3903
|
+
}
|
|
3904
|
+
resolve(token) {
|
|
3905
|
+
return this.container.resolve(token);
|
|
3906
|
+
}
|
|
3907
|
+
resolveAll(token) {
|
|
3908
|
+
return this.container.resolveAll(token);
|
|
3909
|
+
}
|
|
3910
|
+
get(token) {
|
|
3911
|
+
return this.container.resolve(token);
|
|
3912
|
+
}
|
|
3913
|
+
addToken(token) {
|
|
3914
|
+
if (this.tokenSet.has(token))
|
|
3915
|
+
return this;
|
|
3916
|
+
this.tokenSet.add(token);
|
|
3917
|
+
this.tokens.push(token);
|
|
3918
|
+
if (isString(token)) {
|
|
3919
|
+
this.root.addPath(token);
|
|
3920
|
+
}
|
|
3921
|
+
return this;
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
let EmptyJob = class EmptyJob {
|
|
3926
|
+
async process() {
|
|
3927
|
+
return null;
|
|
3928
|
+
}
|
|
3929
|
+
};
|
|
3930
|
+
EmptyJob = __decorate([
|
|
3931
|
+
injectable(),
|
|
3932
|
+
scoped(Lifecycle.ContainerScoped)
|
|
3933
|
+
], EmptyJob);
|
|
3934
|
+
|
|
3935
|
+
async function setupStatic(rootFolder, container) {
|
|
3936
|
+
const browserFolder = resolve(rootFolder || getDirName(), `public_html`);
|
|
3937
|
+
const app = container.get(EXPRESS);
|
|
3938
|
+
const ep = container.get(EndpointProvider);
|
|
3939
|
+
console.log(browserFolder, existsSync(browserFolder));
|
|
3940
|
+
if (existsSync(browserFolder)) {
|
|
3941
|
+
console.log(`public_html exists. setting up static files serving...`);
|
|
3942
|
+
app.use(express_.static(browserFolder, {
|
|
3943
|
+
maxAge: "1y"
|
|
3944
|
+
}));
|
|
3945
|
+
}
|
|
3946
|
+
else {
|
|
3947
|
+
console.log(`public_html does not exist on path: "${browserFolder}"`);
|
|
3948
|
+
}
|
|
3949
|
+
await ep.configure(app);
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
let ClearCommand = class ClearCommand {
|
|
3953
|
+
name = "clear";
|
|
3954
|
+
async execute(args, terminal) {
|
|
3955
|
+
terminal.write(AnsiCodes.clear);
|
|
3956
|
+
}
|
|
3957
|
+
};
|
|
3958
|
+
ClearCommand = __decorate([
|
|
3959
|
+
injectable(),
|
|
3960
|
+
scoped(Lifecycle.ContainerScoped)
|
|
3961
|
+
], ClearCommand);
|
|
3962
|
+
|
|
3963
|
+
let FixturesCommand = class FixturesCommand {
|
|
3964
|
+
fixtures;
|
|
3965
|
+
name = "fixtures";
|
|
3966
|
+
constructor(fixtures) {
|
|
3967
|
+
this.fixtures = fixtures;
|
|
3968
|
+
}
|
|
3969
|
+
async execute(args, terminal) {
|
|
3970
|
+
const output = {
|
|
3971
|
+
write: text => terminal.writeln(text),
|
|
3972
|
+
writeln: text => terminal.writeln(text)
|
|
3973
|
+
};
|
|
3974
|
+
await this.fixtures.load(output);
|
|
3975
|
+
terminal.writeln(colorize(`Fixtures loaded`, ConsoleColor.FgGreen));
|
|
3976
|
+
}
|
|
3977
|
+
};
|
|
3978
|
+
FixturesCommand = __decorate([
|
|
3979
|
+
injectable(),
|
|
3980
|
+
scoped(Lifecycle.ContainerScoped),
|
|
3981
|
+
__metadata("design:paramtypes", [Fixtures])
|
|
3982
|
+
], FixturesCommand);
|
|
3983
|
+
|
|
3984
|
+
const commands = [
|
|
3985
|
+
ClearCommand,
|
|
3986
|
+
FixturesCommand
|
|
3987
|
+
];
|
|
3988
|
+
|
|
3989
|
+
let TtlFixture = class TtlFixture {
|
|
3990
|
+
connector;
|
|
3991
|
+
constructor(connector) {
|
|
3992
|
+
this.connector = connector;
|
|
3993
|
+
}
|
|
3994
|
+
async load() {
|
|
3995
|
+
const db = this.connector.database;
|
|
3996
|
+
if (!db)
|
|
3997
|
+
return null;
|
|
3998
|
+
const expires = { expireAfterSeconds: 3600 * 24 };
|
|
3999
|
+
await db.collection("progresses").createIndex({ updatedAt: 1 }, expires);
|
|
4000
|
+
await db.collection("lazyassets").createIndex({ updatedAt: 1 }, expires);
|
|
4001
|
+
}
|
|
4002
|
+
};
|
|
4003
|
+
TtlFixture = __decorate([
|
|
4004
|
+
injectable(),
|
|
4005
|
+
__metadata("design:paramtypes", [MongoConnector])
|
|
4006
|
+
], TtlFixture);
|
|
4007
|
+
|
|
4008
|
+
const fixtures = [
|
|
4009
|
+
TtlFixture,
|
|
4010
|
+
];
|
|
4011
|
+
|
|
4012
|
+
let AssetGridDriver = class AssetGridDriver {
|
|
4013
|
+
metaCollection;
|
|
4014
|
+
bucket;
|
|
4015
|
+
constructor(connector) {
|
|
4016
|
+
this.bucket = new GridFSBucket(connector.database, { bucketName: 'assets' });
|
|
4017
|
+
this.metaCollection = "assets.files";
|
|
4018
|
+
}
|
|
4019
|
+
openUploadStream(filename, opts) {
|
|
4020
|
+
return this.bucket.openUploadStream(filename, opts);
|
|
4021
|
+
}
|
|
4022
|
+
openDownloadStream(id) {
|
|
4023
|
+
return this.bucket.openDownloadStream(id);
|
|
4024
|
+
}
|
|
4025
|
+
delete(id) {
|
|
4026
|
+
return this.bucket.delete(id);
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
AssetGridDriver = __decorate([
|
|
4030
|
+
injectable(),
|
|
4031
|
+
__metadata("design:paramtypes", [MongoConnector])
|
|
4032
|
+
], AssetGridDriver);
|
|
4033
|
+
|
|
4034
|
+
let AssetLocalDriver = class AssetLocalDriver {
|
|
4035
|
+
dir;
|
|
4036
|
+
metaCollection;
|
|
4037
|
+
constructor(dir) {
|
|
4038
|
+
this.dir = dir;
|
|
4039
|
+
this.metaCollection = "assets.local";
|
|
4040
|
+
}
|
|
4041
|
+
openUploadStream(filename, opts) {
|
|
4042
|
+
const id = new Types.ObjectId();
|
|
4043
|
+
const dir = `${this.dir}/${id.toHexString()}`;
|
|
4044
|
+
mkdirSync(dir, { recursive: true });
|
|
4045
|
+
const stream = createWriteStream(`${dir}/file.bin`);
|
|
4046
|
+
stream.id = id;
|
|
4047
|
+
stream.done = false;
|
|
4048
|
+
stream.on('finish', () => {
|
|
4049
|
+
writeFile$2(`${dir}/filename.txt`, filename);
|
|
4050
|
+
writeFile$2(`${dir}/metadata.json`, JSON.stringify(opts?.metadata || {}));
|
|
4051
|
+
stream.done = true;
|
|
4052
|
+
});
|
|
4053
|
+
return stream;
|
|
4054
|
+
}
|
|
4055
|
+
openDownloadStream(id) {
|
|
4056
|
+
return createReadStream(`${this.dir}/${id.toHexString()}/file.bin`, { autoClose: true, emitClose: true });
|
|
4057
|
+
}
|
|
4058
|
+
delete(id) {
|
|
4059
|
+
return rm(`${this.dir}/${id.toHexString()}`, { recursive: true, force: true });
|
|
4060
|
+
}
|
|
4061
|
+
};
|
|
4062
|
+
AssetLocalDriver = __decorate([
|
|
4063
|
+
injectable(),
|
|
4064
|
+
__param(0, inject(LOCAL_DIR)),
|
|
4065
|
+
__metadata("design:paramtypes", [String])
|
|
4066
|
+
], AssetLocalDriver);
|
|
4067
|
+
|
|
4068
|
+
class BaseDoc {
|
|
4069
|
+
_id;
|
|
4070
|
+
/**
|
|
4071
|
+
* This getter/setter doesn't exist if "schemaOptions.id" being set to "false"
|
|
4072
|
+
*/
|
|
4073
|
+
id;
|
|
4074
|
+
/**
|
|
4075
|
+
* This getter doesn't exist if "schemaOptions.timestamps" being set to "false"
|
|
4076
|
+
*/
|
|
4077
|
+
createdAt;
|
|
4078
|
+
/**
|
|
4079
|
+
* This getter doesn't exist if "schemaOptions.timestamps" being set to "false"
|
|
4080
|
+
*/
|
|
4081
|
+
updatedAt;
|
|
4082
|
+
/**
|
|
4083
|
+
* Casts this to DocumentType<this> to allow using document methods in get/set-s
|
|
4084
|
+
*/
|
|
4085
|
+
cast() {
|
|
4086
|
+
return this;
|
|
4087
|
+
}
|
|
4088
|
+
/**
|
|
4089
|
+
* Gets a pre-compiled model from typegoose cache by its class type
|
|
4090
|
+
* @param type
|
|
4091
|
+
*/
|
|
4092
|
+
model(type) {
|
|
4093
|
+
return getModelForClass(type);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
const PrimitiveArray = mongoose.Types.Array;
|
|
4097
|
+
const DocumentArray = mongoose.Types.DocumentArray;
|
|
4098
|
+
|
|
4099
|
+
function IsDocumented(summary = null, paramDescriptions = {}) {
|
|
4100
|
+
return OpenAPI(op => {
|
|
4101
|
+
op.summary = summary || op.summary;
|
|
4102
|
+
op.tags = ["Documented"].concat(op.tags || []);
|
|
4103
|
+
op.parameters?.forEach(p => {
|
|
4104
|
+
if (p?.$ref)
|
|
4105
|
+
return;
|
|
4106
|
+
const schema = p;
|
|
4107
|
+
schema.description = paramDescriptions[schema.name]
|
|
4108
|
+
|| schema.description
|
|
4109
|
+
|| `param.${op.operationId}.${schema.name}`.toLowerCase();
|
|
4110
|
+
});
|
|
4111
|
+
return op;
|
|
4112
|
+
});
|
|
4113
|
+
}
|
|
4114
|
+
function JsonResponse(description = "Success", statusCode = null) {
|
|
4115
|
+
return OpenAPI((op, route) => {
|
|
4116
|
+
const status = statusCode ?? getStatusCode(route) + "";
|
|
4117
|
+
op.responses = op.responses || {};
|
|
4118
|
+
op.responses[status] = {
|
|
4119
|
+
description,
|
|
4120
|
+
content: {
|
|
4121
|
+
"application/json": {}
|
|
4122
|
+
}
|
|
4123
|
+
};
|
|
4124
|
+
return op;
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
function ResponseType(type, options = {}) {
|
|
4128
|
+
return OpenAPI((op, route) => {
|
|
4129
|
+
const contentType = "application/json";
|
|
4130
|
+
const statusCode = options?.statusCode ?? getStatusCode(route) + "";
|
|
4131
|
+
const reference = {
|
|
4132
|
+
$ref: `#/components/schemas/${type.name}`,
|
|
4133
|
+
};
|
|
4134
|
+
op.responses = op.responses || {};
|
|
4135
|
+
op.responses[statusCode] = {
|
|
4136
|
+
description: options.description || "Success",
|
|
4137
|
+
content: {
|
|
4138
|
+
[contentType]: {
|
|
4139
|
+
schema: options.isArray
|
|
4140
|
+
? { items: reference, type: "array" }
|
|
4141
|
+
: reference
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
4145
|
+
return op;
|
|
4146
|
+
});
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
class LazyAssetGenerator {
|
|
4150
|
+
assetResolver;
|
|
4151
|
+
progresses;
|
|
4152
|
+
lazyId;
|
|
4153
|
+
get assets() {
|
|
4154
|
+
return this.assetResolver.assets;
|
|
4155
|
+
}
|
|
4156
|
+
get lazyAssets() {
|
|
4157
|
+
return this.assetResolver.lazyAssets;
|
|
4158
|
+
}
|
|
4159
|
+
constructor(assetResolver, progresses, lazyId) {
|
|
4160
|
+
this.assetResolver = assetResolver;
|
|
4161
|
+
this.progresses = progresses;
|
|
4162
|
+
this.lazyId = lazyId;
|
|
4163
|
+
}
|
|
4164
|
+
async process(messaging) {
|
|
4165
|
+
const lazyAsset = await this.lazyAssets.read(this.lazyId);
|
|
4166
|
+
let progress = await this.progresses.get(lazyAsset.progressId);
|
|
4167
|
+
if (!progress || progress.canceled)
|
|
4168
|
+
return null;
|
|
4169
|
+
progress.setMessageBridge(messaging);
|
|
4170
|
+
try {
|
|
4171
|
+
const asset = await this.generate(progress, messaging);
|
|
4172
|
+
progress = await progress.load();
|
|
4173
|
+
if (!progress || progress.canceled)
|
|
4174
|
+
return null;
|
|
4175
|
+
await lazyAsset.writeAsset(asset);
|
|
4176
|
+
}
|
|
4177
|
+
catch (e) {
|
|
4178
|
+
await progress.setError(e.message || e);
|
|
4179
|
+
throw e;
|
|
4180
|
+
}
|
|
4181
|
+
if (progress.remaining > 0) {
|
|
4182
|
+
await progress.advance(progress.remaining);
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
const pluginsKey = "typegoose:plugins";
|
|
4188
|
+
/**
|
|
4189
|
+
* A mongoose/typegoose plugin to inject services from the main di container to a schema as virtuals
|
|
4190
|
+
* @param schema
|
|
4191
|
+
* @param services
|
|
4192
|
+
*/
|
|
4193
|
+
function injectServices(schema, services) {
|
|
4194
|
+
const serviceMap = {};
|
|
4195
|
+
if (!isObject(services)) {
|
|
4196
|
+
throw new Error(`services object should be defined to inject services to schema!`);
|
|
4197
|
+
}
|
|
4198
|
+
Object.keys(services).forEach(prop => {
|
|
4199
|
+
schema
|
|
4200
|
+
.virtual(prop)
|
|
4201
|
+
.get(() => {
|
|
4202
|
+
const diContainer = diContainers.appContainer;
|
|
4203
|
+
serviceMap[prop] = serviceMap[prop] || (!diContainer ? {} : diContainer.resolve(services[prop]));
|
|
4204
|
+
return serviceMap[prop];
|
|
4205
|
+
});
|
|
4206
|
+
});
|
|
4207
|
+
}
|
|
4208
|
+
/**
|
|
4209
|
+
* Decorates a property to inject a service with the help of the injectServices mongoose/typegoose plugin
|
|
4210
|
+
* @param token optional InjectionToken to use
|
|
4211
|
+
* @return PropertyDecorator
|
|
4212
|
+
*/
|
|
4213
|
+
function service(token) {
|
|
4214
|
+
return (target, propertyKey) => {
|
|
4215
|
+
const propertyType = Reflect.getOwnMetadata("design:type", target, propertyKey);
|
|
4216
|
+
const plugins = Array.from(Reflect.getMetadata(pluginsKey, target.constructor) ?? []);
|
|
4217
|
+
let plugin = plugins.find(t => t.mongoosePlugin === injectServices);
|
|
4218
|
+
if (!plugin) {
|
|
4219
|
+
plugin = { mongoosePlugin: injectServices, options: {} };
|
|
4220
|
+
plugins.push(plugin);
|
|
4221
|
+
}
|
|
4222
|
+
plugin.options = Object.assign(plugin.options || {}, { [propertyKey]: token ?? propertyType });
|
|
4223
|
+
Reflect.defineMetadata(pluginsKey, plugins, target.constructor);
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
/**
|
|
4227
|
+
* Paginate using a typegoose model using a simple where query and pagination params
|
|
4228
|
+
* @param model Typegoose model
|
|
4229
|
+
* @param where Simple query to filter the results
|
|
4230
|
+
* @param params Pagination params
|
|
4231
|
+
*/
|
|
4232
|
+
function paginate(model, where, params) {
|
|
4233
|
+
return model.countDocuments(where).then(count => {
|
|
4234
|
+
let query = model.find(where);
|
|
4235
|
+
if (isString(params.sort)) {
|
|
4236
|
+
query = query.sort(params.sort);
|
|
4237
|
+
}
|
|
4238
|
+
if (isArray(params.populate)) {
|
|
4239
|
+
params.populate.forEach(field => {
|
|
4240
|
+
query = query.populate(field);
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
return (params.limit > 0 ? query.skip(params.page * params.limit).limit(params.limit) : query).then(items => {
|
|
4244
|
+
const meta = { total: count };
|
|
4245
|
+
return { count, items, meta };
|
|
4246
|
+
});
|
|
4247
|
+
});
|
|
4248
|
+
}
|
|
4249
|
+
function lookupStages(from, localField, as = null, foreignField = "_id", shouldUnwind = true) {
|
|
4250
|
+
as = as || localField.replace("Id", "");
|
|
4251
|
+
const stages = [
|
|
4252
|
+
{
|
|
4253
|
+
$lookup: {
|
|
4254
|
+
from,
|
|
4255
|
+
localField,
|
|
4256
|
+
foreignField,
|
|
4257
|
+
as
|
|
4258
|
+
}
|
|
4259
|
+
},
|
|
4260
|
+
{
|
|
4261
|
+
$unwind: {
|
|
4262
|
+
path: `$${as}`,
|
|
4263
|
+
preserveNullAndEmptyArrays: true
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
];
|
|
4267
|
+
if (!shouldUnwind) {
|
|
4268
|
+
stages.splice(1, 1);
|
|
4269
|
+
}
|
|
4270
|
+
return stages;
|
|
4271
|
+
}
|
|
4272
|
+
function letsLookupStage(from, pipeline, as = null, letFields = null) {
|
|
4273
|
+
as = as || from;
|
|
4274
|
+
letFields = letFields || { id: "$_id" };
|
|
4275
|
+
return {
|
|
4276
|
+
$lookup: {
|
|
4277
|
+
from,
|
|
4278
|
+
let: letFields,
|
|
4279
|
+
pipeline,
|
|
4280
|
+
as
|
|
4281
|
+
}
|
|
4282
|
+
};
|
|
4283
|
+
}
|
|
4284
|
+
function matchStage(match) {
|
|
4285
|
+
return { $match: match };
|
|
4286
|
+
}
|
|
4287
|
+
function matchField(field, filter, when) {
|
|
4288
|
+
return { field, filter, when };
|
|
4289
|
+
}
|
|
4290
|
+
function matchFieldStages(...fields) {
|
|
4291
|
+
const match = {};
|
|
4292
|
+
fields.forEach(field => {
|
|
4293
|
+
if (field.when) {
|
|
4294
|
+
match[field.field] = field.filter;
|
|
4295
|
+
}
|
|
4296
|
+
});
|
|
4297
|
+
return Object.keys(match).length > 0 ? [matchStage(match)] : [];
|
|
4298
|
+
}
|
|
4299
|
+
function projectStage(fields) {
|
|
4300
|
+
return { $project: fields };
|
|
4301
|
+
}
|
|
4302
|
+
function unwindStage(fieldOrOpts) {
|
|
4303
|
+
return { $unwind: fieldOrOpts };
|
|
4304
|
+
}
|
|
4305
|
+
function hydratePopulated(modelType, json) {
|
|
4306
|
+
let object = modelType.hydrate(json);
|
|
4307
|
+
for (const [path, obj] of Object.entries(modelType.schema.obj)) {
|
|
4308
|
+
let { ref, type } = obj;
|
|
4309
|
+
if (Array.isArray(type) && type.length > 0) {
|
|
4310
|
+
ref = type[0].ref;
|
|
4311
|
+
}
|
|
4312
|
+
if (!ref)
|
|
4313
|
+
continue;
|
|
4314
|
+
const value = getValue$1(path, json);
|
|
4315
|
+
const hydrateVal = val => {
|
|
4316
|
+
if (val == null || val instanceof mongoose.Types.ObjectId)
|
|
4317
|
+
return val;
|
|
4318
|
+
return hydratePopulated(mongoose.model(ref), val);
|
|
4319
|
+
};
|
|
4320
|
+
if (Array.isArray(value)) {
|
|
4321
|
+
setValue(path, value.map(hydrateVal), object);
|
|
4322
|
+
continue;
|
|
4323
|
+
}
|
|
4324
|
+
setValue(path, hydrateVal(value), object);
|
|
4325
|
+
}
|
|
4326
|
+
return object;
|
|
4327
|
+
}
|
|
4328
|
+
async function paginateAggregations(model, aggregations, params, metaProjection = {}) {
|
|
4329
|
+
const sortField = !isString(params.sort) || !params.sort ? null : (params.sort.startsWith("-") ? params.sort.substr(1) : params.sort);
|
|
4330
|
+
const sortAggregation = !sortField ? [] : [{
|
|
4331
|
+
$sort: { [sortField]: sortField == params.sort ? 1 : -1 }
|
|
4332
|
+
}];
|
|
4333
|
+
const result = await model.aggregate([
|
|
4334
|
+
...aggregations,
|
|
4335
|
+
...sortAggregation,
|
|
4336
|
+
{
|
|
4337
|
+
$group: {
|
|
4338
|
+
_id: "results",
|
|
4339
|
+
result: { $push: "$$CURRENT" }
|
|
4340
|
+
}
|
|
4341
|
+
},
|
|
4342
|
+
{
|
|
4343
|
+
$project: {
|
|
4344
|
+
_id: 0,
|
|
4345
|
+
items: params.limit > 0 ? { $slice: ["$result", params.page * params.limit, params.limit] } : "$result",
|
|
4346
|
+
count: { $size: "$result" },
|
|
4347
|
+
meta: {
|
|
4348
|
+
total: { $size: "$result" },
|
|
4349
|
+
...metaProjection
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
]);
|
|
4354
|
+
const pagination = result[0];
|
|
4355
|
+
if (!pagination) {
|
|
4356
|
+
return { items: [], count: 0, meta: { total: 0 } };
|
|
4357
|
+
}
|
|
4358
|
+
pagination.items = pagination.items.map(i => hydratePopulated(model, i));
|
|
4359
|
+
return pagination;
|
|
4360
|
+
}
|
|
4361
|
+
function ResolveEntity(model, extraCheck) {
|
|
4362
|
+
const modelName = model.modelName;
|
|
4363
|
+
const paramName = modelName.toLowerCase();
|
|
4364
|
+
return createParamDecorator({
|
|
4365
|
+
required: false,
|
|
4366
|
+
value: async (action) => {
|
|
4367
|
+
const req = action.request;
|
|
4368
|
+
const token = req.header(`x-${paramName}-token`);
|
|
4369
|
+
const id = req.params[`${paramName}Id`];
|
|
4370
|
+
if (!id && !token) {
|
|
4371
|
+
throw new BadRequestError(`${modelName} id or token should be defined!`);
|
|
4372
|
+
}
|
|
4373
|
+
const query = !token
|
|
4374
|
+
? model.findById(id)
|
|
4375
|
+
: model.findOne({ token });
|
|
4376
|
+
let doc = null;
|
|
4377
|
+
if (isFunction(extraCheck)) {
|
|
4378
|
+
try {
|
|
4379
|
+
doc = await valueToPromise(extraCheck(query, action));
|
|
4380
|
+
}
|
|
4381
|
+
catch (e) {
|
|
4382
|
+
throw new BadRequestError(`${modelName} check error: ${e.message || e}`);
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
else {
|
|
4386
|
+
doc = await query;
|
|
4387
|
+
}
|
|
4388
|
+
if (!doc) {
|
|
4389
|
+
throw new HttpError(404, !token
|
|
4390
|
+
? `${modelName} could not be found with id: ${id}`
|
|
4391
|
+
: `${modelName} could not be found with token: ${token}`);
|
|
4392
|
+
}
|
|
4393
|
+
action.request[paramName] = doc;
|
|
4394
|
+
return doc;
|
|
4395
|
+
}
|
|
4396
|
+
});
|
|
4397
|
+
}
|
|
4398
|
+
|
|
4399
|
+
async function resolveUser(req) {
|
|
4400
|
+
if (req.user)
|
|
4401
|
+
return req.user;
|
|
4402
|
+
const container = req.container;
|
|
4403
|
+
const auth = req.header("Authorization") || "";
|
|
4404
|
+
let payload = null;
|
|
4405
|
+
try {
|
|
4406
|
+
const config = container.resolve(Configuration);
|
|
4407
|
+
payload = webToken.verify(auth.split(" ")[1], config.resolve("jwtSecret"));
|
|
4408
|
+
}
|
|
4409
|
+
catch (e) {
|
|
4410
|
+
throw new HttpError(401, `Authentication failed. (${e.message})`);
|
|
4411
|
+
}
|
|
4412
|
+
if (!payload) {
|
|
4413
|
+
throw new HttpError(401, `Authentication failed. (Maybe invalid token)`);
|
|
4414
|
+
}
|
|
4415
|
+
req.user = await container.resolve(UserManager).getById(payload.id);
|
|
4416
|
+
return req.user;
|
|
4417
|
+
}
|
|
4418
|
+
function createServices() {
|
|
4419
|
+
// List of parameters
|
|
4420
|
+
const dirName = getDirName();
|
|
4421
|
+
const params = [
|
|
4422
|
+
new Parameter("serviceName", "Backend"),
|
|
4423
|
+
new Parameter("servicePassword", Math.random().toString(36).substring(7)),
|
|
4424
|
+
new Parameter("serviceUrl", "http://localhost:3000", (value, helper) => {
|
|
4425
|
+
// Replace port number to empty string
|
|
4426
|
+
const url = prepareUrlEmpty(`${value}`.replace(/:[0-9]+$/, ""), helper);
|
|
4427
|
+
const port = helper("appPort");
|
|
4428
|
+
return (!port || port === 80 || port === 443) ? url : `${url}:${port}`;
|
|
4429
|
+
}),
|
|
4430
|
+
new Parameter("templatesDir", join(dirName, "templates")),
|
|
4431
|
+
new Parameter("galleryDir", join(dirName, "gallery")),
|
|
4432
|
+
new Parameter("cacheDir", join(dirName, "cache")),
|
|
4433
|
+
new Parameter("defaultLanguage", "en"),
|
|
4434
|
+
new Parameter("smtpHost", "smtp.sendgrid.net"),
|
|
4435
|
+
new Parameter("smtpPort", 587),
|
|
4436
|
+
new Parameter("smtpUser", "apikey"),
|
|
4437
|
+
new Parameter("smtpPassword", ""),
|
|
4438
|
+
new Parameter("mailSenderAddress", "info@stemy.hu"),
|
|
4439
|
+
new Parameter("translationsTemplate", "https://translation.service/[lang]"),
|
|
4440
|
+
new Parameter("jwtSecret", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"),
|
|
4441
|
+
new Parameter("mongoUri", ""),
|
|
4442
|
+
new Parameter("mongoDb", "node-backend"),
|
|
4443
|
+
new Parameter("mongoUser", null),
|
|
4444
|
+
new Parameter("mongoPassword", null),
|
|
4445
|
+
new Parameter("nodeEnv", "production"),
|
|
4446
|
+
new Parameter("appPort", 80),
|
|
4447
|
+
new Parameter("zmqPort", 3000),
|
|
4448
|
+
new Parameter("zmqBackPort", 3100),
|
|
4449
|
+
new Parameter("zmqRemoteHost", "tcp://127.0.0.1"),
|
|
4450
|
+
new Parameter("isWorker", false),
|
|
4451
|
+
new Parameter("startWorker", false),
|
|
4452
|
+
new Parameter("fixtures", true),
|
|
4453
|
+
new Parameter("mainEndpoint", ""),
|
|
4454
|
+
new Parameter("idChars", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
|
|
4455
|
+
new Parameter("idSeparator", "-"),
|
|
4456
|
+
new Parameter("idPrefix", "ID-"),
|
|
4457
|
+
new Parameter("idParts", [4, 4]),
|
|
4458
|
+
new Parameter("jsonLimit", "250mb"),
|
|
4459
|
+
new Parameter("jobTimeout", 5 * 60 * 1000),
|
|
4460
|
+
new Parameter("cacheCollection", "cache"),
|
|
4461
|
+
new Parameter("logTags", []),
|
|
4462
|
+
new Parameter("ignoredLogTags", []),
|
|
4463
|
+
];
|
|
4464
|
+
// Convert parameters to providers
|
|
4465
|
+
const paramProviders = params.map(p => {
|
|
4466
|
+
return {
|
|
4467
|
+
provide: PARAMETER,
|
|
4468
|
+
useValue: p
|
|
4469
|
+
};
|
|
4470
|
+
});
|
|
4471
|
+
// List of services
|
|
4472
|
+
const services = [
|
|
4473
|
+
AssetProcessor,
|
|
4474
|
+
AssetResolver,
|
|
4475
|
+
Assets,
|
|
4476
|
+
BackendProvider,
|
|
4477
|
+
Cache,
|
|
4478
|
+
CacheProcessor,
|
|
4479
|
+
Configuration,
|
|
4480
|
+
EndpointProvider,
|
|
4481
|
+
Fixtures,
|
|
4482
|
+
Gallery,
|
|
4483
|
+
GalleryCache,
|
|
4484
|
+
IdGenerator,
|
|
4485
|
+
JobManager,
|
|
4486
|
+
LazyAssets,
|
|
4487
|
+
Logger,
|
|
4488
|
+
MailSender,
|
|
4489
|
+
MemoryCache,
|
|
4490
|
+
MongoConnector,
|
|
4491
|
+
OpenApi,
|
|
4492
|
+
Progresses,
|
|
4493
|
+
TemplateRenderer,
|
|
4494
|
+
TerminalManager,
|
|
4495
|
+
TokenGenerator,
|
|
4496
|
+
TranslationProvider,
|
|
4497
|
+
Translator,
|
|
4498
|
+
UserManager
|
|
4499
|
+
];
|
|
4500
|
+
// Create container
|
|
4501
|
+
const diContainer = new DiContainer(container.createChildContainer());
|
|
4502
|
+
paramProviders.forEach(provider => {
|
|
4503
|
+
diContainer.register(provider.provide, provider);
|
|
4504
|
+
});
|
|
4505
|
+
services.forEach(service => {
|
|
4506
|
+
if (!container.isRegistered(service))
|
|
4507
|
+
diContainer.register(service, service);
|
|
4508
|
+
});
|
|
4509
|
+
return diContainer;
|
|
4510
|
+
}
|
|
4511
|
+
async function setupBackend(config, providers, parent) {
|
|
4512
|
+
providers = Array.isArray(providers) ? providers : [];
|
|
4513
|
+
parent = parent || createServices();
|
|
4514
|
+
// Create fixtures
|
|
4515
|
+
const fixtureProviders = fixtures.concat(config.fixtures || []).map(fixture => {
|
|
4516
|
+
return {
|
|
4517
|
+
provide: FIXTURE,
|
|
4518
|
+
useClass: fixture
|
|
4519
|
+
};
|
|
4520
|
+
});
|
|
4521
|
+
// Create params
|
|
4522
|
+
const paramProviders = (config.params || []).map(p => {
|
|
4523
|
+
return {
|
|
4524
|
+
provide: PARAMETER,
|
|
4525
|
+
useValue: p
|
|
4526
|
+
};
|
|
4527
|
+
});
|
|
4528
|
+
// Create jobs
|
|
4529
|
+
const jobProviders = [EmptyJob].concat(config.jobs || []).map(jobType => {
|
|
4530
|
+
return {
|
|
4531
|
+
provide: JOB,
|
|
4532
|
+
useValue: jobType
|
|
4533
|
+
};
|
|
4534
|
+
});
|
|
4535
|
+
// Create commands
|
|
4536
|
+
const commandProviders = commands.concat(config.commands || []).map(commandType => {
|
|
4537
|
+
return {
|
|
4538
|
+
provide: TERMINAL_COMMAND,
|
|
4539
|
+
useClass: commandType
|
|
4540
|
+
};
|
|
4541
|
+
});
|
|
4542
|
+
// Create DI container
|
|
4543
|
+
const diContainer = parent.createChildContainer();
|
|
4544
|
+
// Setup rest API
|
|
4545
|
+
const restOptions = {
|
|
4546
|
+
routePrefix: config.routePrefix || "/api",
|
|
4547
|
+
defaultErrorHandler: false,
|
|
4548
|
+
...(config.restOptions || {})
|
|
4549
|
+
};
|
|
4550
|
+
restOptions.cors = Object.assign({
|
|
4551
|
+
credentials: true,
|
|
4552
|
+
exposedHeaders: ["content-disposition"],
|
|
4553
|
+
origin: (origin, callback) => {
|
|
4554
|
+
callback(null, true);
|
|
4555
|
+
}
|
|
4556
|
+
}, restOptions.cors || {});
|
|
4557
|
+
restOptions.routePrefix = restOptions.routePrefix == "/" ? "" : restOptions.routePrefix;
|
|
4558
|
+
restOptions.middlewares = [ErrorHandlerMiddleware, ContainerMiddleware, LanguageMiddleware, RequestStartedMiddleware, RequestEndedMiddleware]
|
|
4559
|
+
.concat(restOptions.middlewares || []);
|
|
4560
|
+
restOptions.controllers = [AssetsController, AuthController, GalleryController, ProgressesController, TerminalController$1]
|
|
4561
|
+
.concat(restOptions.controllers || []);
|
|
4562
|
+
// Setup socket API
|
|
4563
|
+
const socketOptions = {
|
|
4564
|
+
container: {
|
|
4565
|
+
get(someClass) {
|
|
4566
|
+
return diContainer.get(someClass);
|
|
4567
|
+
}
|
|
4568
|
+
},
|
|
4569
|
+
...(config.socketOptions || {})
|
|
4570
|
+
};
|
|
4571
|
+
socketOptions.middlewares = [CompressionMiddleware].concat(socketOptions.middlewares || []);
|
|
4572
|
+
socketOptions.controllers = [ProgressController, TerminalController].concat(socketOptions.controllers || []);
|
|
4573
|
+
// Create providers
|
|
4574
|
+
const allProviders = [];
|
|
4575
|
+
// Add multi tokens to sub container
|
|
4576
|
+
[PARAMETER].forEach(provide => {
|
|
4577
|
+
const values = parent.resolveAll(provide);
|
|
4578
|
+
values.forEach(useValue => {
|
|
4579
|
+
allProviders.push({ provide, useValue });
|
|
4580
|
+
});
|
|
4581
|
+
});
|
|
4582
|
+
// Add other providers
|
|
4583
|
+
allProviders.push(...fixtureProviders, ...paramProviders, ...jobProviders, ...commandProviders, ...restOptions.middlewares, ...restOptions.controllers, ...socketOptions.middlewares, ...socketOptions.controllers, ...providers, {
|
|
4584
|
+
provide: EXPRESS,
|
|
4585
|
+
useFactory: ctr => {
|
|
4586
|
+
return ctr.resolve(BackendProvider).express;
|
|
4587
|
+
}
|
|
4588
|
+
}, {
|
|
4589
|
+
provide: HTTP_SERVER,
|
|
4590
|
+
useFactory: ctr => {
|
|
4591
|
+
return ctr.resolve(BackendProvider).server;
|
|
4592
|
+
}
|
|
4593
|
+
}, {
|
|
4594
|
+
provide: SOCKET_SERVER,
|
|
4595
|
+
useFactory: ctr => {
|
|
4596
|
+
return ctr.resolve(BackendProvider).io;
|
|
4597
|
+
}
|
|
4598
|
+
});
|
|
4599
|
+
allProviders.forEach(provider => {
|
|
4600
|
+
if (isType(provider)) {
|
|
4601
|
+
if (container.isRegistered(provider))
|
|
4602
|
+
return;
|
|
4603
|
+
diContainer.register(provider, provider);
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
diContainer.register(provider.provide, provider);
|
|
4607
|
+
});
|
|
4608
|
+
diContainer.register(DI_CONTAINER, {
|
|
4609
|
+
useValue: diContainer
|
|
4610
|
+
});
|
|
4611
|
+
diContainer.register(OPENAPI_VALIDATION, {
|
|
4612
|
+
useValue: config.customValidation || (() => null)
|
|
4613
|
+
});
|
|
4614
|
+
diContainer.register(LOCAL_DIR, {
|
|
4615
|
+
useValue: config.assetLocalDir || "assets_files"
|
|
4616
|
+
});
|
|
4617
|
+
diContainer.register(ASSET_DRIVER, {
|
|
4618
|
+
useClass: config.assetDriver || AssetGridDriver
|
|
4619
|
+
});
|
|
4620
|
+
diContainers.appContainer = diContainers.appContainer || diContainer;
|
|
4621
|
+
// Authentication
|
|
4622
|
+
restOptions.authorizationChecker = async (action, roles) => {
|
|
4623
|
+
const user = await resolveUser(action.request);
|
|
4624
|
+
if (!user) {
|
|
4625
|
+
throw new HttpError(401, "Authentication failed. (User can't be found.)");
|
|
4626
|
+
}
|
|
4627
|
+
const userRoles = Array.isArray(user.roles) ? user.roles : [];
|
|
4628
|
+
if (Array.isArray(roles) && roles.length > 0) {
|
|
4629
|
+
let lastError = null;
|
|
4630
|
+
for (let role of roles) {
|
|
4631
|
+
if (isFunction(role)) {
|
|
4632
|
+
try {
|
|
4633
|
+
if (await valueToPromise(role(user, action))) {
|
|
4634
|
+
return true;
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
catch (e) {
|
|
4638
|
+
lastError = e;
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
if (userRoles.indexOf(role) >= 0)
|
|
4642
|
+
return true;
|
|
4643
|
+
}
|
|
4644
|
+
const error = !lastError || (!lastError.message && !isString(lastError))
|
|
4645
|
+
? "User doesn't have access to this resource"
|
|
4646
|
+
: lastError.message || lastError;
|
|
4647
|
+
throw new HttpError(401, `Authentication failed. (${error})`);
|
|
4648
|
+
}
|
|
4649
|
+
return true;
|
|
4650
|
+
};
|
|
4651
|
+
restOptions.currentUserChecker = async (action) => {
|
|
4652
|
+
try {
|
|
4653
|
+
return await resolveUser(action.request);
|
|
4654
|
+
}
|
|
4655
|
+
catch (e) {
|
|
4656
|
+
return null;
|
|
4657
|
+
}
|
|
4658
|
+
};
|
|
4659
|
+
// Final setup
|
|
4660
|
+
const configuration = diContainer.resolve(Configuration);
|
|
4661
|
+
const bp = diContainer.resolve(BackendProvider);
|
|
4662
|
+
if (config.restOptions) {
|
|
4663
|
+
useContainer(diContainer);
|
|
4664
|
+
useExpressServer(bp.express, restOptions);
|
|
4665
|
+
}
|
|
4666
|
+
if (config.socketOptions) {
|
|
4667
|
+
diContainer.register(SOCKET_CONTROLLERS, {
|
|
4668
|
+
useValue: new SocketControllers({
|
|
4669
|
+
io: bp.io,
|
|
4670
|
+
...socketOptions,
|
|
4671
|
+
})
|
|
4672
|
+
});
|
|
4673
|
+
}
|
|
4674
|
+
// Connect to mongo if necessary
|
|
4675
|
+
if (configuration.hasParam("mongoUri") && configuration.resolve("mongoUri")) {
|
|
4676
|
+
console.log("Connecting to MongoDB...");
|
|
4677
|
+
const connector = diContainer.resolve(MongoConnector);
|
|
4678
|
+
await connector.connect();
|
|
4679
|
+
console.log("Successfully connected to MongoDB.");
|
|
4680
|
+
}
|
|
4681
|
+
await setupStatic(config.rootFolder, diContainer);
|
|
4682
|
+
return diContainer;
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
/**
|
|
4686
|
+
* Generated bundle index. Do not edit.
|
|
4687
|
+
*/
|
|
4688
|
+
|
|
4689
|
+
export { AssetGridDriver, AssetImageParams, AssetLocalDriver, AssetProcessor, AssetResolver, Assets, AuthController, BackendProvider, BaseDoc, Cache, CacheProcessor, Configuration, ConsoleColor, DI_CONTAINER, DocumentArray, EXPRESS, EndpointProvider, ErrorHandlerMiddleware, FIXTURE, Fixtures, Gallery, GalleryCache, GalleryController, HTTP_SERVER, IdGenerator, IsDocumented, IsFile, IsObjectId, JOB, JobManager, JsonResponse, LanguageMiddleware, LazyAssetGenerator, LazyAssets, Logger, MailSender, MemoryCache, MongoConnector, OPENAPI_VALIDATION, OpenApi, PARAMETER, Parameter, PrimitiveArray, Progresses, ResolveEntity, ResponseType, SOCKET_CONTROLLERS, SOCKET_SERVER, TERMINAL_COMMAND, TemplateRenderer, TerminalManager, TokenGenerator, TranslationProvider, Translator, Type, UserManager, assign, broadcast, bufferToStream, camelCaseToDash, colorize, convertValue, copy, copyStream, createIdString, createServices, createTransformer, deleteFile, deleteFromBucket, fileTypeFromBuffer, fileTypeFromStream, filter, firstItem, flatten, getConstructorName, getDirName, getExtension, getFileName, getFunctionParams, getType, getValue, groupBy, gunzipPromised, gzipPromised, hydratePopulated, idToString, injectServices, isArray, isBoolean, isBuffer, isConstructor, isDate, isDefined, isFunction, isInterface, isNullOrUndefined, isObject, isObjectId, isPrimitive, isString, isType, jsonHighlight, lastItem, lcFirst, letsLookupStage, lookupStages, matchField, matchFieldStages, matchStage, md5, mkdirRecursive, multiSubscription, observableFromFunction, padLeft, padRight, paginate, paginateAggregations, prepareUrl, prepareUrlEmpty, prepareUrlSlash, projectStage, promiseTimeout, rand, random, readAndDeleteFile, readFile, regexEscape, regroup, replaceSpecialChars, resolveUser, runCommand, service, setupBackend, streamToBuffer, toImage, ucFirst, uniqueItems, unwindStage, valueToPromise, wrapError, writeFile };
|
|
4690
|
+
//# sourceMappingURL=stemy-backend.mjs.map
|