270 lines
7.6 KiB
JavaScript
270 lines
7.6 KiB
JavaScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import session from 'express-session';
|
|
import passport from 'passport';
|
|
import { Strategy as SteamStrategy } from 'passport-steam';
|
|
import dotenv from 'dotenv';
|
|
import { promises as fs } from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
dotenv.config();
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3001;
|
|
const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
const VOTES_FILE = path.join(process.cwd(), 'votes.json');
|
|
|
|
// Poll ends at 11:59 PM Eastern Time on 8/21/25
|
|
const POLL_END_DATE = new Date("2025-08-21T23:59:59-04:00");
|
|
|
|
function isPollEnded() {
|
|
return new Date() > POLL_END_DATE;
|
|
}
|
|
|
|
|
|
// Helper functions for vote storage
|
|
async function loadVotes() {
|
|
try {
|
|
const data = await fs.readFile(VOTES_FILE, 'utf8');
|
|
return JSON.parse(data);
|
|
} catch (error) {
|
|
// File doesn't exist or is empty, return empty object
|
|
return {};
|
|
}
|
|
}
|
|
|
|
async function saveVotes(votes) {
|
|
await fs.writeFile(VOTES_FILE, JSON.stringify(votes, null, 2));
|
|
}
|
|
|
|
// Middleware
|
|
app.use(cors({
|
|
origin: process.env.NODE_ENV === 'production'
|
|
? [process.env.FRONTEND_URL, process.env.DOMAIN]
|
|
: FRONTEND_URL,
|
|
credentials: true
|
|
}));
|
|
|
|
app.use(express.json());
|
|
app.use(session({
|
|
secret: process.env.SESSION_SECRET || 'your-secret-key-change-this',
|
|
resave: true,
|
|
saveUninitialized: false,
|
|
rolling: true,
|
|
cookie: {
|
|
secure: process.env.NODE_ENV === 'production',
|
|
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
httpOnly: true,
|
|
sameSite: process.env.NODE_ENV === 'production' ? 'lax' : 'lax',
|
|
domain: process.env.NODE_ENV === 'production' ? '.ethanf.gg' : undefined
|
|
},
|
|
name: 's22poll.sid'
|
|
}));
|
|
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
|
|
// Serve static files from React build in production
|
|
if (process.env.NODE_ENV === 'production') {
|
|
app.use(express.static(path.join(__dirname, '../dist')));
|
|
}
|
|
|
|
// Passport Steam Strategy
|
|
passport.use(new SteamStrategy({
|
|
returnURL: process.env.NODE_ENV === 'production'
|
|
? `${process.env.DOMAIN}/auth/steam/return`
|
|
: 'http://localhost:3001/auth/steam/return',
|
|
realm: process.env.NODE_ENV === 'production'
|
|
? process.env.DOMAIN
|
|
: 'http://localhost:3001/',
|
|
apiKey: process.env.STEAM_API_KEY
|
|
},
|
|
function(identifier, profile, done) {
|
|
const user = {
|
|
steamId: profile.id,
|
|
displayName: profile.displayName,
|
|
avatar: profile.photos[0].value,
|
|
profileUrl: profile._json.profileurl
|
|
};
|
|
return done(null, user);
|
|
}
|
|
));
|
|
|
|
passport.serializeUser((user, done) => {
|
|
done(null, user);
|
|
});
|
|
|
|
passport.deserializeUser((user, done) => {
|
|
done(null, user);
|
|
});
|
|
|
|
// Routes
|
|
app.get('/auth/steam', passport.authenticate('steam'));
|
|
|
|
app.get('/auth/steam/return',
|
|
passport.authenticate('steam', { failureRedirect: '/' }),
|
|
(req, res) => {
|
|
console.log('=== Steam Auth Callback ===');
|
|
console.log('Session ID after auth:', req.sessionID);
|
|
console.log('User after auth:', req.user);
|
|
console.log('Is authenticated after auth:', req.isAuthenticated());
|
|
|
|
// Force session save before redirect
|
|
req.session.save((err) => {
|
|
if (err) {
|
|
console.error('Session save error:', err);
|
|
return res.redirect('/?auth=error');
|
|
}
|
|
|
|
console.log('Session saved successfully');
|
|
console.log('Session after save:', req.session);
|
|
|
|
// Redirect to frontend with a small delay to ensure session is saved
|
|
setTimeout(() => {
|
|
res.redirect('/?auth=success');
|
|
}, 100);
|
|
});
|
|
}
|
|
);
|
|
|
|
app.get('/auth/logout', (req, res) => {
|
|
req.logout((err) => {
|
|
if (err) {
|
|
return res.status(500).json({ error: 'Logout failed' });
|
|
}
|
|
res.json({ message: 'Logged out successfully' });
|
|
});
|
|
});
|
|
|
|
app.get('/auth/user', (req, res) => {
|
|
console.log('=== /auth/user endpoint ===');
|
|
console.log('Session ID:', req.sessionID);
|
|
console.log('Session:', req.session);
|
|
console.log('User:', req.user);
|
|
console.log('Is authenticated:', req.isAuthenticated());
|
|
console.log('========================');
|
|
|
|
if (req.isAuthenticated()) {
|
|
res.json({ user: req.user });
|
|
} else {
|
|
res.json({ user: null });
|
|
}
|
|
});
|
|
|
|
// Vote submission endpoint
|
|
app.post('/api/submit-vote', async (req, res) => {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: 'Must be logged in to vote' });
|
|
}
|
|
|
|
// Check if poll has ended
|
|
if (isPollEnded()) {
|
|
return res.status(400).json({
|
|
error: 'The poll has ended',
|
|
pollEndDate: POLL_END_DATE.toISOString()
|
|
});
|
|
}
|
|
|
|
const { vote } = req.body;
|
|
const user = req.user;
|
|
|
|
try {
|
|
// Load existing votes
|
|
const votes = await loadVotes();
|
|
|
|
// Check if user has already voted
|
|
if (votes[user.steamId]) {
|
|
return res.status(400).json({ error: 'You have already submitted a vote' });
|
|
}
|
|
|
|
// Save the vote
|
|
votes[user.steamId] = {
|
|
steamId: user.steamId,
|
|
displayName: user.displayName,
|
|
vote: vote,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
await saveVotes(votes);
|
|
|
|
console.log(`Vote submitted by ${user.displayName} (${user.steamId}):`, vote);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Vote submitted successfully',
|
|
user: user.displayName
|
|
});
|
|
} catch (error) {
|
|
console.error('Error saving vote:', error);
|
|
res.status(500).json({ error: 'Failed to save vote' });
|
|
}
|
|
});
|
|
|
|
// Check if user has already voted
|
|
app.get('/api/vote-status', async (req, res) => {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: 'Not authenticated' });
|
|
}
|
|
|
|
try {
|
|
const votes = await loadVotes();
|
|
const userVote = votes[req.user.steamId];
|
|
|
|
res.json({
|
|
hasVoted: !!userVote,
|
|
vote: userVote ? userVote.vote : null,
|
|
timestamp: userVote ? userVote.timestamp : null,
|
|
isPollEnded: isPollEnded(),
|
|
pollEndDate: POLL_END_DATE.toISOString()
|
|
});
|
|
} catch (error) {
|
|
console.error('Error checking vote status:', error);
|
|
res.status(500).json({ error: 'Failed to check vote status' });
|
|
}
|
|
});
|
|
|
|
// Get vote results (admin endpoint - you might want to add authentication)
|
|
app.get('/api/results', async (req, res) => {
|
|
try {
|
|
const votes = await loadVotes();
|
|
const voteArray = Object.values(votes);
|
|
|
|
// Calculate results
|
|
const mapCounts = {};
|
|
const totalVotes = voteArray.length;
|
|
|
|
voteArray.forEach(voteData => {
|
|
voteData.vote.forEach((map, index) => {
|
|
if (!mapCounts[map.name]) {
|
|
mapCounts[map.name] = { name: map.name, positions: [0, 0, 0, 0], totalScore: 0 };
|
|
}
|
|
mapCounts[map.name].positions[index]++;
|
|
// Higher position = higher score (4 points for 1st, 3 for 2nd, etc.)
|
|
mapCounts[map.name].totalScore += (4 - index);
|
|
});
|
|
});
|
|
|
|
res.json({
|
|
totalVotes,
|
|
results: Object.values(mapCounts).sort((a, b) => b.totalScore - a.totalScore)
|
|
});
|
|
} catch (error) {
|
|
console.error('Error getting results:', error);
|
|
res.status(500).json({ error: 'Failed to get results' });
|
|
}
|
|
});
|
|
|
|
if (process.env.NODE_ENV === 'production') {
|
|
app.get('*', (req, res) => {
|
|
res.sendFile(path.join(__dirname, '../dist/index.html'));
|
|
});
|
|
}
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Server running on port ${PORT}`);
|
|
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
}); |