@plusscommunities/pluss-core-aws 2.0.24 → 2.0.25-beta.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/aws/getDefaultEmailAddress.js +21 -21
- package/aws/getEmailService.js +16 -16
- package/aws/getEmailServiceInfo.js +26 -26
- package/aws/sendEmail.js +31 -31
- package/config.js +1 -1
- package/db/activity/publishActivity.js +22 -22
- package/db/analytics/checkActivityExists.js +15 -15
- package/db/analytics/logAnalyticsActivity.js +69 -37
- package/db/analytics/scheduleOldAggregation.js +14 -14
- package/db/auth/getSiteSetting.js +12 -12
- package/db/auth/getSiteUserTypes.js +16 -16
- package/db/auth/getUserAuth.js +13 -13
- package/db/automatedactions/getActionBySiteTrigger.js +9 -9
- package/db/common/deleteRef.js +21 -21
- package/db/common/editRef.js +36 -36
- package/db/common/getRef.js +23 -23
- package/db/common/getTableCount.js +18 -18
- package/db/common/indexQuery.js +17 -17
- package/db/common/indexQueryRecursive.js +20 -20
- package/db/common/scanRef.js +18 -18
- package/db/common/scanRefRecursive.js +20 -20
- package/db/common/updateAttribute.js +27 -27
- package/db/common/updateRef.js +20 -20
- package/db/linkedUsers/getLinkedBy.js +21 -21
- package/db/linkedUsers/getLinkedTo.js +21 -21
- package/db/notifications/deleteNotificationsByEntity.js +21 -21
- package/db/notifications/getNotificationSetting.js +14 -14
- package/db/notifications/publishNotifications.js +39 -39
- package/db/scheduledActions/deleteActionQueue.js +1 -1
- package/db/scheduledActions/getActionQueueByEntityId.js +10 -10
- package/db/scheduledActions/getActionQueueByEntityKey.js +9 -9
- package/db/scheduledActions/getActionQueueById.js +9 -9
- package/db/scheduledActions/getActionQueueByTriggerAt.js +14 -14
- package/db/scheduledActions/updateActionQueue.js +29 -29
- package/db/strings/getString.js +20 -20
- package/db/strings/logUpdate.js +18 -18
- package/db/templates/getTemplateById.js +1 -1
- package/db/templates/getTemplatesList.js +10 -10
- package/db/templates/updateTemplate.js +9 -9
- package/db/users/getRole.js +1 -1
- package/db/users/getUser.js +9 -9
- package/db/users/getUserByEmail.js +17 -17
- package/helper/audience/filterByAudienceType.js +27 -27
- package/helper/audience/filterOnAudienceType.js +26 -26
- package/helper/audience/getAudience.js +187 -187
- package/helper/audience/getMatchingAudienceTypes.js +21 -21
- package/helper/audience/getMatchingAudienceTypesFromPreview.js +60 -60
- package/helper/audience/getMatchingTags.js +15 -15
- package/helper/audience/isValidAudience.js +20 -20
- package/helper/auth/checkTokenBlacklist.js +17 -17
- package/helper/auth/getApiKeyFromReq.js +2 -2
- package/helper/auth/getSessionUser.js +66 -66
- package/helper/auth/getSessionUserFromReq.js +2 -2
- package/helper/auth/getSessionUserFromReqAuthKey.js +11 -11
- package/helper/auth/validateApiKey.js +32 -32
- package/helper/auth/validateMasterAuth.js +174 -174
- package/helper/auth/validateSiteAccess.js +12 -12
- package/helper/auth/validateSiteSetting.js +7 -7
- package/helper/auth/validateUserLoggedIn.js +19 -19
- package/helper/createGuid.js +5 -5
- package/helper/generateJsonResponse.js +27 -27
- package/helper/getUserPreview.js +57 -57
- package/helper/getUserPreviewFromHeader.js +17 -17
- package/helper/getUserPreviewFromReq.js +17 -17
- package/helper/hqPublishing.js +45 -0
- package/helper/index.js +28 -28
- package/helper/notifySiteConfigs.js +67 -52
- package/helper/opengraph/getOpenGraph.js +12 -12
- package/helper/rates/checkRateLimit.js +38 -38
- package/helper/requestToSource.js +10 -10
- package/helper/sendEmail.js +120 -120
- package/helper/templates/replacePlaceHolders.js +29 -29
- package/helper/time/getLocalTimestamp.js +18 -18
- package/helper/time/getSiteTimezone.js +11 -11
- package/helper/triggerAutomatedAction.js +25 -25
- package/helper/userToUserPreview.js +23 -23
- package/helper/users/getUserTypesByPermission.js +24 -24
- package/helper/users/getUsersByPermission.js +20 -20
- package/notification/prepNotification.js +144 -144
- package/notification/sendNotifications.js +166 -166
- package/package.json +35 -35
- package/templates/supportTicketEmails.js +8 -8
|
@@ -2,148 +2,148 @@ const _ = require("lodash");
|
|
|
2
2
|
const { getConfig } = require("../config");
|
|
3
3
|
|
|
4
4
|
module.exports = (noti) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
5
|
+
const thisNoti = { ...noti };
|
|
6
|
+
const type = noti.Type ?? "";
|
|
7
|
+
if (type.startsWith("MaintenanceJobStatusChanged")) {
|
|
8
|
+
thisNoti.Text = `Your request is ${noti.Data.status}`;
|
|
9
|
+
thisNoti.Subtext = noti.Data.title || noti.Data.description;
|
|
10
|
+
thisNoti.Icon = "wrench";
|
|
11
|
+
if (type.indexOf("Forms") > -1) {
|
|
12
|
+
thisNoti.Icon = "file-text-o";
|
|
13
|
+
thisNoti.Text = `Your form is ${noti.Data.status}`;
|
|
14
|
+
}
|
|
15
|
+
} else if (type.startsWith("MaintenanceJobAssigned")) {
|
|
16
|
+
thisNoti.Text = `You have been assigned a request`;
|
|
17
|
+
thisNoti.Subtext = noti.Data.title || noti.Data.description;
|
|
18
|
+
thisNoti.Icon = "wrench";
|
|
19
|
+
if (type.indexOf("Forms") > -1) {
|
|
20
|
+
thisNoti.Icon = "file-text-o";
|
|
21
|
+
thisNoti.Text = `You have been assigned a form`;
|
|
22
|
+
}
|
|
23
|
+
} else if (type.startsWith("MaintenanceJobUnassigned")) {
|
|
24
|
+
thisNoti.Text = `A request you were assigned has been reassigned`;
|
|
25
|
+
thisNoti.Subtext = noti.Data.title || noti.Data.description;
|
|
26
|
+
thisNoti.Icon = "wrench";
|
|
27
|
+
if (type.indexOf("Forms") > -1) {
|
|
28
|
+
thisNoti.Icon = "file-text-o";
|
|
29
|
+
thisNoti.Text = `A form you were assigned has been reassigned`;
|
|
30
|
+
}
|
|
31
|
+
} else if (type.startsWith("MaintenanceJobCommented")) {
|
|
32
|
+
thisNoti.Text = noti.Data.comment;
|
|
33
|
+
thisNoti.Subtext = noti.Data.title || noti.Data.description;
|
|
34
|
+
thisNoti.Icon = "wrench";
|
|
35
|
+
if (type.indexOf("Forms") > -1) {
|
|
36
|
+
thisNoti.Icon = "file-text-o";
|
|
37
|
+
}
|
|
38
|
+
} else if (type.startsWith("EntityCommented")) {
|
|
39
|
+
thisNoti.Text = noti.Data.comment;
|
|
40
|
+
thisNoti.Subtext = noti.Data.title || noti.Data.description;
|
|
41
|
+
thisNoti.Icon = "commenting";
|
|
42
|
+
} else if (type.startsWith("EventRepChange")) {
|
|
43
|
+
let eventChangeText = "";
|
|
44
|
+
if (noti.Data.LocationChanged && noti.Data.TimeChanged) {
|
|
45
|
+
eventChangeText = "time and location";
|
|
46
|
+
} else if (noti.Data.LocationChanged) {
|
|
47
|
+
eventChangeText = "location";
|
|
48
|
+
} else if (noti.Data.TimeChanged) {
|
|
49
|
+
eventChangeText = "time";
|
|
50
|
+
}
|
|
51
|
+
thisNoti.Text = `An event you are attending has changed ${eventChangeText}`;
|
|
52
|
+
thisNoti.Subtext = noti.Data.Title;
|
|
53
|
+
thisNoti.Icon = "calendar";
|
|
54
|
+
} else if (type.startsWith("EventRepRemoved")) {
|
|
55
|
+
thisNoti.Text = "An event you are attending has been cancelled";
|
|
56
|
+
thisNoti.Subtext = noti.Data.Title;
|
|
57
|
+
thisNoti.TimeText = noti.Data.Time;
|
|
58
|
+
thisNoti.Icon = "calendar-times-o";
|
|
59
|
+
} else if (type.startsWith("ApproveNewsSubmission")) {
|
|
60
|
+
thisNoti.Text = "A story you submitted has been approved";
|
|
61
|
+
thisNoti.Subtext = noti.Data.Title;
|
|
62
|
+
thisNoti.Icon = "newspaper-o";
|
|
63
|
+
} else if (type.startsWith("ApproveEventSubmission")) {
|
|
64
|
+
thisNoti.Text = "An event you submitted has been approved";
|
|
65
|
+
thisNoti.Subtext = noti.Data.Title;
|
|
66
|
+
thisNoti.Icon = "calendar-check-o";
|
|
67
|
+
} else if (type.startsWith("EventRegisterFromWaitlist")) {
|
|
68
|
+
thisNoti.Text =
|
|
69
|
+
"A spot has opened up on an event you were on the waitlist for";
|
|
70
|
+
thisNoti.Subtext = noti.Data.Title;
|
|
71
|
+
thisNoti.Icon = "calendar-plus-o";
|
|
72
|
+
} else if (type.startsWith("NewsletterPublished")) {
|
|
73
|
+
thisNoti.Text = `News: ${noti.Data.Title}`;
|
|
74
|
+
thisNoti.Subtext =
|
|
75
|
+
!_.isEmpty(noti.Data.Text) && noti.Data.Text.length > 100
|
|
76
|
+
? `${noti.Data.Text.substring(0, 100)}...`
|
|
77
|
+
: noti.Data.Text;
|
|
78
|
+
thisNoti.Icon = "newspaper-o";
|
|
79
|
+
} else if (type.startsWith("FormLockoutFailure")) {
|
|
80
|
+
thisNoti.Text =
|
|
81
|
+
"Someone answered yes to a question on the kiosk and was denied access";
|
|
82
|
+
thisNoti.Icon = "exclamation-triangle";
|
|
83
|
+
} else if (type.startsWith("FormLockoutSuccess")) {
|
|
84
|
+
thisNoti.Text =
|
|
85
|
+
"Someone answered no to all questions on the kiosk and was granted access";
|
|
86
|
+
thisNoti.Icon = "check";
|
|
87
|
+
} else if (type.startsWith("InformationAdded")) {
|
|
88
|
+
thisNoti.Text = `There is new important information available: ${noti.Data.title}`;
|
|
89
|
+
thisNoti.Icon = "info";
|
|
90
|
+
} else if (type.startsWith("InformationUpdated")) {
|
|
91
|
+
thisNoti.Text = `Important information updated: ${noti.Data.title}`;
|
|
92
|
+
thisNoti.Icon = "info";
|
|
93
|
+
} else if (type.startsWith("PollAdded")) {
|
|
94
|
+
thisNoti.Text = `There is a new survey available: ${noti.Data.Title}`;
|
|
95
|
+
thisNoti.Icon = "pie-chart";
|
|
96
|
+
} else if (type.startsWith("OfferAdded")) {
|
|
97
|
+
thisNoti.Text = `There is a new offer available: ${noti.Data.Title}`;
|
|
98
|
+
thisNoti.Icon = "shopping-bag";
|
|
99
|
+
} else if (type.startsWith("ServiceAdded")) {
|
|
100
|
+
thisNoti.Text = `There is a new service available: ${noti.Data.Title}`;
|
|
101
|
+
thisNoti.Icon = "stethoscope";
|
|
102
|
+
} else if (type.startsWith("EventAdded")) {
|
|
103
|
+
thisNoti.Text = `There is a new event: ${noti.Data.Title}`;
|
|
104
|
+
thisNoti.Icon = "calendar-plus-o";
|
|
105
|
+
} else if (type.startsWith("FacilityAdded")) {
|
|
106
|
+
thisNoti.Text = `There is a new ${getConfig().strings.FACILITY}: ${
|
|
107
|
+
noti.Data.Title
|
|
108
|
+
}`;
|
|
109
|
+
thisNoti.Icon = "building";
|
|
110
|
+
} else if (type.startsWith("MapAdded")) {
|
|
111
|
+
thisNoti.Text = `There is a new map: ${noti.Data.title}`;
|
|
112
|
+
thisNoti.Icon = "map-marker";
|
|
113
|
+
} else if (type.startsWith("ImportantContactAdded")) {
|
|
114
|
+
thisNoti.Text = `There is a new contact: ${noti.Data.title}`;
|
|
115
|
+
thisNoti.Icon = "address-card-o";
|
|
116
|
+
} else if (type.startsWith("PersonalDocumentAdded")) {
|
|
117
|
+
thisNoti.Text = `There is a new document: ${noti.Data.Name}`;
|
|
118
|
+
thisNoti.Icon = "file-o";
|
|
119
|
+
} else if (type.startsWith("AppointmentAdded")) {
|
|
120
|
+
thisNoti.Text = noti.Data.message;
|
|
121
|
+
thisNoti.Icon = "calendar-plus-o";
|
|
122
|
+
} else if (type.startsWith("AppointmentUpdated")) {
|
|
123
|
+
thisNoti.Text = noti.Data.message;
|
|
124
|
+
thisNoti.Icon = "calendar-check-o";
|
|
125
|
+
} else if (type.startsWith("AppointmentCancelled")) {
|
|
126
|
+
thisNoti.Text = noti.Data.message;
|
|
127
|
+
thisNoti.Icon = "calendar-times-o";
|
|
128
|
+
} else if (type.startsWith("AppointmentReminder")) {
|
|
129
|
+
thisNoti.Text = noti.Data.message;
|
|
130
|
+
thisNoti.Icon = "bell-o";
|
|
131
|
+
} else if (type.startsWith("AppointmentFollowup")) {
|
|
132
|
+
thisNoti.Text = noti.Data.message;
|
|
133
|
+
thisNoti.Icon = "calendar-check-o";
|
|
134
|
+
} else if (type.startsWith("ScheduledAction")) {
|
|
135
|
+
thisNoti.Text = noti.Data.message;
|
|
136
|
+
thisNoti.Icon = "bell-o";
|
|
137
|
+
} else if (type.startsWith("FeedPostApproved")) {
|
|
138
|
+
thisNoti.Text = `You have a new post for ${noti.Data.user.displayName}`;
|
|
139
|
+
thisNoti.Subtext = noti.Data.title;
|
|
140
|
+
thisNoti.Icon = "bars";
|
|
141
|
+
} else if (type.startsWith("FeedPostToday")) {
|
|
142
|
+
thisNoti.Text = `Review today's posts for ${noti.Data.displayName}`;
|
|
143
|
+
thisNoti.Icon = "bars";
|
|
144
|
+
} else {
|
|
145
|
+
thisNoti.Text = "You have received a notification";
|
|
146
|
+
thisNoti.Icon = "bell-o";
|
|
147
|
+
}
|
|
148
|
+
return thisNoti;
|
|
149
149
|
};
|
|
@@ -9,186 +9,186 @@ const { getMultiRowId } = require("../helper");
|
|
|
9
9
|
const { log } = require("../helper");
|
|
10
10
|
|
|
11
11
|
const SendNotification = async (tokens, message, type, key, params, config) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
const logId = log("SendNotification", "input", {
|
|
13
|
+
tokens,
|
|
14
|
+
message,
|
|
15
|
+
type,
|
|
16
|
+
key,
|
|
17
|
+
params,
|
|
18
|
+
config,
|
|
19
|
+
});
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
21
|
+
const expo = new Expo();
|
|
22
|
+
const messages = [];
|
|
23
|
+
const data = {
|
|
24
|
+
type,
|
|
25
|
+
key,
|
|
26
|
+
};
|
|
27
|
+
Object.keys(params).forEach((paramKey) => {
|
|
28
|
+
data[paramKey] = params[paramKey];
|
|
29
|
+
});
|
|
30
|
+
data.muteConfig = config;
|
|
31
|
+
log("SendNotification", "params", data, logId);
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
33
|
+
for (const token of tokens) {
|
|
34
|
+
if (Expo.isExpoPushToken(token)) {
|
|
35
|
+
const msg = {
|
|
36
|
+
to: token,
|
|
37
|
+
sound: "default",
|
|
38
|
+
body: message,
|
|
39
|
+
data,
|
|
40
|
+
badge: 1,
|
|
41
|
+
};
|
|
42
|
+
if (data.muteConfig && !data.muteConfig.ignoreMute) {
|
|
43
|
+
msg.categoryId = data.muteConfig.type
|
|
44
|
+
? `mute_${data.muteConfig.type === "app" ? "app" : "entity"}`
|
|
45
|
+
: "mute_app";
|
|
46
|
+
}
|
|
47
|
+
messages.push(msg);
|
|
48
|
+
} else {
|
|
49
|
+
const message = `Push token ${token} is not a valid Expo push token`;
|
|
50
|
+
log("SendNotification", "IsTokenError", message, logId);
|
|
51
|
+
if (config.raiseError) throw new Error(message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
55
|
+
log("SendNotification", "messages", messages);
|
|
56
|
+
const chunks = expo.chunkPushNotifications(messages);
|
|
57
|
+
for (const chunk of chunks) {
|
|
58
|
+
try {
|
|
59
|
+
const receipts = await expo.sendPushNotificationsAsync(chunk);
|
|
60
|
+
log("SendNotification", "Receipts", receipts, logId);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error.code && error.code === "PUSH_TOO_MANY_EXPERIENCE_IDS") {
|
|
63
|
+
log(
|
|
64
|
+
"SendNotification",
|
|
65
|
+
"CaughtError",
|
|
66
|
+
"PUSH_TOO_MANY_EXPERIENCE_IDS",
|
|
67
|
+
logId,
|
|
68
|
+
);
|
|
69
|
+
// send separately
|
|
70
|
+
await Promise.all(
|
|
71
|
+
_.values(error.details).map((appTokens) =>
|
|
72
|
+
SendNotification(appTokens, message, type, key, params, config),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
} else {
|
|
76
|
+
log("SendNotification", "SendError", error, logId);
|
|
77
|
+
if (config.raiseError) throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const checkMuted = async (userId, config) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
if (!config.ignoreMute) {
|
|
85
|
+
const notiSettings = await getNotificationSetting(userId);
|
|
86
|
+
const logId = log("checkMuted", "notiSettings", notiSettings);
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
const appSetting = notiSettings.find((n) => n.EntityType === "app");
|
|
89
|
+
const isAppMuted = appSetting
|
|
90
|
+
? moment() <= moment(appSetting.Expiry)
|
|
91
|
+
: false;
|
|
92
|
+
log("checkMuted", "isAppMuted", isAppMuted, logId);
|
|
93
|
+
if (isAppMuted) return true;
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
const entitySetting = notiSettings.find(
|
|
96
|
+
(n) => n.EntityType === config.type && n.EntityId === config.id,
|
|
97
|
+
);
|
|
98
|
+
const isEntityMuted = entitySetting
|
|
99
|
+
? moment() <= moment(entitySetting.Expiry)
|
|
100
|
+
: false;
|
|
101
|
+
log("checkMuted", "isEntityMuted", isEntityMuted, logId);
|
|
102
|
+
if (isEntityMuted) return true;
|
|
103
|
+
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
return false;
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
module.exports = async (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
receiverKeys,
|
|
110
|
+
message,
|
|
111
|
+
type,
|
|
112
|
+
key,
|
|
113
|
+
value,
|
|
114
|
+
config = { type: "app", id: null, ignoreMute: false, raiseError: false },
|
|
115
115
|
) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
const logId = log("sendNotifications", "input", {
|
|
117
|
+
receiverKeys,
|
|
118
|
+
message,
|
|
119
|
+
type,
|
|
120
|
+
key,
|
|
121
|
+
value,
|
|
122
|
+
config,
|
|
123
|
+
});
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
// Generate content hash from the entire value object for per-user deduplication
|
|
126
|
+
const contentHash = crypto
|
|
127
|
+
.createHash("md5")
|
|
128
|
+
.update(JSON.stringify(value))
|
|
129
|
+
.digest("hex")
|
|
130
|
+
.substring(0, 8); // 8 hex chars for content uniqueness
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
132
|
+
const tokens = [];
|
|
133
|
+
const promises = [];
|
|
134
|
+
receiverKeys.forEach((uid) => {
|
|
135
|
+
promises.push(
|
|
136
|
+
new Promise((resolve, reject) => {
|
|
137
|
+
getUser(uid)
|
|
138
|
+
.then(async (user) => {
|
|
139
|
+
try {
|
|
140
|
+
// User-level rate limiting: check if this specific user can receive this notification
|
|
141
|
+
// Use user ID + content hash + notification type/key for fine-grained deduplication
|
|
142
|
+
const userNotificationKey = getMultiRowId([
|
|
143
|
+
"notification",
|
|
144
|
+
user.Id, // User-specific identifier
|
|
145
|
+
contentHash, // Content hash for this notification
|
|
146
|
+
type,
|
|
147
|
+
key,
|
|
148
|
+
]);
|
|
149
|
+
const canSend = await checkRateLimit(
|
|
150
|
+
userNotificationKey,
|
|
151
|
+
86400000, // 24 hours in milliseconds
|
|
152
|
+
1, // Allow only 1 notification in the timeframe
|
|
153
|
+
false, // Save this check to prevent duplicates
|
|
154
|
+
);
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
156
|
+
const isMuted = await checkMuted(user.Id, config);
|
|
157
|
+
if (!isMuted && canSend) {
|
|
158
|
+
if (!user.tokens) {
|
|
159
|
+
if (user.token) {
|
|
160
|
+
tokens.push(user.token);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
_.values(user.tokens).forEach((entry) => {
|
|
164
|
+
tokens.push(entry.Token);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
log(
|
|
169
|
+
"sendNotifications",
|
|
170
|
+
"userSkipped",
|
|
171
|
+
{
|
|
172
|
+
userId: user.Id,
|
|
173
|
+
isMuted,
|
|
174
|
+
canSend,
|
|
175
|
+
userNotificationKey,
|
|
176
|
+
},
|
|
177
|
+
logId,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
log("sendNotifications", "error", error.toString());
|
|
182
|
+
if (config.raiseError) reject(error);
|
|
183
|
+
}
|
|
184
|
+
resolve();
|
|
185
|
+
})
|
|
186
|
+
.catch((err) => {
|
|
187
|
+
resolve();
|
|
188
|
+
});
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
await Promise.all(promises);
|
|
193
|
+
await SendNotification(tokens, message, type, key, value, config);
|
|
194
194
|
};
|