Source

services/investigation/locks.js

import { Investigation } from "@models/investigation/investigation.model";
import { InvestigationStatus } from "@typez/investigation";
import { getLogger } from "@utils/asyncLocalStorage";
import { Types } from "mongoose";
/**
 * Sends/return investigation to/from InDevelopment.
 * @category Services
 * @param {string} investigationId - The ID of the investigation.
 * @returns {Promise<IInvestigation>} - The modified investigation.
 * @throws {Error} If the investigation is not found.
 */
export async function toggleInvestigationDevelopment(investigationId) {
    const logger = getLogger();
    try {
        const investigation = await Investigation.findById(investigationId);
        if (!investigation) {
            throw new Error("Investigation not found");
        }
        if (investigation.status !== InvestigationStatus.IN_DEVELOPMENT) {
            investigation.status = InvestigationStatus.IN_DEVELOPMENT;
        }
        else {
            investigation.status = InvestigationStatus.DRAFT_NON_CONTRADICTORY_COMPLETE;
        }
        await investigation.save();
        return investigation;
    }
    catch (error) {
        logger.error({ error, investigationId }, "Failed to toggle investigation to InDevelopment.");
        throw new Error("Failed to toggle investigation to InDevelopment.");
    }
}
/**
 * Lock an investigation for editing by a specific user.
 * @param {string} investigationId - The investigation ID to lock.
 * @param {string} userId - The user ID who is locking the investigation.
 * @returns {Promise<IInvestigation>} The updated investigation.
 */
export async function lockInvestigation(investigationId, userId) {
    const logger = getLogger();
    try {
        const investigation = await Investigation.findById(investigationId);
        if (!investigation) {
            throw new Error("Investigation not found");
        }
        investigation.isLocked = true;
        investigation.lockedBy = new Types.ObjectId(userId);
        investigation.lockedAt = new Date();
        await investigation.save();
        logger.info({ investigationId, userId }, "Investigation locked");
        return investigation;
    }
    catch (error) {
        logger.error({ error, investigationId, userId }, "Failed to lock investigation.");
        throw new Error("Failed to lock investigation.");
    }
}
/**
 * Unlock an investigation. Only the user who locked it can unlock it.
 * @param {string} investigationId - The investigation ID to unlock.
 * @param {string} userId - The user ID attempting to unlock (must be the lock owner).
 * @returns {Promise<IInvestigation>} The updated investigation.
 */
export async function unlockInvestigation(investigationId, userId) {
    const logger = getLogger();
    try {
        const investigation = await Investigation.findById(investigationId);
        if (!investigation) {
            throw new Error("Investigation not found");
        }
        // Verify that the user attempting to unlock is the one who locked it
        if (investigation.lockedBy && investigation.lockedBy.toString() !== userId) {
            throw new Error("Only the user who locked the investigation can unlock it");
        }
        investigation.isLocked = false;
        investigation.lockedBy = null;
        investigation.lockedAt = null;
        await investigation.save();
        logger.info({ investigationId, userId }, "Investigation unlocked");
        return investigation;
    }
    catch (error) {
        logger.error({ error, investigationId, userId }, "Failed to unlock investigation.");
        throw new Error("Failed to unlock investigation.");
    }
}
/**
 * Check and unlock stale locks (investigations locked for more than 5 minutes).
 * @returns {Promise<number>} Number of investigations unlocked.
 */
export async function checkAndUnlockStaleLocks() {
    const logger = getLogger();
    try {
        const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
        const result = await Investigation.updateMany({
            isLocked: true,
            lockedAt: { $lt: fiveMinutesAgo },
        }, {
            $set: {
                isLocked: false,
                lockedBy: null,
                lockedAt: null,
            },
        });
        if (result.modifiedCount > 0) {
            logger.info({ unlockedCount: result.modifiedCount }, "Unlocked stale investigation locks");
        }
        return result.modifiedCount;
    }
    catch (error) {
        logger.error({ error }, "Failed to check and unlock stale locks.");
        return 0;
    }
}