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;