This repository has been archived on 2025-08-19. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
coinstorge/website/src/hooks.server.ts

201 lines
7.1 KiB
TypeScript
Raw Normal View History

2025-05-22 13:17:11 +03:00
import { auth } from "$lib/auth";
import { resolveExpiredQuestions, processAccountDeletions } from "$lib/server/job";
2025-05-22 13:17:11 +03:00
import { svelteKitHandler } from "better-auth/svelte-kit";
import { redis } from "$lib/server/redis";
import { building } from '$app/environment';
2025-05-31 19:29:20 +03:00
import { redirect, type Handle } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
import { minesCleanupInactiveGames, minesAutoCashout } from '$lib/server/games/mines';
2025-06-24 19:37:34 +03:00
import { isTurnstileVerifiedRedis } from '$lib/server/redis';
async function initializeScheduler() {
if (building) return;
try {
const lockKey = 'hopium:scheduler';
const lockValue = `${process.pid}-${Date.now()}`;
const lockTTL = 300; // 5 minutes
const result = await redis.set(lockKey, lockValue, {
NX: true,
EX: lockTTL
});
if (result === 'OK') {
console.log(`🕐 Starting scheduler (PID: ${process.pid})`);
// Renew lock periodically
const renewInterval = setInterval(async () => {
try {
const currentValue = await redis.get(lockKey);
if (currentValue === lockValue) {
await redis.expire(lockKey, lockTTL);
} else {
// Lost the lock, stop scheduler
clearInterval(renewInterval);
clearInterval(schedulerInterval);
console.log('Lost scheduler lock, stopping...');
}
} catch (error) {
console.error('Failed to renew scheduler lock:', error);
}
}, (lockTTL / 2) * 1000); // Renew at half the TTL
resolveExpiredQuestions().catch(console.error);
processAccountDeletions().catch(console.error);
const schedulerInterval = setInterval(() => {
resolveExpiredQuestions().catch(console.error);
processAccountDeletions().catch(console.error);
}, 5 * 60 * 1000);
const minesCleanupInterval = setInterval(() => {
minesCleanupInactiveGames().catch(console.error);
minesAutoCashout().catch(console.error);
}, 60 * 1000);
// Cleanup on process exit
const cleanup = async () => {
clearInterval(renewInterval);
clearInterval(schedulerInterval);
clearInterval(minesCleanupInterval);
const currentValue = await redis.get(lockKey);
if (currentValue === lockValue) {
await redis.del(lockKey);
}
};
process.on('SIGTERM', cleanup);
process.on('SIGINT', cleanup);
process.on('beforeExit', cleanup);
} else {
console.log('📋 Scheduler already running');
}
} catch (error) {
console.error('Failed to initialize scheduler:', error);
}
}
initializeScheduler();
2025-05-22 13:17:11 +03:00
2025-05-31 19:29:20 +03:00
const sessionCache = new Map<string, {
userData: any;
timestamp: number;
ttl: number;
}>();
2025-05-22 13:17:11 +03:00
2025-05-31 19:29:20 +03:00
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const CACHE_CLEANUP_INTERVAL = 10 * 60 * 1000; // 10 minutes
setInterval(() => {
const now = Date.now();
for (const [key, value] of sessionCache.entries()) {
if (now - value.timestamp > value.ttl) {
sessionCache.delete(key);
}
}
}, CACHE_CLEANUP_INTERVAL);
export const handle: Handle = async ({ event, resolve }) => {
2025-05-31 16:26:51 +03:00
if (event.url.pathname.startsWith('/.well-known/appspecific/com.chrome.devtools')) {
return new Response(null, { status: 204 });
}
2025-05-31 19:29:20 +03:00
// Get session from auth
const session = await auth.api.getSession({
headers: event.request.headers
});
let userData = null;
if (session?.user) {
const userId = session.user.id;
const cacheKey = `user:${userId}`;
const now = Date.now();
2025-06-24 19:37:34 +03:00
2025-05-31 19:29:20 +03:00
const cached = sessionCache.get(cacheKey);
if (cached && (now - cached.timestamp) < cached.ttl) {
userData = cached.userData;
} else {
const [userRecord] = await db
.select({
id: user.id,
name: user.name,
username: user.username,
email: user.email,
isAdmin: user.isAdmin,
image: user.image,
isBanned: user.isBanned,
banReason: user.banReason,
baseCurrencyBalance: user.baseCurrencyBalance,
bio: user.bio,
volumeMaster: user.volumeMaster,
volumeMuted: user.volumeMuted
})
.from(user)
.where(eq(user.id, Number(userId)))
.limit(1);
if (userRecord?.isBanned) {
try {
await auth.api.signOut({
headers: event.request.headers
});
} catch (e) {
console.error('Failed to sign out banned user:', e);
}
if (event.url.pathname !== '/banned') {
const banReason = encodeURIComponent(userRecord.banReason || 'Account suspended');
throw redirect(302, `/banned?reason=${banReason}`);
}
} else if (userRecord) {
userData = {
id: userRecord.id.toString(),
name: userRecord.name,
username: userRecord.username,
email: userRecord.email,
isAdmin: userRecord.isAdmin || false,
image: userRecord.image || '',
isBanned: userRecord.isBanned || false,
banReason: userRecord.banReason,
avatarUrl: userRecord.image,
baseCurrencyBalance: parseFloat(userRecord.baseCurrencyBalance || '0'),
bio: userRecord.bio || '',
volumeMaster: parseFloat(userRecord.volumeMaster || '0.7'),
volumeMuted: userRecord.volumeMuted || false
};
const cacheTTL = userRecord.isAdmin ? CACHE_TTL * 2 : CACHE_TTL;
sessionCache.set(cacheKey, {
userData,
timestamp: now,
ttl: cacheTTL
});
}
}
}
event.locals.userSession = userData;
2025-06-24 19:37:34 +03:00
if (session?.user?.id) {
event.locals.turnstileVerified = await isTurnstileVerifiedRedis(session.user.id);
} else {
event.locals.turnstileVerified = false;
}
2025-06-23 20:21:56 +03:00
if (event.url.pathname.startsWith('/api/')) {
const response = await svelteKitHandler({ event, resolve, auth });
response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
return response;
}
2025-05-22 13:17:11 +03:00
return svelteKitHandler({ event, resolve, auth });
2025-05-31 19:29:20 +03:00
};
export function clearUserCache(userId: string) {
sessionCache.delete(`user:${userId}`);
2025-05-22 13:17:11 +03:00
}