@stemy/backend 5.1.1 → 5.2.1

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.
@@ -8,15 +8,15 @@ import fontKit_ 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 } from 'fs';
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
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 { canReportError } from 'rxjs/internal/util/canReportError';
18
- import { ObjectId, GridFSBucket } from 'mongodb';
19
- import mongoose from 'mongoose';
18
+ import { ObjectId } from 'mongodb';
19
+ import mongoose, { Types } from 'mongoose';
20
20
  import { Readable, PassThrough } from 'stream';
21
21
  import fileType from 'file-type/core';
22
22
  import dotenv from 'dotenv';
@@ -37,6 +37,8 @@ import * as Handlebars from 'handlebars';
37
37
  import { CommandsAddon, AnsiCodes } from '@stemy/terminal-commands-addon';
38
38
  import { compare } from 'bcrypt';
39
39
  import moment from 'moment';
40
+ import { GridFSBucket } from 'mongodb/lib/gridfs';
41
+ import { writeFile as writeFile$2, rm } from 'fs/promises';
40
42
  import { getModelForClass } from '@typegoose/typegoose';
41
43
  import { getValue as getValue$1, setValue } from 'mongoose/lib/utils';
42
44
 
@@ -53,6 +55,8 @@ const SOCKET_CONTROLLERS = Symbol.for("socket-controllers-token");
53
55
  const PARAMETER = Symbol.for("parameter-token");
54
56
  const DI_CONTAINER = Symbol.for("di-container-token");
55
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');
56
60
  class Parameter {
57
61
  constructor(name, defaultValue, resolver = null) {
58
62
  this.name = name;
@@ -931,14 +935,10 @@ let MongoConnector = class MongoConnector {
931
935
  get database() {
932
936
  return this.db;
933
937
  }
934
- get bucket() {
935
- return this.fsBucket;
936
- }
937
938
  constructor(configuration) {
938
939
  this.configuration = configuration;
939
940
  this.conn = null;
940
941
  this.db = null;
941
- this.fsBucket = null;
942
942
  }
943
943
  async connect() {
944
944
  if (this.db)
@@ -949,7 +949,6 @@ let MongoConnector = class MongoConnector {
949
949
  pass: this.configuration.resolve("mongoPassword")
950
950
  })).connection;
951
951
  this.db = this.conn.db;
952
- this.fsBucket = new GridFSBucket(this.db, { bucketName: "assets" });
953
952
  }
954
953
  };
955
954
  MongoConnector = __decorate([
@@ -959,18 +958,18 @@ MongoConnector = __decorate([
959
958
 
960
959
  class BaseEntity {
961
960
  get id() {
962
- return this.mId.toHexString();
961
+ return this.oid.toHexString();
963
962
  }
964
- constructor(mId, data, collection) {
965
- this.mId = mId;
963
+ constructor(oid, data, collection) {
964
+ this.oid = oid;
966
965
  this.data = data;
967
966
  this.collection = collection;
968
967
  }
969
968
  save() {
970
- return this.collection.updateOne({ _id: this.mId }, { $set: this.toJSON() });
969
+ return this.collection.updateOne({ _id: this.oid }, { $set: this.toJSON() }, { upsert: true });
971
970
  }
972
971
  async load() {
973
- const res = await this.collection.findOne({ _id: this.mId });
972
+ const res = await this.collection.findOne({ _id: this.oid });
974
973
  this.deleted = !res;
975
974
  this.data = res || {};
976
975
  return this;
@@ -996,18 +995,31 @@ class Asset extends BaseEntity {
996
995
  return this.data.metadata;
997
996
  }
998
997
  get stream() {
999
- return this.bucket.openDownloadStream(this.mId);
998
+ return this.driver.openDownloadStream(this.oid);
1000
999
  }
1001
- constructor(id, data, collection, bucket) {
1000
+ constructor(id, data, collection, driver) {
1002
1001
  super(id, data, collection);
1003
- this.bucket = bucket;
1002
+ this.driver = driver;
1004
1003
  }
1005
1004
  async unlink() {
1006
- return deleteFromBucket(this.bucket, this.mId);
1005
+ try {
1006
+ await this.driver.delete(this.oid);
1007
+ await this.collection.deleteOne({ _id: this.oid });
1008
+ }
1009
+ catch (error) {
1010
+ let err = error;
1011
+ if (error) {
1012
+ err = error.message || error || "";
1013
+ if (!isString(err) || !err.startsWith("FileNotFound")) {
1014
+ throw err;
1015
+ }
1016
+ }
1017
+ }
1018
+ return this.id;
1007
1019
  }
1008
1020
  async setMeta(metadata) {
1009
1021
  metadata = Object.assign(this.metadata, metadata || {});
1010
- await this.collection.updateOne({ _id: this.mId }, { $set: { metadata } });
1022
+ await this.collection.updateOne({ _id: this.oid }, { $set: { metadata } });
1011
1023
  }
1012
1024
  getBuffer() {
1013
1025
  return streamToBuffer(this.stream);
@@ -1019,7 +1031,7 @@ class Asset extends BaseEntity {
1019
1031
  : metadata.downloadCount + 1;
1020
1032
  metadata.firstDownload = metadata.firstDownload || new Date();
1021
1033
  metadata.lastDownload = new Date();
1022
- await this.collection.updateOne({ _id: this.mId }, { $set: { metadata } });
1034
+ await this.collection.updateOne({ _id: this.oid }, { $set: { metadata } });
1023
1035
  return this.stream;
1024
1036
  }
1025
1037
  async getImage(params = null) {
@@ -1077,11 +1089,11 @@ class TempAsset {
1077
1089
  }
1078
1090
 
1079
1091
  let Assets = class Assets {
1080
- constructor(connector, assetProcessor) {
1092
+ constructor(connector, assetProcessor, driver) {
1081
1093
  this.connector = connector;
1082
1094
  this.assetProcessor = assetProcessor;
1083
- this.bucket = connector.bucket;
1084
- this.collection = connector.database?.collection("assets.files");
1095
+ this.driver = driver;
1096
+ this.collection = connector.database?.collection(driver.metaCollection);
1085
1097
  }
1086
1098
  async write(stream, contentType = null, metadata = null) {
1087
1099
  const uploadStream = copyStream(stream);
@@ -1150,7 +1162,7 @@ let Assets = class Assets {
1150
1162
  }
1151
1163
  async find(where) {
1152
1164
  const data = await this.collection.findOne(where);
1153
- return !data ? null : new Asset(data._id, data, this.collection, this.bucket);
1165
+ return !data ? null : new Asset(data._id, data, this.collection, this.driver);
1154
1166
  }
1155
1167
  async findMany(where) {
1156
1168
  const cursor = this.collection.find(where);
@@ -1159,7 +1171,7 @@ let Assets = class Assets {
1159
1171
  for (let item of items) {
1160
1172
  if (!item)
1161
1173
  continue;
1162
- result.push(new Asset(item._id, item, this.collection, this.bucket));
1174
+ result.push(new Asset(item._id, item, this.collection, this.driver));
1163
1175
  }
1164
1176
  return result;
1165
1177
  }
@@ -1183,7 +1195,11 @@ let Assets = class Assets {
1183
1195
  metadata.filename = metadata.filename || new ObjectId$1().toHexString();
1184
1196
  metadata.extension = (fileType.ext || "").trim();
1185
1197
  return new Promise(((resolve, reject) => {
1186
- const uploaderStream = this.bucket.openUploadStream(metadata.filename);
1198
+ const uploaderStream = this.driver.openUploadStream(metadata.filename, {
1199
+ chunkSizeBytes: 1048576,
1200
+ metadata,
1201
+ contentType: fileType.mime
1202
+ });
1187
1203
  stream.pipe(uploaderStream)
1188
1204
  .on("error", error => {
1189
1205
  reject(error.message || error);
@@ -1193,7 +1209,7 @@ let Assets = class Assets {
1193
1209
  filename: metadata.filename,
1194
1210
  contentType,
1195
1211
  metadata
1196
- }, this.collection, this.bucket);
1212
+ }, this.collection, this.driver);
1197
1213
  asset.save().then(() => {
1198
1214
  resolve(asset);
1199
1215
  }, error => {
@@ -1206,7 +1222,9 @@ let Assets = class Assets {
1206
1222
  Assets = __decorate([
1207
1223
  injectable(),
1208
1224
  scoped(Lifecycle.ContainerScoped),
1209
- __metadata("design:paramtypes", [MongoConnector, AssetProcessor])
1225
+ __param(2, inject(ASSET_DRIVER)),
1226
+ __metadata("design:paramtypes", [MongoConnector,
1227
+ AssetProcessor, Object])
1210
1228
  ], Assets);
1211
1229
 
1212
1230
  class LazyAsset extends BaseEntity {
@@ -1240,9 +1258,9 @@ class LazyAsset extends BaseEntity {
1240
1258
  async unlink() {
1241
1259
  await this.load();
1242
1260
  if (!this.progressId) {
1243
- await this.collection.deleteOne({ _id: this.mId });
1261
+ await this.collection.deleteOne({ _id: this.oid });
1244
1262
  }
1245
- return deleteFromBucket(this.assets.bucket, new ObjectId$1(this.assetId));
1263
+ return this.assets.unlink(this.assetId);
1246
1264
  }
1247
1265
  startWorking() {
1248
1266
  this.load().then(() => {
@@ -1285,7 +1303,7 @@ class LazyAsset extends BaseEntity {
1285
1303
  this.data.progressId = (await this.progresses.create()).id;
1286
1304
  this.data.assetId = null;
1287
1305
  await this.save();
1288
- await deleteFromBucket(this.assets.bucket, oldAsset);
1306
+ await this.assets.unlink(oldAsset);
1289
1307
  const jobParams = JSON.parse(await gunzipPromised(this.data.jobParams));
1290
1308
  await this.progresses.jobMan.enqueueWithName(this.data.jobName, { ...jobParams, lazyId: this.id, fromLoad });
1291
1309
  }
@@ -2835,10 +2853,7 @@ let AssetsController = class AssetsController {
2835
2853
  const asset = await this.getAssetByName("Image", name, res);
2836
2854
  return asset.downloadImage(params);
2837
2855
  }
2838
- setAssetHeaders(type, asset, res) {
2839
- if (asset.metadata?.classified) {
2840
- throw new HttpError(403, `${type} is classified, and can be only downloaded from a custom url.`);
2841
- }
2856
+ setAssetHeaders(asset, res) {
2842
2857
  const ext = asset.metadata?.extension;
2843
2858
  if (ext) {
2844
2859
  res.header("content-disposition", `inline; filename=${asset.filename}.${ext}`);
@@ -2848,19 +2863,30 @@ let AssetsController = class AssetsController {
2848
2863
  }
2849
2864
  }
2850
2865
  async getAsset(type, id, lazy, res) {
2851
- const asset = await this.assetResolver.resolve(id, lazy);
2866
+ let asset = await this.assetResolver.resolve(id, lazy);
2852
2867
  if (!asset) {
2853
2868
  throw new HttpError(404, `${type} with id: '${id}' not found.`);
2854
2869
  }
2855
- this.setAssetHeaders(type, asset, res);
2870
+ asset = await this.resolveFinalAsset(type, asset);
2871
+ this.setAssetHeaders(asset, res);
2856
2872
  return asset;
2857
2873
  }
2858
2874
  async getAssetByName(type, filename, res) {
2859
- const asset = await this.assets.find({ filename });
2875
+ let asset = await this.assets.find({ filename });
2860
2876
  if (!asset) {
2861
2877
  throw new HttpError(404, `${type} with filename: '${filename}' not found.`);
2862
2878
  }
2863
- this.setAssetHeaders(type, asset, res);
2879
+ asset = await this.resolveFinalAsset(type, asset);
2880
+ this.setAssetHeaders(asset, res);
2881
+ return asset;
2882
+ }
2883
+ async resolveFinalAsset(type, asset) {
2884
+ if (asset.metadata?.classified) {
2885
+ throw new HttpError(403, `${type} is classified, and can be only downloaded from a custom url.`);
2886
+ }
2887
+ if (type == 'Image' && asset.metadata.preview) {
2888
+ return this.resolveFinalAsset(type, await this.assetResolver.resolve(asset.metadata.preview));
2889
+ }
2864
2890
  return asset;
2865
2891
  }
2866
2892
  };
@@ -3843,6 +3869,58 @@ const fixtures = [
3843
3869
  TtlFixture,
3844
3870
  ];
3845
3871
 
3872
+ let AssetGridDriver = class AssetGridDriver {
3873
+ constructor(connector) {
3874
+ this.bucket = new GridFSBucket(connector.database, { bucketName: 'assets' });
3875
+ this.metaCollection = "assets.files";
3876
+ }
3877
+ openUploadStream(filename, opts) {
3878
+ return this.bucket.openUploadStream(filename, opts);
3879
+ }
3880
+ openDownloadStream(id) {
3881
+ return this.bucket.openDownloadStream(id);
3882
+ }
3883
+ delete(id) {
3884
+ return this.bucket.delete(id);
3885
+ }
3886
+ };
3887
+ AssetGridDriver = __decorate([
3888
+ injectable(),
3889
+ __metadata("design:paramtypes", [MongoConnector])
3890
+ ], AssetGridDriver);
3891
+
3892
+ let AssetLocalDriver = class AssetLocalDriver {
3893
+ constructor(dir) {
3894
+ this.dir = dir;
3895
+ this.metaCollection = "assets.local";
3896
+ }
3897
+ openUploadStream(filename, opts) {
3898
+ const id = new Types.ObjectId();
3899
+ const dir = `${this.dir}/${id.toHexString()}`;
3900
+ mkdirSync(dir, { recursive: true });
3901
+ const stream = createWriteStream(`${dir}/file.bin`);
3902
+ stream.id = id;
3903
+ stream.done = false;
3904
+ stream.on('finish', () => {
3905
+ writeFile$2(`${dir}/filename.txt`, filename);
3906
+ writeFile$2(`${dir}/metadata.json`, JSON.stringify(opts?.metadata || {}));
3907
+ stream.done = true;
3908
+ });
3909
+ return stream;
3910
+ }
3911
+ openDownloadStream(id) {
3912
+ return createReadStream(`${this.dir}/${id.toHexString()}/file.bin`, { autoClose: true, emitClose: true });
3913
+ }
3914
+ delete(id) {
3915
+ return rm(`${this.dir}/${id.toHexString()}`, { recursive: true, force: true });
3916
+ }
3917
+ };
3918
+ AssetLocalDriver = __decorate([
3919
+ injectable(),
3920
+ __param(0, inject(LOCAL_DIR)),
3921
+ __metadata("design:paramtypes", [String])
3922
+ ], AssetLocalDriver);
3923
+
3846
3924
  class BaseDoc {
3847
3925
  /**
3848
3926
  * Casts this to DocumentType<this> to allow using document methods in get/set-s
@@ -4373,6 +4451,12 @@ async function setupBackend(config, providers, parent) {
4373
4451
  diContainer.register(OPENAPI_VALIDATION, {
4374
4452
  useValue: config.customValidation || (() => null)
4375
4453
  });
4454
+ diContainer.register(LOCAL_DIR, {
4455
+ useValue: config.assetLocalDir || "assets_files"
4456
+ });
4457
+ diContainer.register(ASSET_DRIVER, {
4458
+ useClass: config.assetDriver || AssetGridDriver
4459
+ });
4376
4460
  diContainers.appContainer = diContainers.appContainer || diContainer;
4377
4461
  // Authentication
4378
4462
  restOptions.authorizationChecker = async (action, roles) => {
@@ -4442,5 +4526,5 @@ async function setupBackend(config, providers, parent) {
4442
4526
  * Generated bundle index. Do not edit.
4443
4527
  */
4444
4528
 
4445
- export { AssetImageParams, 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 };
4529
+ 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 };
4446
4530
  //# sourceMappingURL=stemy-backend.mjs.map