diff --git a/config/db.js b/config/db.js index 4819f1b..dc036ae 100644 --- a/config/db.js +++ b/config/db.js @@ -40,8 +40,4 @@ export async function query(sql, params = []) { throw lastErr; } -export async function getConnection() { - return await pool.getConnection(); -} -export default pool; diff --git a/package.json b/package.json index 11de401..d8631e5 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ }, "dependencies": { "argon2": "^0.41.1", - "better-sqlite3": "^11.7.0", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.7", @@ -17,8 +16,6 @@ "express-rate-limit": "^7.4.1", "helmet": "^8.0.0", "jsonwebtoken": "^9.0.2", - "mariadb": "^3.4.0", - "uuid": "^11.0.3", - "zod": "^3.24.1" + "mariadb": "^3.4.0" } } diff --git a/public/css/style.css b/public/css/style.css index 0a03740..ea0d304 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -47,21 +47,6 @@ html, body { a { color: var(--accent-3); text-decoration: none; } a:hover { color: var(--accent-1); } -.gradient-text { - background: linear-gradient(135deg, var(--accent-1), var(--accent-2), var(--accent-cyan)); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - background-size: 200% 200%; - animation: gradientShift 4s ease infinite; -} - -@keyframes gradientShift { - 0% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } - 100% { background-position: 0% 50%; } -} - /* ===== AUTH PAGES ===== */ .auth-page { min-height: 100vh; @@ -969,34 +954,6 @@ tbody tr:hover { transition: width 0.5s ease; } -/* ===== LOADING SCREEN ===== */ -.loading-screen { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - background: var(--bg-primary); -} - -.loading-content { - text-align: center; -} - -.loading-spinner { - width: 40px; - height: 40px; - border: 3px solid rgba(99, 102, 241, 0.15); - border-top-color: var(--accent-1); - border-radius: 50%; - animation: spin 0.7s linear infinite; - margin: 0 auto 16px; -} - -.loading-text { - font-size: 0.95rem; - color: var(--text-secondary); -} - /* ===== RESPONSIVE ===== */ .hamburger-toggle { display: none; @@ -1189,26 +1146,6 @@ tbody tr:hover { color: var(--accent-orange); } -.exp-col { - font-size: 0.82rem; - display: flex; - gap: 4px; - align-items: center; -} - -.exp-col.expired { - color: var(--accent-red); -} - -.exp-col.expiring { - color: var(--accent-orange); -} - -.exp-days { - font-size: 0.75rem; - opacity: 0.8; -} - /* ===== SERVER DETAIL PAGE ===== */ .server-detail-grid { display: grid; @@ -1317,20 +1254,6 @@ tbody tr:hover { gap: 10px; } -.input-readonly { - width: 100%; - padding: 12px 16px; - background: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - color: var(--text-muted); - font-family: 'Inter', sans-serif; - font-size: 0.95rem; - outline: none; - cursor: default; - opacity: 0.7; -} - .consent-group input[type="checkbox"] { width: 18px; height: 18px; @@ -1352,5 +1275,4 @@ tbody tr:hover { text-decoration: underline; } -/* ===== PRIVACY / LEGAL PAGES ===== */ diff --git a/public/js/app.js b/public/js/app.js index 7babbbc..d11f187 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -9,10 +9,6 @@ const state = { serverDetailTab: 'info', }; -function getRgpdConsent() { - return state.rgpdConsent; -} - function setRgpdConsent(preferences) { state.rgpdConsent = preferences; localStorage.setItem('zh_rgpd_consent', JSON.stringify(preferences)); @@ -50,8 +46,6 @@ function renderCookieBanner() { } function $(sel) { return document.querySelector(sel); } -function $$(sel) { return document.querySelectorAll(sel); } - function md5(s) { function F(x,y,z) { return (x & y) | (~x & z); } function G(x,y,z) { return (x & z) | (y & ~z); } @@ -404,7 +398,7 @@ async function renderDashboard() {
-
v0.9.5 BETA
+
v0.9.6 BETA
diff --git a/routes/auth.js b/routes/auth.js index 7605028..bf44cf7 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -1,17 +1,13 @@ import { Router } from 'express'; import jwt from 'jsonwebtoken'; import argon2 from 'argon2'; -import { randomBytes } from 'crypto'; import { query } from '../config/db.js'; -import { generateToken } from '../middleware/auth.js'; -import { createPteroUser, getPteroUserByEmail, updatePteroPassword, updatePteroEmail, deletePteroUser, getServersByUser, deletePteroServer } from '../services/pterodactyl.js'; -import { authenticateToken } from '../middleware/auth.js'; +import { generateToken, authenticateToken } from '../middleware/auth.js'; +import { createPteroUser, updatePteroPassword, updatePteroEmail, deletePteroUser, getServersByUser, deletePteroServer } from '../services/pterodactyl.js'; +import { verifyTurnstile } from '../config/turnstile.js'; const router = Router(); -import { verifyTurnstile } from '../config/turnstile.js'; -import { v4 as uuidv4 } from 'uuid'; - function getClientIp(req) { const forwarded = req.headers['x-forwarded-for']; if (forwarded) return forwarded.split(',')[0].trim(); @@ -344,13 +340,14 @@ router.post('/change-email', authenticateToken, async (req, res) => { router.post('/delete-account', authenticateToken, async (req, res) => { try { const { password } = req.body; + const userId = req.user?.userId; const pteroId = req.user?.pteroId; if (!password) { return res.status(400).json({ error: 'Password is required' }); } - const users = await query('SELECT * FROM users WHERE ptero_user_id = ?', [pteroId]); + const users = await query('SELECT * FROM users WHERE id = ?', [userId]); if (users.length === 0) { return res.status(404).json({ error: 'User not found' }); } @@ -362,30 +359,31 @@ router.post('/delete-account', authenticateToken, async (req, res) => { return res.status(401).json({ error: 'Password is incorrect' }); } - // Delete all Pterodactyl servers first - try { - const servers = await getServersByUser(pteroId); - for (const server of servers) { - try { - await deletePteroServer(server.id); - } catch (err) { - console.error(`Failed to delete server ${server.id}:`, err.message); + // Delete from local DB first (cascades to user_ips) + await query('DELETE FROM users WHERE id = ?', [user.id]); + + // Then try to clean up Pterodactyl (best effort) + if (pteroId) { + try { + const servers = await getServersByUser(pteroId); + for (const server of servers) { + try { + await deletePteroServer(server.id); + } catch (err) { + console.error(`Failed to delete server ${server.id}:`, err.message); + } } + } catch (err) { + console.error('Failed to fetch servers for deletion:', err.message); } - } catch (err) { - console.error('Failed to fetch servers for deletion:', err.message); - } - // Delete Pterodactyl user - try { - await deletePteroUser(pteroId); - } catch (err) { - console.error('Failed to delete Pterodactyl user:', err.message); + try { + await deletePteroUser(pteroId); + } catch (err) { + console.error('Failed to delete Pterodactyl user:', err.message); + } } - // Delete from local DB (cascades to user_ips) - await query('DELETE FROM users WHERE id = ?', [user.id]); - res.json({ message: 'Account deleted successfully' }); } catch (err) { console.error('Delete account error:', err.message); diff --git a/routes/servers.js b/routes/servers.js index 314143c..38fd384 100644 --- a/routes/servers.js +++ b/routes/servers.js @@ -7,11 +7,9 @@ import { deletePteroServer, reinstallPteroServer, renamePteroServer, - suspendPteroServer, unsuspendPteroServer, getEgg, getAllEggs, - getPteroUserById, } from '../services/pterodactyl.js'; import { query } from '../config/db.js'; import { verifyTurnstile } from '../config/turnstile.js'; diff --git a/services/pterodactyl.js b/services/pterodactyl.js index 4864de0..6fbf19a 100644 --- a/services/pterodactyl.js +++ b/services/pterodactyl.js @@ -51,7 +51,7 @@ export async function createPteroUser({ email, username, firstName, lastName, pa return data.attributes; } -export async function getPteroUserByEmail(email) { +async function getPteroUserByEmail(email) { const data = await pteroFetch(`/users?filter[email]=${encodeURIComponent(email)}`); if (data.data.length > 0) { return data.data[0].attributes;