import express from 'express'; import { query } from '../db/index.js'; import { authMiddleware } from '../middleware/auth.js'; import { asyncHandler, AppError } from '../middleware/errorHandler.js'; const router = express.Router(); // Get all challenges for the current user router.get('/', authMiddleware, asyncHandler(async (req, res) => { const challenges = await query( `SELECT c.*, u.username as creator_username, cp.status as participation_status, (SELECT COUNT(*) FROM predictions WHERE challenge_id = c.id AND status = 'validated' AND user_id = ?) as my_points FROM challenges c INNER JOIN users u ON c.created_by = u.id LEFT JOIN challenge_participants cp ON cp.challenge_id = c.id AND cp.user_id = ? WHERE c.created_by = ? OR cp.user_id IS NOT NULL ORDER BY c.created_at DESC`, [req.user.userId, req.user.userId, req.user.userId] ); res.json({ challenges }); })); // Get a single challenge with details router.get('/:id', authMiddleware, asyncHandler(async (req, res) => { const challengeId = req.params.id; // Get challenge details const challenges = await query( `SELECT c.*, u.username as creator_username FROM challenges c INNER JOIN users u ON c.created_by = u.id WHERE c.id = ?`, [challengeId] ); if (challenges.length === 0) { throw new AppError('Challenge not found', 404); } const challenge = challenges[0]; // Check if user has access const access = await query( `SELECT * FROM challenge_participants WHERE challenge_id = ? AND user_id = ? AND status = 'accepted'`, [challengeId, req.user.userId] ); if (challenge.created_by !== req.user.userId && access.length === 0) { throw new AppError('Access denied', 403); } // Get participants with their points const participants = await query( `SELECT u.id, u.username, u.email, cp.status, (SELECT COUNT(*) FROM predictions WHERE challenge_id = ? AND user_id = u.id AND status = 'validated') as points FROM challenge_participants cp INNER JOIN users u ON cp.user_id = u.id WHERE cp.challenge_id = ? ORDER BY points DESC`, [challengeId, challengeId] ); // Get creator's points const creatorPoints = await query( `SELECT COUNT(*) as points FROM predictions WHERE challenge_id = ? AND user_id = ? AND status = 'validated'`, [challengeId, challenge.created_by] ); res.json({ challenge, participants, creator_points: creatorPoints[0].points }); })); // Create a new challenge router.post('/', authMiddleware, asyncHandler(async (req, res) => { const { title, cover_image_url, tmdb_id, media_type } = req.body; if (!title) { throw new AppError('Title is required', 400); } const result = await query( 'INSERT INTO challenges (title, cover_image_url, tmdb_id, media_type, created_by) VALUES (?, ?, ?, ?, ?)', [title, cover_image_url || null, tmdb_id || null, media_type || 'movie', req.user.userId] ); const challenge = { id: result.insertId, title, cover_image_url, tmdb_id, media_type, created_by: req.user.userId, creator_username: req.user.username }; res.json({ challenge }); })); // Invite users to a challenge router.post('/:id/invite', authMiddleware, asyncHandler(async (req, res) => { const challengeId = req.params.id; const { user_ids, emails } = req.body; // Verify user owns the challenge or is a participant const challenges = await query( 'SELECT * FROM challenges WHERE id = ?', [challengeId] ); if (challenges.length === 0) { throw new AppError('Challenge not found', 404); } const challenge = challenges[0]; if (challenge.created_by !== req.user.userId) { // Check if user is an accepted participant const participation = await query( 'SELECT * FROM challenge_participants WHERE challenge_id = ? AND user_id = ? AND status = "accepted"', [challengeId, req.user.userId] ); if (participation.length === 0) { throw new AppError('Only challenge participants can invite others', 403); } } const invitedUsers = []; // Invite by user IDs if (user_ids && Array.isArray(user_ids)) { for (const userId of user_ids) { try { await query( 'INSERT INTO challenge_participants (challenge_id, user_id, status) VALUES (?, ?, "pending")', [challengeId, userId] ); invitedUsers.push(userId); } catch (err) { // Ignore duplicate key errors } } } // Invite by emails if (emails && Array.isArray(emails)) { for (const email of emails) { const users = await query('SELECT id FROM users WHERE email = ?', [email]); if (users.length > 0) { try { await query( 'INSERT INTO challenge_participants (challenge_id, user_id, status) VALUES (?, ?, "pending")', [challengeId, users[0].id] ); invitedUsers.push(users[0].id); } catch (err) { // Ignore duplicate key errors } } } } res.json({ invited: invitedUsers.length }); })); // Accept/reject challenge invitation router.post('/:id/respond', authMiddleware, asyncHandler(async (req, res) => { const challengeId = req.params.id; const { status } = req.body; // 'accepted' or 'rejected' if (!['accepted', 'rejected'].includes(status)) { throw new AppError('Invalid status', 400); } await query( 'UPDATE challenge_participants SET status = ?, responded_at = NOW() WHERE challenge_id = ? AND user_id = ?', [status, challengeId, req.user.userId] ); res.json({ status }); })); export default router;