Files
whats-the-point/backend/src/routes/predictions.js
2026-01-29 02:16:24 -05:00

144 lines
4.1 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';
import { socketEvents } from '../sockets/index.js';
const router = express.Router();
// Get all predictions for a challenge
router.get('/challenge/:challengeId', authMiddleware, asyncHandler(async (req, res) => {
const { challengeId } = req.params;
// Verify access
const access = await query(
`SELECT * FROM challenges WHERE id = ? AND (created_by = ? OR id IN (
SELECT challenge_id FROM challenge_participants WHERE user_id = ? AND status = 'accepted'
))`,
[challengeId, req.user.userId, req.user.userId]
);
if (access.length === 0) {
throw new AppError('Access denied', 403);
}
// Get predictions
const predictions = await query(
`SELECT
p.*,
u.username,
v.username as validated_by_username
FROM predictions p
INNER JOIN users u ON p.user_id = u.id
LEFT JOIN users v ON p.validated_by = v.id
WHERE p.challenge_id = ?
ORDER BY p.created_at DESC`,
[challengeId]
);
res.json({ predictions });
}));
// Create a new prediction
router.post('/', authMiddleware, asyncHandler(async (req, res) => {
const { challenge_id, content } = req.body;
if (!challenge_id || !content || !content.trim()) {
throw new AppError('Challenge ID and content are required', 400);
}
// Verify access
const access = await query(
`SELECT * FROM challenges WHERE id = ? AND (created_by = ? OR id IN (
SELECT challenge_id FROM challenge_participants WHERE user_id = ? AND status = 'accepted'
))`,
[challenge_id, req.user.userId, req.user.userId]
);
if (access.length === 0) {
throw new AppError('Access denied', 403);
}
const result = await query(
'INSERT INTO predictions (challenge_id, user_id, content) VALUES (?, ?, ?)',
[challenge_id, req.user.userId, content.trim()]
);
const prediction = {
id: result.insertId,
challenge_id,
user_id: req.user.userId,
username: req.user.username,
content: content.trim(),
status: 'pending',
created_at: new Date()
};
// Emit real-time event to all users in the challenge
socketEvents.predictionCreated(challenge_id, prediction);
res.json({ prediction });
}));
// Validate/invalidate a prediction (approve someone else's)
router.post('/:id/validate', authMiddleware, asyncHandler(async (req, res) => {
const predictionId = req.params.id;
const { status } = req.body; // 'validated' or 'invalidated'
if (!['validated', 'invalidated'].includes(status)) {
throw new AppError('Invalid status', 400);
}
// Get the prediction
const predictions = await query(
'SELECT * FROM predictions WHERE id = ?',
[predictionId]
);
if (predictions.length === 0) {
throw new AppError('Prediction not found', 404);
}
const prediction = predictions[0];
// Cannot validate own prediction
if (prediction.user_id === req.user.userId) {
throw new AppError('Cannot validate your own prediction', 403);
}
// Verify access to the challenge
const access = await query(
`SELECT * FROM challenges WHERE id = ? AND (created_by = ? OR id IN (
SELECT challenge_id FROM challenge_participants WHERE user_id = ? AND status = 'accepted'
))`,
[prediction.challenge_id, req.user.userId, req.user.userId]
);
if (access.length === 0) {
throw new AppError('Access denied', 403);
}
// Update prediction
await query(
'UPDATE predictions SET status = ?, validated_by = ?, validated_at = NOW() WHERE id = ?',
[status, req.user.userId, predictionId]
);
// Get updated prediction with usernames
const updatedPrediction = await query(
`SELECT p.*, u.username, v.username as validated_by_username
FROM predictions p
INNER JOIN users u ON p.user_id = u.id
LEFT JOIN users v ON p.validated_by = v.id
WHERE p.id = ?`,
[predictionId]
);
// Emit real-time event to all users in the challenge
socketEvents.predictionValidated(prediction.challenge_id, updatedPrediction[0]);
res.json({ status });
}));
export default router;