All files / server/src/features/results resultsRepository.ts

75.75% Statements 25/33
75% Branches 3/4
100% Functions 6/6
75.75% Lines 25/33

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 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                                    49x 507x 507x 507x 507x                           49x                 110x   110x 110x             110x 315x   110x 51x     110x                     110x     110x                                       49x     25x 25x 25x   25x 22x 22x                         22x     25x                
import { startOfMinute } from "date-fns";
import { AssignmentAlgorithm } from "shared/config/eventConfigTypes";
import { MongoDbError } from "shared/types/api/errors";
import {
  AssignmentResultGroup,
  UserAssignmentResult,
} from "shared/types/models/result";
import {
  Result,
  makeErrorResult,
  makeSuccessResult,
} from "shared/utils/result";
import {
  ResultsModel,
  ResultsSchemaDb,
} from "server/features/results/resultsSchema";
import { logger } from "server/utils/logger";
 
export const removeResults = async (): Promise<Result<void, MongoDbError>> => {
  logger.info("MongoDB: remove ALL results from db");
  try {
    await ResultsModel.deleteMany({});
    return makeSuccessResult();
  } catch (error) {
    logger.error(
      new Error("MongoDB: Error removing ALL results", { cause: error }),
    );
    return makeErrorResult(MongoDbError.UNKNOWN_ERROR);
  }
};
 
// One document per start time, and the collection is dumped and kept once the event is over,
// so it is the only lasting account of what the lottery did. A start time is lotteried once,
// but an attempt that saved its spots and failed before marking them can be run again - and
// the second attempt skips the program items the first one filled, so replacing the document
// would drop the first attempt's placements from the record for good.
export const saveResult = async (
  signupResultData: readonly UserAssignmentResult[],
  groups: readonly AssignmentResultGroup[],
  assignmentTime: string,
  algorithm: AssignmentAlgorithm,
  message: string,
): Promise<Result<void, MongoDbError>> => {
  // Matched to the minute like every other start time comparison, so the document is found
  // again by a lookup whose time carries seconds
  const assignmentTimeMinute = startOfMinute(new Date(assignmentTime));
 
  try {
    const previous = await ResultsModel.findOne({
      assignmentTime: assignmentTimeMinute,
    }).lean();
 
    // An attendee holds one spot per start time, so a placement written again replaces the
    // earlier record of it rather than joining it. Only the placements merge: the algorithm,
    // the message and the group snapshot describe the attempt that wrote them.
    const placedNow = new Set(
      signupResultData.map((result) => result.username),
    );
    const previousResults = (previous?.results ?? []).filter(
      (result) => !placedNow.has(result.username),
    );
 
    await ResultsModel.replaceOne(
      { assignmentTime: assignmentTimeMinute },
      {
        assignmentTime: assignmentTimeMinute,
        results: [...previousResults, ...signupResultData],
        groups,
        algorithm,
        message,
      },
      { upsert: true },
    );
    logger.debug(
      `MongoDB: Signup results for assignment time ${assignmentTime} stored to separate collection`,
    );
    return makeSuccessResult();
  } catch (error) {
    logger.error(
      new Error(
        `MongoDB: Error storing signup results for assignment time ${assignmentTime} to separate collection`,
        { cause: error },
      ),
    );
    return makeErrorResult(MongoDbError.UNKNOWN_ERROR);
  }
};
 
interface AssignmentResult {
  results: UserAssignmentResult[];
  groups: AssignmentResultGroup[];
  assignmentTime: string;
  algorithm: string;
  message: string;
}
 
export const findResults = async (): Promise<
  Result<AssignmentResult[], MongoDbError>
> => {
  try {
    const response = await ResultsModel.find({}).lean();
    logger.debug("MongoDB: Find all results");
 
    const results = response.flatMap((assignmentResult) => {
      const result = ResultsSchemaDb.safeParse(assignmentResult);
      Iif (!result.success) {
        logger.error(
          new Error(
            // Logged raw rather than formatted: this document just failed
            // validation, so its assignmentTime may not be a usable date and
            // formatting it would throw, turning one skipped row into a failed
            // query for every result
            `Error validating findResults DB value: assignmentTime: ${JSON.stringify(assignmentResult.assignmentTime)}`,
            { cause: result.error },
          ),
        );
        return [];
      }
      return result.data;
    });
 
    return makeSuccessResult(results);
  } catch (error) {
    logger.error(
      new Error("MongoDB: Error fetching results", { cause: error }),
    );
    return makeErrorResult(MongoDbError.UNKNOWN_ERROR);
  }
};