@stemy/backend 6.0.4 → 6.1.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.
Files changed (39) hide show
  1. package/commands/clear.command.d.ts +6 -0
  2. package/commands/fixtures.command.d.ts +9 -0
  3. package/commands/move-assets.command.d.ts +10 -0
  4. package/common-types.d.ts +22 -15
  5. package/esm2022/commands/clear.command.mjs +15 -0
  6. package/esm2022/commands/fixtures.command.mjs +26 -0
  7. package/esm2022/commands/index.mjs +6 -4
  8. package/esm2022/commands/move-assets.command.mjs +45 -0
  9. package/esm2022/common-types.mjs +3 -5
  10. package/esm2022/public_api.mjs +15 -14
  11. package/esm2022/services/assets.mjs +46 -13
  12. package/esm2022/services/backend-provider.mjs +7 -1
  13. package/esm2022/services/cli-terminal.mjs +56 -0
  14. package/esm2022/services/configuration.mjs +11 -4
  15. package/esm2022/services/drivers/asset-grid.driver.mjs +5 -5
  16. package/esm2022/services/drivers/asset-local.driver.mjs +19 -16
  17. package/esm2022/services/drivers/asset-storage-proxy.driver.mjs +15 -10
  18. package/esm2022/services/drivers/fallback-streams.mjs +1 -39
  19. package/esm2022/services/entities/asset.mjs +47 -6
  20. package/esm2022/services/entities/temp-asset.mjs +15 -3
  21. package/esm2022/services/terminal-manager.mjs +30 -16
  22. package/esm2022/socket-controllers/socket-terminal.mjs +89 -0
  23. package/esm2022/socket-controllers/terminal.controller.mjs +3 -3
  24. package/fesm2022/stemy-backend.mjs +357 -213
  25. package/fesm2022/stemy-backend.mjs.map +1 -1
  26. package/package.json +2 -2
  27. package/public_api.d.ts +1 -1
  28. package/services/assets.d.ts +13 -5
  29. package/services/cli-terminal.d.ts +18 -0
  30. package/services/configuration.d.ts +2 -0
  31. package/services/drivers/asset-grid.driver.d.ts +4 -4
  32. package/services/drivers/asset-local.driver.d.ts +6 -5
  33. package/services/drivers/asset-storage-proxy.driver.d.ts +5 -5
  34. package/services/drivers/fallback-streams.d.ts +1 -8
  35. package/services/entities/asset.d.ts +8 -3
  36. package/services/entities/temp-asset.d.ts +6 -1
  37. package/services/terminal-manager.d.ts +3 -0
  38. package/socket-controllers/socket-terminal.d.ts +19 -0
  39. package/socket-controllers/terminal.controller.d.ts +2 -2
@@ -8,14 +8,14 @@ import { create } from 'fontkit';
8
8
  import sharp_ from 'sharp';
9
9
  import { ObjectId as ObjectId$1 } from 'bson';
10
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';
11
+ import { mkdir, unlink, readFile as readFile$1, writeFile as writeFile$1, lstat, readdir, access, constants, lstatSync, readFileSync, existsSync, mkdirSync, createWriteStream, writeFileSync, createReadStream } from 'fs';
12
12
  import { gzip, gunzip } from 'zlib';
13
13
  import { fileURLToPath } from 'url';
14
14
  import { exec } from 'child_process';
15
15
  import { createHash } from 'crypto';
16
16
  import { Subscription, Observable, Subject, from, BehaviorSubject } from 'rxjs';
17
17
  import { ObjectId } from 'mongodb';
18
- import mongoose, { Types } from 'mongoose';
18
+ import mongoose from 'mongoose';
19
19
  import { Readable, PassThrough } from 'stream';
20
20
  import fileType from 'file-type/core';
21
21
  import dotenv from 'dotenv';
@@ -31,14 +31,15 @@ import { routingControllersToSpec, OpenAPI, getStatusCode } from 'routing-contro
31
31
  import { defaultMetadataStorage } from 'class-transformer/cjs/storage';
32
32
  import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
33
33
  import { ValidatorConstraint, ValidationTypes, Min, Max, IsOptional, IsBoolean } from 'class-validator';
34
+ import { CommandsAddon, AnsiCodes } from '@stemy/terminal-commands-addon';
35
+ import * as readline from 'readline';
34
36
  import { v4 } from 'uuid';
35
37
  import { createTransport } from 'nodemailer';
36
38
  import * as Handlebars from 'handlebars';
37
- import { CommandsAddon, AnsiCodes } from '@stemy/terminal-commands-addon';
38
39
  import { compare } from 'bcrypt';
39
40
  import moment from 'moment';
40
41
  import { GridFSBucket } from 'mongodb/lib/gridfs';
41
- import { writeFile as writeFile$2, rm } from 'fs/promises';
42
+ import { rm } from 'fs/promises';
42
43
  import got from 'got';
43
44
  import { getModelForClass } from '@typegoose/typegoose';
44
45
  import { getValue as getValue$1, setValue } from 'mongoose/lib/utils';
@@ -56,10 +57,8 @@ const SOCKET_CONTROLLERS = Symbol.for("socket-controllers-token");
56
57
  const PARAMETER = Symbol.for("parameter-token");
57
58
  const DI_CONTAINER = Symbol.for("di-container-token");
58
59
  const OPENAPI_VALIDATION = Symbol.for("openapi-validation-token");
59
- const LOCAL_DIR = Symbol.for('asset-local-dir');
60
- const ASSET_DRIVER = Symbol.for('assets-driver');
61
- const ASSET_MAIN_DRIVER = Symbol.for('assets-main-driver');
62
- const ASSET_FALLBACK_DRIVER = Symbol.for('assets-fallback-driver');
60
+ const ASSET_LOCAL_DIR = Symbol.for('asset-local-dir');
61
+ const ASSET_DRIVER_FACTORIES = Symbol.for('assets-driver-factories');
63
62
  class Parameter {
64
63
  name;
65
64
  defaultValue;
@@ -867,10 +866,12 @@ async function fileTypeFromBuffer(buffer) {
867
866
  }
868
867
 
869
868
  let Configuration = class Configuration {
869
+ isCli;
870
870
  paramMap;
871
871
  paramValues;
872
872
  constructor(params) {
873
873
  dotenv.config();
874
+ this.isCli = process.env.IS_CLI === 'true';
874
875
  this.paramMap = {};
875
876
  this.paramValues = {};
876
877
  (params || []).forEach(param => this.add(param));
@@ -891,15 +892,15 @@ let Configuration = class Configuration {
891
892
  const value = isFunction(param.resolver)
892
893
  ? param.resolver(envValue, helper)
893
894
  : convertValue(envValue, getType(param.defaultValue));
894
- console.log(colorize(`Processing param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), colorize(envName, ConsoleColor.FgBlue), `"${envValue}"`, value);
895
+ this.log(colorize(`Processing param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), colorize(envName, ConsoleColor.FgBlue), `"${envValue}"`, value);
895
896
  return value;
896
897
  }
897
898
  else if (isFunction(param.resolver)) {
898
899
  const value = param.resolver(param.defaultValue, helper);
899
- console.log(colorize(`Processing default param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), param.defaultValue, value);
900
+ this.log(colorize(`Processing default param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), param.defaultValue, value);
900
901
  return value;
901
902
  }
902
- console.log(colorize(`Using default param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), param.defaultValue);
903
+ this.log(colorize(`Using default param value`, ConsoleColor.FgYellow), colorize(param.name, ConsoleColor.FgGreen), param.defaultValue);
903
904
  return param.defaultValue;
904
905
  }
905
906
  hasParam(name) {
@@ -921,6 +922,11 @@ let Configuration = class Configuration {
921
922
  }
922
923
  return this.paramValues[name];
923
924
  }
925
+ log(...args) {
926
+ if (this.isCli)
927
+ return;
928
+ console.log(...args);
929
+ }
924
930
  };
925
931
  Configuration = __decorate([
926
932
  singleton(),
@@ -992,10 +998,19 @@ class BaseEntity {
992
998
  }
993
999
 
994
1000
  class Asset extends BaseEntity {
995
- driver;
1001
+ drivers;
996
1002
  get filename() {
997
1003
  return this.data.filename;
998
1004
  }
1005
+ get streamId() {
1006
+ return this.data.streamId || this.oid;
1007
+ }
1008
+ get driverId() {
1009
+ return this.data.driverId;
1010
+ }
1011
+ get driver() {
1012
+ return this.drivers.getDriver(this.driverId || this.drivers.missingDriver);
1013
+ }
999
1014
  get contentType() {
1000
1015
  return this.data.contentType;
1001
1016
  }
@@ -1003,15 +1018,15 @@ class Asset extends BaseEntity {
1003
1018
  return this.data.metadata;
1004
1019
  }
1005
1020
  get stream() {
1006
- return this.driver.openDownloadStream(this.oid);
1021
+ return this.driver.openDownloadStream(this);
1007
1022
  }
1008
- constructor(id, data, collection, driver) {
1023
+ constructor(id, data, collection, drivers) {
1009
1024
  super(id, data, collection);
1010
- this.driver = driver;
1025
+ this.drivers = drivers;
1011
1026
  }
1012
1027
  async unlink() {
1013
1028
  try {
1014
- await this.driver.delete(this.oid);
1029
+ await this.driver.delete(this);
1015
1030
  }
1016
1031
  catch (error) {
1017
1032
  console.log("Failed to unlink", error?.message);
@@ -1026,6 +1041,38 @@ class Asset extends BaseEntity {
1026
1041
  getBuffer() {
1027
1042
  return streamToBuffer(this.stream);
1028
1043
  }
1044
+ async move(driverId) {
1045
+ const oldDriver = this.driver;
1046
+ const targetDriver = this.drivers.getDriver(driverId);
1047
+ if (targetDriver === oldDriver)
1048
+ return this;
1049
+ const oldAsset = new Asset(this.oid, this.data, this.collection, this.drivers);
1050
+ const streamId = await this.uploadTo(targetDriver);
1051
+ this.data = {
1052
+ ...this.data,
1053
+ streamId,
1054
+ driverId,
1055
+ };
1056
+ await this.save();
1057
+ await oldDriver.delete(oldAsset);
1058
+ }
1059
+ uploadTo(driver) {
1060
+ return new Promise((resolve, reject) => {
1061
+ const uploaderStream = driver.openUploadStream(this.filename, {
1062
+ chunkSizeBytes: 1048576,
1063
+ contentType: this.contentType,
1064
+ extension: this.metadata.extension,
1065
+ metadata: this.metadata,
1066
+ });
1067
+ this.stream.pipe(uploaderStream)
1068
+ .on("error", error => {
1069
+ reject(error.message || error);
1070
+ })
1071
+ .on("finish", async () => {
1072
+ resolve(uploaderStream.id);
1073
+ });
1074
+ });
1075
+ }
1029
1076
  async download(metadata) {
1030
1077
  metadata = Object.assign(this.metadata, metadata || {});
1031
1078
  metadata.downloadCount = isNaN(metadata.downloadCount) || !metadata.firstDownload
@@ -1049,16 +1096,25 @@ class TempAsset {
1049
1096
  filename;
1050
1097
  contentType;
1051
1098
  metadata;
1052
- id;
1099
+ get id() {
1100
+ return this.oid.toHexString();
1101
+ }
1102
+ get streamId() {
1103
+ return this.oid;
1104
+ }
1105
+ get driverId() {
1106
+ return "temp";
1107
+ }
1053
1108
  get stream() {
1054
1109
  return bufferToStream(this.buffer);
1055
1110
  }
1111
+ oid;
1056
1112
  constructor(buffer, filename, contentType, metadata) {
1057
1113
  this.buffer = buffer;
1058
1114
  this.filename = filename;
1059
1115
  this.contentType = contentType;
1060
1116
  this.metadata = metadata;
1061
- this.id = new ObjectId$1().toHexString();
1117
+ this.oid = new ObjectId$1();
1062
1118
  }
1063
1119
  async unlink() {
1064
1120
  throw new Error(`Temp asset '${this.id}' can not be removed!`);
@@ -1069,6 +1125,9 @@ class TempAsset {
1069
1125
  async getBuffer() {
1070
1126
  return this.buffer;
1071
1127
  }
1128
+ async move() {
1129
+ throw new Error(`Temp asset '${this.id}' can not be moved!`);
1130
+ }
1072
1131
  async download(metadata) {
1073
1132
  return this.stream;
1074
1133
  }
@@ -1098,13 +1157,38 @@ class TempAsset {
1098
1157
  let Assets = class Assets {
1099
1158
  connector;
1100
1159
  assetProcessor;
1101
- driver;
1160
+ config;
1161
+ container;
1162
+ driverFactoryMap;
1102
1163
  collection;
1103
- constructor(connector, assetProcessor, driver) {
1164
+ driverMap;
1165
+ mainDriver;
1166
+ missingDriver;
1167
+ drivers;
1168
+ constructor(connector, assetProcessor, config, container, driverFactoryMap) {
1104
1169
  this.connector = connector;
1105
1170
  this.assetProcessor = assetProcessor;
1106
- this.driver = driver;
1171
+ this.config = config;
1172
+ this.container = container;
1173
+ this.driverFactoryMap = driverFactoryMap;
1107
1174
  this.collection = connector.database?.collection("assets.metadata");
1175
+ this.driverMap = new Map();
1176
+ this.mainDriver = this.config.resolve("assetsMainDriver");
1177
+ this.missingDriver = this.config.resolve("assetsMissingDriver");
1178
+ this.drivers = Object.keys(driverFactoryMap);
1179
+ }
1180
+ getDriver(name) {
1181
+ if (!name) {
1182
+ throw new Error(`No name provider for asset driver!`);
1183
+ }
1184
+ if (!this.driverMap.has(name)) {
1185
+ const factory = this.driverFactoryMap[name];
1186
+ if (!factory) {
1187
+ throw new Error(`No asset driver factory found with name: '${name}'!`);
1188
+ }
1189
+ this.driverMap.set(name, factory(this.container));
1190
+ }
1191
+ return this.driverMap.get(name);
1108
1192
  }
1109
1193
  async write(stream, contentType = null, metadata = null) {
1110
1194
  const uploadStream = copyStream(stream);
@@ -1173,7 +1257,7 @@ let Assets = class Assets {
1173
1257
  }
1174
1258
  async find(where) {
1175
1259
  const data = await this.collection.findOne(where);
1176
- return !data ? null : new Asset(data._id, data, this.collection, this.driver);
1260
+ return !data ? null : new Asset(data._id, data, this.collection, this);
1177
1261
  }
1178
1262
  async findMany(where) {
1179
1263
  const cursor = this.collection.find(where);
@@ -1182,7 +1266,7 @@ let Assets = class Assets {
1182
1266
  for (let item of items) {
1183
1267
  if (!item)
1184
1268
  continue;
1185
- result.push(new Asset(item._id, item, this.collection, this.driver));
1269
+ result.push(new Asset(item._id, item, this.collection, this));
1186
1270
  }
1187
1271
  return result;
1188
1272
  }
@@ -1206,10 +1290,13 @@ let Assets = class Assets {
1206
1290
  metadata.filename = metadata.filename || new ObjectId$1().toHexString();
1207
1291
  metadata.extension = (fileType.ext || "").trim();
1208
1292
  return new Promise(((resolve, reject) => {
1209
- const uploaderStream = this.driver.openUploadStream(metadata.filename, {
1293
+ const driverId = this.mainDriver;
1294
+ const driver = this.getDriver(driverId);
1295
+ const uploaderStream = driver.openUploadStream(metadata.filename, {
1210
1296
  chunkSizeBytes: 1048576,
1297
+ contentType: fileType.mime,
1298
+ extension: fileType.ext,
1211
1299
  metadata,
1212
- contentType: fileType.mime
1213
1300
  });
1214
1301
  stream.pipe(uploaderStream)
1215
1302
  .on("error", error => {
@@ -1217,10 +1304,12 @@ let Assets = class Assets {
1217
1304
  })
1218
1305
  .on("finish", () => {
1219
1306
  const asset = new Asset(uploaderStream.id, {
1307
+ streamId: uploaderStream.id,
1220
1308
  filename: metadata.filename,
1221
1309
  contentType,
1222
- metadata
1223
- }, this.collection, this.driver);
1310
+ metadata,
1311
+ driverId
1312
+ }, this.collection, this);
1224
1313
  asset.save().then(() => {
1225
1314
  resolve(asset);
1226
1315
  }, error => {
@@ -1233,9 +1322,11 @@ let Assets = class Assets {
1233
1322
  Assets = __decorate([
1234
1323
  injectable(),
1235
1324
  scoped(Lifecycle.ContainerScoped),
1236
- __param(2, inject(ASSET_DRIVER)),
1325
+ __param(3, inject(DI_CONTAINER)),
1326
+ __param(4, inject(ASSET_DRIVER_FACTORIES)),
1237
1327
  __metadata("design:paramtypes", [MongoConnector,
1238
- AssetProcessor, Object])
1328
+ AssetProcessor,
1329
+ Configuration, Object, Object])
1239
1330
  ], Assets);
1240
1331
 
1241
1332
  class LazyAsset extends BaseEntity {
@@ -2083,6 +2174,154 @@ Fixtures = __decorate([
2083
2174
  __metadata("design:paramtypes", [Object])
2084
2175
  ], Fixtures);
2085
2176
 
2177
+ class CliTerminal {
2178
+ callbacks = new Set();
2179
+ stdin = process.stdin;
2180
+ constructor() {
2181
+ // Set stdin to raw mode to get character-by-character data
2182
+ if (this.stdin.isTTY) {
2183
+ this.stdin.setRawMode(true);
2184
+ }
2185
+ this.clearScreen();
2186
+ this.stdin.setEncoding('utf8');
2187
+ this.stdin.on('data', this.handleInput);
2188
+ }
2189
+ clearScreen() {
2190
+ readline.cursorTo(process.stdout, 0, 0);
2191
+ readline.clearScreenDown(process.stdout);
2192
+ }
2193
+ handleInput = (data) => {
2194
+ const input = data.toString();
2195
+ // Standard "Ctrl+C" exit handling
2196
+ if (input === '\u0003') {
2197
+ this.clearScreen();
2198
+ this.dispose();
2199
+ }
2200
+ this.callbacks.forEach((cb) => cb(input));
2201
+ };
2202
+ /**
2203
+ * Registers a listener for data input.
2204
+ * Returns an IDisposable to unregister the listener.
2205
+ */
2206
+ onData(cb) {
2207
+ this.callbacks.add(cb);
2208
+ return {
2209
+ dispose: () => this.callbacks.delete(cb),
2210
+ };
2211
+ }
2212
+ write(data) {
2213
+ process.stdout.write(data);
2214
+ }
2215
+ writeln(data) {
2216
+ process.stdout.write(data + '\n');
2217
+ }
2218
+ loadAddon(addon) {
2219
+ addon.activate(this);
2220
+ }
2221
+ dispose() {
2222
+ if (this.stdin.isTTY) {
2223
+ this.stdin.setRawMode(false);
2224
+ }
2225
+ this.stdin.removeListener('data', this.handleInput);
2226
+ this.stdin.pause();
2227
+ this.callbacks.clear();
2228
+ process.exit();
2229
+ }
2230
+ }
2231
+
2232
+ let TerminalManager = class TerminalManager {
2233
+ logger;
2234
+ config;
2235
+ cliTerminal;
2236
+ servicePassword;
2237
+ suggestions;
2238
+ commands;
2239
+ loggedOutCommands;
2240
+ loggedInCommands;
2241
+ constructor(logger, config, commands) {
2242
+ this.logger = logger;
2243
+ this.config = config;
2244
+ this.servicePassword = config.resolve("servicePassword");
2245
+ this.suggestions = {
2246
+ login: async (args) => {
2247
+ if (args.length > 2) {
2248
+ return null;
2249
+ }
2250
+ const input = `${args.at(1).label}`;
2251
+ return (!input) ? [] : [{
2252
+ id: input,
2253
+ label: input,
2254
+ masked: true
2255
+ }];
2256
+ },
2257
+ ...commands.reduce((acc, command) => {
2258
+ command.name = camelCaseToDash(command.name || command.constructor.name || "");
2259
+ if (!command.name || !command.suggest)
2260
+ return acc;
2261
+ acc[command.name] = async (a, t) => command.suggest(a, t);
2262
+ return acc;
2263
+ }, {})
2264
+ };
2265
+ this.commands = commands.reduce((acc, command) => {
2266
+ if (!command.name)
2267
+ return acc;
2268
+ acc[command.name] = async (a, t, p) => command.execute(a, t, p);
2269
+ return acc;
2270
+ }, {});
2271
+ this.loggedOutCommands = ["login", "clear"];
2272
+ this.loggedInCommands = Object.keys(this.commands);
2273
+ this.loggedInCommands.push("logout");
2274
+ console.log(`Current service password is: ${colorize(this.servicePassword, ConsoleColor.FgGreen)}`);
2275
+ }
2276
+ runCli() {
2277
+ this.cliTerminal = new CliTerminal();
2278
+ this.loadAddons(this.cliTerminal);
2279
+ }
2280
+ loadAddons(terminal) {
2281
+ const isCli = terminal === this.cliTerminal;
2282
+ let loggedIn = isCli;
2283
+ const commands = isCli ? {
2284
+ logout: async () => {
2285
+ terminal.dispose();
2286
+ }
2287
+ } : {
2288
+ login: async (args, terminal) => {
2289
+ if (args.at(1).label === this.servicePassword) {
2290
+ loggedIn = true;
2291
+ terminal.writeln("Logged in as admin");
2292
+ }
2293
+ else {
2294
+ throw new Error("Invalid login");
2295
+ }
2296
+ },
2297
+ logout: async (args, terminal) => {
2298
+ loggedIn = false;
2299
+ terminal.writeln("Logged out");
2300
+ }
2301
+ };
2302
+ const addon = new CommandsAddon({
2303
+ commands: {
2304
+ ...commands,
2305
+ ...this.commands
2306
+ },
2307
+ suggestCommands: async () => {
2308
+ if (loggedIn) {
2309
+ return this.loggedInCommands;
2310
+ }
2311
+ return this.loggedOutCommands;
2312
+ },
2313
+ suggestions: this.suggestions
2314
+ });
2315
+ terminal.loadAddon(addon);
2316
+ }
2317
+ };
2318
+ TerminalManager = __decorate([
2319
+ singleton(),
2320
+ __param(2, injectAll(TERMINAL_COMMAND)),
2321
+ __metadata("design:paramtypes", [Logger,
2322
+ Configuration, Array])
2323
+ ], TerminalManager);
2324
+
2086
2325
  const express = express_;
2087
2326
  let BackendProvider = class BackendProvider {
2088
2327
  config;
@@ -2131,6 +2370,11 @@ let BackendProvider = class BackendProvider {
2131
2370
  }
2132
2371
  async quickStart() {
2133
2372
  const port = this.config.resolve("appPort");
2373
+ const isCli = this.config.isCli;
2374
+ if (isCli) {
2375
+ this.container.resolve(TerminalManager).runCli();
2376
+ return `Running CLI mode`;
2377
+ }
2134
2378
  const isWorker = this.config.resolve("isWorker");
2135
2379
  if (isWorker || this.config.resolve("startWorker")) {
2136
2380
  await this.container.resolve(JobManager).startProcessing();
@@ -2714,86 +2958,6 @@ MemoryCache = __decorate([
2714
2958
  __metadata("design:paramtypes", [Cache])
2715
2959
  ], MemoryCache);
2716
2960
 
2717
- let TerminalManager = class TerminalManager {
2718
- logger;
2719
- config;
2720
- servicePassword;
2721
- suggestions;
2722
- commands;
2723
- loggedOutCommands;
2724
- loggedInCommands;
2725
- constructor(logger, config, commands) {
2726
- this.logger = logger;
2727
- this.config = config;
2728
- this.servicePassword = config.resolve("servicePassword");
2729
- this.suggestions = {
2730
- login: async (args) => {
2731
- if (args.length > 2) {
2732
- return null;
2733
- }
2734
- const input = `${args.at(1).label}`;
2735
- return (!input) ? [] : [{
2736
- id: input,
2737
- label: input,
2738
- masked: true
2739
- }];
2740
- },
2741
- ...commands.reduce((acc, command) => {
2742
- command.name = camelCaseToDash(command.name || command.constructor.name || "");
2743
- if (!command.name || !command.suggest)
2744
- return acc;
2745
- acc[command.name] = async (a, t) => command.suggest(a, t);
2746
- return acc;
2747
- }, {})
2748
- };
2749
- this.commands = commands.reduce((acc, command) => {
2750
- if (!command.name)
2751
- return acc;
2752
- acc[command.name] = async (a, t) => command.execute(a, t);
2753
- return acc;
2754
- }, {});
2755
- this.loggedOutCommands = ["login", "clear"];
2756
- this.loggedInCommands = Object.keys(this.commands);
2757
- this.loggedInCommands.push("logout");
2758
- console.log(`Current service password is: ${colorize(this.servicePassword, ConsoleColor.FgGreen)}`);
2759
- }
2760
- loadAddons(terminal) {
2761
- let loggedIn = false;
2762
- const addon = new CommandsAddon({
2763
- commands: {
2764
- login: async (args, terminal) => {
2765
- if (args.at(1).label === this.servicePassword) {
2766
- loggedIn = true;
2767
- terminal.writeln("Logged in as admin");
2768
- }
2769
- else {
2770
- throw new Error("Invalid login");
2771
- }
2772
- },
2773
- logout: async (args, terminal) => {
2774
- loggedIn = false;
2775
- terminal.writeln("Logged out");
2776
- },
2777
- ...this.commands
2778
- },
2779
- suggestCommands: async () => {
2780
- if (loggedIn) {
2781
- return this.loggedInCommands;
2782
- }
2783
- return this.loggedOutCommands;
2784
- },
2785
- suggestions: this.suggestions
2786
- });
2787
- terminal.loadAddon(addon);
2788
- }
2789
- };
2790
- TerminalManager = __decorate([
2791
- singleton(),
2792
- __param(2, injectAll(TERMINAL_COMMAND)),
2793
- __metadata("design:paramtypes", [Logger,
2794
- Configuration, Array])
2795
- ], TerminalManager);
2796
-
2797
2961
  let TokenGenerator = class TokenGenerator {
2798
2962
  chars;
2799
2963
  constructor() {
@@ -3573,7 +3737,7 @@ ProgressController = __decorate([
3573
3737
  __metadata("design:paramtypes", [Progresses, Server])
3574
3738
  ], ProgressController);
3575
3739
 
3576
- class Terminal {
3740
+ class SocketTerminal {
3577
3741
  client;
3578
3742
  addons;
3579
3743
  files$;
@@ -3667,7 +3831,7 @@ let TerminalController = class TerminalController {
3667
3831
  this.terminals = {};
3668
3832
  }
3669
3833
  async terminalInit(client) {
3670
- const terminal = new Terminal(client);
3834
+ const terminal = new SocketTerminal(client);
3671
3835
  this.manager.loadAddons(terminal);
3672
3836
  this.terminals[client.id] = terminal;
3673
3837
  client.on("disconnect", () => terminal.dispose());
@@ -3986,9 +4150,50 @@ FixturesCommand = __decorate([
3986
4150
  __metadata("design:paramtypes", [Fixtures])
3987
4151
  ], FixturesCommand);
3988
4152
 
4153
+ let MoveAssetsCommand = class MoveAssetsCommand {
4154
+ assets;
4155
+ name = "move-assets";
4156
+ constructor(assets) {
4157
+ this.assets = assets;
4158
+ }
4159
+ async execute(args, terminal, progress) {
4160
+ await promiseTimeout(1000);
4161
+ const from = `${args.at(1).id}`;
4162
+ const to = `${args.at(2).id}`;
4163
+ const assets = await this.assets.findMany(from === this.assets.missingDriver ? {
4164
+ $or: [
4165
+ { driverId: from },
4166
+ { driverId: { "$exists": false } }
4167
+ ]
4168
+ } : { driverId: from });
4169
+ progress.setMax(assets.length);
4170
+ for (const asset of assets) {
4171
+ await asset.move(to);
4172
+ progress.advance();
4173
+ }
4174
+ terminal.writeln(colorize(`Assets successfully moved to: ${to}`, ConsoleColor.FgGreen));
4175
+ }
4176
+ async suggest(args) {
4177
+ if (args.length > 3) {
4178
+ return null;
4179
+ }
4180
+ // Replace regex special characters
4181
+ const prev = args.length === 3 ? args.at(1) : null;
4182
+ const id = regexEscape(args.search);
4183
+ const regex = new RegExp(id, "g");
4184
+ return this.assets.drivers.filter(d => d.match(regex) && (!prev || d !== prev.id));
4185
+ }
4186
+ };
4187
+ MoveAssetsCommand = __decorate([
4188
+ injectable(),
4189
+ scoped(Lifecycle.ContainerScoped),
4190
+ __metadata("design:paramtypes", [Assets])
4191
+ ], MoveAssetsCommand);
4192
+
3989
4193
  const commands = [
3990
4194
  ClearCommand,
3991
- FixturesCommand
4195
+ FixturesCommand,
4196
+ MoveAssetsCommand
3992
4197
  ];
3993
4198
 
3994
4199
  let TtlFixture = class TtlFixture {
@@ -4022,11 +4227,11 @@ let AssetGridDriver = class AssetGridDriver {
4022
4227
  openUploadStream(filename, opts) {
4023
4228
  return this.bucket.openUploadStream(filename, opts);
4024
4229
  }
4025
- openDownloadStream(id) {
4026
- return this.bucket.openDownloadStream(id);
4230
+ openDownloadStream(asset) {
4231
+ return this.bucket.openDownloadStream(asset.streamId);
4027
4232
  }
4028
- delete(id) {
4029
- return this.bucket.delete(id);
4233
+ delete(asset) {
4234
+ return this.bucket.delete(asset.streamId);
4030
4235
  }
4031
4236
  };
4032
4237
  AssetGridDriver = __decorate([
@@ -4040,29 +4245,31 @@ let AssetLocalDriver = class AssetLocalDriver {
4040
4245
  this.dir = dir;
4041
4246
  }
4042
4247
  openUploadStream(filename, opts) {
4043
- const id = new Types.ObjectId();
4248
+ const id = new ObjectId$1();
4044
4249
  const dir = `${this.dir}/${id.toHexString()}`;
4045
4250
  mkdirSync(dir, { recursive: true });
4046
- const stream = createWriteStream(`${dir}/file.bin`);
4047
- stream.id = id;
4251
+ const stream = createWriteStream(this.getPath(id, filename, opts.extension));
4048
4252
  stream.done = false;
4049
4253
  stream.on('finish', () => {
4050
- writeFile$2(`${dir}/filename.txt`, filename);
4051
- writeFile$2(`${dir}/metadata.json`, JSON.stringify(opts?.metadata || {}));
4254
+ stream.id = id;
4052
4255
  stream.done = true;
4256
+ writeFileSync(`${dir}/metadata.json`, JSON.stringify(opts?.metadata || {}));
4053
4257
  });
4054
4258
  return stream;
4055
4259
  }
4056
- openDownloadStream(id) {
4057
- return createReadStream(`${this.dir}/${id.toHexString()}/file.bin`, { autoClose: true, emitClose: true });
4260
+ openDownloadStream(asset) {
4261
+ return createReadStream(this.getPath(asset.streamId, asset.filename, asset.metadata.extension), { autoClose: true, emitClose: true });
4058
4262
  }
4059
- delete(id) {
4060
- return rm(`${this.dir}/${id.toHexString()}`, { recursive: true, force: true });
4263
+ delete(asset) {
4264
+ return rm(this.getPath(asset.streamId, asset.filename, asset.metadata.extension), { recursive: true, force: true });
4265
+ }
4266
+ getPath(id, name, ext) {
4267
+ return join(this.dir, id.toHexString(), `${name}.${ext}`);
4061
4268
  }
4062
4269
  };
4063
4270
  AssetLocalDriver = __decorate([
4064
4271
  injectable(),
4065
- __param(0, inject(LOCAL_DIR)),
4272
+ __param(0, inject(ASSET_LOCAL_DIR)),
4066
4273
  __metadata("design:paramtypes", [String])
4067
4274
  ], AssetLocalDriver);
4068
4275
 
@@ -4098,74 +4305,6 @@ function createFallbackReadable(primary, fallbackFactory) {
4098
4305
  });
4099
4306
  return proxy;
4100
4307
  }
4101
- /**
4102
- * Creates a Writable proxy that switches to a fallback destination on error.
4103
- * @param primary The initial Writable destination.
4104
- * @param fallbackFactory A function that returns a new Writable destination.
4105
- */
4106
- function createFallbackWritable(primary, fallbackFactory) {
4107
- // 1. The Proxy acts as the stable "entry point" for your data
4108
- const proxy = new PassThrough();
4109
- let currentDestination = primary;
4110
- let hasSwitched = false;
4111
- // Function to handle the swap
4112
- const handleSwitch = (err) => {
4113
- if (hasSwitched)
4114
- return; // Prevent infinite loops if fallback also fails
4115
- hasSwitched = true;
4116
- // 2. Unpipe from the failed stream and destroy it
4117
- proxy.unpipe(currentDestination);
4118
- currentDestination.destroy();
4119
- // 3. Create the new destination and pipe the remaining data to it
4120
- const fallback = fallbackFactory();
4121
- currentDestination = fallback;
4122
- // Pipe the proxy into the fallback
4123
- proxy.pipe(fallback);
4124
- // Ensure errors on the fallback are bubbled up
4125
- fallback.on('error', (fallbackErr) => {
4126
- proxy.emit('error', fallbackErr);
4127
- });
4128
- };
4129
- // Initial setup
4130
- primary.on('error', handleSwitch);
4131
- proxy.on('finish', () => {
4132
- const final = proxy;
4133
- final.id = currentDestination.id;
4134
- final.done = true;
4135
- });
4136
- proxy.pipe(primary);
4137
- return proxy;
4138
- }
4139
-
4140
- let AssetFallbackDriver = class AssetFallbackDriver {
4141
- main;
4142
- fallback;
4143
- constructor(main, fallback) {
4144
- this.main = main;
4145
- this.fallback = fallback;
4146
- }
4147
- openDownloadStream(id) {
4148
- return createFallbackReadable(this.main.openDownloadStream(id), () => this.fallback.openDownloadStream(id));
4149
- }
4150
- openUploadStream(filename, opts) {
4151
- return createFallbackWritable(this.main.openUploadStream(filename, opts), () => this.fallback.openUploadStream(filename, opts));
4152
- }
4153
- async delete(id) {
4154
- try {
4155
- await this.main.delete(id);
4156
- }
4157
- catch (e) {
4158
- console.log("Failed to delete from primary: ", e.message);
4159
- await this.fallback.delete(id);
4160
- }
4161
- }
4162
- };
4163
- AssetFallbackDriver = __decorate([
4164
- injectable(),
4165
- __param(0, inject(ASSET_MAIN_DRIVER)),
4166
- __param(1, inject(ASSET_FALLBACK_DRIVER)),
4167
- __metadata("design:paramtypes", [Object, Object])
4168
- ], AssetFallbackDriver);
4169
4308
 
4170
4309
  let AssetStorageProxyDriver = class AssetStorageProxyDriver {
4171
4310
  config;
@@ -4176,12 +4315,9 @@ let AssetStorageProxyDriver = class AssetStorageProxyDriver {
4176
4315
  this.baseUrl = this.config.resolve("storageProxyUri");
4177
4316
  this.url = this.baseUrl + this.config.resolve("storageProxyBucket");
4178
4317
  }
4179
- openDownloadStream(id) {
4180
- return got.stream.get(this.getUrl(id));
4181
- }
4182
4318
  openUploadStream(_, opts) {
4183
4319
  const id = new ObjectId$1();
4184
- const stream = got.stream.put(this.getUrl(id), { headers: {
4320
+ const stream = got.stream.put(this.getUrl(id, opts.extension), { headers: {
4185
4321
  'Content-Type': opts?.contentType || 'application/octet-stream'
4186
4322
  } });
4187
4323
  stream.done = false;
@@ -4191,12 +4327,19 @@ let AssetStorageProxyDriver = class AssetStorageProxyDriver {
4191
4327
  });
4192
4328
  return stream;
4193
4329
  }
4194
- async delete(id) {
4195
- console.log("DELETE", id.toHexString());
4196
- await got.delete(this.getUrl(id));
4330
+ openDownloadStream(asset) {
4331
+ return createFallbackReadable(got.stream.get(this.getUrl(asset.streamId, asset.metadata.extension)), () => got.stream.get(this.getUrl(asset.streamId)));
4197
4332
  }
4198
- getUrl(id) {
4199
- return `${this.url}/${id.toHexString()}`;
4333
+ async delete(asset) {
4334
+ try {
4335
+ await got.delete(this.getUrl(asset.streamId, asset.metadata.extension));
4336
+ }
4337
+ catch (e) {
4338
+ await got.delete(this.getUrl(asset.streamId));
4339
+ }
4340
+ }
4341
+ getUrl(id, ext) {
4342
+ return !ext ? `${this.url}/${id.toHexString()}` : `${this.url}/${id.toHexString()}.${ext}`;
4200
4343
  }
4201
4344
  };
4202
4345
  AssetStorageProxyDriver = __decorate([
@@ -4581,8 +4724,10 @@ function createServices() {
4581
4724
  new Parameter("mongoDb", "node-backend"),
4582
4725
  new Parameter("mongoUser", null),
4583
4726
  new Parameter("mongoPassword", null),
4584
- new Parameter("storageProxyUri", "http://localhost:4500/", prepareUrl),
4727
+ new Parameter("storageProxyUri", "http://localhost:4500/files", prepareUrlSlash),
4585
4728
  new Parameter("storageProxyBucket", "something"),
4729
+ new Parameter("assetsMainDriver", "grid"),
4730
+ new Parameter("assetsMissingDriver", "grid"),
4586
4731
  new Parameter("nodeEnv", "production"),
4587
4732
  new Parameter("appPort", 80),
4588
4733
  new Parameter("zmqPort", 3000),
@@ -4752,17 +4897,16 @@ async function setupBackend(config, providers, parent) {
4752
4897
  diContainer.register(OPENAPI_VALIDATION, {
4753
4898
  useValue: config.customValidation || (() => null)
4754
4899
  });
4755
- diContainer.register(LOCAL_DIR, {
4900
+ diContainer.register(ASSET_LOCAL_DIR, {
4756
4901
  useValue: config.assetLocalDir || "assets_files"
4757
4902
  });
4758
- diContainer.register(ASSET_DRIVER, {
4759
- useClass: config.assetDriver || AssetFallbackDriver
4760
- });
4761
- diContainer.register(ASSET_MAIN_DRIVER, {
4762
- useClass: config.assetMainDriver || AssetGridDriver
4763
- });
4764
- diContainer.register(ASSET_FALLBACK_DRIVER, {
4765
- useClass: config.assetFallbackDriver || AssetLocalDriver
4903
+ diContainer.register(ASSET_DRIVER_FACTORIES, {
4904
+ useValue: {
4905
+ grid: container => container.resolve(AssetGridDriver),
4906
+ storageProxy: container => container.resolve(AssetStorageProxyDriver),
4907
+ local: container => container.resolve(AssetLocalDriver),
4908
+ ...(config.assetDrivers || {}),
4909
+ }
4766
4910
  });
4767
4911
  diContainers.appContainer = diContainers.appContainer || diContainer;
4768
4912
  // Authentication