singsalot/functions/lib/index.js

160 lines
7.2 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.recalculateTopPlayed = exports.updateTopPlayedOnHistoryChange = void 0;
const functions = require("firebase-functions");
const admin = require("firebase-admin");
// Initialize Firebase Admin
admin.initializeApp();
// Database reference
const db = admin.database();
/**
* Cloud Function that triggers when a song is added to history
* This function aggregates all history items to create/update the topPlayed collection
* based on unique combinations of artist and title (not path)
*/
exports.updateTopPlayedOnHistoryChange = functions.database
.ref('/controllers/{controllerName}/history/{historyId}')
.onCreate(async (snapshot, context) => {
const { controllerName } = context.params;
console.log(`TopPlayed update triggered for controller: ${controllerName}`);
try {
// Get the controller reference
const controllerRef = db.ref(`/controllers/${controllerName}`);
// Get all history items for this controller
const historySnapshot = await controllerRef.child('history').once('value');
const historyData = historySnapshot.val();
if (!historyData) {
console.log('No history data found, skipping TopPlayed update');
return;
}
// Aggregate history items by artist + title combination
const aggregation = {};
Object.values(historyData).forEach((song) => {
const historySong = song;
if (historySong && historySong.artist && historySong.title) {
// Create a unique key based on artist and title (case-insensitive)
// Replace invalid Firebase key characters with underscores
const sanitizedArtist = String(historySong.artist || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_');
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 {
// Create new entry
aggregation[key] = {
artist: historySong.artist,
title: historySong.title,
count: historySong.count || 1
};
}
}
});
// Convert aggregation to array, sort by count (descending), and take top 100
const sortedSongs = Object.entries(aggregation)
.map(([key, songData]) => ({
key,
artist: songData.artist,
title: songData.title,
count: songData.count
}))
.sort((a, b) => b.count - a.count) // Sort by count descending
.slice(0, 100); // Take only top 100
// Convert back to object format for Firebase
const topPlayedData = {};
sortedSongs.forEach((song) => {
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) {
console.error('Error updating TopPlayed:', error);
throw error;
}
});
/**
* Alternative function that can be called manually to recalculate TopPlayed
* This is useful for initial setup or data migration
*/
exports.recalculateTopPlayed = functions.https.onCall(async (data, context) => {
const { controllerName } = data;
if (!controllerName) {
throw new functions.https.HttpsError('invalid-argument', 'controllerName is required');
}
console.log(`Manual TopPlayed recalculation requested for controller: ${controllerName}`);
try {
// Get the controller reference
const controllerRef = db.ref(`/controllers/${controllerName}`);
// Get all history items for this controller
const historySnapshot = await controllerRef.child('history').once('value');
const historyData = historySnapshot.val();
if (!historyData) {
console.log('No history data found, returning empty TopPlayed');
await controllerRef.child('topPlayed').set({});
return { success: true, message: 'No history data found, TopPlayed cleared' };
}
// Aggregate history items by artist + title combination
const aggregation = {};
Object.values(historyData).forEach((song) => {
const historySong = song;
if (historySong && historySong.artist && historySong.title) {
// Create a unique key based on artist and title (case-insensitive)
// Replace invalid Firebase key characters with underscores
const sanitizedArtist = String(historySong.artist || '').toLowerCase().trim().replace(/[.#$/[\]]/g, '_');
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 {
// Create new entry
aggregation[key] = {
artist: historySong.artist,
title: historySong.title,
count: historySong.count || 1
};
}
}
});
// Convert aggregation to array, sort by count (descending), and take top 100
const sortedSongs = Object.entries(aggregation)
.map(([key, songData]) => ({
key,
artist: songData.artist,
title: songData.title,
count: songData.count
}))
.sort((a, b) => b.count - a.count) // Sort by count descending
.slice(0, 100); // Take only top 100
// Convert back to object format for Firebase
const topPlayedData = {};
sortedSongs.forEach((song) => {
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 {
success: true,
message: `TopPlayed recalculated successfully`,
songCount: Object.keys(topPlayedData).length
};
}
catch (error) {
console.error('Error recalculating TopPlayed:', error);
throw new functions.https.HttpsError('internal', 'Failed to recalculate TopPlayed');
}
});
//# sourceMappingURL=index.js.map