195 lines
5.7 KiB
JavaScript
195 lines
5.7 KiB
JavaScript
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;
|