Add persistent EPG priority ordering, source reorder validation, and priority-aware EPG matching for live channels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
62 lines
2.9 KiB
JavaScript
62 lines
2.9 KiB
JavaScript
const PRIORITY_STEP = 10;
|
|
|
|
function numericPriority(source) {
|
|
return Number.isInteger(source.epgPriority) && source.epgPriority >= 0
|
|
? source.epgPriority
|
|
: Number.MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
function sortEpgSources(list, { enabledOnly = false } = {}) {
|
|
return list
|
|
.filter(source => source.type === 'epg' && (!enabledOnly || source.enabled))
|
|
.slice()
|
|
.sort((left, right) => numericPriority(left) - numericPriority(right) || left.id - right.id);
|
|
}
|
|
|
|
function migrateEpgPriorities(database) {
|
|
const epgSources = database.sources.filter(source => source.type === 'epg');
|
|
let next = epgSources.reduce((maximum, source) => Number.isInteger(source.epgPriority)
|
|
? Math.max(maximum, source.epgPriority + PRIORITY_STEP)
|
|
: maximum, 0);
|
|
for (const source of epgSources) {
|
|
if (!Number.isInteger(source.epgPriority) || source.epgPriority < 0) {
|
|
source.epgPriority = next;
|
|
next += PRIORITY_STEP;
|
|
}
|
|
}
|
|
return database;
|
|
}
|
|
|
|
function nextEpgPriority(sources) {
|
|
return sortEpgSources(sources).reduce((maximum, source) => Math.max(maximum, numericPriority(source) + PRIORITY_STEP), 0);
|
|
}
|
|
|
|
function validateCompleteOrder(allSources, orderedIds) {
|
|
if (!Array.isArray(orderedIds)) return { code: 'INVALID_ORDER', message: 'orderedIds must be an array' };
|
|
if (orderedIds.some(id => !Number.isInteger(id) || id <= 0)) return { code: 'INVALID_ORDER_ID', message: 'orderedIds must contain positive integer IDs' };
|
|
if (new Set(orderedIds).size !== orderedIds.length) return { code: 'DUPLICATE_SOURCE_ID', message: 'orderedIds contains duplicate IDs' };
|
|
const byId = new Map(allSources.map(source => [source.id, source]));
|
|
const unknown = orderedIds.find(id => !byId.has(id));
|
|
if (unknown !== undefined) return { code: 'UNKNOWN_SOURCE_ID', message: `Unknown source ID: ${unknown}` };
|
|
const nonEpg = orderedIds.find(id => byId.get(id).type !== 'epg');
|
|
if (nonEpg !== undefined) return { code: 'SOURCE_NOT_EPG', message: `Source ${nonEpg} is not an EPG source` };
|
|
const expected = allSources.filter(source => source.type === 'epg').map(source => source.id);
|
|
if (orderedIds.length !== expected.length || expected.some(id => !orderedIds.includes(id))) {
|
|
return { code: 'INCOMPLETE_EPG_ORDER', message: 'orderedIds must contain every EPG source' };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function applyEpgOrder(allSources, orderedIds, updatedAt = new Date().toISOString()) {
|
|
const error = validateCompleteOrder(allSources, orderedIds);
|
|
if (error) throw Object.assign(new Error(error.message), error);
|
|
orderedIds.forEach((id, index) => {
|
|
const source = allSources.find(item => item.id === id);
|
|
source.epgPriority = index * PRIORITY_STEP;
|
|
source.updated_at = updatedAt;
|
|
});
|
|
return sortEpgSources(allSources);
|
|
}
|
|
|
|
module.exports = { applyEpgOrder, migrateEpgPriorities, nextEpgPriority, numericPriority, PRIORITY_STEP, sortEpgSources, validateCompleteOrder };
|