const express = require('express'); const router = express.Router(); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const { authenticate } = require('../middleware/auth'); const { modifyLimiter, readLimiter } = require('../middleware/rateLimiter'); const { db } = require('../database/db'); const logger = require('../utils/logger'); const { validateIdParam, validateChannelUpdate, validatePagination, validateSearch } = require('../middleware/inputValidation'); // Configure multer for logo uploads const storage = multer.diskStorage({ destination: (req, file, cb) => { const uploadDir = path.join(__dirname, '../uploads/logos'); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } cb(null, uploadDir); }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, 'channel-' + uniqueSuffix + path.extname(file.originalname)); } }); const upload = multer({ storage: storage, limits: { fileSize: 5 * 1024 * 1024 }, // 5MB limit fileFilter: (req, file, cb) => { const allowedTypes = /jpeg|jpg|png|gif|svg|webp/; const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = allowedTypes.test(file.mimetype); if (mimetype && extname) { return cb(null, true); } cb(new Error('Only image files are allowed')); } }); // Get channels with filters router.get('/', authenticate, readLimiter, validatePagination, validateSearch, (req, res) => { const { playlistId, isRadio, groupName, search } = req.query; const limit = req.sanitizedQuery?.limit || 100; const offset = req.sanitizedQuery?.offset || 0; let query = ` SELECT c.*, p.name as playlist_name, COALESCE(c.custom_logo, c.logo) as logo FROM channels c JOIN playlists p ON c.playlist_id = p.id WHERE p.user_id = ? AND c.is_active = 1 `; const params = [req.user.userId]; if (playlistId) { query += ' AND c.playlist_id = ?'; params.push(playlistId); } if (isRadio !== undefined) { query += ' AND c.is_radio = ?'; params.push(isRadio === 'true' ? 1 : 0); } if (groupName) { query += ' AND c.group_name = ?'; params.push(groupName); } if (search) { query += ' AND (c.name LIKE ? OR c.group_name LIKE ?)'; params.push(`%${search}%`, `%${search}%`); } query += ' ORDER BY c.name LIMIT ? OFFSET ?'; params.push(parseInt(limit), parseInt(offset)); db.all(query, params, (err, channels) => { if (err) { return res.status(500).json({ error: 'Failed to fetch channels' }); } res.json(channels); }); }); // Get channel groups router.get('/groups', authenticate, readLimiter, (req, res) => { const { playlistId, isRadio } = req.query; // Validate playlist ID if provided if (playlistId && (isNaN(parseInt(playlistId)) || parseInt(playlistId) < 1)) { return res.status(400).json({ error: 'Invalid playlist ID' }); } let query = ` SELECT DISTINCT c.group_name, COUNT(*) as count FROM channels c JOIN playlists p ON c.playlist_id = p.id WHERE p.user_id = ? AND c.is_active = 1 `; const params = [req.user.userId]; if (playlistId) { query += ' AND c.playlist_id = ?'; params.push(playlistId); } if (isRadio !== undefined) { query += ' AND c.is_radio = ?'; params.push(isRadio === 'true' ? 1 : 0); } query += ' GROUP BY c.group_name ORDER BY c.group_name'; db.all(query, params, (err, groups) => { if (err) { return res.status(500).json({ error: 'Failed to fetch groups' }); } res.json(groups); }); }); // Upload custom logo for channel router.post('/:id/logo', authenticate, modifyLimiter, validateIdParam, upload.single('logo'), (req, res) => { const channelId = req.params.id; const userId = req.user.userId; if (!req.file) { return res.status(400).json({ error: 'No file uploaded' }); } // Verify channel belongs to user db.get( `SELECT c.*, c.custom_logo as old_logo FROM channels c JOIN playlists p ON c.playlist_id = p.id WHERE c.id = ? AND p.user_id = ?`, [channelId, userId], (err, channel) => { if (err) { return res.status(500).json({ error: 'Database error' }); } if (!channel) { // Delete uploaded file if channel not found fs.unlinkSync(req.file.path); return res.status(404).json({ error: 'Channel not found' }); } // Delete old custom logo if exists if (channel.old_logo) { const oldLogoPath = path.join(__dirname, '..', channel.old_logo); if (fs.existsSync(oldLogoPath)) { fs.unlinkSync(oldLogoPath); } } // Save new logo path const logoPath = `/uploads/logos/${req.file.filename}`; db.run( 'UPDATE channels SET custom_logo = ? WHERE id = ?', [logoPath, channelId], (err) => { if (err) { return res.status(500).json({ error: 'Failed to update logo' }); } res.json({ message: 'Logo uploaded successfully', logoUrl: logoPath }); } ); } ); }); // Delete custom logo for a channel router.delete('/:id/logo', authenticate, modifyLimiter, (req, res) => { const channelId = req.params.id; const userId = req.user.userId; // Verify channel belongs to user and get current logo db.get( `SELECT c.*, c.custom_logo FROM channels c JOIN playlists p ON c.playlist_id = p.id WHERE c.id = ? AND p.user_id = ?`, [channelId, userId], (err, channel) => { if (err) { return res.status(500).json({ error: 'Database error' }); } if (!channel) { return res.status(404).json({ error: 'Channel not found' }); } if (!channel.custom_logo) { return res.status(400).json({ error: 'No custom logo to delete' }); } // Delete file from filesystem const logoPath = path.join(__dirname, '..', channel.custom_logo); if (fs.existsSync(logoPath)) { fs.unlinkSync(logoPath); } // Remove logo from database db.run( 'UPDATE channels SET custom_logo = NULL WHERE id = ?', [channelId], (err) => { if (err) { return res.status(500).json({ error: 'Failed to delete logo' }); } res.json({ message: 'Logo deleted successfully' }); } ); } ); }); // Get single channel by ID router.get('/:id', authenticate, readLimiter, (req, res) => { const channelId = req.params.id; const userId = req.user.userId; db.get( `SELECT c.*, p.name as playlist_name, COALESCE(c.custom_logo, c.logo) as logo FROM channels c JOIN playlists p ON c.playlist_id = p.id WHERE c.id = ? AND p.user_id = ?`, [channelId, userId], (err, channel) => { if (err) { return res.status(500).json({ error: 'Database error' }); } if (!channel) { return res.status(404).json({ error: 'Channel not found' }); } res.json(channel); } ); }); // Delete channel from playlist router.delete('/:id', authenticate, modifyLimiter, validateIdParam, (req, res) => { const channelId = req.params.id; const userId = req.user.userId; // Verify channel belongs to user db.get( `SELECT c.id, c.playlist_id, c.custom_logo FROM channels c JOIN playlists p ON c.playlist_id = p.id WHERE c.id = ? AND p.user_id = ?`, [channelId, userId], (err, channel) => { if (err) { logger.error('Error fetching channel for deletion:', err); return res.status(500).json({ error: 'Database error' }); } if (!channel) { return res.status(404).json({ error: 'Channel not found' }); } // Delete custom logo file if exists if (channel.custom_logo) { const logoPath = path.join(__dirname, '..', channel.custom_logo); if (fs.existsSync(logoPath)) { try { fs.unlinkSync(logoPath); } catch (err) { logger.error('Error deleting custom logo file:', err); } } } // Delete channel from database db.run('DELETE FROM channels WHERE id = ?', [channelId], function(err) { if (err) { logger.error('Error deleting channel:', err); return res.status(500).json({ error: 'Failed to delete channel' }); } // Update playlist channel count db.run( 'UPDATE playlists SET channel_count = (SELECT COUNT(*) FROM channels WHERE playlist_id = ?) WHERE id = ?', [channel.playlist_id, channel.playlist_id], (updateErr) => { if (updateErr) { logger.error('Error updating playlist count:', updateErr); } } ); logger.info(`Channel ${channelId} deleted by user ${userId}`); res.json({ message: 'Channel deleted successfully', deletedId: channelId }); }); } ); }); module.exports = router;