import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; import { FilterQuery } from "mongodb"; import { FormatChart, Game, GoalSubscriptionDocument, integer, MilestoneSubscriptionDocument, PBScoreDocument, ScoreDocument, } from "tachi-common"; const logger = CreateLogCtx(__filename); export async function GetNextCounterValue(counterName: string): Promise { const sequenceDoc = await db.counters.findOneAndUpdate( { counterName, }, { $inc: { value: 1, }, }, { // this is marked as deprecated, but it shouldn't be, as returnDocument: "before" // does nothing. returnOriginal: true, } ); if (!sequenceDoc) { logger.error(`Could not find sequence document for ${counterName}`); throw new Error(`Could not find sequence document for ${counterName}.`); } return sequenceDoc.value; } export async function DecrementCounterValue(counterName: string): Promise { logger.verbose(`Decrementing Counter Value ${counterName}.`); const sequenceDoc = await db.counters.findOneAndUpdate( { counterName, }, { $inc: { value: -1, }, }, { returnOriginal: false, } ); if (!sequenceDoc) { logger.error(`Could not find sequence document for ${counterName}`); throw new Error(`Could not find sequence document for ${counterName}.`); } return sequenceDoc.value; } export async function GetRelevantSongsAndCharts( scores: (ScoreDocument | PBScoreDocument)[], game: Game ) { const [songs, charts] = await Promise.all([ db.songs[game].find({ id: { $in: scores.map((e) => e.songID) }, }), db.charts[game].find({ chartID: { $in: scores.map((e) => e.chartID) }, }), ]); return { songs, charts }; } export async function UpdateGameSongIDCounter(game: "bms" | "pms") { const largestSongID = await db.songs[game].findOne( {}, { sort: { id: -1 }, projection: { id: 1 }, } ); if (!largestSongID) { logger.severe( `No ${game} charts loaded, yet BMS sync was attempted? Lost state on ${game}-song-id counter. Panicking.` ); throw new Error(`No BMS charts loaded, yet BMS sync was attempted.`); } await db.counters.update( { counterName: `${game}-song-id`, }, { $set: { value: largestSongID.id + 1, }, } ); } export async function GetChartForIDGuaranteed(game: Game, chartID: string) { const chart = await db.charts[game].findOne({ chartID }); if (!chart) { throw new Error(`Couldn't find chart with ID ${chartID} (${game}).`); } return chart; } export async function GetSongForIDGuaranteed(game: Game, songID: integer) { const song = await db.songs[game].findOne({ id: songID }); if (!song) { throw new Error(`Couldn't find song with ID ${songID} (${game}).`); } return song; } export async function GetFolderForIDGuaranteed(folderID: string) { const folder = await db.folders.findOne({ folderID }); if (!folder) { throw new Error(`Couldn't find folder with ID ${folderID}.`); } return folder; } export async function GetGoalForIDGuaranteed(goalID: string) { const goal = await db.goals.findOne({ goalID }); if (!goal) { throw new Error(`Couldn't find goal with ID ${goalID}`); } return goal; } export async function GetMilestoneForIDGuaranteed(milestoneID: string) { const milestone = await db.milestones.findOne({ milestoneID }); if (!milestone) { throw new Error(`Couldn't find milestone with ID ${milestoneID}`); } return milestone; } export async function HumaniseChartID(game: Game, chartID: string) { const chart = await GetChartForIDGuaranteed(game, chartID); const song = await GetSongForIDGuaranteed(game, chart.songID); return FormatChart(game, song, chart); } /** * Get recently achieved goals for this query. * * @param baseQuery - A base query, used to limit results on GPTs or UGPTs. * @param limit - How many recently achieved goals to search for. * @returns - The goals and their subs. */ export async function GetRecentlyAchievedGoals( baseQuery: FilterQuery, limit = 50 ) { const query = Object.assign( { wasInstantlyAchieved: false, achieved: true, }, baseQuery ); const goalSubs = await db["goal-subs"].find(query, { sort: { timeAchieved: -1, }, limit, }); const goals = await db.goals.find({ goalID: { $in: goalSubs.map((e) => e.goalID) }, }); return { goals, goalSubs }; } /** * Get recently interacted-with goals for this query. * * @param baseQuery - A base query, used to limit results on GPTs or UGPTs. * @param limit - How many recently achieved goals to search for. * @returns - The goals and their subs. */ export async function GetRecentlyInteractedGoals( baseQuery: FilterQuery, limit = 50 ) { const query = Object.assign( { lastInteraction: { $ne: null }, }, baseQuery ); const goalSubs = await db["goal-subs"].find(query, { sort: { lastInteraction: -1, }, limit, }); const goals = await db.goals.find({ goalID: { $in: goalSubs.map((e) => e.goalID) }, }); return { goals, goalSubs }; } /** * Get recently achieved goals for this query. * * @param baseQuery - A base query, used to limit results on GPTs or UGPTs. * @param limit - How many recently achieved goals to search for. * @returns - The goals and their subs. */ export async function GetRecentlyAchievedMilestones( baseQuery: FilterQuery, limit = 50 ) { const query = Object.assign( { wasInstantlyAchieved: false, achieved: true, }, baseQuery ); const milestoneSubs = await db["milestone-subs"].find(query, { sort: { timeAchieved: -1, }, limit, }); const milestones = await db.goals.find({ goalID: { $in: milestoneSubs.map((e) => e.milestoneID) }, }); return { milestones, milestoneSubs }; } /** * Get recently interacted-with milestones for this query. * * @param baseQuery - A base query, used to limit results on GPTs or UGPTs. * @param limit - How many recently achieved milestones to search for. * @returns - The milestones and their subs. */ export async function GetRecentlyInteractedMilestones( baseQuery: FilterQuery, limit = 50 ) { const query = Object.assign( { lastInteraction: { $ne: null }, }, baseQuery ); const milestoneSubs = await db["milestone-subs"].find(query, { sort: { lastInteraction: -1, }, limit, }); const milestones = await db.goals.find({ milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, }); return { milestones, milestoneSubs }; }