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;
}
}
Source