diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index d48a363fc..cbb9d98cd 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -174,7 +174,9 @@ const db = { "bms-course-lookup": monkDB.get("bms-course-lookup"), "api-tokens": monkDB.get("api-tokens"), "orphan-scores": monkDB.get("orphan-scores"), - "import-locks": monkDB.get<{ userID: integer; locked: boolean }>("import-locks"), + "import-locks": monkDB.get<{ userID: integer; locked: boolean; lockedAt: integer | null }>( + "import-locks" + ), tables: monkDB.get("tables"), "invite-locks": monkDB.get<{ userID: integer; locked: boolean }>("invite-locks"), "game-settings": monkDB.get("game-settings"), diff --git a/server/src/lib/migration/migrations.ts b/server/src/lib/migration/migrations.ts index 9cb2036e5..236accc75 100644 --- a/server/src/lib/migration/migrations.ts +++ b/server/src/lib/migration/migrations.ts @@ -1,4 +1,5 @@ import UserFollowersMigration from "./migrations/add-following-to-users"; +import AddLockedAt from "./migrations/add-lockedat"; import UGPTAddPreferredRanking from "./migrations/add-preferredRanking-to-ugpt"; import UGPTRivalsMigration from "./migrations/add-rivals-to-ugpt"; import FixUndefinedBMSData from "./migrations/fix-undefined-bms-data"; @@ -50,6 +51,7 @@ const REGISTERED_MIGRATIONS: Array = SessionsToScoreIDs, V3PropsMigration, V3ScoresMigration, + AddLockedAt, ]; // only apply type-specific migrations if we're not in testing diff --git a/server/src/lib/migration/migrations/add-lockedat.ts b/server/src/lib/migration/migrations/add-lockedat.ts new file mode 100644 index 000000000..0933919e7 --- /dev/null +++ b/server/src/lib/migration/migrations/add-lockedat.ts @@ -0,0 +1,30 @@ +import db from "external/mongo/db"; +import type { Migration } from "utils/types"; + +const migration: Migration = { + id: "add-lockedat", + up: async () => { + await db["import-locks"].update( + {}, + { + $set: { + lockedAt: null, + }, + }, + { multi: true } + ); + }, + down: async () => { + await db["import-locks"].update( + {}, + { + $unset: { + lockedAt: 1, + }, + }, + { multi: true } + ); + }, +}; + +export default migration; diff --git a/server/src/lib/score-import/framework/import-locks/lock.ts b/server/src/lib/score-import/framework/import-locks/lock.ts index 419f818a1..fa1077a22 100644 --- a/server/src/lib/score-import/framework/import-locks/lock.ts +++ b/server/src/lib/score-import/framework/import-locks/lock.ts @@ -1,6 +1,10 @@ import db from "external/mongo/db"; +import { ONE_HOUR } from "lib/constants/time"; +import CreateLogCtx from "lib/logger/logger"; import type { integer } from "tachi-common"; +const logger = CreateLogCtx(__filename); + /** * If a user has no ongoing import, enable the import lock and return true. * If a user has an ongoing import, return false. @@ -18,6 +22,7 @@ export async function CheckAndSetOngoingImportLock(userID: integer) { await db["import-locks"].insert({ userID, locked: false, + lockedAt: null, }); } @@ -27,10 +32,23 @@ export async function CheckAndSetOngoingImportLock(userID: integer) { locked: false, }, { - $set: { locked: true }, + $set: { locked: true, lockedAt: Date.now() }, } ); + if (!lockWasSet) { + return true; + } + + if (lockWasSet.lockedAt !== null) { + if (Date.now() - lockWasSet.lockedAt > ONE_HOUR) { + logger.error( + `User ${userID} has been locked for an hour. Automatically freeing the lock as they're stuck.` + ); + await UnsetOngoingImportLock(userID); + } + } + return !lockWasSet; } @@ -44,7 +62,7 @@ export function UnsetOngoingImportLock(userID: integer) { locked: true, }, { - $set: { locked: false }, + $set: { locked: false, lockedAt: null }, } ); }