streamflow/backend/routes/search.js
2025-12-17 00:42:43 +00:00

139 lines
4.5 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { db } = require('../database/db');
const { authenticate, requireAdmin } = require('../middleware/auth');
const { readLimiter } = require('../middleware/rateLimiter');
const { sanitizeString } = require('../utils/inputValidator');
const logger = require('../utils/logger');
/**
* Global search endpoint
* Searches across channels, radio stations, users, settings, etc.
*/
router.get('/', authenticate, readLimiter, async (req, res) => {
try {
const { q } = req.query;
const isAdmin = req.user.role === 'admin';
if (!q || q.trim().length < 2) {
return res.json({
channels: [],
radio: [],
users: [],
settings: [],
groups: []
});
}
// Validate and sanitize search query
const sanitized = sanitizeString(q.trim());
if (sanitized.length > 100) {
return res.status(400).json({ error: 'Search query too long' });
}
const searchTerm = `%${sanitized}%`;
const results = {
channels: [],
radio: [],
users: [],
settings: [],
groups: []
};
// Search TV channels (only from user's playlists)
results.channels = await new Promise((resolve, reject) => {
db.all(
`SELECT DISTINCT c.id, c.name, c.url, COALESCE(c.custom_logo, c.logo) as logo, c.group_name, c.is_radio
FROM channels c
JOIN playlists p ON c.playlist_id = p.id
WHERE p.user_id = ? AND c.is_radio = 0 AND c.is_active = 1
AND (c.name LIKE ? OR c.group_name LIKE ?)
ORDER BY c.name
LIMIT 20`,
[req.user.userId, searchTerm, searchTerm],
(err, rows) => {
if (err) reject(err);
else resolve(rows || []);
}
);
});
// Search Radio channels (only from user's playlists)
results.radio = await new Promise((resolve, reject) => {
db.all(
`SELECT DISTINCT c.id, c.name, c.url, COALESCE(c.custom_logo, c.logo) as logo, c.group_name, c.is_radio
FROM channels c
JOIN playlists p ON c.playlist_id = p.id
WHERE p.user_id = ? AND c.is_radio = 1 AND c.is_active = 1
AND (c.name LIKE ? OR c.group_name LIKE ?)
ORDER BY c.name
LIMIT 20`,
[req.user.userId, searchTerm, searchTerm],
(err, rows) => {
if (err) reject(err);
else resolve(rows || []);
}
);
});
// Search groups (only from user's playlists)
results.groups = await new Promise((resolve, reject) => {
db.all(
`SELECT DISTINCT c.group_name as name, c.is_radio
FROM channels c
JOIN playlists p ON c.playlist_id = p.id
WHERE p.user_id = ? AND c.is_active = 1
AND c.group_name LIKE ?
ORDER BY c.group_name
LIMIT 10`,
[req.user.userId, searchTerm],
(err, rows) => {
if (err) reject(err);
else resolve(rows || []);
}
);
});
// Search users (admin only)
if (isAdmin) {
results.users = await new Promise((resolve, reject) => {
db.all(
`SELECT id, username, email, role, created_at
FROM users
WHERE username LIKE ? OR email LIKE ?
ORDER BY username
LIMIT 10`,
[searchTerm, searchTerm],
(err, rows) => {
if (err) reject(err);
else resolve(rows || []);
}
);
});
}
// Add settings/pages results (static)
const settingsOptions = [
{ id: 'settings', name: 'Settings', path: '/settings', icon: 'settings' },
{ id: 'user-management', name: 'User Management', path: '/settings?tab=users', icon: 'people' },
{ id: 'vpn-settings', name: 'VPN Settings', path: '/settings?tab=vpn', icon: 'vpn_lock' },
{ id: '2fa', name: 'Two-Factor Authentication', path: '/settings?tab=2fa', icon: 'security' },
{ id: 'live-tv', name: 'Live TV', path: '/live', icon: 'tv' },
{ id: 'radio', name: 'Radio', path: '/radio', icon: 'radio' },
{ id: 'movies', name: 'Movies', path: '/movies', icon: 'movie' },
{ id: 'series', name: 'Series', path: '/series', icon: 'subscriptions' },
{ id: 'favorites', name: 'Favorites', path: '/favorites', icon: 'favorite' },
];
results.settings = settingsOptions.filter(option =>
option.name.toLowerCase().includes(q.toLowerCase())
);
res.json(results);
} catch (error) {
console.error('Search error:', error);
res.status(500).json({ error: 'Search failed' });
}
});
module.exports = router;