// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Pure filter / sort / search utilities for Ombi requests. * Must stay in sync with client/src/utils/ombiFilters.js */ /** * Derive a single status string from an Ombi request object. * Priority: available > denied > approved > pending > unknown * * @param {Object} request * @returns {string} 'available' | 'denied' | 'approved' | 'pending' | 'unknown' */ function getRequestStatus(request) { if (!request) return 'unknown'; if (request.available) return 'available'; if (request.denied) return 'denied'; if (request.approved) return 'approved'; if (request.requested) return 'pending'; // Ombi TV requests store status flags inside childRequests if (Array.isArray(request.childRequests) && request.childRequests.length > 0) { for (const child of request.childRequests) { if (child && child.available) return 'available'; } for (const child of request.childRequests) { if (child && child.denied) return 'denied'; } for (const child of request.childRequests) { if (child && child.approved) return 'approved'; } for (const child of request.childRequests) { if (child && child.requested) return 'pending'; } } return 'unknown'; } /** * Filter requests by media type. * * @param {Array} requests * @param {string[]} types - e.g. ['movie', 'tv'] or ['all'] * @returns {Array} */ function filterByType(requests, types) { if (!types || types.length === 0) return requests; const normalized = types.map(t => t.toLowerCase()); if (normalized.includes('all')) return requests; return requests.filter(r => normalized.includes(r.mediaType)); } /** * Filter requests by status. * * @param {Array} requests * @param {string[]} statuses - e.g. ['pending', 'approved', 'available', 'denied'] * @returns {Array} */ function filterByStatus(requests, statuses) { if (!statuses || statuses.length === 0) return requests; const normalized = statuses.map(s => s.toLowerCase()); return requests.filter(r => normalized.includes(getRequestStatus(r))); } /** * Filter requests by case-insensitive title substring. * * @param {Array} requests * @param {string} query * @returns {Array} */ function filterBySearch(requests, query) { if (!query || query.trim() === '') return requests; const q = query.trim().toLowerCase(); return requests.filter(r => (r.title || '').toLowerCase().includes(q)); } /** * Sort requests by the given sort mode. * * @param {Array} requests * @param {string} sortMode - requestedDate_desc | requestedDate_asc | title_asc | title_desc * @returns {Array} new sorted array */ function sortRequests(requests, sortMode) { const sorted = [...requests]; switch (sortMode) { case 'requestedDate_asc': return sorted.sort((a, b) => { const da = a.requestedDate ? new Date(a.requestedDate).getTime() : 0; const db = b.requestedDate ? new Date(b.requestedDate).getTime() : 0; return da - db; }); case 'title_asc': return sorted.sort((a, b) => (a.title || '').localeCompare(b.title || '')); case 'title_desc': return sorted.sort((a, b) => (b.title || '').localeCompare(a.title || '')); case 'requestedDate_desc': default: return sorted.sort((a, b) => { const da = a.requestedDate ? new Date(a.requestedDate).getTime() : 0; const db = b.requestedDate ? new Date(b.requestedDate).getTime() : 0; return db - da; }); } } /** * Apply all filters and sorting in one call. * * @param {Array} requests * @param {Object} options * @param {string[]} options.types * @param {string[]} options.statuses * @param {string} options.sort * @param {string} options.search * @returns {Array} */ function applyRequestFilters(requests, { types, statuses, sort, search } = {}) { let result = [...requests]; result = filterByType(result, types); result = filterByStatus(result, statuses); result = filterBySearch(result, search); result = sortRequests(result, sort); return result; } module.exports = { getRequestStatus, filterByType, filterByStatus, filterBySearch, sortRequests, applyRequestFilters };