Bleibende Sektor Elemente für UI fix

This commit is contained in:
2026-06-26 11:09:03 +02:00
parent 7a39007712
commit 47552ea448
79 changed files with 5526 additions and 190 deletions
@@ -34,129 +34,34 @@ export const getSectorsList = async (req, res) => {
* @param {import('express').Response} res - Das Express Response-Objekt
*/
export const joinSector = async (req, res) => {
const accountId = req.user.accountId;
const { sectorId, localUsername, gender } = req.body;
const lobby_uuid = req.user.lobby_uuid;
const { sector_id } = req.body;
if (!sector_id) {
// Basis-Validierung: Fehlt ein Feld, brechen wir ab
if (!sectorId || !localUsername || !gender) {
return res.status(400).json({
error: 'ERR_MISSING_SECTOR_ID'
error: 'ERR_MISSING_FIELDS'
});
}
const client = await pool.connect();
try {
// Spieler-Daten aus der Lobby-Datenbank laden
const userQuery = await client.query(`
SELECT username, gender
FROM lobby_accounts
WHERE lobby_uuid = $1
`, [lobby_uuid]);
if (userQuery.rows.length === 0) {
return res.status(404).json({
error: 'ERR_USER_NOT_FOUND'
});
}
const { username, gender } = userQuery.rows[0];
// Sektor-Daten laden, um die korrekte interne URL zu erfahren
const sectorQuery = await client.query(`
SELECT internal_url, registration_open
FROM sectors
WHERE id = $1
`, [sector_id]);
if (sectorQuery.rows.length === 0) {
return res.status(404).json({
error: 'ERR_SECTOR_NOT_FOUND'
});
}
const sector = sectorQuery.rows[0];
// Sicherheits-Check: Ist die Registrierung für diesen Sektor noch offen?
if (!sector.registration_open) {
return res.status(403).json({
error: 'ERR_SECTOR_REGISTRATION_CLOSED'
});
}
// Den internen POST-Request an das Sektor-Backend feuern
const sectorApiUrl = `${sector.internal_url}/api/internal/accounts`;
await sectorService.joinSector(accountId, sectorId, localUsername, gender);
const response = await fetch(sectorApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
lobby_uuid,
username,
gender
})
});
// Fehlerbehandlung bei negativen HTTP-Statuscodes
// fetch springt bei HTTP-Fehlern nicht in den catch-Block, daher prüfen wir response.ok
if (!response.ok) {
const errorData = await response.json();
const sectorError = errorData.error;
// Wenn der Sektor exakt in diesem Moment voll geworden ist
if (sectorError === 'ERR_SECTOR_FULL') {
// Sektor sicherheitshalber in der Lobby sofort schließen
await client.query(`
UPDATE sectors SET registration_open = false WHERE id = $1
`, [sector_id]);
return res.status(503).json({
error: 'ERR_SECTOR_FULL'
});
}
if (sectorError === 'ERR_ACCOUNT_ALREADY_EXISTS') {
return res.status(409).json({
error: 'ERR_ACCOUNT_ALREADY_EXISTS'
});
}
throw new Error(`Sektor-Backend meldet unerwarteten Fehler: ${sectorError}`);
}
const responseData = await response.json();
const { triggerSectorClose } = responseData;
await client.query('BEGIN');
await client.query(`
INSERT INTO account_sectors (lobby_uuid, sector_id)
VALUES ($1, $2)
`, [lobby_uuid, sector_id]);
// Auswertung der Notfall-Schließung
// Wenn der Sektor uns meldet, dass das Puffer-Limit erreicht ist,
// machen wir ihn dicht, damit er im Frontend verschwindet.
if (triggerSectorClose) {
console.warn(`[Lobby] Sektor ${sector_id} meldet Voll-Auslastung. Schließe Registrierung...`);
await client.query(`
UPDATE sectors
SET registration_open = false
WHERE id = $1
`, [sector_id]);
}
await client.query('COMMIT');
return res.status(200).json({
message: 'MSG_SECTOR_JOINED_SUCCESSFULLY'
message: 'MSG_SECTOR_JOINED_SUCCESS'
});
} catch (error) {
await client.query('ROLLBACK');
console.error('[Lobby] Fehler beim Beitritt zum Sektor:', error.message);
return res.status(500).json({ error: 'ERR_INTERNAL_SERVER_ERROR' });
} finally {
client.release();
console.error('[SectorController] Fehler beim Beitritt zum Sektor:', error.message);
if (error.code) {
return res.status(error.code === 'ERR_SECTOR_FULL' ? 503 : 409).json({
error: error.code
});
}
return res.status(500).json({
error: 'ERR_INTERNAL_SERVER'
});
}
};
@@ -21,9 +21,9 @@ export const initLobbyCronJobs = () => {
try {
// Alle Sektoren finden, deren Registrierung aktuell geschlossen ist
const closedSectorsQuery = await client.query(`
SELECT id, internal_url, name
SELECT id, name, api_url
FROM sectors
WHERE registration_open = false
WHERE is_registration_open = false
`);
const closedSectors = closedSectorsQuery.rows;
@@ -36,7 +36,7 @@ export const initLobbyCronJobs = () => {
// Jeden geschlossenen Sektor einzeln über das Netzwerk abfragen
for (const sector of closedSectors) {
try {
const response = await fetch(`${sector.internal_url}/api/internal/slots`);
const response = await fetch(`${sector.api_url}/api/internal/slots`);
if (!response.ok) {
console.warn(`⚠️ [CRON] Sektor ${sector.name} hat einen Fehler gemeldet (Status: ${response.status}). Überspringe.`);
@@ -53,7 +53,7 @@ export const initLobbyCronJobs = () => {
await client.query(`
UPDATE sectors
SET registration_open = true
SET is_registration_open = true
WHERE id = $1
`, [sector.id]);
} else {
@@ -4,11 +4,21 @@ import pool from '../db.js';
// Void-Genesis - Heartbeat Service
// ==============================================================================
// Dieser Service pingt alle registrierten Sektoren regelmäßig an.
export const pingSectors = async () => {
const client = await pool.connect();
try {
const result = await client.query('SELECT id, name, api_url FROM sectors WHERE api_url IS NOT NULL');
const result = await client.query(`
SELECT
id, name, api_url, status,
online_players, current_players, planets_count,
max_galaxies, speed_economy, research_speed,
fleet_speed_hostile, fleet_speed_peaceful, fleet_speed_friendly,
fuel_consumption_factor, fleet_to_debris_percentage,
defense_to_debris_percentage, fuel_included_in_debris,
initial_res_premium, bonus_fields
FROM sectors
WHERE api_url IS NOT NULL
`);
const sectors = result.rows;
for (const sector of sectors) {
@@ -23,63 +33,67 @@ export const pingSectors = async () => {
if (response.ok) {
const data = await response.json();
if (data.status === 'ok') {
await client.query(
`UPDATE sectors SET
status = 'online',
online_players = $1,
current_players = $2,
planets_count = $3,
max_galaxies = $4,
speed_economy = $5,
research_speed = $6,
fleet_speed_hostile = $7,
fleet_speed_peaceful = $8,
fleet_speed_friendly = $9,
fuel_consumption_factor = $10,
fleet_to_debris_percentage = $11,
defense_to_debris_percentage = $12,
fuel_included_in_debris = $13,
initial_res_premium = $14,
bonus_fields = $15
WHERE id = $16`,
[
data.stats.online_players,
data.stats.total_players,
data.stats.planets_count,
data.config.max_galaxies,
data.config.speed_economy,
data.config.research_speed,
data.config.fleet_speed_hostile,
data.config.fleet_speed_peaceful,
data.config.fleet_speed_friendly,
data.config.fuel_consumption_factor,
data.config.fleet_to_debris_percentage,
data.config.defense_to_debris_percentage,
data.config.fuel_included_in_debris,
data.config.initial_res_premium,
data.config.bonus_fields,
sector.id
]
);
const hasChanged = checkSectorChanges(sector, data);
if (hasChanged) {
await client.query(
`UPDATE sectors SET
status = 'online',
online_players = $1,
current_players = $2,
planets_count = $3,
max_galaxies = $4,
speed_economy = $5,
research_speed = $6,
fleet_speed_hostile = $7,
fleet_speed_peaceful = $8,
fleet_speed_friendly = $9,
fuel_consumption_factor = $10,
fleet_to_debris_percentage = $11,
defense_to_debris_percentage = $12,
fuel_included_in_debris = $13,
initial_res_premium = $14,
bonus_fields = $15
WHERE id = $16`,
[
data.stats.online_players,
data.stats.total_players,
data.stats.planets_count,
data.config.max_galaxies,
data.config.speed_economy,
data.config.research_speed,
data.config.fleet_speed_hostile,
data.config.fleet_speed_peaceful,
data.config.fleet_speed_friendly,
data.config.fuel_consumption_factor,
data.config.fleet_to_debris_percentage,
data.config.defense_to_debris_percentage,
data.config.fuel_included_in_debris,
data.config.initial_res_premium,
data.config.bonus_fields,
sector.id
]
);
}
} else {
if (sector.name === 'Development') {
console.warn(`[Heartbeat-Trace] ⚠️ Fehler: JSON-Status war nicht 'ok', sondern:`, data.status);
}
await setSectorOffline(client, sector.id);
await setSectorOffline(client, sector);
}
} else {
if (sector.name === 'Development') {
console.warn(`[Heartbeat-Trace] ⚠️ HTTP-Response nicht OK! Status-Code:`, response.status);
}
await setSectorOffline(client, sector.id);
await setSectorOffline(client, sector);
}
} catch (error) {
if (sector.name === 'Development') {
console.error(`[Heartbeat-Trace] ❌ Crash bei Sektor 'Development'. Grund:`, error.message);
}
await setSectorOffline(client, sector.id);
await setSectorOffline(client, sector);
}
}
} catch (error) {
@@ -90,11 +104,55 @@ export const pingSectors = async () => {
};
/**
* Hilfsfunktion, um den Status eines Sektors auf 'offline' zu setzen.
* Hilfsfunktion zum Vergleichen des alten DB-Stands mit den frischen API-Daten.
* Wir nutzen Number() und Boolean(), da PostgreSQL manche Datentypen
* (wie NUMERIC oder BIGINT) im Node.js-Treiber als String zurückgibt.
*/
const setSectorOffline = async (client, sectorId) => {
await client.query(
"UPDATE sectors SET status = 'offline' WHERE id = $1",
[sectorId]
);
const checkSectorChanges = (oldData, newData) => {
// Wenn der Sektor vorher als offline oder in Wartung markiert war,
// muss er definitiv geupdatet werden, da er jetzt wieder erreichbar ist.
if (oldData.status !== 'online') return true;
// Spielerzahlen und Planeten prüfen
if (Number(oldData.online_players) !== newData.stats.online_players) return true;
if (Number(oldData.current_players) !== newData.stats.total_players) return true;
if (Number(oldData.planets_count) !== newData.stats.planets_count) return true;
// Globale Sektor-Einstellungen auf Änderungen prüfen
if (Number(oldData.max_galaxies) !== newData.config.max_galaxies) return true;
if (Number(oldData.speed_economy) !== newData.config.speed_economy) return true;
if (Number(oldData.research_speed) !== newData.config.research_speed) return true;
if (Number(oldData.fleet_speed_hostile) !== newData.config.fleet_speed_hostile) return true;
if (Number(oldData.fleet_speed_peaceful) !== newData.config.fleet_speed_peaceful) return true;
if (Number(oldData.fleet_speed_friendly) !== newData.config.fleet_speed_friendly) return true;
// Float-Werte
if (Number(oldData.fuel_consumption_factor) !== Number(newData.config.fuel_consumption_factor)) return true;
if (Number(oldData.fleet_to_debris_percentage) !== newData.config.fleet_to_debris_percentage) return true;
if (Number(oldData.defense_to_debris_percentage) !== newData.config.defense_to_debris_percentage) return true;
// Boolean-Werte
if (Boolean(oldData.fuel_included_in_debris) !== Boolean(newData.config.fuel_included_in_debris)) return true;
// Start-Boni
if (Number(oldData.initial_res_premium) !== newData.config.initial_res_premium) return true;
if (Number(oldData.bonus_fields) !== newData.config.bonus_fields) return true;
// Wurde kein Unterschied gefunden, geben wir false zurück
return false;
};
/**
* Setzt den Status eines Sektors auf 'offline'.
* Prüft vorher, ob der Sektor nicht ohnehin schon als offline in der DB steht,
* um auch hier sinnlose Schreibzugriffe und Checkpoint-Logs zu verhindern.
*/
const setSectorOffline = async (client, sector) => {
if (sector.status !== 'offline') {
await client.query(
"UPDATE sectors SET status = 'offline' WHERE id = $1",
[sector.id]
);
sector.status = 'offline';
}
};
+30 -21
View File
@@ -591,32 +591,40 @@ function renderSectorList(sectors, type) {
`;
cardLink.onclick = (e) => {
e.preventDefault();
console.log(`Starte Onboarding für Sektor: ${sector.name} (ID: ${sector.id})`);
selectedSectorId = sector.id;
if (type === 'my') {
console.log(`Betrete bekannten Sektor: ${sector.name}`);
// todo für später, hier muss dann variable der link stehen oder man müsste ihn aus der datenbank auslesen
window.open('https://dev.void-genesis.de', '_blank');
} else {
console.log(`Starte Onboarding für Sektor: ${sector.name} (ID: ${sector.id})`);
const sectorBrowserLayer = document.getElementById('sector-browser-layer');
if (sectorBrowserLayer) {
sectorBrowserLayer.style.opacity = 0.05;
sectorBrowserLayer.style.pointerEvents = 'none';
}
selectedSectorId = sector.id;
const onboardingHtmlLayer = document.getElementById('sector-onboarding-layer');
if (onboardingHtmlLayer) onboardingHtmlLayer.style.display = 'flex';
if (onboardingPixiContainer) onboardingPixiContainer.visible = true;
const sectorBrowserLayer = document.getElementById('sector-browser-layer');
if (sectorBrowserLayer) {
sectorBrowserLayer.style.opacity = 0.05;
sectorBrowserLayer.style.pointerEvents = 'none';
}
const localUsernameInput = document.getElementById('local-username');
if (localUsernameInput) {
const userDataStr = sessionStorage.getItem('void_user') || localStorage.getItem('void_user');
if (userDataStr) {
try {
const userData = JSON.parse(userDataStr);
if (userData.username) {
localUsernameInput.value = userData.username;
const onboardingHtmlLayer = document.getElementById('sector-onboarding-layer');
if (onboardingHtmlLayer) onboardingHtmlLayer.style.display = 'flex';
if (onboardingPixiContainer) onboardingPixiContainer.visible = true;
const localUsernameInput = document.getElementById('local-username');
if (localUsernameInput) {
const userDataStr = sessionStorage.getItem('void_user') || localStorage.getItem('void_user');
if (userDataStr) {
try {
const userData = JSON.parse(userDataStr);
if (userData.username) {
localUsernameInput.value = userData.username;
}
} catch (err) {
console.error("Fehler beim Auslesen der User-Daten für das Onboarding:", err);
}
} catch (err) {
console.error("Fehler beim Auslesen der User-Daten für das Onboarding:", err);
}
}
}
@@ -971,6 +979,7 @@ if (onboardingForm) {
if (btnCancelOnboarding) btnCancelOnboarding.click();
loadAndRenderSectorBrowser();
window.open('https://dev.void-genesis.de', '_blank');
}, 1500);
} catch (error) {