diff --git a/website/src/lib/server/games/mines.ts b/website/src/lib/server/games/mines.ts index 766e10d..d2a86b8 100644 --- a/website/src/lib/server/games/mines.ts +++ b/website/src/lib/server/games/mines.ts @@ -1,7 +1,7 @@ import { db } from '$lib/server/db'; import { user } from '$lib/server/db/schema'; import { eq } from 'drizzle-orm'; - +import { redis } from '$lib/server/redis'; interface MinesSession { sessionToken: string; @@ -16,16 +16,20 @@ interface MinesSession { userId: number; } - -export const activeGames = new Map(); +const MINES_SESSION_PREFIX = 'mines:session:'; +export const getSessionKey = (token: string) => `${MINES_SESSION_PREFIX}${token}`; // Clean up old games every minute. (5 Minute system) setInterval(async () => { const now = Date.now(); - for (const [token, game] of activeGames.entries()) { + const keys = await redis.keys(`${MINES_SESSION_PREFIX}*`); + for (const key of keys) { + const sessionRaw = await redis.get(key); + if (!sessionRaw) continue; + const game = JSON.parse(sessionRaw) as MinesSession; if (now - game.lastActivity > 5 * 60 * 1000) { if (game.revealedTiles.length === 0) { - try { + try { const [userData] = await db .select({ baseCurrencyBalance: user.baseCurrencyBalance }) .from(user) @@ -43,19 +47,22 @@ setInterval(async () => { updatedAt: new Date() }) .where(eq(user.id, game.userId)); - } catch (error) { - console.error(`Failed to refund inactive game ${token}:`, error); + console.error(`Failed to refund inactive game ${game.sessionToken}:`, error); } } - activeGames.delete(token); + await redis.del(getSessionKey(game.sessionToken)); } } }, 60000); setInterval(async () => { const now = Date.now(); - for (const [token, game] of activeGames.entries()) { + const keys = await redis.keys(`${MINES_SESSION_PREFIX}*`); + for (const key of keys) { + const sessionRaw = await redis.get(key); + if (!sessionRaw) continue; + const game = JSON.parse(sessionRaw) as MinesSession; if (game.status === 'active' && game.revealedTiles.length > 0 && now - game.lastActivity > 20000) { try { const [userData] = await db @@ -78,9 +85,9 @@ setInterval(async () => { }) .where(eq(user.id, game.userId)); - activeGames.delete(token); + await redis.del(getSessionKey(game.sessionToken)); } catch (error) { - console.error(`Failed to auto cashout game ${token}:`, error); + console.error(`Failed to auto cashout game ${game.sessionToken}:`, error); } } } diff --git a/website/src/routes/api/gambling/mines/cashout/+server.ts b/website/src/routes/api/gambling/mines/cashout/+server.ts index 773e9ad..dd9b7a7 100644 --- a/website/src/routes/api/gambling/mines/cashout/+server.ts +++ b/website/src/routes/api/gambling/mines/cashout/+server.ts @@ -3,7 +3,8 @@ import { error, json } from '@sveltejs/kit'; import { db } from '$lib/server/db'; import { user } from '$lib/server/db/schema'; import { eq } from 'drizzle-orm'; -import { activeGames } from '$lib/server/games/mines'; +import { redis } from '$lib/server/redis'; +import { getSessionKey } from '$lib/server/games/mines'; import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ request }) => { @@ -17,7 +18,8 @@ export const POST: RequestHandler = async ({ request }) => { try { const { sessionToken } = await request.json(); - const game = activeGames.get(sessionToken); + const sessionRaw = await redis.get(getSessionKey(sessionToken)); + const game = sessionRaw ? JSON.parse(sessionRaw) : null; const userId = Number(session.user.id); if (!game) { @@ -36,18 +38,16 @@ export const POST: RequestHandler = async ({ request }) => { let payout: number; let newBalance: number; - // If no tiles revealed, treat as abort and return full bet. This could be changed later to keep the initial bet on the Server + // If no tiles revealed, treat as abort and return full bet. if (game.revealedTiles.length === 0) { payout = game.betAmount; newBalance = Math.round((currentBalance + payout) * 100000000) / 100000000; } else { - // Calculate payout payout = game.betAmount * game.currentMultiplier; const roundedPayout = Math.round(payout * 100000000) / 100000000; newBalance = Math.round((currentBalance + roundedPayout) * 100000000) / 100000000; } - await tx .update(user) .set({ @@ -56,8 +56,7 @@ export const POST: RequestHandler = async ({ request }) => { }) .where(eq(user.id, userId)); - - activeGames.delete(sessionToken); + await redis.del(getSessionKey(sessionToken)); return { newBalance, @@ -74,4 +73,4 @@ export const POST: RequestHandler = async ({ request }) => { const errorMessage = e instanceof Error ? e.message : 'Internal server error'; return json({ error: errorMessage }, { status: 400 }); } -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/website/src/routes/api/gambling/mines/reveal/+server.ts b/website/src/routes/api/gambling/mines/reveal/+server.ts index 6a711ec..4e1cb59 100644 --- a/website/src/routes/api/gambling/mines/reveal/+server.ts +++ b/website/src/routes/api/gambling/mines/reveal/+server.ts @@ -1,10 +1,12 @@ import { auth } from '$lib/auth'; import { error, json } from '@sveltejs/kit'; -import { activeGames, calculateMultiplier } from '$lib/server/games/mines'; +import { calculateMultiplier } from '$lib/server/games/mines'; import type { RequestHandler } from './$types'; import { db } from '$lib/server/db'; import { user } from '$lib/server/db/schema'; import { eq } from 'drizzle-orm'; +import { redis, } from '$lib/server/redis'; +import { getSessionKey } from '$lib/server/games/mines'; export const POST: RequestHandler = async ({ request }) => { const session = await auth.api.getSession({ @@ -17,7 +19,8 @@ export const POST: RequestHandler = async ({ request }) => { try { const { sessionToken, tileIndex } = await request.json(); - const game = activeGames.get(sessionToken); + const sessionRaw = await redis.get(getSessionKey(sessionToken)); + const game = sessionRaw ? JSON.parse(sessionRaw) : null; if (!game) { return json({ error: 'Invalid session' }, { status: 400 }); @@ -29,7 +32,6 @@ export const POST: RequestHandler = async ({ request }) => { game.lastActivity = Date.now(); - if (game.minePositions.includes(tileIndex)) { game.status = 'lost'; const minePositions = game.minePositions; @@ -44,7 +46,6 @@ export const POST: RequestHandler = async ({ request }) => { const currentBalance = Number(userData.baseCurrencyBalance); - await db .update(user) .set({ @@ -53,9 +54,8 @@ export const POST: RequestHandler = async ({ request }) => { }) .where(eq(user.id, userId)); - activeGames.delete(sessionToken); + await redis.del(getSessionKey(sessionToken)); - return json({ hitMine: true, minePositions, @@ -65,15 +65,14 @@ export const POST: RequestHandler = async ({ request }) => { }); } - - // Safe tile (Yipeee) + // Safe tile game.revealedTiles.push(tileIndex); game.currentMultiplier = calculateMultiplier( game.revealedTiles.length, game.mineCount, game.betAmount ); - + if (game.revealedTiles.length === 25 - game.mineCount) { game.status = 'won'; const userId = Number(session.user.id); @@ -97,7 +96,7 @@ export const POST: RequestHandler = async ({ request }) => { }) .where(eq(user.id, userId)); - activeGames.delete(sessionToken); + await redis.del(getSessionKey(sessionToken)); return json({ hitMine: false, @@ -108,6 +107,8 @@ export const POST: RequestHandler = async ({ request }) => { }); } + await redis.set(getSessionKey(sessionToken), JSON.stringify(game)); + return json({ hitMine: false, currentMultiplier: game.currentMultiplier, @@ -118,4 +119,4 @@ export const POST: RequestHandler = async ({ request }) => { const errorMessage = e instanceof Error ? e.message : 'Internal server error'; return json({ error: errorMessage }, { status: 400 }); } -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/website/src/routes/api/gambling/mines/start/+server.ts b/website/src/routes/api/gambling/mines/start/+server.ts index cff9ab4..92b8de3 100644 --- a/website/src/routes/api/gambling/mines/start/+server.ts +++ b/website/src/routes/api/gambling/mines/start/+server.ts @@ -3,7 +3,8 @@ import { error, json } from '@sveltejs/kit'; import { db } from '$lib/server/db'; import { user } from '$lib/server/db/schema'; import { eq } from 'drizzle-orm'; -import { activeGames } from '$lib/server/games/mines'; +import { redis } from '$lib/server/redis'; +import { getSessionKey } from '$lib/server/games/mines'; import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ request }) => { @@ -43,17 +44,12 @@ export const POST: RequestHandler = async ({ request }) => { throw new Error(`Insufficient funds. You need *${roundedAmount.toFixed(2)} but only have *${roundedBalance.toFixed(2)}`); } - // Generate mine positions const positions = new Set(); while (positions.size < mineCount) { positions.add(Math.floor(Math.random() * 25)); } - const safePositions = []; - for (let i = 0; i < 25; i++) { - if (!positions.has(i)) safePositions.push(i); - } - + // transaction token for authentication stuff const randomBytes = new Uint8Array(8); crypto.getRandomValues(randomBytes); @@ -64,19 +60,21 @@ export const POST: RequestHandler = async ({ request }) => { const now = Date.now(); const newBalance = roundedBalance - roundedAmount; - // Create session - activeGames.set(sessionToken, { - sessionToken, - betAmount: roundedAmount, - mineCount, - minePositions: Array.from(positions), - revealedTiles: [], - startTime: now, - lastActivity: now, - currentMultiplier: 1, - status: 'active', - userId - }); + await redis.set( + getSessionKey(sessionToken), + JSON.stringify({ + sessionToken, + betAmount: roundedAmount, + mineCount, + minePositions: Array.from(positions), + revealedTiles: [], + startTime: now, + lastActivity: now, + currentMultiplier: 1, + status: 'active', + userId + }) + ); // Update user balance await tx @@ -87,7 +85,6 @@ export const POST: RequestHandler = async ({ request }) => { }) .where(eq(user.id, userId)); - return { sessionToken, newBalance @@ -100,4 +97,4 @@ export const POST: RequestHandler = async ({ request }) => { const errorMessage = e instanceof Error ? e.message : 'Internal server error'; return json({ error: errorMessage }, { status: 400 }); } -}; \ No newline at end of file +}; \ No newline at end of file