All files / server/src/features/assignment assignmentService.ts

84.21% Statements 16/19
62.5% Branches 5/8
100% Functions 1/1
84.21% Lines 16/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 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                          39x           20x 20x                       20x 20x 1x 1x 1x                       19x   19x 19x         19x             19x     19x   19x               19x      
import dayjs from "dayjs";
import { runAssignment } from "server/features/assignment/run-assignment/runAssignment";
import { PostAssignmentResponse } from "shared/types/api/assignment";
import { config } from "shared/config";
import {
  acquireAssignmentLock,
  findSettings,
  releaseAssignmentLock,
  setAssignmentLastRun,
} from "server/features/settings/settingsRepository";
import { MongoDbError } from "shared/types/api/errors";
import { logger } from "server/utils/logger";
 
export const storeAssignment = async (
  assignmentTime: string,
): Promise<PostAssignmentResponse> => {
  // Ensure a settings row exists (a fresh one starts with a free lock) so the lock check
  // below fails only when an assignment genuinely ran within the window, not when the row
  // is missing
  const settingsResult = await findSettings();
  Iif (!settingsResult.ok) {
    return {
      message: "Assignment failed",
      status: "error",
      errorId: "unknown",
    };
  }
 
  // Hold the same in-progress lock the auto-assign cron uses for the whole run so a manual run
  // can't overlap a cron run (or another manual run) and corrupt results via the non-atomic
  // save. Released in the finally below, so a failed run is immediately retryable and a crash
  // can't hold the lock past the stale timeout
  const lockResult = await acquireAssignmentLock();
  if (!lockResult.ok) {
    Eif (lockResult.error === MongoDbError.ASSIGNMENT_LOCK_HELD) {
      logger.warn("Assignment already running, skip manual assignment");
      return {
        message: "Assignment already running",
        status: "error",
        errorId: "assignmentInProgress",
      };
    }
    return {
      message: "Assignment failed",
      status: "error",
      errorId: "unknown",
    };
  }
  const lockToken = lockResult.value;
 
  try {
    const assignResultsResult = await runAssignment({
      assignmentAlgorithm: config.event().assignmentAlgorithm,
      assignmentTime,
    });
 
    Iif (!assignResultsResult.ok) {
      return {
        message: "Assignment failed",
        status: "error",
        errorId: "unknown",
      };
    }
    const assignResults = assignResultsResult.value;
 
    // Record the last successful run time
    await setAssignmentLastRun(dayjs().toISOString());
 
    return {
      message: "Assignment success",
      status: "success",
      results: assignResults.results,
      resultMessage: assignResults.message,
      assignmentTime,
    };
  } finally {
    await releaseAssignmentLock(lockToken);
  }
};