Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>

This commit is contained in:
mbrucedogs 2025-07-22 11:09:42 -05:00
parent b6aa7da1bf
commit 58669a0abc
4 changed files with 70 additions and 134 deletions

View File

@ -0,0 +1,4 @@
Invoke-WebRequest -Uri "https://us-central1-firebase-herse.cloudfunctions.net/recalculateTopPlayed" `
>> -Method POST `
>> -Headers @{"Content-Type"="application/json"} `
>> -Body '{"data": {"controllerName": "bsully5150"}}'

View File

@ -27,25 +27,20 @@ exports.updateTopPlayedOnHistoryChange = functions.database
console.log('No history data found, skipping TopPlayed update'); console.log('No history data found, skipping TopPlayed update');
return; return;
} }
// Aggregate history items by artist + title combination // Aggregate history items by path
const aggregation = {}; const aggregation = {};
Object.values(historyData).forEach((song) => { Object.values(historyData).forEach((song) => {
const historySong = song; const historySong = song;
if (historySong && historySong.artist && historySong.title) { if (historySong && historySong.path) {
// Create a unique key based on artist and title (case-insensitive) const path = historySong.path;
// Replace invalid Firebase key characters with underscores if (aggregation[path]) {
const sanitizedArtist = String(historySong.artist || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_'); aggregation[path].count += historySong.count || 1;
const sanitizedTitle = String(historySong.title || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_');
const key = `${sanitizedArtist}_${sanitizedTitle}`;
if (aggregation[key]) {
// Increment count for existing song
aggregation[key].count += historySong.count || 1;
} }
else { else {
// Create new entry aggregation[path] = {
aggregation[key] = {
artist: historySong.artist, artist: historySong.artist,
title: historySong.title, title: historySong.title,
path: historySong.path,
count: historySong.count || 1 count: historySong.count || 1
}; };
} }
@ -53,26 +48,17 @@ exports.updateTopPlayedOnHistoryChange = functions.database
}); });
// Convert aggregation to array, sort by count (descending), and take top 100 // Convert aggregation to array, sort by count (descending), and take top 100
const sortedSongs = Object.entries(aggregation) const sortedSongs = Object.entries(aggregation)
.map(([key, songData]) => ({ .map(([, songData]) => ({
key,
artist: songData.artist, artist: songData.artist,
title: songData.title, title: songData.title,
path: songData.path,
count: songData.count count: songData.count
})) }))
.sort((a, b) => b.count - a.count) // Sort by count descending .sort((a, b) => b.count - a.count)
.slice(0, 100); // Take only top 100 .slice(0, 100);
// Convert back to object format for Firebase // Write as an array so Firebase uses numeric keys
const topPlayedData = {}; await controllerRef.child('topPlayed').set(sortedSongs);
sortedSongs.forEach((song) => { console.log(`Successfully updated TopPlayed for controller ${controllerName} with ${sortedSongs.length} unique songs (by path)`);
topPlayedData[song.key] = {
artist: song.artist,
title: song.title,
count: song.count
};
});
// Update the topPlayed collection
await controllerRef.child('topPlayed').set(topPlayedData);
console.log(`Successfully updated TopPlayed for controller ${controllerName} with ${Object.keys(topPlayedData).length} unique songs`);
} }
catch (error) { catch (error) {
console.error('Error updating TopPlayed:', error); console.error('Error updating TopPlayed:', error);
@ -83,7 +69,7 @@ exports.updateTopPlayedOnHistoryChange = functions.database
* Alternative function that can be called manually to recalculate TopPlayed * Alternative function that can be called manually to recalculate TopPlayed
* This is useful for initial setup or data migration * This is useful for initial setup or data migration
*/ */
exports.recalculateTopPlayed = functions.https.onCall(async (data, context) => { exports.recalculateTopPlayed = functions.https.onCall(async (data) => {
const { controllerName } = data; const { controllerName } = data;
if (!controllerName) { if (!controllerName) {
throw new functions.https.HttpsError('invalid-argument', 'controllerName is required'); throw new functions.https.HttpsError('invalid-argument', 'controllerName is required');
@ -100,25 +86,20 @@ exports.recalculateTopPlayed = functions.https.onCall(async (data, context) => {
await controllerRef.child('topPlayed').set({}); await controllerRef.child('topPlayed').set({});
return { success: true, message: 'No history data found, TopPlayed cleared' }; return { success: true, message: 'No history data found, TopPlayed cleared' };
} }
// Aggregate history items by artist + title combination // Aggregate history items by path
const aggregation = {}; const aggregation = {};
Object.values(historyData).forEach((song) => { Object.values(historyData).forEach((song) => {
const historySong = song; const historySong = song;
if (historySong && historySong.artist && historySong.title) { if (historySong && historySong.path) {
// Create a unique key based on artist and title (case-insensitive) const path = historySong.path;
// Replace invalid Firebase key characters with underscores if (aggregation[path]) {
const sanitizedArtist = String(historySong.artist || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_'); aggregation[path].count += historySong.count || 1;
const sanitizedTitle = String(historySong.title || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_');
const key = `${sanitizedArtist}_${sanitizedTitle}`;
if (aggregation[key]) {
// Increment count for existing song
aggregation[key].count += historySong.count || 1;
} }
else { else {
// Create new entry aggregation[path] = {
aggregation[key] = {
artist: historySong.artist, artist: historySong.artist,
title: historySong.title, title: historySong.title,
path: historySong.path,
count: historySong.count || 1 count: historySong.count || 1
}; };
} }
@ -126,30 +107,21 @@ exports.recalculateTopPlayed = functions.https.onCall(async (data, context) => {
}); });
// Convert aggregation to array, sort by count (descending), and take top 100 // Convert aggregation to array, sort by count (descending), and take top 100
const sortedSongs = Object.entries(aggregation) const sortedSongs = Object.entries(aggregation)
.map(([key, songData]) => ({ .map(([, songData]) => ({
key,
artist: songData.artist, artist: songData.artist,
title: songData.title, title: songData.title,
path: songData.path,
count: songData.count count: songData.count
})) }))
.sort((a, b) => b.count - a.count) // Sort by count descending .sort((a, b) => b.count - a.count)
.slice(0, 100); // Take only top 100 .slice(0, 100);
// Convert back to object format for Firebase // Write as an array so Firebase uses numeric keys
const topPlayedData = {}; await controllerRef.child('topPlayed').set(sortedSongs);
sortedSongs.forEach((song) => { console.log(`Successfully recalculated TopPlayed for controller ${controllerName} with ${sortedSongs.length} unique songs (by path)`);
topPlayedData[song.key] = {
artist: song.artist,
title: song.title,
count: song.count
};
});
// Update the topPlayed collection
await controllerRef.child('topPlayed').set(topPlayedData);
console.log(`Successfully recalculated TopPlayed for controller ${controllerName} with ${Object.keys(topPlayedData).length} unique songs`);
return { return {
success: true, success: true,
message: `TopPlayed recalculated successfully`, message: `TopPlayed recalculated successfully`,
songCount: Object.keys(topPlayedData).length songCount: sortedSongs.length
}; };
} }
catch (error) { catch (error) {

File diff suppressed because one or more lines are too long

View File

@ -19,17 +19,11 @@ interface HistorySong {
key?: string; key?: string;
} }
interface TopPlayed { interface HistoryAggregationByPath {
artist: string; [path: string]: {
title: string;
count: number;
key?: string;
}
interface HistoryAggregation {
[key: string]: {
artist: string; artist: string;
title: string; title: string;
path: string;
count: number; count: number;
}; };
} }
@ -59,26 +53,20 @@ export const updateTopPlayedOnHistoryChange = functions.database
return; return;
} }
// Aggregate history items by artist + title combination // Aggregate history items by path
const aggregation: HistoryAggregation = {}; const aggregation: HistoryAggregationByPath = {};
Object.values(historyData).forEach((song: unknown) => { Object.values(historyData).forEach((song: unknown) => {
const historySong = song as HistorySong; const historySong = song as HistorySong;
if (historySong && historySong.artist && historySong.title) { if (historySong && historySong.path) {
// Create a unique key based on artist and title (case-insensitive) const path = historySong.path;
// Replace invalid Firebase key characters with underscores if (aggregation[path]) {
const sanitizedArtist = String(historySong.artist || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_'); aggregation[path].count += historySong.count || 1;
const sanitizedTitle = String(historySong.title || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_');
const key = `${sanitizedArtist}_${sanitizedTitle}`;
if (aggregation[key]) {
// Increment count for existing song
aggregation[key].count += historySong.count || 1;
} else { } else {
// Create new entry aggregation[path] = {
aggregation[key] = {
artist: historySong.artist, artist: historySong.artist,
title: historySong.title, title: historySong.title,
path: historySong.path,
count: historySong.count || 1 count: historySong.count || 1
}; };
} }
@ -87,30 +75,19 @@ export const updateTopPlayedOnHistoryChange = functions.database
// Convert aggregation to array, sort by count (descending), and take top 100 // Convert aggregation to array, sort by count (descending), and take top 100
const sortedSongs = Object.entries(aggregation) const sortedSongs = Object.entries(aggregation)
.map(([key, songData]) => ({ .map(([, songData]) => ({
key,
artist: songData.artist, artist: songData.artist,
title: songData.title, title: songData.title,
path: songData.path,
count: songData.count count: songData.count
})) }))
.sort((a, b) => b.count - a.count) // Sort by count descending .sort((a, b) => b.count - a.count)
.slice(0, 100); // Take only top 100 .slice(0, 100);
// Convert back to object format for Firebase // Write as an array so Firebase uses numeric keys
const topPlayedData: { [key: string]: TopPlayed } = {}; await controllerRef.child('topPlayed').set(sortedSongs);
sortedSongs.forEach((song) => { console.log(`Successfully updated TopPlayed for controller ${controllerName} with ${sortedSongs.length} unique songs (by path)`);
topPlayedData[song.key] = {
artist: song.artist,
title: song.title,
count: song.count
};
});
// Update the topPlayed collection
await controllerRef.child('topPlayed').set(topPlayedData);
console.log(`Successfully updated TopPlayed for controller ${controllerName} with ${Object.keys(topPlayedData).length} unique songs`);
} catch (error) { } catch (error) {
console.error('Error updating TopPlayed:', error); console.error('Error updating TopPlayed:', error);
@ -122,7 +99,7 @@ export const updateTopPlayedOnHistoryChange = functions.database
* Alternative function that can be called manually to recalculate TopPlayed * Alternative function that can be called manually to recalculate TopPlayed
* This is useful for initial setup or data migration * This is useful for initial setup or data migration
*/ */
export const recalculateTopPlayed = functions.https.onCall(async (data, context) => { export const recalculateTopPlayed = functions.https.onCall(async (data) => {
const { controllerName } = data; const { controllerName } = data;
if (!controllerName) { if (!controllerName) {
@ -145,26 +122,20 @@ export const recalculateTopPlayed = functions.https.onCall(async (data, context)
return { success: true, message: 'No history data found, TopPlayed cleared' }; return { success: true, message: 'No history data found, TopPlayed cleared' };
} }
// Aggregate history items by artist + title combination // Aggregate history items by path
const aggregation: HistoryAggregation = {}; const aggregation: HistoryAggregationByPath = {};
Object.values(historyData).forEach((song: unknown) => { Object.values(historyData).forEach((song: unknown) => {
const historySong = song as HistorySong; const historySong = song as HistorySong;
if (historySong && historySong.artist && historySong.title) { if (historySong && historySong.path) {
// Create a unique key based on artist and title (case-insensitive) const path = historySong.path;
// Replace invalid Firebase key characters with underscores if (aggregation[path]) {
const sanitizedArtist = String(historySong.artist || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_'); aggregation[path].count += historySong.count || 1;
const sanitizedTitle = String(historySong.title || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_');
const key = `${sanitizedArtist}_${sanitizedTitle}`;
if (aggregation[key]) {
// Increment count for existing song
aggregation[key].count += historySong.count || 1;
} else { } else {
// Create new entry aggregation[path] = {
aggregation[key] = {
artist: historySong.artist, artist: historySong.artist,
title: historySong.title, title: historySong.title,
path: historySong.path,
count: historySong.count || 1 count: historySong.count || 1
}; };
} }
@ -173,35 +144,24 @@ export const recalculateTopPlayed = functions.https.onCall(async (data, context)
// Convert aggregation to array, sort by count (descending), and take top 100 // Convert aggregation to array, sort by count (descending), and take top 100
const sortedSongs = Object.entries(aggregation) const sortedSongs = Object.entries(aggregation)
.map(([key, songData]) => ({ .map(([, songData]) => ({
key,
artist: songData.artist, artist: songData.artist,
title: songData.title, title: songData.title,
path: songData.path,
count: songData.count count: songData.count
})) }))
.sort((a, b) => b.count - a.count) // Sort by count descending .sort((a, b) => b.count - a.count)
.slice(0, 100); // Take only top 100 .slice(0, 100);
// Convert back to object format for Firebase // Write as an array so Firebase uses numeric keys
const topPlayedData: { [key: string]: TopPlayed } = {}; await controllerRef.child('topPlayed').set(sortedSongs);
sortedSongs.forEach((song) => { console.log(`Successfully recalculated TopPlayed for controller ${controllerName} with ${sortedSongs.length} unique songs (by path)`);
topPlayedData[song.key] = {
artist: song.artist,
title: song.title,
count: song.count
};
});
// Update the topPlayed collection
await controllerRef.child('topPlayed').set(topPlayedData);
console.log(`Successfully recalculated TopPlayed for controller ${controllerName} with ${Object.keys(topPlayedData).length} unique songs`);
return { return {
success: true, success: true,
message: `TopPlayed recalculated successfully`, message: `TopPlayed recalculated successfully`,
songCount: Object.keys(topPlayedData).length songCount: sortedSongs.length
}; };
} catch (error) { } catch (error) {