Planetengenerierung
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import cron from 'node-cron';
|
||||
import pool from '../db.js';
|
||||
|
||||
/**
|
||||
* Initialisiert alle zeitgesteuerten Hintergrund-Aufgaben (Cronjobs) für die Lobby.
|
||||
* Wird einmalig beim Serverstart aufgerufen.
|
||||
*/
|
||||
export const initLobbyCronJobs = () => {
|
||||
console.log('⏰ Initialisiere Lobby Cronjobs...');
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// CRON: Prüfung geschlossener Sektoren auf freie Kapazitäten
|
||||
// Format: 'Minute Stunde Tag Monat Wochentag'
|
||||
// '0 4 * * *' bedeutet: Jeden Tag exakt um 04:00 Uhr nachts.
|
||||
// --------------------------------------------------------------------------
|
||||
cron.schedule('0 4 * * *', async () => {
|
||||
console.log('🔍 [CRON] Prüfe geschlossene Sektoren auf freie Startplätze...');
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
// Alle Sektoren finden, deren Registrierung aktuell geschlossen ist
|
||||
const closedSectorsQuery = await client.query(`
|
||||
SELECT id, internal_url, name
|
||||
FROM sectors
|
||||
WHERE registration_open = false
|
||||
`);
|
||||
|
||||
const closedSectors = closedSectorsQuery.rows;
|
||||
|
||||
if (closedSectors.length === 0) {
|
||||
console.log('✅ [CRON] Keine geschlossenen Sektoren gefunden. Nichts zu tun.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Jeden geschlossenen Sektor einzeln über das Netzwerk abfragen
|
||||
for (const sector of closedSectors) {
|
||||
try {
|
||||
const response = await fetch(`${sector.internal_url}/api/internal/slots`);
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`⚠️ [CRON] Sektor ${sector.name} hat einen Fehler gemeldet (Status: ${response.status}). Überspringe.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const freeSlots = data.freeSlots;
|
||||
|
||||
// Auswertung der freien Plätze
|
||||
// Wenn der Sektor wieder mindestens 150 freie Plätze hat, machen wir ihn auf.
|
||||
if (freeSlots >= 150) {
|
||||
console.log(`🔓 [CRON] Sektor ${sector.name} hat ${freeSlots} freie Plätze. Öffne Sektor für Neuregistrierungen!`);
|
||||
|
||||
await client.query(`
|
||||
UPDATE sectors
|
||||
SET registration_open = true
|
||||
WHERE id = $1
|
||||
`, [sector.id]);
|
||||
} else {
|
||||
console.log(`🔒 [CRON] Sektor ${sector.name} hat nur ${freeSlots} freie Plätze (unter 150). Bleibt geschlossen.`);
|
||||
}
|
||||
|
||||
} catch (fetchError) {
|
||||
console.warn(`⚠️ [CRON] Netzwerkfehler bei Abfrage von Sektor ${sector.name}:`, fetchError.message);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [CRON] Kritischer Datenbank-Fehler beim Prüfen der Sektoren:', error);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
// === File: lobby-backend/src/services/heartbeat.service.js ===
|
||||
|
||||
import pool from '../db.js';
|
||||
|
||||
// ==============================================================================
|
||||
@@ -9,49 +7,77 @@ import pool from '../db.js';
|
||||
|
||||
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 sectors = result.rows;
|
||||
|
||||
for (const sector of sectors) {
|
||||
try {
|
||||
// Wir nutzen einen AbortController, um blockierende Anfragen
|
||||
// nach 3 Sekunden hart abzubrechen (Timeout).
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
||||
|
||||
// Nativer, sauberer fetch-Aufruf (Dank NODE_OPTIONS in Docker nun bugfrei)
|
||||
const response = await fetch(sector.api_url + '/api/health', {
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
if (data.status === 'ok') {
|
||||
// Sektor antwortet korrekt -> Status auf online setzen
|
||||
await client.query(
|
||||
"UPDATE sectors SET status = 'online' WHERE id = $1",
|
||||
[sector.id]
|
||||
`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
|
||||
]
|
||||
);
|
||||
|
||||
// Log-Ausgabe nur für unseren aktiven Entwicklungs-Sektor,
|
||||
// um die Konsole sauber zu halten.
|
||||
} else {
|
||||
if (sector.name === 'Development') {
|
||||
//console.log(`[Heartbeat] ✅ Sektor '${sector.name}' ist online!`);
|
||||
console.warn(`[Heartbeat-Trace] ⚠️ Fehler: JSON-Status war nicht 'ok', sondern:`, data.status);
|
||||
}
|
||||
await setSectorOffline(client, sector.id);
|
||||
}
|
||||
} else {
|
||||
// Server erreichbar, liefert aber keinen 2xx Status
|
||||
if (sector.name === 'Development') {
|
||||
console.warn(`[Heartbeat-Trace] ⚠️ HTTP-Response nicht OK! Status-Code:`, response.status);
|
||||
}
|
||||
await setSectorOffline(client, sector.id);
|
||||
}
|
||||
} catch (error) {
|
||||
// Sektor gar nicht erreichbar (Container down, Netzwerkfehler, Timeout)
|
||||
if (sector.name === 'Development') {
|
||||
console.error(`[Heartbeat] ❌ Sektor '${sector.name}' nicht erreichbar:`, error.message);
|
||||
console.error(`[Heartbeat-Trace] ❌ Crash bei Sektor 'Development'. Grund:`, error.message);
|
||||
}
|
||||
await setSectorOffline(client, sector.id);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,18 @@ import pool from '../db.js';
|
||||
*/
|
||||
export const getSectorsList = async (accountId) => {
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
// WURDE ERWEITERT: Wir selektieren nun die echten Cache-Werte (online_players, planets_count)
|
||||
// sowie Konfigurationswerte (initial_res_premium, fleet_speed_hostile) und das Erstelldatum (created_at).
|
||||
const mySectorsResult = await client.query(`
|
||||
SELECT
|
||||
s.id, s.name, s.status, s.speed_fleet, s.speed_economy,
|
||||
s.id, s.name, s.status, s.is_registration_open,
|
||||
s.online_players, s.current_players, s.planets_count, s.created_at,
|
||||
s.speed_economy, s.research_speed, s.max_galaxies,
|
||||
s.fleet_speed_hostile, s.fleet_speed_peaceful, s.fleet_speed_friendly,
|
||||
s.fuel_consumption_factor, s.fleet_to_debris_percentage,
|
||||
s.defense_to_debris_percentage, s.fuel_included_in_debris,
|
||||
s.initial_res_premium, s.bonus_fields,
|
||||
las.local_username, las.gender,
|
||||
las.created_at as joined_at,
|
||||
las.updated_at as last_played
|
||||
@@ -27,31 +34,35 @@ export const getSectorsList = async (accountId) => {
|
||||
|
||||
const newSectorsResult = await client.query(`
|
||||
SELECT
|
||||
id, name, status, speed_fleet, speed_economy
|
||||
id, name, status, is_registration_open,
|
||||
online_players, current_players, planets_count, created_at,
|
||||
speed_economy, research_speed, max_galaxies,
|
||||
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 id NOT IN (
|
||||
SELECT sector_id
|
||||
FROM lobby_accounts_sectors
|
||||
WHERE lobby_account_id = $1
|
||||
)
|
||||
-- Wir zeigen bei den "Neuen Sektoren" nur die an,
|
||||
-- bei denen die Registrierung noch nicht serverseitig geschlossen wurde.
|
||||
AND is_registration_open = true
|
||||
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
|
||||
});
|
||||
|
||||
// ALTE LOGIK ENTFERNT: Wir brauchen kein mapSectorData mit Math.random() mehr!
|
||||
// Wir geben die echten Datenbank-Ergebnisse direkt ans Frontend weiter.
|
||||
return {
|
||||
mySectors: mySectorsResult.rows.map(mapSectorData),
|
||||
newSectors: newSectorsResult.rows.map(mapSectorData)
|
||||
mySectors: mySectorsResult.rows,
|
||||
newSectors: newSectorsResult.rows
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('[SectorService] Fehler beim Auslesen der Sektordaten:', error);
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
@@ -59,8 +70,8 @@ export const getSectorsList = async (accountId) => {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Komuniziert zuest mit dem Ziel-Sektor und speichert den Psielstand in der lobby erst,
|
||||
* wenn des Sektor-Backend den Account erfolgreich akzeptiert hat.
|
||||
*
|
||||
* @param {string} accountId - Die UUID des Nutzers
|
||||
* @param {string} sectorId - Die UUID des Sektors
|
||||
@@ -73,6 +84,61 @@ export const joinSector = async (accountId, sectorId, localUsername, gender) =>
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
const sectorQuery = await client.query(
|
||||
'SELECT name, api_url, status FROM sectors WHERE id = $1',
|
||||
[sectorId]
|
||||
);
|
||||
|
||||
if (sectorQuery.rows.length === 0) {
|
||||
const customErr = new Error('Sector not found');
|
||||
customErr.code = 'ERR_SECTOR_NOT_FOUND';
|
||||
throw customErr;
|
||||
}
|
||||
|
||||
const sector = sectorQuery.rows[0];
|
||||
|
||||
if (sector.status !== 'online') {
|
||||
const customErr = new Error('Sector is offline or under maintenance');
|
||||
customErr.code = 'ERR_SECTOR_OFFLINE';
|
||||
throw customErr;
|
||||
}
|
||||
|
||||
// Ziel-Endpunkt des Sektors: POST /api/internal/accounts
|
||||
const sectorInternalUrl = `${sector.api_url}/api/internal/accounts`;
|
||||
|
||||
console.log(`[Lobby-to-Server] Sende Registierung für '${localUsername}' an Sektor '${sector.name}' (${sectorInternalUrl})...`);
|
||||
|
||||
const response = await fetch(sectorInternalUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
lobby_uuid: accountId,
|
||||
username: localUsername,
|
||||
gender: gender
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
console.error(`[Lobby-to-Server] Sektor meldet Fehler (Status ${response.status}):`, errorData);
|
||||
|
||||
if (response.status == 409) {
|
||||
const customErr = new Error(errorData.error || 'Conflict in Sector');
|
||||
customErr.code = errorData.error || 'ERR_LOCAL_USERNAME_TAKEN';
|
||||
throw customErr;
|
||||
}
|
||||
|
||||
const customErr = new Error('Sector communication failed');
|
||||
customErr.code = 'ERR_SECTOR_CONNECTION_FAILED';
|
||||
throw customErr;
|
||||
}
|
||||
|
||||
const sectorResultData = await response.json();
|
||||
console.log(`[Lobby-to-Server] ✅ Sektor-Account erfolgreich angelegt! Local-UUID: ${sectorResultData.account?.local_uuid}`);
|
||||
|
||||
|
||||
// Wir versuchen, den Datensatz direkt einzufügen.
|
||||
await client.query(`
|
||||
INSERT INTO lobby_accounts_sectors (lobby_account_id, sector_id, local_username, gender)
|
||||
|
||||
Reference in New Issue
Block a user