mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 19:14:16 +00:00
- Introduces a new Music Library system for managing favorites (tracks, playlists, releases), replacing the previous TrackLike model. - Completely revamps the Studio TV profile page, adding live statistics, stream configuration, restream management, and media URL display. - Enhances the media player with a custom seekbar and improved audio playback logic for MPD and non-MPD sources. - Lays foundational groundwork for chat encryption with new models and APIs. - Refactors critical UI components like PlaylistView and PagePanel. - Standardizes monorepo development scripts to use npm. - Updates comty.js submodule and adds various new UI components.
181 lines
5.2 KiB
JavaScript
181 lines
5.2 KiB
JavaScript
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)
|
|
}
|
|
}
|