diff --git a/dev/lobby-backend/src/controllers/sector.controller.js b/dev/lobby-backend/src/controllers/sector.controller.js new file mode 100644 index 0000000..6a3bd7c --- /dev/null +++ b/dev/lobby-backend/src/controllers/sector.controller.js @@ -0,0 +1,62 @@ +import pool from '../db.js'; + +/** + * Liefert alle Sektoren für das Frontend. + * Unterteilt in "Meine Sektoren" (wo der User schon spielt) + * und "Alle Sektoren" (die generell verfügbar sind) + */ +export const getSectorsList = async(req, res) => { + const accountId = req.user.accountId; + const client = await pool.connect(); + + try { + // "Meine Sektoren" abfragen + const mySectorsResult = await client.query(` + SELECT + s.id, s.name, s.status, s.speed_fleet, s.speed_economy, + las.local_username, + las.created_at as joined_at, + las.updated_at as last_played + FROM sectors s + JOIN lobby_accounts_sectors las ON s.id = las.sector_id + WHERE las.lobby_account_id = $1 + ORDER BY las.updated_at DESC + `, [accountId]); + + // "Neue Sektoren" + const newSectorsResult = await client.query(` + SELECT + id, name, status, speed_fleet, speed_economy + FROM sectors + WHERE status != 'offline' + AND id NOT IN ( + SELECT sector_id + FROM lobby_accounts_sectors + WHERE lobby_account_id = $1 + ) + ORDER BY created_at ASC + `, [accountId]); + + // Daten platzhalter + const mapSectorData = (sector) => ({ + ...sector, + online_players: Math.floor(Math.random() * 150) + 10, + planets_count: 1337 + }); + + + return res.status(200).json({ + success: true, + mySectors: mySectorsResult.rows.map(mapSectorData), + newSectors: newSectorsResult.rows.map(mapSectorData) + }); + + } catch (error) { + console.error('[SectorController] Fehler beim Abrufen der Sektoren:', error); + return res.status(500).json({ + error: 'ERR_INTERNAL_SERVER' + }); + } finally { + client.release(); + } +}; \ No newline at end of file diff --git a/dev/lobby-backend/src/middlewares/auth.middleware.js b/dev/lobby-backend/src/middlewares/auth.middleware.js new file mode 100644 index 0000000..ca3ad26 --- /dev/null +++ b/dev/lobby-backend/src/middlewares/auth.middleware.js @@ -0,0 +1,30 @@ +import jwt from 'jsonwebtoken'; + +/** + * Middleware zum Schützen privater API-Routen. + * Prüft, ob ein gültiges Access_Token im Aithorization-Headder mitgesendet wurde. + */ +export const requireAuth = (req, res, next) => { + // Der Standard für Tokens ist der Header: "Authorization: Bearer " + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + error: 'ERR_UNAUTHORIZED_NO_TOKEN' + }); + } + + const token = authHeader.split(' ')[1]; + + try { + const decoded = jwt.verify(token, process.env.JWT_ACCESS_SECRET); + + req.user = decoded; + + next(); + } catch (error) { + return res.status(401).json({ + error: 'ERR_UNAUTHORIZED_INVALID_TOKEN' + }); + } +}; \ No newline at end of file diff --git a/dev/lobby-backend/src/routes/sector.routes.js b/dev/lobby-backend/src/routes/sector.routes.js new file mode 100644 index 0000000..193a053 --- /dev/null +++ b/dev/lobby-backend/src/routes/sector.routes.js @@ -0,0 +1,9 @@ +import express from 'express'; +import * as sectorController from '../controllers/sector.controller.js'; +import { requireAuth } from '../middlewares/auth.middleware.js'; + +const router = express.Router(); + +router.get('/list', requireAuth, sectorController.getSectorsList); + +export default router; \ No newline at end of file diff --git a/dev/lobby-backend/src/scripts/initDb.js b/dev/lobby-backend/src/scripts/initDb.js index f48c59a..dff1241 100644 --- a/dev/lobby-backend/src/scripts/initDb.js +++ b/dev/lobby-backend/src/scripts/initDb.js @@ -7,40 +7,72 @@ import pool from '../db.js'; const createTablesQuery = ` +-- ------------------------------------------------------------------------------ +-- DROP TABLES +-- WICHTIG: Die Reihenfolge beim Löschen ist entscheidend! +-- Tabellen mit Fremdschlüsseln (Foreign Keys) müssen zuerst gelöscht werden. +-- ------------------------------------------------------------------------------ DROP TABLE IF EXISTS refresh_tokens; DROP TABLE IF EXISTS auth_otps; -DROP TABLE IF EXISTS lobby_accounts; +DROP TABLE IF EXISTS lobby_accounts_sectors; -- Zuerst die Brückentabelle löschen +DROP TABLE IF EXISTS sectors; -- Dann die Sektoren-Tabelle +DROP TABLE IF EXISTS lobby_accounts; -- Zuletzt die Haupt-Nutzer-Tabelle -- ------------------------------------------------------------------------------ -- TABLE: lobby_accounts --- Base user data for the entire Void-Genesis universe. +-- Basis-Nutzerdaten für das gesamte Void-Genesis Universum. -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS lobby_accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, - - -- Status flags and balances (English naming) is_email_verified BOOLEAN DEFAULT false, res_premium INT DEFAULT 0, is_admin BOOLEAN DEFAULT false, - - -- Timestamps for record keeping created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); +-- ------------------------------------------------------------------------------ +-- TABLE: sectors +-- Beinhaltet die globalen Metadaten zu allen verfügbaren Spiel-Sektoren. +-- Hier definieren wir, welche Server existieren und welche Eigenschaften sie haben. +-- ------------------------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS sectors ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(50) UNIQUE NOT NULL, + status VARCHAR(20) DEFAULT 'offline', + speed_fleet INT DEFAULT 1, + speed_economy INT DEFAULT 1, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ------------------------------------------------------------------------------ +-- TABLE: lobby_accounts_sectors (Brückentabelle) +-- Verknüpft einen Lobby-Account mit einem Sektor. +-- Existiert hier ein Eintrag, bedeutet das: Der Nutzer hat in diesem Sektor einen Spielstand. +-- ------------------------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS lobby_accounts_sectors ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + lobby_account_id UUID REFERENCES lobby_accounts(id) ON DELETE CASCADE, + sector_id UUID REFERENCES sectors(id) ON DELETE CASCADE, + local_username VARCHAR(50) UNIQUE NOT NULL, + res_premium INT DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE(lobby_account_id, sector_id) +); + -- ------------------------------------------------------------------------------ -- TABLE: auth_otps --- Stores temporary 6-digit codes for registration or password resets. +-- Speichert temporäre 6-stellige Codes für Registrierung oder Passwort-Reset. -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS auth_otps ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), account_id UUID REFERENCES lobby_accounts(id) ON DELETE CASCADE, otp_code VARCHAR(6) NOT NULL, - - -- Defines the purpose of the OTP (e.g., 'register', 'password_reset') type VARCHAR(20) NOT NULL, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() @@ -48,7 +80,7 @@ CREATE TABLE IF NOT EXISTS auth_otps ( -- ------------------------------------------------------------------------------ -- TABLE: refresh_tokens --- Stores long-lived tokens for the "Keep me logged in" and single-session feature. +-- Speichert langlebige Tokens für die "Angemeldet bleiben"-Funktion. -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS refresh_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -60,8 +92,8 @@ CREATE TABLE IF NOT EXISTS refresh_tokens ( -- ------------------------------------------------------------------------------ -- TRIGGER: Auto-update 'updated_at' timestamp +-- Diese Funktion setzt das 'updated_at' Feld bei jeder Änderung auf die aktuelle Zeit. -- ------------------------------------------------------------------------------ --- This function sets the 'updated_at' field to the current time whenever a row is modified. CREATE OR REPLACE FUNCTION update_modified_column() RETURNS TRIGGER AS $$ BEGIN @@ -70,15 +102,32 @@ BEGIN END; $$ language 'plpgsql'; --- We only create the trigger if it doesn't exist to avoid errors on multiple runs. +-- Wir wenden den Trigger nur an, wenn er noch nicht existiert. DO $$ BEGIN + -- Trigger für lobby_accounts IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_lobby_accounts_modtime') THEN CREATE TRIGGER update_lobby_accounts_modtime BEFORE UPDATE ON lobby_accounts FOR EACH ROW EXECUTE FUNCTION update_modified_column(); END IF; + + -- Trigger für sectors + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_sectors_modtime') THEN + CREATE TRIGGER update_sectors_modtime + BEFORE UPDATE ON sectors + FOR EACH ROW + EXECUTE FUNCTION update_modified_column(); + END IF; + + -- Trigger für lobby_accounts_sectors (WICHTIG für unsere "Zuletzt gespielt" Funktion) + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_lobby_accounts_sectors_modtime') THEN + CREATE TRIGGER update_lobby_accounts_sectors_modtime + BEFORE UPDATE ON lobby_accounts_sectors + FOR EACH ROW + EXECUTE FUNCTION update_modified_column(); + END IF; END $$; `; @@ -90,11 +139,37 @@ async function initDatabase() { // Wir holen uns eine Verbindung aus dem Pool const client = await pool.connect(); - + try { // Wir senden den kompletten SQL-String an die Datenbank await client.query(createTablesQuery); console.log('✅ Tabellen wurden erfolgreich erstellt (oder existierten bereits).'); + +const sectorCheck = await client.query('SELECT count(*) FROM sectors'); + if (parseInt(sectorCheck.rows[0].count) === 0) { + console.log('🌱 Füge 24 Test-Sektoren ein...'); + + const greekAlphabet = [ + 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta', + 'Eta', 'Theta', 'Iota', 'Kappa', 'Lambda', 'Mu', + 'Nu', 'Xi', 'Omicron', 'Pi', 'Rho', 'Sigma', + 'Tau', 'Upsilon', 'Phi', 'Chi', 'Psi', 'Omega' + ]; + + // Wir generieren für jeden Namen ein dynamisches INSERT + const insertPromises = greekAlphabet.map((name, index) => { + // Wir variieren die Geschwindigkeiten leicht für optische Vielfalt + const speed = (index % 4) + 1; + return client.query(` + INSERT INTO sectors (name, status, speed_fleet, speed_economy) + VALUES ($1, 'online', $2, $2) + `, [name, speed]); + }); + + await Promise.all(insertPromises); + console.log('✅ 24 Test-Sektoren erfolgreich eingefügt.'); + } + } catch (error) { console.error('❌ Fehler beim Erstellen der Tabellen:', error); } finally { diff --git a/dev/lobby-backend/src/server.js b/dev/lobby-backend/src/server.js index 716a67e..87ce497 100644 --- a/dev/lobby-backend/src/server.js +++ b/dev/lobby-backend/src/server.js @@ -7,6 +7,7 @@ import cors from 'cors'; import pool from './db.js'; import authRoutes from './routes/auth.routes.js'; +import sectorRoutes from './routes/sector.routes.js'; const app = express(); const PORT = process.env.PORT || 3000; @@ -16,6 +17,7 @@ app.use(cors()); app.use(express.json()); app.use('/api/auth', authRoutes); +app.use('/api/sectors', sectorRoutes); // Unsere bestehende Health-Route app.get('/api/health', (req, res) => { diff --git a/dev/lobby-backend/src/services/auth.service.js b/dev/lobby-backend/src/services/auth.service.js index 94107ea..3dbacdd 100644 --- a/dev/lobby-backend/src/services/auth.service.js +++ b/dev/lobby-backend/src/services/auth.service.js @@ -342,7 +342,7 @@ export const refreshAccessToken = async (refreshToken) => { id: account.id, username: account.username, email: account.email, - res_premium: res_premium + res_premium: account.res_premium } }; } catch (error) { diff --git a/dev/lobby-frontend/index.html b/dev/lobby-frontend/index.html index e1c4564..a4fce10 100644 --- a/dev/lobby-frontend/index.html +++ b/dev/lobby-frontend/index.html @@ -9,110 +9,115 @@
-
-
-
-
+ +
-

- Bitte gib den 6-stelligen Code aus deiner E-Mail ein. -

+

+ Bitte gib den 6-stelligen Code aus deiner E-Mail ein. +

-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
- + -
- - -
+
+ + +
-
+
- + - - - + + + - + - + +
+ +