@plusscommunities/pluss-core-aws 2.0.25-beta.7 → 2.0.25-beta.9
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/helper/hqPublishing.js +93 -6
- package/package.json +1 -1
package/helper/hqPublishing.js
CHANGED
|
@@ -144,6 +144,23 @@ const fanOutHqSource = async (hqSourceRow, tableName) => {
|
|
|
144
144
|
if (targets.length === 0) {
|
|
145
145
|
return [];
|
|
146
146
|
}
|
|
147
|
+
return writeCopiesForTargets(hqSourceRow, targets, tableName);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Write one community-copy row per site in `targetSites` (all of the source's
|
|
152
|
+
* PublishedToSites on INSERT fan-out, or just the newly-added subset on a
|
|
153
|
+
* MODIFY target-set change). Shared by fanOutHqSource and syncHqTargets. The
|
|
154
|
+
* deterministic RowId makes each write idempotent AND makes re-adding a
|
|
155
|
+
* previously-removed (soft-deleted) site a lossless resurrection — the PUT
|
|
156
|
+
* overwrites the row and drops the Deleted flag.
|
|
157
|
+
*
|
|
158
|
+
* @param {object} hqSourceRow — the HQ-source row.
|
|
159
|
+
* @param {string[]} targetSites — sites to write copies for.
|
|
160
|
+
* @param {string} tableName — entity's table name.
|
|
161
|
+
* @returns {Promise<object[]>} the copy rows written.
|
|
162
|
+
*/
|
|
163
|
+
const writeCopiesForTargets = (hqSourceRow, targetSites, tableName) => {
|
|
147
164
|
const copyId = `hqcopy-${hqSourceRow.Id}`;
|
|
148
165
|
// Preserve the HQ-source row's own UnixTimestamp on each copy rather than
|
|
149
166
|
// stamping "now". For an immediate publish the source timestamp already IS
|
|
@@ -163,7 +180,7 @@ const fanOutHqSource = async (hqSourceRow, tableName) => {
|
|
|
163
180
|
const now = moment.utc().valueOf();
|
|
164
181
|
const publishTs = hqSourceRow.UnixTimestamp || now;
|
|
165
182
|
|
|
166
|
-
const writes =
|
|
183
|
+
const writes = targetSites.map((targetSiteId) => {
|
|
167
184
|
// Spread + override pattern: keep all content fields, swap identity
|
|
168
185
|
// fields, drop HQ-only fields. Spread is shallow — nested objects
|
|
169
186
|
// (Author, etc.) share references with the source row, which is fine
|
|
@@ -192,6 +209,63 @@ const fanOutHqSource = async (hqSourceRow, tableName) => {
|
|
|
192
209
|
return Promise.all(writes);
|
|
193
210
|
};
|
|
194
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Reconcile community copies when an HQ-source MODIFY changes PublishedToSites:
|
|
214
|
+
* fan out copies for newly-ADDED sites, soft-delete copies for REMOVED sites.
|
|
215
|
+
* Content propagation to surviving copies is propagateHqEdit's job; this only
|
|
216
|
+
* reconciles the target SET. Re-adding a removed site resurrects its copy
|
|
217
|
+
* losslessly (deterministic RowId + PUT — see writeCopiesForTargets).
|
|
218
|
+
*
|
|
219
|
+
* MUST run sequentially relative to propagateHqEdit (never concurrently): both
|
|
220
|
+
* use editRef (get-merge-put), and two concurrent writes to the same removed-
|
|
221
|
+
* copy RowId would race (one clobbering the other's Deleted flag).
|
|
222
|
+
*
|
|
223
|
+
* @param {object} prevRow — OldImage of the HQ-source row.
|
|
224
|
+
* @param {object} nextRow — NewImage of the HQ-source row.
|
|
225
|
+
* @param {string} tableName — entity's table name.
|
|
226
|
+
* @param {object} [options]
|
|
227
|
+
* @param {string} [options.indexName="NewsletterEntriesHqSourceIdIndex"]
|
|
228
|
+
* @returns {Promise<{added: number, removed: number}>}
|
|
229
|
+
*/
|
|
230
|
+
const syncHqTargets = async (prevRow, nextRow, tableName, options = {}) => {
|
|
231
|
+
const indexName = options.indexName || "NewsletterEntriesHqSourceIdIndex";
|
|
232
|
+
if (!nextRow || nextRow.Site !== "hq" || nextRow.Deleted) {
|
|
233
|
+
return { added: 0, removed: 0 };
|
|
234
|
+
}
|
|
235
|
+
const prev = Array.isArray(prevRow?.PublishedToSites)
|
|
236
|
+
? prevRow.PublishedToSites
|
|
237
|
+
: [];
|
|
238
|
+
const next = Array.isArray(nextRow.PublishedToSites)
|
|
239
|
+
? nextRow.PublishedToSites
|
|
240
|
+
: [];
|
|
241
|
+
const added = next.filter((s) => !prev.includes(s));
|
|
242
|
+
const removed = prev.filter((s) => !next.includes(s));
|
|
243
|
+
|
|
244
|
+
if (added.length > 0) {
|
|
245
|
+
await writeCopiesForTargets(nextRow, added, tableName);
|
|
246
|
+
}
|
|
247
|
+
if (removed.length > 0) {
|
|
248
|
+
const copies = await findCopiesByHqSourceId(
|
|
249
|
+
nextRow.Id,
|
|
250
|
+
tableName,
|
|
251
|
+
indexName,
|
|
252
|
+
);
|
|
253
|
+
const toRemove = copies.filter((c) => removed.includes(c.Site));
|
|
254
|
+
// Per-call fresh payload — editRef sync-mutates updates[keyCol]; sharing
|
|
255
|
+
// across Promise.all(map) would leak RowId between iterations (same
|
|
256
|
+
// rationale as propagateHqEdit / cascadeHqRetract).
|
|
257
|
+
await Promise.all(
|
|
258
|
+
toRemove.map((c) =>
|
|
259
|
+
editRef(tableName, "RowId", c.RowId, {
|
|
260
|
+
Deleted: true,
|
|
261
|
+
Changed: moment.utc().unix(),
|
|
262
|
+
}),
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return { added: added.length, removed: removed.length };
|
|
267
|
+
};
|
|
268
|
+
|
|
195
269
|
/**
|
|
196
270
|
* Propagate an HQ-source MODIFY to every community copy via the GSI lookup.
|
|
197
271
|
* Skips the retract case (where `Deleted: true`) — that flows through
|
|
@@ -326,8 +400,8 @@ const cascadeHqRetract = async (hqSourceRow, tableName, options = {}) => {
|
|
|
326
400
|
* @param {string} templateId — the `Id` of the contentLibrary row.
|
|
327
401
|
* @param {string} contentLibraryTable — entity-agnostic; caller passes the
|
|
328
402
|
* table name (without prefix).
|
|
329
|
-
* @returns {Promise<boolean>} `true` if increment applied; `false`
|
|
330
|
-
*
|
|
403
|
+
* @returns {Promise<boolean>} `true` if increment applied; `false` on graceful
|
|
404
|
+
* skip (table not shipped yet, or no template with that Id exists).
|
|
331
405
|
*/
|
|
332
406
|
const incrementTemplateUseCount = async (templateId, contentLibraryTable) => {
|
|
333
407
|
if (!templateId || !contentLibraryTable) {
|
|
@@ -336,11 +410,16 @@ const incrementTemplateUseCount = async (templateId, contentLibraryTable) => {
|
|
|
336
410
|
const params = {
|
|
337
411
|
TableName: `${process.env.tablePrefix}${contentLibraryTable}`,
|
|
338
412
|
Key: { Id: templateId },
|
|
339
|
-
UpdateExpression:
|
|
340
|
-
|
|
413
|
+
UpdateExpression: "ADD UseCount :inc SET LastUsedTimestamp = :now",
|
|
414
|
+
// Guard against minting a phantom row: ADD on a missing key would CREATE
|
|
415
|
+
// it with UseCount=1. Only increment a template that actually exists; a
|
|
416
|
+
// stale/bogus TemplateId is skipped (ConditionalCheckFailed below).
|
|
417
|
+
ConditionExpression: "attribute_exists(Id)",
|
|
341
418
|
ExpressionAttributeValues: {
|
|
342
419
|
":inc": 1,
|
|
343
|
-
|
|
420
|
+
// epoch ms — the contentLibrary timestamp convention (matches
|
|
421
|
+
// UpdatedAt). Was .unix() (seconds); reconciled to .valueOf().
|
|
422
|
+
":now": moment().utc().valueOf(),
|
|
344
423
|
},
|
|
345
424
|
};
|
|
346
425
|
try {
|
|
@@ -358,6 +437,12 @@ const incrementTemplateUseCount = async (templateId, contentLibraryTable) => {
|
|
|
358
437
|
);
|
|
359
438
|
return false;
|
|
360
439
|
}
|
|
440
|
+
if (error?.code === "ConditionalCheckFailedException") {
|
|
441
|
+
// TemplateId references no existing template — skip rather than
|
|
442
|
+
// create a phantom row. Not an error; continue the stream handler.
|
|
443
|
+
log("incrementTemplateUseCount", "NoSuchTemplate", { templateId });
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
361
446
|
// Anything else is genuinely unexpected — surface it.
|
|
362
447
|
log("incrementTemplateUseCount", "Error", error);
|
|
363
448
|
throw error;
|
|
@@ -370,6 +455,8 @@ module.exports = {
|
|
|
370
455
|
// Story B
|
|
371
456
|
findCopiesByHqSourceId,
|
|
372
457
|
fanOutHqSource,
|
|
458
|
+
writeCopiesForTargets,
|
|
459
|
+
syncHqTargets,
|
|
373
460
|
propagateHqEdit,
|
|
374
461
|
cascadeHqRetract,
|
|
375
462
|
incrementTemplateUseCount,
|
package/package.json
CHANGED