From fae6914acace1a3b470f9d243fe8a2ba0f141388 Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Mon, 30 Jun 2025 00:58:56 -0700 Subject: add basic batch manual score upload route --- backend/src/index.ts | 9 ++- backend/src/routes/auth.ts | 116 +++++++++++++++++++++++++++++++++++++++ backend/src/routes/authRoutes.ts | 116 --------------------------------------- backend/src/routes/game.ts | 19 +++++++ backend/src/routes/gameRoutes.ts | 19 ------- backend/src/routes/score.ts | 50 +++++++++++++++++ backend/src/routes/user.ts | 41 ++++++++++++++ backend/src/routes/userRoutes.ts | 41 -------------- 8 files changed, 232 insertions(+), 179 deletions(-) create mode 100644 backend/src/routes/auth.ts delete mode 100644 backend/src/routes/authRoutes.ts create mode 100644 backend/src/routes/game.ts delete mode 100644 backend/src/routes/gameRoutes.ts create mode 100644 backend/src/routes/score.ts create mode 100644 backend/src/routes/user.ts delete mode 100644 backend/src/routes/userRoutes.ts (limited to 'backend/src') diff --git a/backend/src/index.ts b/backend/src/index.ts index c776ce1..4a63c41 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -6,9 +6,10 @@ import { requireAuth } from './middleware/requireAuth'; import { startSessionCleanup } from './utils/session'; // Routes -import * as authRoutes from './routes/authRoutes'; -import * as userRoutes from './routes/userRoutes'; -import * as gameRoutes from './routes/gameRoutes'; +import * as authRoutes from './routes/auth'; +import * as userRoutes from './routes/user'; +import * as gameRoutes from './routes/game'; +import * as scoreRoutes from './routes/score'; const app = express(); const port = 5000; @@ -52,6 +53,8 @@ app.get('/api/session', userRoutes.handleGetCurrentSession); app.get('/api/supportedGames', gameRoutes.handleGetSupportedGames); +app.post('/api/uploadScore', requireAuth, scoreRoutes.handleScoreUpload); + app.listen(port, () => { console.log(`Server listening on port ${port}`); }); diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts new file mode 100644 index 0000000..4c6c374 --- /dev/null +++ b/backend/src/routes/auth.ts @@ -0,0 +1,116 @@ +import { prisma } from '../config/db'; +import express from 'express'; +import { createSession } from '../utils/session' +import bcrypt from 'bcrypt'; +import crypto from 'crypto'; + +export const handleRegistration = async (req: express.Request, res: express.Response) => { + try { + const { username, password, email } = req.body; + + if (!username || !password || !email) { + return res.status(400).json({ error: 'All fields are required' }); + } + + const existingUser = await prisma.user.findFirst({ + where: { + OR: [ + { username }, + { email } + ] + } + }); + + if (existingUser) { + return res.status(400).json({ error: 'Username or email already exists' }); + } + + const salt = crypto.randomBytes(16).toString('hex'); + const hashedPassword = await bcrypt.hash(password + salt, 12); + + const user = await prisma.user.create({ + data: { + username, + password: hashedPassword, + salt, + email, + } + }); + + // Create session for the new user + req.session.userId = user.id; + const sessionId = await createSession(user.id); + + res.status(201).json({ + id: user.id, + username: user.username, + email: user.email, + sessionId + }); + } catch (error) { + console.error('Registration error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export const handleAuthentication = async (req: express.Request, res: express.Response) => { + try { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ error: 'Username and password are required' }); + } + + const user = await prisma.user.findUnique({ + where: { username } + }); + + if (!user) { + return res.status(401).json({ error: 'Invalid credentials' }); + } + + const isValidPassword = await bcrypt.compare(password + user.salt, user.password); + if (!isValidPassword) { + return res.status(401).json({ error: 'Invalid credentials' }); + } + + // Create session + req.session.userId = user.id; + const sessionId = await createSession(user.id); + + res.json({ + id: user.id, + username: user.username, + email: user.email, + sessionId + }); + } catch (error) { + console.error('Login error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export const handleLogout = async (req: express.Request, res: express.Response,) => { + try { + const userId = req.session.userId; + + // Remove all sessions for this user from database + await prisma.session.deleteMany({ + where: { userId } + }); + + // Destroy the session + req.session.destroy((err) => { + if (err) { + console.error('Session destroy error:', err); + return res.status(500).json({ error: 'Logout failed' }); + } + + res.clearCookie('connect.sid'); // Clear the session cookie + res.json({ message: 'Logged out successfully' }); + }); + } catch (error) { + console.error('Logout error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/backend/src/routes/authRoutes.ts b/backend/src/routes/authRoutes.ts deleted file mode 100644 index 4c6c374..0000000 --- a/backend/src/routes/authRoutes.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { prisma } from '../config/db'; -import express from 'express'; -import { createSession } from '../utils/session' -import bcrypt from 'bcrypt'; -import crypto from 'crypto'; - -export const handleRegistration = async (req: express.Request, res: express.Response) => { - try { - const { username, password, email } = req.body; - - if (!username || !password || !email) { - return res.status(400).json({ error: 'All fields are required' }); - } - - const existingUser = await prisma.user.findFirst({ - where: { - OR: [ - { username }, - { email } - ] - } - }); - - if (existingUser) { - return res.status(400).json({ error: 'Username or email already exists' }); - } - - const salt = crypto.randomBytes(16).toString('hex'); - const hashedPassword = await bcrypt.hash(password + salt, 12); - - const user = await prisma.user.create({ - data: { - username, - password: hashedPassword, - salt, - email, - } - }); - - // Create session for the new user - req.session.userId = user.id; - const sessionId = await createSession(user.id); - - res.status(201).json({ - id: user.id, - username: user.username, - email: user.email, - sessionId - }); - } catch (error) { - console.error('Registration error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -} - -export const handleAuthentication = async (req: express.Request, res: express.Response) => { - try { - const { username, password } = req.body; - - if (!username || !password) { - return res.status(400).json({ error: 'Username and password are required' }); - } - - const user = await prisma.user.findUnique({ - where: { username } - }); - - if (!user) { - return res.status(401).json({ error: 'Invalid credentials' }); - } - - const isValidPassword = await bcrypt.compare(password + user.salt, user.password); - if (!isValidPassword) { - return res.status(401).json({ error: 'Invalid credentials' }); - } - - // Create session - req.session.userId = user.id; - const sessionId = await createSession(user.id); - - res.json({ - id: user.id, - username: user.username, - email: user.email, - sessionId - }); - } catch (error) { - console.error('Login error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -} - -export const handleLogout = async (req: express.Request, res: express.Response,) => { - try { - const userId = req.session.userId; - - // Remove all sessions for this user from database - await prisma.session.deleteMany({ - where: { userId } - }); - - // Destroy the session - req.session.destroy((err) => { - if (err) { - console.error('Session destroy error:', err); - return res.status(500).json({ error: 'Logout failed' }); - } - - res.clearCookie('connect.sid'); // Clear the session cookie - res.json({ message: 'Logged out successfully' }); - }); - } catch (error) { - console.error('Logout error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -} diff --git a/backend/src/routes/game.ts b/backend/src/routes/game.ts new file mode 100644 index 0000000..26e7171 --- /dev/null +++ b/backend/src/routes/game.ts @@ -0,0 +1,19 @@ +import express from 'express'; +import { prisma } from '../config/db'; + +export const handleGetSupportedGames = async (req: express.Request, res: express.Response) => { + try { + const supportedGames = await prisma.game.findMany({ + select: { + internalName: true, + formattedName: true, + description: true + } + }); + res.status(200).json(supportedGames); + + } catch (error) { + console.error('Supported Games endpoint error:', error); + res.status(500).json({ error: 'Internal server error. Unable to fetch supported games' }); + } +} diff --git a/backend/src/routes/gameRoutes.ts b/backend/src/routes/gameRoutes.ts deleted file mode 100644 index 26e7171..0000000 --- a/backend/src/routes/gameRoutes.ts +++ /dev/null @@ -1,19 +0,0 @@ -import express from 'express'; -import { prisma } from '../config/db'; - -export const handleGetSupportedGames = async (req: express.Request, res: express.Response) => { - try { - const supportedGames = await prisma.game.findMany({ - select: { - internalName: true, - formattedName: true, - description: true - } - }); - res.status(200).json(supportedGames); - - } catch (error) { - console.error('Supported Games endpoint error:', error); - res.status(500).json({ error: 'Internal server error. Unable to fetch supported games' }); - } -} diff --git a/backend/src/routes/score.ts b/backend/src/routes/score.ts new file mode 100644 index 0000000..03026af --- /dev/null +++ b/backend/src/routes/score.ts @@ -0,0 +1,50 @@ +import express from 'express'; +import { prisma } from '../config/db'; + +export const handleScoreUpload = async (req: express.Request, res: express.Response) => { + try { + const { meta, scores } = req.body; + const userId = req.session.userId; + if (!userId) { + return res.status(401).json({ error: 'Unauthorized. Please log in to upload scores.' }); + } + + // Basic universal validation + if (!meta || !meta.game || !meta.service || !scores) { + return res.status(400).json({ error: 'Invalid request format. Expected meta with game/service and scores array' }); + } + let game = await prisma.game.findUnique({ + where: { internalName: meta.game } + }); + if (!game) { + game = await prisma.game.findFirst({ + where: { formattedName: meta.game } + }); + } + if (!game) { + return res.status(400).json({ error: `Game '${meta.game}' is not supported. Ensure that you are using the case-sensitive version of either the internal name or formatted name` }); + } + const internalGameName = game.internalName; + const scoresArray = Array.isArray(scores) ? scores : [scores]; + + // Create score records + const createdScores = await prisma.score.createMany({ + data: scoresArray.map(scoreData => ({ + gameInternalName: internalGameName, + userId: userId, + data: scoreData + })) + }); + + res.status(200).json({ + message: 'Score upload received successfully', + game: meta.game, + service: meta.service, + scoreCount: createdScores.count + }); + + } catch (error) { + console.error('Score upload endpoint error:', error); + res.status(500).json({ error: 'Internal server error. Unable to process score upload' }); + } +} diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts new file mode 100644 index 0000000..a03ece0 --- /dev/null +++ b/backend/src/routes/user.ts @@ -0,0 +1,41 @@ +// Routes about self (or users in general) +import express from 'express'; +import { prisma } from '../config/db'; + +export const handleMeRoute = async (req: express.Request, res: express.Response) => { + try { + const user = (req as any).user; + res.json(user); + } catch (error) { + console.error('Me endpoint error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export const handleGetCurrentSession = async (req: express.Request, res: express.Response) => { + try { + if (!req.session.userId) { + return res.json({ authenticated: false }); + } + + const user = await prisma.user.findUnique({ + where: { id: req.session.userId }, + select: { id: true, username: true, email: true } + }); + + if (!user) { + req.session.destroy((err) => { + if (err) console.error('Session destroy error:', err); + }); + return res.json({ authenticated: false }); + } + + res.json({ + authenticated: true, + user + }); + } catch (error) { + console.error('Session check error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts deleted file mode 100644 index a03ece0..0000000 --- a/backend/src/routes/userRoutes.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Routes about self (or users in general) -import express from 'express'; -import { prisma } from '../config/db'; - -export const handleMeRoute = async (req: express.Request, res: express.Response) => { - try { - const user = (req as any).user; - res.json(user); - } catch (error) { - console.error('Me endpoint error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -} - -export const handleGetCurrentSession = async (req: express.Request, res: express.Response) => { - try { - if (!req.session.userId) { - return res.json({ authenticated: false }); - } - - const user = await prisma.user.findUnique({ - where: { id: req.session.userId }, - select: { id: true, username: true, email: true } - }); - - if (!user) { - req.session.destroy((err) => { - if (err) console.error('Session destroy error:', err); - }); - return res.json({ authenticated: false }); - } - - res.json({ - authenticated: true, - user - }); - } catch (error) { - console.error('Session check error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -} -- cgit v1.2.3