@stemy/backend 2.9.8 → 3.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.
@@ -23,9 +23,8 @@ import sharp_ from 'sharp';
23
23
  import axios from 'axios';
24
24
  import { GridFSBucket } from 'mongodb';
25
25
  import dotenv from 'dotenv';
26
- import { Queue, Worker, Scheduler } from 'node-resque';
27
26
  import { validate, schedule } from 'node-cron';
28
- import ioredis from 'ioredis';
27
+ import { socket } from 'zeromq';
29
28
  import socket_io_client from 'socket.io-client';
30
29
  import { createServer } from 'http';
31
30
  import express_, { static as static$1 } from 'express';
@@ -363,10 +362,14 @@ function writeFile(path, data) {
363
362
  function valueToPromise(value) {
364
363
  return value instanceof Promise ? value : Promise.resolve(value);
365
364
  }
366
- function promiseTimeout(timeout = 1000) {
367
- return new Promise((resolve) => {
365
+ function promiseTimeout(timeout = 1000, error = false) {
366
+ return new Promise((resolve, reject) => {
368
367
  setTimeout(() => {
369
- resolve();
368
+ if (error) {
369
+ reject(`Timeout exceeded: ${timeout}ms`);
370
+ return;
371
+ }
372
+ resolve(`Timeout: ${timeout}ms`);
370
373
  }, timeout);
371
374
  });
372
375
  }
@@ -640,6 +643,66 @@ function runCommand(scriptPath, expectedCode = 0) {
640
643
  console.error(data.toString());
641
644
  });
642
645
  });
646
+ }
647
+ var ConsoleColor;
648
+ (function (ConsoleColor) {
649
+ ConsoleColor["Reset"] = "\u001B[0m";
650
+ ConsoleColor["Bright"] = "\u001B[1m";
651
+ ConsoleColor["Dim"] = "\u001B[2m";
652
+ ConsoleColor["Underscore"] = "\u001B[4m";
653
+ ConsoleColor["Blink"] = "\u001B[5m";
654
+ ConsoleColor["Reverse"] = "\u001B[7m";
655
+ ConsoleColor["Hidden"] = "\u001B[8m";
656
+ ConsoleColor["FgBlack"] = "\u001B[30m";
657
+ ConsoleColor["FgRed"] = "\u001B[31m";
658
+ ConsoleColor["FgGreen"] = "\u001B[32m";
659
+ ConsoleColor["FgYellow"] = "\u001B[33m";
660
+ ConsoleColor["FgBlue"] = "\u001B[34m";
661
+ ConsoleColor["FgMagenta"] = "\u001B[35m";
662
+ ConsoleColor["FgCyan"] = "\u001B[36m";
663
+ ConsoleColor["FgWhite"] = "\u001B[37m";
664
+ ConsoleColor["BgBlack"] = "\u001B[40m";
665
+ ConsoleColor["BgRed"] = "\u001B[41m";
666
+ ConsoleColor["BgGreen"] = "\u001B[42m";
667
+ ConsoleColor["BgYellow"] = "\u001B[43m";
668
+ ConsoleColor["BgBlue"] = "\u001B[44m";
669
+ ConsoleColor["BgMagenta"] = "\u001B[45m";
670
+ ConsoleColor["BgCyan"] = "\u001B[46m";
671
+ ConsoleColor["BgWhite"] = "\u001B[47m";
672
+ })(ConsoleColor || (ConsoleColor = {}));
673
+ const defaultColors = {
674
+ keyColor: ConsoleColor.Dim,
675
+ numberColor: ConsoleColor.FgBlue,
676
+ stringColor: ConsoleColor.FgCyan,
677
+ trueColor: ConsoleColor.FgGreen,
678
+ falseColor: ConsoleColor.FgRed,
679
+ nullColor: ConsoleColor.BgMagenta
680
+ };
681
+ function jsonHighlight(input, colorOptions) {
682
+ const colors = Object.assign({}, defaultColors, colorOptions);
683
+ const json = (isString(input) ? input : JSON.stringify(input, null, 2)).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
684
+ return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+]?\d+)?)/g, (match) => {
685
+ let color = colors.numberColor;
686
+ if (/^"/.test(match)) {
687
+ if (/:$/.test(match)) {
688
+ color = colors.keyColor;
689
+ }
690
+ else {
691
+ color = colors.stringColor;
692
+ match = '"' + match.substr(1, match.length - 2) + '"';
693
+ }
694
+ }
695
+ else {
696
+ color = /true/.test(match)
697
+ ? colors.trueColor
698
+ : /false/.test(match)
699
+ ? colors.falseColor
700
+ : /null/.test(match)
701
+ ? colors.nullColor
702
+ : color;
703
+ }
704
+ return `${color}${match}${ConsoleColor.Reset}`;
705
+ });
643
706
  }
644
707
 
645
708
  var __decorate$x = (this && this.__decorate) || function (decorators, target, key, desc) {
@@ -1058,9 +1121,10 @@ class Asset extends BaseEntity {
1058
1121
  params = params || {};
1059
1122
  // Get default crop info
1060
1123
  const crop = Asset.toCropRegion(meta.crop);
1061
- // Return back the stream if there is no params and no default crop exists
1062
- if (Object.keys(params).length == 0 && !crop)
1124
+ // Return the stream if there is no params and no default crop exists
1125
+ if ((meta === null || meta === void 0 ? void 0 : meta.extension) === "svg" || (Object.keys(params).length == 0 && !crop)) {
1063
1126
  return stream;
1127
+ }
1064
1128
  // Parse params
1065
1129
  params.rotation = isNaN(params.rotation) ? 0 : Math.round(params.rotation / 90) * 90;
1066
1130
  params.canvasScaleX = isNaN(params.canvasScaleX) ? 1 : Number(params.canvasScaleX);
@@ -1075,51 +1139,43 @@ class Asset extends BaseEntity {
1075
1139
  const cropBefore = Asset.toCropRegion(params.cropBefore || (params.crop ? meta.cropBefore : null));
1076
1140
  const cropAfter = Asset.toCropRegion(params.cropAfter || (params.crop ? meta.cropAfter : null));
1077
1141
  // Get metadata
1078
- const imgMeta = yield sharp$2(buffer).metadata();
1079
- let width = imgMeta.width;
1080
- let height = imgMeta.height;
1142
+ let img = sharp$2(buffer);
1143
+ let { width, height } = yield img.metadata();
1081
1144
  // Crop before resize
1082
1145
  if (cropBefore) {
1083
- buffer = yield sharp$2(buffer)
1084
- .extract(cropBefore)
1085
- .toBuffer();
1086
1146
  width = cropBefore.width;
1087
1147
  height = cropBefore.height;
1148
+ img = img.extract(cropBefore);
1088
1149
  }
1089
1150
  else if (crop) {
1090
- buffer = yield sharp$2(buffer)
1091
- .extract(crop)
1092
- .toBuffer();
1093
1151
  width = crop.width;
1094
1152
  height = crop.height;
1153
+ img = img.extract(crop);
1095
1154
  }
1096
1155
  // Resize canvas
1097
- if (params.canvasScaleX !== 1 || params.canvasScaleY !== 1) {
1156
+ const canvasScaleX = (meta === null || meta === void 0 ? void 0 : meta.canvasScaleX) || 1;
1157
+ const canvasScaleY = (meta === null || meta === void 0 ? void 0 : meta.canvasScaleY) || 1;
1158
+ if (params.canvasScaleX !== canvasScaleX || params.canvasScaleY !== canvasScaleY) {
1098
1159
  width = Math.round(width * params.canvasScaleX);
1099
1160
  height = Math.round(height * params.canvasScaleY);
1100
- buffer = yield sharp$2(buffer)
1101
- .resize({ width, height, background: "#00000000", fit: "contain" })
1102
- .toBuffer();
1161
+ img = img.resize({ width, height, background: "#00000000", fit: "contain" });
1103
1162
  }
1104
1163
  // Resize image
1105
1164
  if (params.scaleX !== 1 || params.scaleY !== 1) {
1106
1165
  width = Math.round(width * params.scaleX);
1107
1166
  height = Math.round(height * params.scaleY);
1108
- buffer = yield sharp$2(buffer)
1109
- .resize({ width, height, background: "#00000000", fit: "fill" })
1110
- .toBuffer();
1167
+ img = img.resize({ width, height, background: "#00000000", fit: "fill" });
1111
1168
  }
1112
1169
  // Crop after resize
1113
1170
  if (cropAfter) {
1114
- buffer = yield sharp$2(buffer)
1115
- .extract(cropAfter)
1116
- .toBuffer();
1171
+ img = img.extract(cropAfter);
1117
1172
  }
1118
1173
  // Rotate
1119
1174
  if (params.rotation !== 0) {
1120
- buffer = yield sharp$2(buffer).rotate(params.rotation).toBuffer();
1175
+ buffer = yield img.toBuffer();
1176
+ img = sharp$2(buffer).rotate(params.rotation);
1121
1177
  }
1122
- return bufferToStream(buffer);
1178
+ return bufferToStream(yield img.toBuffer());
1123
1179
  }
1124
1180
  catch (e) {
1125
1181
  console.log("Asset image conversion error", e);
@@ -1499,15 +1555,15 @@ var __awaiter$o = (this && this.__awaiter) || function (thisArg, _arguments, P,
1499
1555
  step((generator = generator.apply(thisArg, _arguments || [])).next());
1500
1556
  });
1501
1557
  };
1502
- const IORedis = ioredis;
1503
1558
  let JobManager = class JobManager {
1504
1559
  constructor(config, container, jobTypes) {
1505
1560
  this.config = config;
1506
1561
  this.container = container;
1507
1562
  this.jobTypes = jobTypes || [];
1508
1563
  this.jobs = this.jobTypes.reduce((res, jobType) => {
1509
- res[getConstructorName(jobType)] = {
1510
- perform: this.toPerformFunction(jobType)
1564
+ res[getConstructorName(jobType)] = (jobParams) => {
1565
+ const job = this.resolveJobInstance(jobType, jobParams);
1566
+ return job.process();
1511
1567
  };
1512
1568
  return res;
1513
1569
  }, {});
@@ -1525,31 +1581,25 @@ let JobManager = class JobManager {
1525
1581
  return instance.process();
1526
1582
  });
1527
1583
  }
1528
- enqueueWithName(name, params = {}, que = "main") {
1584
+ enqueueWithName(name, params = {}) {
1529
1585
  return __awaiter$o(this, void 0, void 0, function* () {
1530
1586
  const jobName = yield this.tryResolveFromName(name, params);
1531
- yield this.queue.enqueue(que, jobName, [params]);
1587
+ return this.sendToWorkers(jobName, params);
1532
1588
  });
1533
1589
  }
1534
- enqueue(jobType, params = {}, que = "main") {
1590
+ enqueue(jobType, params = {}) {
1535
1591
  return __awaiter$o(this, void 0, void 0, function* () {
1536
1592
  const jobName = yield this.tryResolveAndConnect(jobType, params);
1537
- yield this.queue.enqueue(que, jobName, [params]);
1593
+ return this.sendToWorkers(jobName, params);
1538
1594
  });
1539
1595
  }
1540
- enqueueAt(timestamp, jobType, params = {}, que = "main") {
1596
+ sendToWorkers(jobName, params) {
1541
1597
  return __awaiter$o(this, void 0, void 0, function* () {
1542
- const jobName = yield this.tryResolveAndConnect(jobType, params);
1543
- yield this.queue.enqueueAt(timestamp, que, jobName, [params]);
1598
+ const publisher = yield this.scheduler;
1599
+ yield publisher.send([jobName, JSON.stringify(params), new ObjectId().toHexString()]);
1544
1600
  });
1545
1601
  }
1546
- enqueueIn(time, jobType, params = {}, que = "main") {
1547
- return __awaiter$o(this, void 0, void 0, function* () {
1548
- const jobName = yield this.tryResolveAndConnect(jobType, params);
1549
- yield this.queue.enqueueIn(time, que, jobName, [params]);
1550
- });
1551
- }
1552
- schedule(minute, hour, dayOfMonth, month, dayOfWeek, jobType, params = {}, que = "main") {
1602
+ schedule(minute, hour, dayOfMonth, month, dayOfWeek, jobType, params = {}) {
1553
1603
  const expression = [minute, hour, dayOfMonth, month, dayOfWeek].map(t => {
1554
1604
  if (isObject(t)) {
1555
1605
  const range = t;
@@ -1566,19 +1616,38 @@ let JobManager = class JobManager {
1566
1616
  return null;
1567
1617
  }
1568
1618
  return schedule(expression, () => {
1569
- this.enqueue(jobType, params, que).catch(e => {
1619
+ this.enqueue(jobType, params).catch(e => {
1570
1620
  console.log(`Can't enqueue job: '${jobName}' because: ${e}`);
1571
1621
  });
1572
1622
  });
1573
1623
  }
1574
1624
  startProcessing() {
1575
- return __awaiter$o(this, void 0, void 0, function* () {
1576
- this.initialize();
1577
- yield this.worker.connect();
1578
- yield this.worker.start();
1579
- yield this.scheduler.connect();
1580
- yield this.scheduler.start();
1581
- });
1625
+ const host = this.config.resolve("zmqRemoteHost");
1626
+ this.worker = socket("pull");
1627
+ this.worker.connect(host);
1628
+ this.worker.on("message", (name, args, uniqueId) => __awaiter$o(this, void 0, void 0, function* () {
1629
+ try {
1630
+ const jobName = name.toString("utf8");
1631
+ const jobParams = JSON.parse(args.toString("utf8"));
1632
+ const timerId = uniqueId === null || uniqueId === void 0 ? void 0 : uniqueId.toString("utf8");
1633
+ const jobNameLog = `\x1b[36m"${jobName}"\x1b[0m`;
1634
+ const jobArgsLog = `\n${jsonHighlight(jobParams)}\n`;
1635
+ console.time(timerId);
1636
+ console.timeLog(timerId, `Started working on background job: ${jobNameLog} with args: ${jobArgsLog}`);
1637
+ try {
1638
+ yield Promise.race([this.jobs[jobName](jobParams), promiseTimeout(15000, true)]);
1639
+ console.timeLog(timerId, `Finished working on background job: ${jobNameLog} with args: ${jobArgsLog}`);
1640
+ }
1641
+ catch (e) {
1642
+ console.timeLog(timerId, `Background job failed: ${jobNameLog} with args: ${jobArgsLog}${e.message}\n\n`);
1643
+ }
1644
+ console.timeEnd(timerId);
1645
+ }
1646
+ catch (e) {
1647
+ console.log(`Failed to start job: ${e.message}`);
1648
+ }
1649
+ }));
1650
+ console.log(`Waiting for jobs at: ${host}`);
1582
1651
  }
1583
1652
  tryResolve(jobType, params) {
1584
1653
  const jobName = getConstructorName(jobType);
@@ -1593,47 +1662,6 @@ let JobManager = class JobManager {
1593
1662
  }
1594
1663
  return jobName;
1595
1664
  }
1596
- initialize() {
1597
- if (this.queue)
1598
- return;
1599
- const config = this.config;
1600
- const options = { password: config.resolve("redisPassword") };
1601
- const sentinels = config.resolve("redisSentinels");
1602
- const redis = !sentinels
1603
- ? null
1604
- : new IORedis({
1605
- sentinels,
1606
- name: config.resolve("redisCluster"),
1607
- });
1608
- const connection = {
1609
- pkg: "ioredis",
1610
- host: config.resolve("redisHost"),
1611
- password: options.password,
1612
- port: config.resolve("redisPort"),
1613
- namespace: config.resolve("redisNamespace"),
1614
- redis,
1615
- options
1616
- };
1617
- const queues = config.resolve("workQueues");
1618
- this.queue = new Queue({ connection }, this.jobs);
1619
- this.worker = new Worker({ connection, queues }, this.jobs);
1620
- this.worker.on("job", (queue, job) => {
1621
- console.log(`working job ${queue} ${JSON.stringify(job)}`);
1622
- });
1623
- this.worker.on("reEnqueue", (queue, job, plugin) => {
1624
- console.log(`reEnqueue job (${plugin}) ${queue} ${JSON.stringify(job)}`);
1625
- });
1626
- this.worker.on("success", (queue, job, result, duration) => {
1627
- console.log(`job success ${queue} ${JSON.stringify(job)} >> ${result} (${duration}ms)`);
1628
- });
1629
- this.worker.on("failure", (queue, job, failure, duration) => {
1630
- console.log(`job failure ${queue} ${JSON.stringify(job)} >> ${failure} (${duration}ms)`);
1631
- });
1632
- this.worker.on("error", (error, queue, job) => {
1633
- console.log(`error ${queue} ${JSON.stringify(job)} >> ${error}`);
1634
- });
1635
- this.scheduler = new Scheduler({ connection }, this.jobs);
1636
- }
1637
1665
  tryResolveFromName(jobName, params) {
1638
1666
  const jobType = this.jobTypes.find(type => {
1639
1667
  return getConstructorName(type) == jobName;
@@ -1645,10 +1673,14 @@ let JobManager = class JobManager {
1645
1673
  }
1646
1674
  tryResolveAndConnect(jobType, params) {
1647
1675
  return __awaiter$o(this, void 0, void 0, function* () {
1648
- this.initialize();
1649
- const jobName = this.tryResolve(jobType, params);
1650
- yield this.queue.connect();
1651
- return jobName;
1676
+ this.scheduler = this.scheduler || new Promise((resolve) => __awaiter$o(this, void 0, void 0, function* () {
1677
+ const port = this.config.resolve("zmqPort");
1678
+ const publisher = socket("push");
1679
+ yield publisher.bind(`tcp://0.0.0.0:${port}`);
1680
+ console.log(`Publisher bound to port: ${port}`);
1681
+ resolve(publisher);
1682
+ }));
1683
+ return this.tryResolve(jobType, params);
1652
1684
  });
1653
1685
  }
1654
1686
  resolveJobInstance(jobType, params) {
@@ -1659,12 +1691,6 @@ let JobManager = class JobManager {
1659
1691
  container.register(jobType, jobType);
1660
1692
  return container.resolve(jobType);
1661
1693
  }
1662
- toPerformFunction(jobType) {
1663
- return (jobParams) => {
1664
- const job = this.resolveJobInstance(jobType, jobParams);
1665
- return job.process();
1666
- };
1667
- }
1668
1694
  };
1669
1695
  JobManager = __decorate$s([
1670
1696
  injectable(),
@@ -2986,7 +3012,9 @@ let AssetsController = class AssetsController {
2986
3012
  getImageRotation(id, params, res, rotation = 0) {
2987
3013
  return __awaiter$7(this, void 0, void 0, function* () {
2988
3014
  const asset = yield this.getAsset("Image", id, params.lazy, res);
2989
- params.rotation = params.rotation || rotation;
3015
+ if (rotation !== 0) {
3016
+ params.rotation = params.rotation || rotation;
3017
+ }
2990
3018
  return asset.downloadImage(params);
2991
3019
  });
2992
3020
  }
@@ -3797,20 +3825,8 @@ function createServices() {
3797
3825
  new Parameter("mongoPassword", null),
3798
3826
  new Parameter("nodeEnv", "development"),
3799
3827
  new Parameter("appPort", 80),
3800
- new Parameter("redisHost", "127.0.0.1"),
3801
- new Parameter("redisPort", 6379),
3802
- new Parameter("redisPassword", "123456"),
3803
- new Parameter("redisNamespace", "resque"),
3804
- new Parameter("redisCluster", "mymaster"),
3805
- new Parameter("redisSentinels", null, value => {
3806
- if (!value)
3807
- return null;
3808
- return value.split(", ").map(item => {
3809
- const values = item.split(":");
3810
- return { host: values[0], port: Number(values[1]) };
3811
- });
3812
- }),
3813
- new Parameter("workQueues", ["main"]),
3828
+ new Parameter("zmqPort", 3000),
3829
+ new Parameter("zmqRemoteHost", "tcp://127.0.0.1:3000"),
3814
3830
  new Parameter("isWorker", false),
3815
3831
  new Parameter("mainEndpoint", ""),
3816
3832
  new Parameter("idChars", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
@@ -4016,5 +4032,5 @@ function setupBackend(config, providers, parent) {
4016
4032
  * Generated bundle index. Do not edit.
4017
4033
  */
4018
4034
 
4019
- export { AssetProcessor, AssetResolver, Assets, AuthController, BackendProvider, Cache, CacheProcessor, Configuration, DI_CONTAINER, EXPRESS, EndpointProvider, ErrorHandlerMiddleware, FIXTURE, Fixtures, Gallery, GalleryCache, GalleryController, HTTP_SERVER, IdGenerator, IsFile, IsObjectId, JOB, JobManager, LanguageMiddleware, LazyAssetGenerator, LazyAssets, MailSender, MemoryCache, MongoConnector, PARAMETER, Parameter, Progresses, ResolveEntity, SOCKET_SERVER, TemplateRenderer, TranslationProvider, Translator, Type, UserManager, assign, broadcast, bufferToStream, convertValue, copy, copyStream, createServices, createTransformer, deleteFile, deleteFromBucket, filter, firstItem, getConstructorName, getExtension, getFileName, getFunctionParams, getType, getValue, groupBy, hydratePopulated, idToString, injectServices, isArray, isBoolean, isConstructor, isDate, isDefined, isFunction, isInterface, isNullOrUndefined, isObject, isPrimitive, isString, isType, lastItem, lcFirst, lookupPipelines, md5, mkdirRecursive, multiSubscription, observableFromFunction, padLeft, padRight, paginate, paginateAggregations, promiseTimeout, proxyFunction, proxyFunctions, rand, random, readAndDeleteFile, readFile, runCommand, setupBackend, streamToBuffer, ucFirst, valueToPromise, writeFile };
4035
+ export { AssetProcessor, AssetResolver, Assets, AuthController, BackendProvider, Cache, CacheProcessor, Configuration, ConsoleColor, DI_CONTAINER, EXPRESS, EndpointProvider, ErrorHandlerMiddleware, FIXTURE, Fixtures, Gallery, GalleryCache, GalleryController, HTTP_SERVER, IdGenerator, IsFile, IsObjectId, JOB, JobManager, LanguageMiddleware, LazyAssetGenerator, LazyAssets, MailSender, MemoryCache, MongoConnector, PARAMETER, Parameter, Progresses, ResolveEntity, SOCKET_SERVER, TemplateRenderer, TranslationProvider, Translator, Type, UserManager, assign, broadcast, bufferToStream, convertValue, copy, copyStream, createServices, createTransformer, deleteFile, deleteFromBucket, filter, firstItem, getConstructorName, getExtension, getFileName, getFunctionParams, getType, getValue, groupBy, hydratePopulated, idToString, injectServices, isArray, isBoolean, isConstructor, isDate, isDefined, isFunction, isInterface, isNullOrUndefined, isObject, isPrimitive, isString, isType, jsonHighlight, lastItem, lcFirst, lookupPipelines, md5, mkdirRecursive, multiSubscription, observableFromFunction, padLeft, padRight, paginate, paginateAggregations, promiseTimeout, proxyFunction, proxyFunctions, rand, random, readAndDeleteFile, readFile, runCommand, setupBackend, streamToBuffer, ucFirst, valueToPromise, writeFile };
4020
4036
  //# sourceMappingURL=stemy-backend.js.map