Sektor-Onboarding & Charaktererstellung (Frontend)

This commit is contained in:
2026-06-07 00:53:55 +02:00
parent 18f99e896c
commit 99721bde8f
16 changed files with 858 additions and 148 deletions
@@ -1,63 +1,69 @@
import pool from '../db.js';
import * as sectorService from '../services/sector.service.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)
* Übergibt die HTTP-Anfrage an den zuständigen Service und liefert das Ergebnis aus.
*/
export const getSectorsList = async(req, res) => {
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: (Math.floor(Math.random() * 150) + 10) * 12
});
const sectorData = await sectorService.getSectorsList(accountId);
return res.status(200).json({
success: true,
mySectors: mySectorsResult.rows.map(mapSectorData),
newSectors: newSectorsResult.rows.map(mapSectorData)
...sectorData
});
} catch (error) {
console.error('[SectorController] Fehler beim Abrufen der Sektoren:', error);
console.error('[SectorController] Fehler in getSectorsList:', error);
return res.status(500).json({
error: 'ERR_INTERNAL_SERVER'
});
}
};
/**
* Controller-Methode für den Sektor-Beitritt (Charaktererstellung).
* Nimmt die Request an, validiert grob und leitet an den Service weiter.
*/
export const joinSector = async (req, res) => {
const accountId = req.user.accountId;
const { sectorId, localUsername, gender } = req.body;
if (!sectorId || !localUsername || !gender) {
return res.status(400).json({
error: 'ERR_MISSING_FIELDS'
});
}
if (localUsername.length < 3) {
return res.status(400).json({
error: 'ERR_USERNAME_TOO_SHORT'
});
}
try {
await sectorService.joinSector(accountId, sectorId, localUsername, gender);
return res.status(201).json({
success: true,
message: 'MSG_SECTOR_JOINED_SUCCESS'
});
} catch (error) {
if (error.code === 'ERR_ALREADY_IN_SECTOR' || error.code === 'ERR_LOCAL_USERNAME_TAKEN') {
return res.status(409).json({
error: error.code
});
}
// Generischer Fallback für echte Systemabstürze
console.error('[SectorController] Fehler beim Sektor-Beitritt:', error);
return res.status(500).json({
error: 'ERR_INTERNAL_SERVER'
});
} finally {
client.release();
}
};
@@ -5,5 +5,6 @@ import { requireAuth } from '../middlewares/auth.middleware.js';
const router = express.Router();
router.get('/list', requireAuth, sectorController.getSectorsList);
router.post('/join', requireAuth, sectorController.joinSector);
export default router;
+4 -2
View File
@@ -58,11 +58,13 @@ 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,
local_username VARCHAR(50) NOT NULL,
gender VARCHAR(20) NOT NULL,
res_premium INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(lobby_account_id, sector_id)
UNIQUE(lobby_account_id, sector_id),
UNIQUE(sector_id, local_username)
);
-- ------------------------------------------------------------------------------
@@ -0,0 +1,107 @@
import pool from '../db.js';
/**
* Ruft die Sektorlisten für einen bestimmten Account ab.
* Unterteilt das Ergebnis in bereits beigetretene Sektoren ("Meine Sektoren")
* und noch verfügbare Sektoren ("Neue Sektoren").
*
* @param {string} accountId - Die UUID des aktuell eingeloggten Spielers
*
* @returns {Promise<Object>} - Ein Objekt mit den Arrays 'mySectors' und 'newSectors'
*/
export const getSectorsList = async (accountId) => {
const client = await pool.connect();
try {
const mySectorsResult = await client.query(`
SELECT
s.id, s.name, s.status, s.speed_fleet, s.speed_economy,
las.local_username, las.gender,
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]);
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]);
const mapSectorData = (sector) => ({
...sector,
online_players: Math.floor(Math.random() * 150) + 10,
planets_count: (Math.floor(Math.random() * 150) + 10) * 12
});
return {
mySectors: mySectorsResult.rows.map(mapSectorData),
newSectors: newSectorsResult.rows.map(mapSectorData)
};
} catch (error) {
console.error('[SectorService] Fehler beim Auslesen der Sektordaten:', error);
throw error;
} finally {
client.release();
}
};
/**
* Trägt einen Spieler in einen neuen Sektor ein.
* Führt die eigentliche Datenbankoperation durch und wirft spezifische
* Fehler-Objekte, falls Constraints (UNIQUE-Regeln) verletzt werden.
*
* @param {string} accountId - Die UUID des Nutzers
* @param {string} sectorId - Die UUID des Sektors
* @param {string} localUsername - Der Commander-Name für diesen Sektor
* @param {string} gender - Das gewählte Geschlecht ('male' oder 'female')
*
* @returns {boolean} - true bei Erfolg
*/
export const joinSector = async (accountId, sectorId, localUsername, gender) => {
const client = await pool.connect();
try {
// Wir versuchen, den Datensatz direkt einzufügen.
await client.query(`
INSERT INTO lobby_accounts_sectors (lobby_account_id, sector_id, local_username, gender)
VALUES ($1, $2, $3, $4)
`, [accountId, sectorId, localUsername, gender]);
return true;
} catch (error) {
// Fehlercode 23505 = Postgres "unique_violation"
if (error.code === '23505') {
if (error.constraint === 'lobby_accounts_sectors_lobby_account_id_sector_id_key') {
const customErr = new Error('Already in sector');
customErr.code = 'ERR_ALREADY_IN_SECTOR';
throw customErr;
}
if (error.constraint === 'lobby_accounts_sectors_sector_id_local_username_key') {
const customErr = new Error('Local username taken');
customErr.code = 'ERR_LOCAL_USERNAME_TAKEN';
throw customErr;
}
}
throw error;
} finally {
client.release();
}
};