@tiledesk/tiledesk-server 2.15.4 → 2.15.5

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/CHANGELOG.md CHANGED
@@ -5,6 +5,9 @@
5
5
  🚀 IN PRODUCTION 🚀
6
6
  (https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
7
7
 
8
+ # 2.15.5
9
+ - Fixed email flooding when smart assignment is active and there are no operators available
10
+
8
11
  # 2.15.4
9
12
  - Updated endpoint to get project users adding the query parameter "trashed" in order to obtain also the trashed users.
10
13
  - Added endpoint /restore to restore a deleted project user
package/app.js CHANGED
@@ -200,7 +200,7 @@ var faqBotHandler = require('./services/faqBotHandler');
200
200
  faqBotHandler.listen();
201
201
 
202
202
  var pubModulesManager = require('./pubmodules/pubModulesManager');
203
- pubModulesManager.init({express:express, mongoose:mongoose, passport:passport, databaseUri:databaseUri, routes:{}, jobsManager:jobsManager});
203
+ pubModulesManager.init({express:express, mongoose:mongoose, passport:passport, databaseUri:databaseUri, routes:{}, jobsManager:jobsManager, tdCache:tdCache});
204
204
 
205
205
  jobsManager.listen(); //listen after pubmodules to enabled queued *.queueEnabled events
206
206
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiledesk/tiledesk-server",
3
3
  "description": "The Tiledesk server module",
4
- "version": "2.15.4",
4
+ "version": "2.15.5",
5
5
  "scripts": {
6
6
  "start": "node ./bin/www",
7
7
  "pretest": "mongodb-runner start",
@@ -14,6 +14,7 @@ var winston = require('../../config/winston');
14
14
  var RoleConstants = require("../../models/roleConstants");
15
15
  var ChannelConstants = require("../../models/channelConstants");
16
16
  var cacheUtil = require('../../utils/cacheUtil');
17
+ const { TdCache } = require('../../utils/TdCache');
17
18
 
18
19
  const messageEvent = require('../../event/messageEvent');
19
20
  var mongoose = require('mongoose');
@@ -40,6 +41,23 @@ if (pKey) {
40
41
  let apiUrl = process.env.API_URL || configGlobal.apiUrl;
41
42
  winston.debug('********* RequestNotification apiUrl: ' + apiUrl);
42
43
 
44
+ /** Redis cache for throttling pooled-request email. Injected by app (shared TdCache) or lazy-created fallback. */
45
+ let _pooledEmailTdCache = null;
46
+ function getPooledEmailCache() {
47
+ if (_pooledEmailTdCache) return _pooledEmailTdCache;
48
+ const fallback = new TdCache({
49
+ host: process.env.CACHE_REDIS_HOST,
50
+ port: process.env.CACHE_REDIS_PORT,
51
+ password: process.env.CACHE_REDIS_PASSWORD
52
+ });
53
+ fallback.connect();
54
+ _pooledEmailTdCache = fallback;
55
+ return _pooledEmailTdCache;
56
+ }
57
+
58
+ /** TTL in seconds: do not send pooled email again for the same request within this window (avoids flooding when smart assignment retries) */
59
+ const POOLED_EMAIL_THROTTLE_TTL = Number(process.env.POOLED_EMAIL_THROTTLE_TTL) || cacheUtil.defaultTTL;
60
+
43
61
  class RequestNotification {
44
62
 
45
63
  constructor() {
@@ -48,7 +66,18 @@ class RequestNotification {
48
66
  this.enabled = false;
49
67
  }
50
68
  winston.debug("RequestNotification this.enabled: "+ this.enabled);
51
- }
69
+ }
70
+
71
+ /**
72
+ * Reuse app's TdCache instance instead of opening a new Redis connection (optional, called by PubModulesManager).
73
+ * @param {TdCache} tdCache - shared Redis cache from app.get('redis_client')
74
+ */
75
+ setTdCache(tdCache) {
76
+ if (tdCache && typeof tdCache.setNX === 'function') {
77
+ _pooledEmailTdCache = tdCache;
78
+ winston.debug("RequestNotification using shared TdCache for pooled email throttle");
79
+ }
80
+ }
52
81
 
53
82
  listen() {
54
83
 
@@ -892,6 +921,18 @@ sendAgentEmail(projectid, savedRequest) {
892
921
  return winston.warn("RequestNotification savedRequest.snapshot is null :(. You are closing an old request?");
893
922
  }
894
923
 
924
+ // Throttle: send pooled email only once per request within TTL (avoids flooding when smart assignment keeps retrying)
925
+ const requestId = (savedRequest._id || savedRequest.id).toString();
926
+ const pooledEmailKey = 'pooled_request_email:' + requestId;
927
+ try {
928
+ const shouldSend = await getPooledEmailCache().setNX(pooledEmailKey, '1', POOLED_EMAIL_THROTTLE_TTL);
929
+ if (!shouldSend) {
930
+ return winston.debug("RequestNotification pooled email already sent for request " + requestId + " (Redis throttle, TTL " + POOLED_EMAIL_THROTTLE_TTL + "s)");
931
+ }
932
+ } catch (redisErr) {
933
+ winston.warn("RequestNotification Redis setNX for pooled email throttle failed, skipping send", { error: redisErr, requestId });
934
+ return;
935
+ }
895
936
 
896
937
 
897
938
  var snapshotAgents = savedRequest; //riassegno varibile cosi nn cambio righe successive
@@ -175,6 +175,7 @@ class PubModulesManager {
175
175
  winston.debug("PubModulesManager init");
176
176
 
177
177
  this.jobsManager = config.jobsManager;
178
+ this.tdCache = config.tdCache;
178
179
 
179
180
  try {
180
181
  this.appRules = require('./rules/appRules');
@@ -631,6 +632,9 @@ class PubModulesManager {
631
632
  // job_here
632
633
  if (this.emailNotification) {
633
634
  try {
635
+ if (this.tdCache) {
636
+ this.emailNotification.requestNotification.setTdCache(this.tdCache);
637
+ }
634
638
  // this.emailNotification.requestNotification.listen();
635
639
  this.jobsManager.listenEmailNotification(this.emailNotification);
636
640
  winston.info("PubModulesManager emailNotification started.");
package/utils/TdCache.js CHANGED
@@ -76,7 +76,18 @@ class TdCache {
76
76
  });
77
77
  }
78
78
 
79
-
79
+ /**
80
+ * Set key only if it does not exist, with TTL (seconds). Returns true if key was set, false if key already existed.
81
+ * Uses Redis SET key value EX ttl NX to avoid email/notification flooding.
82
+ */
83
+ async setNX(key, value, ttlSeconds) {
84
+ return new Promise((resolve, reject) => {
85
+ this.client.set(key, value, 'EX', ttlSeconds, 'NX', (err, reply) => {
86
+ if (err) return reject(err);
87
+ resolve(reply === 'OK');
88
+ });
89
+ });
90
+ }
80
91
 
81
92
 
82
93