@plusscommunities/pluss-newsletter-aws-training 2.0.9 → 2.0.10

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 (54) hide show
  1. package/addNewsletterEntry.js +168 -156
  2. package/common/helper/index.js +3 -3
  3. package/common/templates/newsletterSubmissionEmail.js +2 -2
  4. package/config/validVideoURLs.js +30 -30
  5. package/deleteNewsletterEntry.js +81 -60
  6. package/editNewsletterEntry.js +127 -90
  7. package/editNewsletterSubmission.js +70 -53
  8. package/feature.config.js +316 -305
  9. package/feature.config.news2.js +329 -329
  10. package/feature.config.newsa.js +329 -329
  11. package/feature.config.newsb.js +329 -329
  12. package/feature.config.newsc.js +329 -329
  13. package/feature.config.newsd.js +329 -329
  14. package/feature.config.shifts.js +328 -328
  15. package/getAvailableNews.js +39 -39
  16. package/getFeaturedNewsletterEntries.js +70 -72
  17. package/getNews.js +51 -51
  18. package/getNewsletterEntries.js +97 -100
  19. package/getNewsletterEntry.js +30 -27
  20. package/getNewsletterSubmission.js +27 -24
  21. package/getNewsletterSubmissions.js +45 -45
  22. package/getPublishedAvailableNews.js +35 -35
  23. package/getPublishedEntries.js +55 -55
  24. package/getRecentNewsletterEntries.js +87 -89
  25. package/getSubmissionCount.js +33 -33
  26. package/getTaggedNews.js +98 -98
  27. package/handleNewsletterSubmission.js +77 -77
  28. package/hasNewsletterAccess.js +47 -47
  29. package/helper/checkUpdateSource.js +58 -58
  30. package/newsletterChanged.js +369 -167
  31. package/package.json +3 -3
  32. package/searchAvailableNews.js +52 -52
  33. package/values.config.awards.js +23 -23
  34. package/values.config.coaching.js +24 -24
  35. package/values.config.curatedposts.js +24 -24
  36. package/values.config.default.js +24 -24
  37. package/values.config.headoffice.js +24 -24
  38. package/values.config.js +24 -24
  39. package/values.config.menu.js +23 -23
  40. package/values.config.news2.js +24 -24
  41. package/values.config.newsa.js +24 -24
  42. package/values.config.newsb.js +24 -24
  43. package/values.config.newsc.js +24 -24
  44. package/values.config.newsd.js +24 -24
  45. package/values.config.noticeboard.js +24 -24
  46. package/values.config.personaldev.js +24 -24
  47. package/values.config.products.js +23 -23
  48. package/values.config.projects.js +23 -23
  49. package/values.config.sales.js +23 -23
  50. package/values.config.sharing.js +24 -24
  51. package/values.config.shifts.js +23 -23
  52. package/values.config.training.js +24 -24
  53. package/watchNewsletter.js +198 -198
  54. package/package-lock.json +0 -7693
@@ -15,170 +15,182 @@ const publishActivity = require("./common/db/activity/publishActivity");
15
15
  const { values } = require("./values.config");
16
16
 
17
17
  const sendNewsSubmissionEmail = (update) => {
18
- getRef(
19
- "strings",
20
- "RowId",
21
- `${update.Site}_${values.stringPostfixSubmissionEmail}`
22
- )
23
- .then((ev) => {
24
- sendEmail(
25
- ev.Value,
26
- newsletterSubmissionEmail.subject.replace("___TITLE___", update.Title),
27
- newsletterSubmissionEmail.content
28
- .replace("___TITLE___", update.Title)
29
- .replace("___TEXT___", update.Text)
30
- .replace("___NAME___", update.SubmittedBy.displayName)
31
- .replace(/\n/g, " <br /> "),
32
- true
33
- );
34
- })
35
- .catch((err) => {
36
- console.log("No email to send to");
37
- });
18
+ getRef(
19
+ "strings",
20
+ "RowId",
21
+ `${update.Site}_${values.stringPostfixSubmissionEmail}`,
22
+ )
23
+ .then((ev) => {
24
+ sendEmail(
25
+ ev.Value,
26
+ newsletterSubmissionEmail.subject.replace("___TITLE___", update.Title),
27
+ newsletterSubmissionEmail.content
28
+ .replace("___TITLE___", update.Title)
29
+ .replace("___TEXT___", update.Text)
30
+ .replace("___NAME___", update.SubmittedBy.displayName)
31
+ .replace(/\n/g, " <br /> "),
32
+ true,
33
+ );
34
+ })
35
+ .catch((err) => {
36
+ console.log("No email to send to");
37
+ });
38
38
  };
39
39
 
40
40
  module.exports.addNewsletterEntry = (event, context, callback) => {
41
- initConfig();
42
- const data = JSON.parse(event.body);
41
+ initConfig();
42
+ const data = JSON.parse(event.body);
43
43
 
44
- data.update.Id = uuid.v1();
44
+ // PC-1444 §AC-B.7: strip caller-supplied HqSourceId. Only the stream
45
+ // handler (newsletterChanged.js) writes HqSourceId on community-copy rows
46
+ // (via fanOutHqSource). Allowing callers to set it would let them forge
47
+ // "ghost-locked" rows that bypass Story A's Boolean(HqSourceId) lock guard
48
+ // — defense-in-depth pairing with the lock guard.
49
+ if (data.update && data.update.HqSourceId !== undefined) {
50
+ console.warn(
51
+ "addNewsletterEntry: stripped caller-supplied HqSourceId",
52
+ );
53
+ delete data.update.HqSourceId;
54
+ }
45
55
 
46
- if (!data.update.Timestamp && !data.update.UnixTimestamp) {
47
- data.update.Timestamp = moment.utc().toISOString();
48
- data.update.UnixTimestamp = moment.utc().valueOf();
49
- }
50
- data.update.UnixTimestampReverse =
51
- Number.MAX_SAFE_INTEGER - data.update.UnixTimestamp;
52
- data.update.RowId = getRowId(data.update.Site, data.update.Id);
53
- if (data.update.IsFeatured && !data.update.IsFeaturedDate) {
54
- data.update.IsFeaturedDate = moment.utc().unix();
55
- } else if (!data.update.IsFeatured) {
56
- data.update.IsFeaturedDate = null;
57
- }
56
+ data.update.Id = uuid.v1();
58
57
 
59
- if (data.update.Video) {
60
- if (!isValidVideo(data.update.Video)) {
61
- return callback(
62
- null,
63
- generateJsonResponse(400, {
64
- message:
65
- "The video URL is from a domain that is not recognized and therefore blocked for security reasons.",
66
- })
67
- );
68
- }
69
- }
58
+ if (!data.update.Timestamp && !data.update.UnixTimestamp) {
59
+ data.update.Timestamp = moment.utc().toISOString();
60
+ data.update.UnixTimestamp = moment.utc().valueOf();
61
+ }
62
+ data.update.UnixTimestampReverse =
63
+ Number.MAX_SAFE_INTEGER - data.update.UnixTimestamp;
64
+ data.update.RowId = getRowId(data.update.Site, data.update.Id);
65
+ if (data.update.IsFeatured && !data.update.IsFeaturedDate) {
66
+ data.update.IsFeaturedDate = moment.utc().unix();
67
+ } else if (!data.update.IsFeatured) {
68
+ data.update.IsFeaturedDate = null;
69
+ }
70
70
 
71
- if (data.update.Notification) data.update.UnsentNotification = "X";
71
+ if (data.update.Video) {
72
+ if (!isValidVideo(data.update.Video)) {
73
+ return callback(
74
+ null,
75
+ generateJsonResponse(400, {
76
+ message:
77
+ "The video URL is from a domain that is not recognized and therefore blocked for security reasons.",
78
+ }),
79
+ );
80
+ }
81
+ }
72
82
 
73
- getSessionUserFromReqAuthKey(event).then((uid) => {
74
- getUserPreview(uid).then((user) => {
75
- data.update.SubmittedBy = user;
76
- if (data.update.AuthorDisplay === "name") {
77
- data.update.Author = user;
78
- }
83
+ if (data.update.Notification) data.update.UnsentNotification = "X";
79
84
 
80
- validateMasterAuth(event, values.permissionNewsletter).then(
81
- (authorised) => {
82
- if (!authorised) {
83
- validateMasterAuth(event, values.permissionNewsletterSubmit).then(
84
- (authorised) => {
85
- if (!authorised) {
86
- console.error("Authorization not valid");
87
- callback(
88
- null,
89
- generateJsonResponse(403, {
90
- message: "Authorization not valid.",
91
- })
92
- );
93
- return;
94
- }
85
+ getSessionUserFromReqAuthKey(event).then((uid) => {
86
+ getUserPreview(uid).then((user) => {
87
+ data.update.SubmittedBy = user;
88
+ if (data.update.AuthorDisplay === "name") {
89
+ data.update.Author = user;
90
+ }
95
91
 
96
- if (data.update.Private) {
97
- updateRef(values.tableNameNewsletterEntries, data.update)
98
- .then((result) => {
99
- callback(
100
- null,
101
- generateJsonResponse(200, {
102
- success: true,
103
- update: result,
104
- reviewRequired: false,
105
- })
106
- );
107
- })
108
- .catch((error) => {
109
- console.log("error", error);
110
- callback(
111
- null,
112
- generateJsonResponse(500, {
113
- message: "Something went wrong.",
114
- })
115
- );
116
- });
117
- } else {
118
- updateRef(values.tableNameNewsletterSubmissions, data.update)
119
- .then((result) => {
120
- publishActivity(
121
- values.activityAddNewsSubmission,
122
- data.update.Site,
123
- data.update.RowId,
124
- user,
125
- { title: data.update.Title }
126
- );
127
- sendNewsSubmissionEmail(data.update);
128
- callback(
129
- null,
130
- generateJsonResponse(200, {
131
- success: true,
132
- update: result,
133
- reviewRequired: true,
134
- })
135
- );
136
- })
137
- .catch((error) => {
138
- console.log("error", error);
139
- callback(
140
- null,
141
- generateJsonResponse(500, {
142
- message: "Something went wrong.",
143
- })
144
- );
145
- });
146
- }
147
- }
148
- );
149
- } else {
150
- updateRef(values.tableNameNewsletterEntries, data.update)
151
- .then((result) => {
152
- if (!data.update.Private) {
153
- publishActivity(
154
- values.activityAddNews,
155
- data.update.Site,
156
- data.update.RowId,
157
- user,
158
- { title: data.update.Title }
159
- );
160
- }
161
- callback(
162
- null,
163
- generateJsonResponse(200, {
164
- success: true,
165
- update: result,
166
- reviewRequired: false,
167
- })
168
- );
169
- })
170
- .catch((error) => {
171
- console.log("error", error);
172
- callback(
173
- null,
174
- generateJsonResponse(500, {
175
- message: "Something went wrong.",
176
- })
177
- );
178
- });
179
- }
180
- }
181
- );
182
- });
183
- });
92
+ validateMasterAuth(event, values.permissionNewsletter).then(
93
+ (authorised) => {
94
+ if (!authorised) {
95
+ validateMasterAuth(event, values.permissionNewsletterSubmit).then(
96
+ (authorised) => {
97
+ if (!authorised) {
98
+ console.error("Authorization not valid");
99
+ callback(
100
+ null,
101
+ generateJsonResponse(403, {
102
+ message: "Authorization not valid.",
103
+ }),
104
+ );
105
+ return;
106
+ }
107
+
108
+ if (data.update.Private) {
109
+ updateRef(values.tableNameNewsletterEntries, data.update)
110
+ .then((result) => {
111
+ callback(
112
+ null,
113
+ generateJsonResponse(200, {
114
+ success: true,
115
+ update: result,
116
+ reviewRequired: false,
117
+ }),
118
+ );
119
+ })
120
+ .catch((error) => {
121
+ console.log("error", error);
122
+ callback(
123
+ null,
124
+ generateJsonResponse(500, {
125
+ message: "Something went wrong.",
126
+ }),
127
+ );
128
+ });
129
+ } else {
130
+ updateRef(values.tableNameNewsletterSubmissions, data.update)
131
+ .then((result) => {
132
+ publishActivity(
133
+ values.activityAddNewsSubmission,
134
+ data.update.Site,
135
+ data.update.RowId,
136
+ user,
137
+ { title: data.update.Title },
138
+ );
139
+ sendNewsSubmissionEmail(data.update);
140
+ callback(
141
+ null,
142
+ generateJsonResponse(200, {
143
+ success: true,
144
+ update: result,
145
+ reviewRequired: true,
146
+ }),
147
+ );
148
+ })
149
+ .catch((error) => {
150
+ console.log("error", error);
151
+ callback(
152
+ null,
153
+ generateJsonResponse(500, {
154
+ message: "Something went wrong.",
155
+ }),
156
+ );
157
+ });
158
+ }
159
+ },
160
+ );
161
+ } else {
162
+ updateRef(values.tableNameNewsletterEntries, data.update)
163
+ .then((result) => {
164
+ if (!data.update.Private) {
165
+ publishActivity(
166
+ values.activityAddNews,
167
+ data.update.Site,
168
+ data.update.RowId,
169
+ user,
170
+ { title: data.update.Title },
171
+ );
172
+ }
173
+ callback(
174
+ null,
175
+ generateJsonResponse(200, {
176
+ success: true,
177
+ update: result,
178
+ reviewRequired: false,
179
+ }),
180
+ );
181
+ })
182
+ .catch((error) => {
183
+ console.log("error", error);
184
+ callback(
185
+ null,
186
+ generateJsonResponse(500, {
187
+ message: "Something went wrong.",
188
+ }),
189
+ );
190
+ });
191
+ }
192
+ },
193
+ );
194
+ });
195
+ });
184
196
  };
@@ -1,7 +1,7 @@
1
1
  const {
2
- getRowId,
3
- thisOrDefault,
4
- log,
2
+ getRowId,
3
+ thisOrDefault,
4
+ log,
5
5
  } = require("@plusscommunities/pluss-core-aws/helper");
6
6
 
7
7
  module.exports.getRowId = getRowId;
@@ -1,8 +1,8 @@
1
1
  const { values } = require("../../values.config");
2
2
 
3
3
  module.exports = {
4
- subject: `${values.textNewNewsletterSubmission}: ___TITLE___`,
5
- content: `<div style='margin-bottom: 40px; font-size: 14px; line-height: 23px; color: #3e4245;'>
4
+ subject: `${values.textNewNewsletterSubmission}: ___TITLE___`,
5
+ content: `<div style='margin-bottom: 40px; font-size: 14px; line-height: 23px; color: #3e4245;'>
6
6
  ___NAME___ ${values.textHasSubmittedANewNewsletterPost}
7
7
  </div>
8
8
  <div style='margin-bottom: 16px; font-size: 22px; line-height: 30px; color: #0a2246; font-weight: bold;'>
@@ -3,10 +3,10 @@ const { log } = require("@plusscommunities/pluss-core-aws/helper");
3
3
  const { getConfig } = require("@plusscommunities/pluss-core-aws/config");
4
4
 
5
5
  const validVideoURLs = [
6
- "youtube.com",
7
- "youtu.be",
8
- "vimeo.com",
9
- `${process.env.tablePrefix}uploads.s3.ap-southeast-2.amazonaws.com`,
6
+ "youtube.com",
7
+ "youtu.be",
8
+ "vimeo.com",
9
+ `${process.env.tablePrefix}uploads.s3.ap-southeast-2.amazonaws.com`,
10
10
  ];
11
11
 
12
12
  /**
@@ -15,32 +15,32 @@ const validVideoURLs = [
15
15
  * @returns {Boolean} true if the URL is valid. false if it should be blocked.
16
16
  */
17
17
  exports.isValidVideo = (videoUrl) => {
18
- const logId = log("isValidVideo", "Start", videoUrl);
19
- if (_.isEmpty(videoUrl)) {
20
- log("isValidVideo", "Empty", videoUrl, logId);
21
- return true;
22
- }
18
+ const logId = log("isValidVideo", "Start", videoUrl);
19
+ if (_.isEmpty(videoUrl)) {
20
+ log("isValidVideo", "Empty", videoUrl, logId);
21
+ return true;
22
+ }
23
23
 
24
- const { featureConfig } = getConfig();
25
- if (!featureConfig.limitVideoURLs) {
26
- return true;
27
- }
28
- try {
29
- const host = new URL(videoUrl.toLowerCase()).host;
30
- log("isValidVideo", "host", host, logId);
24
+ const { featureConfig } = getConfig();
25
+ if (!featureConfig.limitVideoURLs) {
26
+ return true;
27
+ }
28
+ try {
29
+ const host = new URL(videoUrl.toLowerCase()).host;
30
+ log("isValidVideo", "host", host, logId);
31
31
 
32
- return _.some(validVideoURLs, (validUrl) => {
33
- log("isValidVideo", "validUrl", validUrl, logId);
34
- log(
35
- "isValidVideo",
36
- "isValid",
37
- host === validUrl || host === `www.${validUrl}`,
38
- logId
39
- );
40
- return host === validUrl || host === `www.${validUrl}`;
41
- });
42
- } catch (e) {
43
- log("isValidVideo", "Error", e, logId);
44
- }
45
- return false;
32
+ return _.some(validVideoURLs, (validUrl) => {
33
+ log("isValidVideo", "validUrl", validUrl, logId);
34
+ log(
35
+ "isValidVideo",
36
+ "isValid",
37
+ host === validUrl || host === `www.${validUrl}`,
38
+ logId,
39
+ );
40
+ return host === validUrl || host === `www.${validUrl}`;
41
+ });
42
+ } catch (e) {
43
+ log("isValidVideo", "Error", e, logId);
44
+ }
45
+ return false;
46
46
  };
@@ -6,70 +6,91 @@ const getRef = require("./common/db/common/getRef");
6
6
  const validateMasterAuth = require("./common/helper/auth/validateMasterAuth");
7
7
  const publishActivity = require("./common/db/activity/publishActivity");
8
8
  const getUserPreviewFromHeader = require("./common/helper/getUserPreviewFromHeader");
9
+ const {
10
+ isHqLocked,
11
+ } = require("@plusscommunities/pluss-core-aws/helper/hqPublishing");
9
12
  const { values } = require("./values.config");
10
13
 
11
14
  module.exports.deleteNewsletterEntry = (event, context, callback) => {
12
- initConfig();
13
- const data = JSON.parse(event.body);
15
+ initConfig();
16
+ const data = JSON.parse(event.body);
14
17
 
15
- if (!data.site || !data.id) {
16
- callback(
17
- null,
18
- generateJsonResponse(422, { error: "No site or id attached" })
19
- );
20
- return;
21
- }
18
+ if (!data.site || !data.id) {
19
+ callback(
20
+ null,
21
+ generateJsonResponse(422, { error: "No site or id attached" }),
22
+ );
23
+ return;
24
+ }
22
25
 
23
- validateMasterAuth(event, values.permissionNewsletter).then((authorised) => {
24
- if (!authorised) {
25
- console.error("Authorization not valid");
26
- callback(
27
- null,
28
- generateJsonResponse(422, {
29
- error: {
30
- message: "Authorization not valid.",
31
- },
32
- })
33
- );
34
- return;
35
- }
26
+ validateMasterAuth(event, values.permissionNewsletter).then((authorised) => {
27
+ if (!authorised) {
28
+ console.error("Authorization not valid");
29
+ callback(
30
+ null,
31
+ generateJsonResponse(422, {
32
+ error: {
33
+ message: "Authorization not valid.",
34
+ },
35
+ }),
36
+ );
37
+ return;
38
+ }
36
39
 
37
- getRef(values.tableNameNewsletterEntries, "RowId", data.id).then(
38
- (result) => {
39
- if (result.Site !== data.site) {
40
- return callback(
41
- null,
42
- generateJsonResponse(422, {
43
- error: {
44
- message: "Incorrect site.",
45
- },
46
- })
47
- );
48
- }
49
- editRef(values.tableNameNewsletterEntries, "RowId", data.id, {
50
- Deleted: true,
51
- Changed: moment.utc().unix(),
52
- })
53
- .then(() => {
54
- getUserPreviewFromHeader(event.headers.authkey).then((user) => {
55
- publishActivity(
56
- values.activityDeleteNews,
57
- result.Site,
58
- result.RowId,
59
- user,
60
- {
61
- title: result.Title,
62
- }
63
- );
64
- });
65
- callback(null, generateJsonResponse(200, { success: true }));
66
- return;
67
- })
68
- .catch((error) => {
69
- callback(null, generateJsonResponse(422, { error }));
70
- return;
71
- });
72
- }
73
- );
74
- });
40
+ getRef(values.tableNameNewsletterEntries, "RowId", data.id).then(
41
+ (result) => {
42
+ if (result.Site !== data.site) {
43
+ return callback(
44
+ null,
45
+ generateJsonResponse(422, {
46
+ error: {
47
+ message: "Incorrect site.",
48
+ },
49
+ }),
50
+ );
51
+ }
52
+ // PC-1441 §3.2 AC2.3: community copies of HQ-published posts are
53
+ // universally locked (Boolean(HqSourceId) falsy test — no role
54
+ // exception, including masters). Retract happens on the HQ-source
55
+ // row (where !HqSourceId) and the stream cascades Deleted: true.
56
+ if (isHqLocked(result)) {
57
+ console.error(
58
+ "deleteNewsletterEntry: blocked — row is HQ-managed",
59
+ );
60
+ return callback(
61
+ null,
62
+ generateJsonResponse(403, {
63
+ error: {
64
+ message:
65
+ "This post is managed by Enterprise HQ and cannot be deleted locally.",
66
+ },
67
+ }),
68
+ );
69
+ }
70
+ editRef(values.tableNameNewsletterEntries, "RowId", data.id, {
71
+ Deleted: true,
72
+ Changed: moment.utc().unix(),
73
+ })
74
+ .then(() => {
75
+ getUserPreviewFromHeader(event.headers.authkey).then((user) => {
76
+ publishActivity(
77
+ values.activityDeleteNews,
78
+ result.Site,
79
+ result.RowId,
80
+ user,
81
+ {
82
+ title: result.Title,
83
+ },
84
+ );
85
+ });
86
+ callback(null, generateJsonResponse(200, { success: true }));
87
+ return;
88
+ })
89
+ .catch((error) => {
90
+ callback(null, generateJsonResponse(422, { error }));
91
+ return;
92
+ });
93
+ },
94
+ );
95
+ });
75
96
  };