import { MusicLibraryItem } from "@db_models" import Library from ".." async function fetchSingleKindData(userId, kind, limit, offsetStr) { const Model = Library.kindToModel[kind] const parsedOffset = parseInt(offsetStr, 10) // this should be redundant if the initial check in `fn` was already done, // but its a good safeguard. if (!Model) { console.warn(`Model not found for kind: ${kind} in fetchSingleKindData`) return { items: [], total_items: 0, offset: parsedOffset } } const query = { user_id: userId, kind: kind } const libraryItems = await MusicLibraryItem.find(query) .limit(limit) .skip(parsedOffset) .sort({ created_at: -1 }) .lean() if (libraryItems.length === 0) { // we get total_items even if the current page is empty, // as there might be items on other pages. const total_items = await MusicLibraryItem.countDocuments(query) return { items: [], total_items: total_items, offset: parsedOffset } } const total_items = await MusicLibraryItem.countDocuments(query) const itemIds = libraryItems.map((item) => item.item_id) const actualItems = await Model.find({ _id: { $in: itemIds } }).lean() const actualItemsMap = new Map( actualItems.map((item) => [item._id.toString(), item]), ) const enrichedItems = libraryItems .map((libraryItem) => { const actualItem = actualItemsMap.get( libraryItem.item_id.toString(), ) if (actualItem) { return { ...actualItem, liked: true, liked_at: libraryItem.created_at, library_item_id: libraryItem._id, } } console.warn( `Actual item not found for kind ${kind} with ID ${libraryItem.item_id}`, ) return null }) .filter((item) => item !== null) return { items: enrichedItems, total_items: total_items, offset: parsedOffset, } } async function fetchAllKindsData(userId, limit, offsetStr) { const parsedOffset = parseInt(offsetStr, 10) const baseQuery = { user_id: userId } // initialize the result structure for all kinds const resultForAllKinds = {} for (const kindName in Library.kindToModel) { resultForAllKinds[kindName] = { items: [], total_items: 0, offset: parsedOffset, } } // get the paginated MusicLibraryItems const paginatedLibraryItems = await MusicLibraryItem.find(baseQuery) .limit(limit) .skip(parsedOffset) .sort({ created_at: -1 }) .lean() // group MusicLibraryItems and collect item_ids by kind const libraryItemsGroupedByKind = {} // contain MusicLibraryItem objects const itemIdsToFetchByKind = {} // contain arrays of item_id for (const kindName in Library.kindToModel) { libraryItemsGroupedByKind[kindName] = [] itemIdsToFetchByKind[kindName] = [] } paginatedLibraryItems.forEach((libItem) => { if ( Library.kindToModel[libItem.kind] && libraryItemsGroupedByKind[libItem.kind] ) { libraryItemsGroupedByKind[libItem.kind].push(libItem) itemIdsToFetchByKind[libItem.kind].push(libItem.item_id) } else { console.warn(`Unknown or unhandled kind found: ${libItem.kind}`) } }) // fetch the actual item data for each kind in parallel const detailFetchPromises = Object.keys(itemIdsToFetchByKind).map( async (currentKind) => { const itemIds = itemIdsToFetchByKind[currentKind] if (itemIds.length === 0) { return // no items of this kind on the current page } const Model = Library.kindToModel[currentKind] // the check for Library.kindToModel[currentKind] was already done when populating itemIdsToFetchByKind // so Model should be defined here if itemIds.length > 0. const actualItems = await Model.find({ _id: { $in: itemIds }, }).lean() const actualItemsMap = new Map( actualItems.map((item) => [item._id.toString(), item]), ) // enrich items for this kind and add to the final result structure resultForAllKinds[currentKind].items = libraryItemsGroupedByKind[ currentKind ] .map((libraryItem) => { const actualItem = actualItemsMap.get( libraryItem.item_id.toString(), ) if (actualItem) { return { ...actualItem, liked: true, liked_at: libraryItem.created_at, library_item_id: libraryItem._id, } } console.warn( `Actual item not found for kind ${currentKind} with ID ${libraryItem.item_id} in fetchAllKindsData`, ) return null }) .filter((item) => item !== null) }, ) // fetch total counts for all kinds for the user in parallel const totalCountsPromise = MusicLibraryItem.aggregate([ { $match: baseQuery }, { $group: { _id: "$kind", count: { $sum: 1 } } }, ]).exec() // wait for all detail fetches and the count aggregation await Promise.all([...detailFetchPromises, totalCountsPromise]) // populate total_items from the resolved count aggregation const totalCountsResult = await totalCountsPromise totalCountsResult.forEach((countEntry) => { if (resultForAllKinds[countEntry._id]) { resultForAllKinds[countEntry._id].total_items = countEntry.count } }) return resultForAllKinds } export default async ({ user_id, kind, limit = 100, offset = 0 } = {}) => { if (typeof kind === "string" && Library.kindToModel[kind]) { return await fetchSingleKindData(user_id, kind, limit, offset) } else { return await fetchAllKindsData(user_id, limit, offset) } }