@natlibfi/melinda-rest-api-commons 6.0.1-alpha.1 → 6.1.0-alpha.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/dist/mongo.js CHANGED
@@ -1,25 +1,29 @@
1
1
  import { MongoClient, GridFSBucket, MongoDriverError } from "mongodb";
2
- import moment from "moment";
3
2
  import httpStatus from "http-status";
4
- import sanitize from "mongo-sanitize";
5
3
  import createDebugLogger from "debug";
6
- import { createLogger } from "@natlibfi/melinda-backend-commons";
7
- import { Error as ApiError, parseBoolean } from "@natlibfi/melinda-commons";
4
+ import { createLogger, Error as ApiError, parseBoolean } from "@natlibfi/melinda-commons";
8
5
  import { QUEUE_ITEM_STATE, IMPORT_JOB_STATE, OPERATIONS } from "./constants.js";
9
- import { logError } from "./utils.js";
10
- export default async function(MONGO_URI, collection, db = "rest-api") {
6
+ import { logError, assertString, assertNumber, assertImportJobStateFilter } from "./utils.js";
7
+ export default async function(MONGO_URI, collection, db = "rest-api", deps = {}) {
11
8
  const logger = createLogger();
9
+ const timeoutMinutes = Number.parseInt(process.env.MONGO_TIMEOUT_MINUTES, 10) || 1;
10
+ logger.debug(`checkTimeOut window: ${timeoutMinutes} minute(s) (override with MONGO_TIMEOUT_MINUTES)`);
12
11
  const debug = createDebugLogger("@natlibfi/melinda-rest-api-commons/mongo");
13
12
  const debugDev = debug.extend(":dev");
14
- const client = await MongoClient.connect(MONGO_URI);
15
- const dbConnection = client.db(db);
16
- const gridFSBucket = new GridFSBucket(dbConnection, { bucketName: collection });
17
- const operator = dbConnection.collection(collection);
13
+ const stubbed = Boolean(deps.operator) && Boolean(deps.gridFSBucket);
14
+ let gridFSBucket = deps.gridFSBucket;
15
+ let operator = deps.operator;
16
+ if (!stubbed) {
17
+ const client = await MongoClient.connect(MONGO_URI);
18
+ const realDb = client.db(db);
19
+ gridFSBucket = gridFSBucket ?? new GridFSBucket(realDb, { bucketName: collection });
20
+ operator = operator ?? realDb.collection(collection);
21
+ }
18
22
  debugDev(`mongo: ${MONGO_URI}, db: ${db}, collection: ${collection}`);
19
23
  return { createPrio, createBulk, checkAndSetState, checkAndSetImportJobState, query, createProjection, queryById, checkTimeOut, remove, readContent, removeContent, getOne, getStream, setState, setImportJobState, pushIds, pushMessages, setOperation, setOperations, addBlobSize, setBlobSize };
20
24
  async function createPrio({ correlationId, cataloger, oCatalogerIn, operation, operationSettings }) {
21
25
  debugDev("createPrio");
22
- const time = moment().toDate();
26
+ const time = /* @__PURE__ */ new Date();
23
27
  const newQueueItem = {
24
28
  correlationId,
25
29
  cataloger,
@@ -59,7 +63,7 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
59
63
  }
60
64
  }
61
65
  async function createBulk({ correlationId, cataloger, oCatalogerIn, operation, contentType, recordLoadParams, stream, operationSettings }) {
62
- const time = moment().toDate();
66
+ const time = /* @__PURE__ */ new Date();
63
67
  const newQueueItem = {
64
68
  correlationId,
65
69
  cataloger,
@@ -90,19 +94,42 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
90
94
  try {
91
95
  await new Promise((resolve, reject) => {
92
96
  const outputStream = gridFSBucket.openUploadStream(correlationId);
93
- stream.on("error", reject).on("data", (chunk) => outputStream.write(chunk)).on("end", () => outputStream.end(void 0, void 0, () => {
97
+ outputStream.on("error", (error) => {
98
+ outputStream.destroy();
99
+ reject(error);
100
+ });
101
+ stream.on("error", (error) => {
102
+ outputStream.destroy();
103
+ reject(error);
104
+ }).on("data", (chunk) => outputStream.write(chunk)).on("end", () => outputStream.end(void 0, void 0, () => {
94
105
  resolve(correlationId);
95
106
  }));
96
107
  });
97
108
  logger.info(`New BULK queue item for ${operation} ${correlationId} has been made in ${collection}!`);
98
109
  debug(`New BULK queue item for ${operation} ${correlationId} has been made in ${collection}!`);
99
- return operator.insertOne(newQueueItem);
110
+ const result = await operator.insertOne(newQueueItem);
111
+ if (!result.acknowledged) {
112
+ throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR);
113
+ }
114
+ return { correlationId, acknowledged: result.acknowledged, queueItemState: newQueueItem.queueItemState };
100
115
  } catch (error) {
101
116
  const errorMessage = error.payload || error.message || "";
102
117
  logError(error);
118
+ await removePartialContent(correlationId);
103
119
  throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, `Mongo errored: ${errorMessage}`);
104
120
  }
105
121
  }
122
+ async function removePartialContent(correlationId2) {
123
+ try {
124
+ const files = await gridFSBucket.find({ filename: correlationId2 }).toArray();
125
+ for (const file of files) {
126
+ await gridFSBucket.delete(file._id);
127
+ logger.debug(`mongo/createBulk: removed partial GridFS file for ${correlationId2}`);
128
+ }
129
+ } catch (cleanupError) {
130
+ logger.debug(`mongo/createBulk: partial GridFS cleanup failed for ${correlationId2}: ${cleanupError.message}`);
131
+ }
132
+ }
106
133
  logger.debug(`No stream`);
107
134
  debug(`Creating noStreamBulk`);
108
135
  if (!stream) {
@@ -111,7 +138,7 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
111
138
  if (result.acknowledged) {
112
139
  logger.info(`New noStream BULK queue item for ${operation} ${correlationId} has been made in ${collection}`);
113
140
  debug(`New noStream BULK queue item for ${operation} ${correlationId} has been made in ${collection}`);
114
- return { correlationId, queueItemState: QUEUE_ITEM_STATE.VALIDATOR.WAITING_FOR_RECORDS };
141
+ return { correlationId, acknowledged: result.acknowledged, queueItemState: QUEUE_ITEM_STATE.VALIDATOR.WAITING_FOR_RECORDS };
115
142
  }
116
143
  throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR);
117
144
  } catch (error) {
@@ -122,19 +149,27 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
122
149
  }
123
150
  }
124
151
  async function checkAndSetState({ correlationId, state, errorMessage = void 0, errorStatus = void 0 }) {
152
+ assertString(correlationId, "correlationId");
125
153
  const timeOut = await checkTimeOut({ correlationId });
126
154
  if (timeOut) {
127
- return setState({ correlationId, state, errorMessage, errorStatus });
155
+ const queueItem2 = await setState({ correlationId, state, errorMessage, errorStatus });
156
+ return { status: "updated", queueItem: queueItem2 };
128
157
  }
129
- return false;
158
+ const queueItem = await operator.findOne({ correlationId }, { projection: { _id: 0 } });
159
+ const status = queueItem && queueItem.queueItemState === QUEUE_ITEM_STATE.ABORT ? "timeout" : "unchanged";
160
+ return { status, queueItem };
130
161
  }
131
162
  async function checkAndSetImportJobState({ correlationId, operation, importJobState }) {
163
+ assertString(correlationId, "correlationId");
132
164
  logger.debug(`${correlationId}, ${importJobState}, ${operation}`);
133
165
  const timeOut = await checkTimeOut({ correlationId, operation, importJobState });
134
166
  if (timeOut) {
135
- return setImportJobState({ correlationId, operation, importJobState });
167
+ const queueItem2 = await setImportJobState({ correlationId, operation, importJobState });
168
+ return { status: "updated", queueItem: queueItem2 };
136
169
  }
137
- return false;
170
+ const queueItem = await operator.findOne({ correlationId }, { projection: { _id: 0 } });
171
+ const status = queueItem && queueItem.queueItemState === QUEUE_ITEM_STATE.ABORT ? "timeout" : "unchanged";
172
+ return { status, queueItem };
138
173
  }
139
174
  async function query(params, showParams = {}) {
140
175
  logger.debug(`Querying: ${JSON.stringify(params)}, ${JSON.stringify(showParams)}`);
@@ -184,6 +219,7 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
184
219
  };
185
220
  }
186
221
  async function queryById({ correlationId, checkModTime = false }) {
222
+ assertString(correlationId, "correlationId");
187
223
  if (checkModTime) {
188
224
  debug(`queryById checkModTime: ${checkModTime}`);
189
225
  await checkTimeOut({ correlationId });
@@ -193,14 +229,20 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
193
229
  return result;
194
230
  }
195
231
  async function checkTimeOut({ correlationId }) {
196
- const { modificationTime, queueItemState: oldState, importJobState } = await operator.findOne({ correlationId });
232
+ assertString(correlationId, "correlationId");
233
+ const queueItem = await operator.findOne({ correlationId });
234
+ if (!queueItem) {
235
+ logger.silly(`${correlationId} not found in ${collection} - not a timeout`);
236
+ return false;
237
+ }
238
+ const { modificationTime, queueItemState: oldState, importJobState = { CREATE: IMPORT_JOB_STATE.EMPTY, UPDATE: IMPORT_JOB_STATE.EMPTY, FIX: IMPORT_JOB_STATE.EMPTY } } = queueItem;
197
239
  if ([QUEUE_ITEM_STATE.ABORT, QUEUE_ITEM_STATE.ERROR, QUEUE_ITEM_STATE.DONE].includes(oldState)) {
198
240
  logger.silly(`${correlationId} has already state: ${oldState}`);
199
241
  return false;
200
242
  }
201
- const timeoutTime = moment(modificationTime).add(1, "m");
243
+ const timeoutTime = new Date(new Date(modificationTime).getTime() + timeoutMinutes * 6e4);
202
244
  logger.silly(`${correlationId} timeOut @ ${timeoutTime}`);
203
- if (timeoutTime.isBefore()) {
245
+ if (timeoutTime < /* @__PURE__ */ new Date()) {
204
246
  const finalImportJobStates = [IMPORT_JOB_STATE.ABORT, IMPORT_JOB_STATE.DONE, IMPORT_JOB_STATE.EMPTY, IMPORT_JOB_STATE.ERROR];
205
247
  if (!finalImportJobStates.includes(importJobState.CREATE)) {
206
248
  await setImportJobState({ correlationId, operation: "CREATE", importJobState: IMPORT_JOB_STATE.ABORT });
@@ -221,25 +263,21 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
221
263
  logger.info(`Removing from Mongo (${collection}) id: ${params.correlationId}`);
222
264
  debug(`${JSON.stringify(params)}`);
223
265
  debug(`Removing from Mongo (${collection}) id: ${params.correlationId}`);
224
- const clean = sanitize(params.correlationId);
225
- logger.silly(`mongo/remove: clean: ${JSON.stringify(clean)}`);
226
- debug(`mongo/remove: clean: ${JSON.stringify(clean)}`);
266
+ assertString(params.correlationId, "correlationId");
227
267
  try {
228
- const noContent = await removeContent({ correlationId: params.correlationId });
229
- debug(`noContent (removeContent result): ${JSON.stringify(noContent)}`);
230
- if (noContent) {
231
- const deleteResult = await operator.deleteOne({ correlationId: clean });
232
- debug(`deleteResult: ${JSON.stringify(deleteResult)}`);
233
- return true;
234
- }
268
+ const removedContent = await removeContent({ correlationId: params.correlationId });
269
+ debug(`removedContent (removeContent result): ${JSON.stringify(removedContent)}`);
270
+ const deleteResult = await operator.deleteOne({ correlationId: params.correlationId });
271
+ debug(`deleteResult: ${JSON.stringify(deleteResult)}`);
272
+ return { removed: deleteResult.deletedCount > 0, deletedContent: removedContent };
235
273
  } catch (err) {
236
274
  if (err instanceof MongoDriverError) {
237
275
  debug(err);
238
276
  if (err.message.indexOf("File not found for id") !== -1) {
239
- logger.silly(`mongo/remove: File not found, removing queueItem ${JSON.stringify(clean)} from ${collection}`);
240
- debug(`mongo/remove: File not found, removing queueItem ${JSON.stringify(clean)} from ${collection}`);
241
- await operator.deleteOne({ correlationId: clean });
242
- return true;
277
+ logger.silly(`mongo/remove: File not found, removing queueItem ${JSON.stringify(params.correlationId)} from ${collection}`);
278
+ debug(`mongo/remove: File not found, removing queueItem ${JSON.stringify(params.correlationId)} from ${collection}`);
279
+ const deleteResult = await operator.deleteOne({ correlationId: params.correlationId });
280
+ return { removed: deleteResult.deletedCount > 0, deletedContent: false };
243
281
  }
244
282
  logger.error(err.message);
245
283
  throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, err.message);
@@ -250,16 +288,24 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
250
288
  async function readContent(correlationId) {
251
289
  logger.info(`Reading content from mongo for id: ${correlationId} in ${collection}`);
252
290
  debug(`Reading content from mongo for id: ${correlationId} in ${collection}`);
253
- const clean = sanitize(correlationId);
254
- debug(clean);
255
- const result = await operator.findOne({ correlationId: clean });
291
+ assertString(correlationId, "correlationId");
292
+ const result = await operator.findOne({ correlationId });
256
293
  debug(result);
257
294
  if (result) {
258
295
  const { operationSettings } = result;
259
- if (operationSettings.prio === false && operationSettings.noStream === false) {
296
+ let files = [];
297
+ if (operationSettings.prio === false) {
298
+ files = await gridFSBucket.find({ filename: correlationId }).toArray();
299
+ }
300
+ const hasContent = files.length > 0;
301
+ if (hasContent) {
260
302
  return {
261
303
  contentType: result.contentType,
262
- readStream: gridFSBucket.openDownloadStreamByName(clean)
304
+ // the file existence was verified by the find() above; a
305
+ // 'error' event on the returned stream (e.g. file deleted
306
+ // concurrently) is an unhandled event for the caller -> the
307
+ // contract is: the caller MUST attach an 'error' handler.
308
+ readStream: gridFSBucket.openDownloadStreamByName(correlationId)
263
309
  };
264
310
  }
265
311
  logger.debug(`OperationSettings for ${correlationId}: ${JSON.stringify(operationSettings)}`);
@@ -272,8 +318,8 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
272
318
  async function removeContent(params) {
273
319
  logger.info(`Removing content from mongo for id: ${params.correlationId} in ${collection}`);
274
320
  debug(`Removing content from mongo for id: ${params.correlationId} in ${collection}`);
275
- const clean = sanitize(params.correlationId);
276
- const result = await gridFSBucket.find({ filename: clean }).toArray();
321
+ assertString(params.correlationId, "correlationId");
322
+ const result = await gridFSBucket.find({ filename: params.correlationId }).toArray();
277
323
  debug(`mongo/removeContent: find bucket result ${JSON.stringify(result)}`);
278
324
  if (result.length > 0) {
279
325
  const [firstBucketResult] = result;
@@ -282,11 +328,17 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
282
328
  return true;
283
329
  }
284
330
  logger.debug(`No files for ${params.correlationId} to delete`);
285
- return true;
331
+ return false;
286
332
  }
287
- function getOne({ queueItemState, importJobState = void 0 }) {
333
+ async function getOne({ queueItemState, importJobState = void 0 }) {
288
334
  logger.silly(`queueItemState: ${queueItemState}, importJobState: ${JSON.stringify(importJobState)}`);
289
- const cleanQueueItemState = queueItemState ? { queueItemState: sanitize(queueItemState) } : void 0;
335
+ if (queueItemState) {
336
+ assertString(queueItemState, "queueItemState");
337
+ }
338
+ if (importJobState) {
339
+ assertImportJobStateFilter(importJobState, "importJobState");
340
+ }
341
+ const cleanQueueItemState = queueItemState ? { queueItemState } : void 0;
290
342
  const options = { projection: { _id: 0 } };
291
343
  try {
292
344
  if (queueItemState && importJobState === void 0) {
@@ -309,51 +361,77 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
309
361
  throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, `Mongo errored: ${errorMessage}`);
310
362
  }
311
363
  }
312
- function getStream(correlationId) {
364
+ async function getStream(correlationId) {
313
365
  logger.info(`Forming stream from mongo for ${correlationId} in ${collection}`);
314
- const clean = sanitize(correlationId);
366
+ assertString(correlationId, "correlationId");
315
367
  try {
316
- return gridFSBucket.openDownloadStreamByName(clean);
368
+ const files = await gridFSBucket.find({ filename: correlationId }).toArray();
369
+ if (files.length === 0) {
370
+ logger.info(`getStream: file ${correlationId} not found in ${collection}`);
371
+ throw new ApiError(httpStatus.NOT_FOUND, { message: `File ${correlationId} not found in ${collection}` });
372
+ }
373
+ return gridFSBucket.openDownloadStreamByName(correlationId);
317
374
  } catch (error) {
375
+ if (error instanceof ApiError) {
376
+ throw error;
377
+ }
318
378
  const errorMessage = error.payload || error.message || "";
319
379
  logError(error);
320
380
  throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, `Mongo errored: ${errorMessage}`);
321
381
  }
322
382
  }
323
383
  async function pushIds({ correlationId, handledIds, rejectedIds }) {
384
+ if (!Array.isArray(handledIds) || !Array.isArray(rejectedIds)) {
385
+ throw new ApiError(httpStatus.BAD_REQUEST, "pushIds: handledIds and rejectedIds must be arrays");
386
+ }
387
+ assertString(correlationId, "correlationId");
324
388
  logger.verbose(`Push ids (${handledIds.length}) and rejectedIds (${rejectedIds.length}) ${correlationId} to ${collection}`);
325
389
  logger.debug(`ids (${handledIds.length}): ${JSON.stringify(handledIds)}, rejectedIds ${rejectedIds.length}: ${JSON.stringify(rejectedIds)}`);
326
- const clean = sanitize(correlationId);
327
- await operator.updateOne({
328
- correlationId: clean
329
- }, {
330
- $set: {
331
- modificationTime: moment().toDate()
332
- },
333
- $push: {
334
- handledIds: { $each: handledIds },
335
- rejectedIds: { $each: rejectedIds }
336
- }
337
- });
390
+ try {
391
+ await operator.updateOne({
392
+ correlationId
393
+ }, {
394
+ $set: {
395
+ modificationTime: /* @__PURE__ */ new Date()
396
+ },
397
+ $push: {
398
+ handledIds: { $each: handledIds },
399
+ rejectedIds: { $each: rejectedIds }
400
+ }
401
+ });
402
+ } catch (error) {
403
+ const errorMessage = error.payload || error.message || "";
404
+ logError(error);
405
+ throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, `Mongo errored: ${errorMessage}`);
406
+ }
338
407
  }
339
408
  async function pushMessages({ correlationId, messages, messageField = "messages" }) {
409
+ if (!Array.isArray(messages)) {
410
+ throw new ApiError(httpStatus.BAD_REQUEST, "pushMessages: messages must be an array");
411
+ }
412
+ assertString(correlationId, "correlationId");
413
+ assertString(messageField, "messageField");
340
414
  logger.verbose(`Push messages (${messages.length}) to ${messageField} ${correlationId} to ${collection}`);
341
- logger.silly(`Messages (${messages.length}): ${JSON.stringify(messages)}}`);
342
- const clean = sanitize(correlationId);
343
- const cleanMessageField = sanitize(messageField);
344
- debug(`${messageField} -> ${cleanMessageField}`);
345
- await operator.updateOne({
346
- correlationId: clean
347
- }, {
348
- $set: {
349
- modificationTime: moment().toDate()
350
- },
351
- $push: {
352
- [cleanMessageField]: { $each: messages }
353
- }
354
- });
415
+ logger.silly(`Messages (${messages.length}): ${JSON.stringify(messages)}`);
416
+ try {
417
+ await operator.updateOne({
418
+ correlationId
419
+ }, {
420
+ $set: {
421
+ modificationTime: /* @__PURE__ */ new Date()
422
+ },
423
+ $push: {
424
+ [messageField]: { $each: messages }
425
+ }
426
+ });
427
+ } catch (error) {
428
+ const errorMessage = error.payload || error.message || "";
429
+ logError(error);
430
+ throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, `Mongo errored: ${errorMessage}`);
431
+ }
355
432
  }
356
433
  function setState({ correlationId, state, errorMessage = void 0, errorStatus = void 0 }) {
434
+ assertString(correlationId, "correlationId");
357
435
  const errorString = errorMessage || errorStatus ? `, Error message: '${JSON.stringify(errorMessage) || ""}', Error status: '${errorStatus || ""}'` : "";
358
436
  logger.info(`Setting queue-item state ${state} for ${correlationId}${errorString} to ${collection}`);
359
437
  const stateInQueueItemStates = Object.values(QUEUE_ITEM_STATE).indexOf(state) > -1;
@@ -363,76 +441,89 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
363
441
  debug(`Trying to set invalid state ${state}`);
364
442
  throw new ApiError(httpStatus.BAD_REQUEST, "Trying to set invalid state");
365
443
  }
366
- const clean = sanitize(correlationId);
367
444
  const updateValues = {
368
445
  queueItemState: state,
369
- modificationTime: moment().toDate(),
446
+ modificationTime: /* @__PURE__ */ new Date(),
370
447
  errorMessage,
371
448
  errorStatus
372
449
  };
373
450
  Object.keys(updateValues).forEach((key) => updateValues[key] === void 0 && delete updateValues[key]);
374
451
  return operator.findOneAndUpdate(
375
- { correlationId: clean },
452
+ { correlationId },
376
453
  { $set: updateValues },
377
454
  { projection: { _id: 0 }, returnDocument: "after" }
378
455
  );
379
456
  }
380
457
  function setImportJobState({ correlationId, operation, importJobState }) {
458
+ assertString(correlationId, "correlationId");
381
459
  logger.info(`Setting queue-item importJobState: {${operation}: ${importJobState}} for ${correlationId}`);
382
460
  debug(`Setting queue-item importJobState: {${operation}: ${importJobState}} for ${correlationId}`);
383
- const cleanCorrelationId = sanitize(correlationId);
384
- const cleanImportJobState = sanitize(importJobState);
385
461
  if (!(operation in OPERATIONS)) {
386
462
  debug(`Error: ${operation} is not in ${JSON.stringify(OPERATIONS)}`);
387
463
  throw new ApiError(httpStatus.BAD_REQUEST, "Invalid operation for import job state");
388
464
  }
389
- if (!(cleanImportJobState in IMPORT_JOB_STATE)) {
390
- debug(`Error: ${cleanImportJobState} is not in ${JSON.stringify(IMPORT_JOB_STATE)}`);
465
+ if (!(importJobState in IMPORT_JOB_STATE)) {
466
+ debug(`Error: ${importJobState} is not in ${JSON.stringify(IMPORT_JOB_STATE)}`);
391
467
  throw new ApiError(httpStatus.BAD_REQUEST, "Invalid import job state");
392
468
  }
393
- const newJobState = getNewJobState(operation, cleanImportJobState);
394
- function getNewJobState(operation2, cleanImportJobState2) {
469
+ const newJobState = getNewJobState(operation, importJobState);
470
+ function getNewJobState(operation2, importJobState2) {
395
471
  if (operation2 === OPERATIONS.CREATE) {
396
- return { "importJobState.CREATE": cleanImportJobState2 };
472
+ return { "importJobState.CREATE": importJobState2 };
397
473
  }
398
474
  if (operation2 === OPERATIONS.UPDATE) {
399
- return { "importJobState.UPDATE": cleanImportJobState2 };
475
+ return { "importJobState.UPDATE": importJobState2 };
400
476
  }
401
477
  if (operation2 === OPERATIONS.FIX) {
402
- return { "importJobState.FIX": cleanImportJobState2 };
478
+ return { "importJobState.FIX": importJobState2 };
403
479
  }
404
480
  throw new ApiError("400", "Invalid operation for import job state");
405
481
  }
406
482
  return operator.findOneAndUpdate({
407
- correlationId: cleanCorrelationId
483
+ correlationId
408
484
  }, {
409
485
  $set: {
410
486
  ...newJobState,
411
- modificationTime: moment().toDate()
487
+ modificationTime: /* @__PURE__ */ new Date()
412
488
  }
413
489
  }, { projection: { _id: 0 }, returnDocument: "after" });
414
490
  }
491
+ function unwrapAndUpdateResult(result, correlationId) {
492
+ if (result === null || result === void 0) {
493
+ logger.info(`Queue item ${correlationId} not found in ${collection}`);
494
+ throw new ApiError(httpStatus.NOT_FOUND, { message: `Job ${correlationId} not found.` });
495
+ }
496
+ return result;
497
+ }
415
498
  async function setOperation({ correlationId, operation }) {
499
+ assertString(correlationId, "correlationId");
416
500
  const newOperation = operation;
417
- const { operation: oldOperation } = await operator.findOne({ correlationId });
501
+ const existing = await operator.findOne({ correlationId });
502
+ if (!existing) {
503
+ logger.info(`Setting queue-item operation: queue item ${correlationId} not found in ${collection}`);
504
+ throw new ApiError(httpStatus.NOT_FOUND, { message: `Job ${correlationId} not found.` });
505
+ }
506
+ const { operation: oldOperation } = existing;
418
507
  logger.info(`Setting queue-item operation from ${oldOperation} to ${newOperation} for ${correlationId} to ${collection}`);
419
- const cleanCorrelationId = sanitize(correlationId);
420
- const cleanNewOperation = sanitize(newOperation);
421
508
  const result = await operator.findOneAndUpdate({
422
- correlationId: cleanCorrelationId
509
+ correlationId
423
510
  }, {
424
511
  $set: {
425
- operation: cleanNewOperation,
426
- modificationTime: moment().toDate()
512
+ operation: newOperation,
513
+ modificationTime: /* @__PURE__ */ new Date()
427
514
  }
428
515
  }, { projection: { _id: 0 }, returnDocument: "after" });
429
516
  debugDev(`${JSON.stringify(result)}`);
430
- return result.ok || result;
517
+ return unwrapAndUpdateResult(result, correlationId);
431
518
  }
432
519
  async function setOperations({ correlationId, addOperation, removeOperation = void 0 }) {
433
- const cleanCorrelationId = sanitize(correlationId);
434
- const queueItem = await operator.findOne({ correlationId: cleanCorrelationId });
520
+ assertString(correlationId, "correlationId");
521
+ const queueItem = await operator.findOne({ correlationId });
435
522
  logger.silly(`We found a queueItem: ${JSON.stringify(queueItem)}`);
523
+ if (!queueItem) {
524
+ logger.info(`Setting queue-item operations: queue item ${correlationId} not found in ${collection}`);
525
+ throw new ApiError(httpStatus.NOT_FOUND, { message: `Job ${correlationId} not found.` });
526
+ }
436
527
  const oldOperations = queueItem.operations;
437
528
  logger.info(`Setting queue-item operations from ${JSON.stringify(oldOperations)} by adding ${addOperation} and removing ${removeOperation} for ${correlationId} to ${collection}`);
438
529
  const operationsAfterRemove = oldOperations.filter((operation) => operation !== removeOperation);
@@ -440,27 +531,27 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
440
531
  logger.silly(`operationsAfterRemove: ${operationsAfterRemove}`);
441
532
  logger.silly(`operationAfterRemoveAndAdd: ${operationsAfterRemoveAndAdd}`);
442
533
  const result = await operator.findOneAndUpdate({
443
- correlationId: cleanCorrelationId
534
+ correlationId
444
535
  }, {
445
536
  $set: {
446
537
  operations: operationsAfterRemoveAndAdd,
447
- modificationTime: moment().toDate()
538
+ modificationTime: /* @__PURE__ */ new Date()
448
539
  }
449
540
  }, { projection: { _id: 0 }, returnDocument: "after" });
450
541
  debugDev(`${JSON.stringify(result)}`);
451
- return result.ok || result;
542
+ return unwrapAndUpdateResult(result, correlationId);
452
543
  }
453
544
  async function addBlobSize({ correlationId }) {
454
- const cleanCorrelationId = sanitize(correlationId);
545
+ assertString(correlationId, "correlationId");
455
546
  const result = await operator.findOneAndUpdate({
456
- correlationId: cleanCorrelationId,
547
+ correlationId,
457
548
  queueItemState: QUEUE_ITEM_STATE.VALIDATOR.WAITING_FOR_RECORDS
458
549
  }, {
459
550
  $inc: {
460
551
  blobSize: 1
461
552
  },
462
553
  $set: {
463
- modificationTime: moment().toDate()
554
+ modificationTime: /* @__PURE__ */ new Date()
464
555
  }
465
556
  }, { projection: { _id: 0 }, returnDocument: "after" });
466
557
  logger.verbose(`AddBlobSizeResult in mongo: ${JSON.stringify(result)}`);
@@ -468,14 +559,14 @@ export default async function(MONGO_URI, collection, db = "rest-api") {
468
559
  return result;
469
560
  }
470
561
  async function setBlobSize({ correlationId, blobSize }) {
471
- const cleanCorrelationId = sanitize(correlationId);
472
- const cleanBlobSize = sanitize(blobSize);
562
+ assertString(correlationId, "correlationId");
563
+ assertNumber(blobSize, "blobSize");
473
564
  const result = await operator.findOneAndUpdate({
474
- correlationId: cleanCorrelationId
565
+ correlationId
475
566
  }, {
476
567
  $set: {
477
- blobSize: cleanBlobSize,
478
- modificationTime: moment().toDate()
568
+ blobSize,
569
+ modificationTime: /* @__PURE__ */ new Date()
479
570
  }
480
571
  }, { projection: { _id: 0 }, returnDocument: "after" });
481
572
  logger.verbose(`SetBlobSizeResult in mongo: ${JSON.stringify(result)}`);