setup features

This commit is contained in:
2026-01-29 00:24:10 -05:00
parent 787c97a52f
commit 4a6e2c307c
34 changed files with 2891 additions and 71 deletions
+218
View File
@@ -0,0 +1,218 @@
import express from 'express';
import { query } from '../db/index.js';
import { authMiddleware } from '../middleware/auth.js';
const router = express.Router();
// Get all challenges for the current user
router.get('/', authMiddleware, async (req, res) => {
try {
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 = ? AND cp.status = 'accepted')
ORDER BY c.created_at DESC`,
[req.user.userId, req.user.userId, req.user.userId, req.user.userId]
);
res.json({ challenges });
} catch (error) {
console.error('Get challenges error:', error);
res.status(500).json({ error: 'Failed to fetch challenges' });
}
});
// Get a single challenge with details
router.get('/:id', authMiddleware, async (req, res) => {
try {
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) {
return res.status(404).json({ error: 'Challenge not found' });
}
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) {
return res.status(403).json({ error: 'Access denied' });
}
// 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
});
} catch (error) {
console.error('Get challenge error:', error);
res.status(500).json({ error: 'Failed to fetch challenge' });
}
});
// Create a new challenge
router.post('/', authMiddleware, async (req, res) => {
try {
const { title, cover_image_url, tmdb_id, media_type } = req.body;
if (!title) {
return res.status(400).json({ error: 'Title is required' });
}
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 });
} catch (error) {
console.error('Create challenge error:', error);
res.status(500).json({ error: 'Failed to create challenge' });
}
});
// Invite users to a challenge
router.post('/:id/invite', authMiddleware, async (req, res) => {
try {
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) {
return res.status(404).json({ error: 'Challenge not found' });
}
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) {
return res.status(403).json({ error: 'Only challenge participants can invite others' });
}
}
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 });
} catch (error) {
console.error('Invite error:', error);
res.status(500).json({ error: 'Failed to send invites' });
}
});
// Accept/reject challenge invitation
router.post('/:id/respond', authMiddleware, async (req, res) => {
try {
const challengeId = req.params.id;
const { status } = req.body; // 'accepted' or 'rejected'
if (!['accepted', 'rejected'].includes(status)) {
return res.status(400).json({ error: 'Invalid status' });
}
await query(
'UPDATE challenge_participants SET status = ?, responded_at = NOW() WHERE challenge_id = ? AND user_id = ?',
[status, challengeId, req.user.userId]
);
res.json({ status });
} catch (error) {
console.error('Respond error:', error);
res.status(500).json({ error: 'Failed to respond to invitation' });
}
});
export default router;