import models from '../models/index.js'; import BaseService from './BaseService.js'; const { TaxiHighscore, User, TaxiMap } = models; class TaxiHighscoreService extends BaseService { constructor() { super(); this.model = TaxiHighscore; } /** * Speichert oder aktualisiert einen Highscore-Eintrag * Jeder User kann nur einen Eintrag pro Map haben (der beste wird gespeichert) * @param {Object} highscoreData - Die Highscore-Daten * @param {number} highscoreData.userId - ID des Users * @param {string} highscoreData.nickname - Nickname des Users * @param {number} highscoreData.passengersDelivered - Anzahl abgelieferter Passagiere * @param {number} highscoreData.playtime - Spielzeit in Sekunden * @param {number} highscoreData.points - Erreichte Punkte * @param {number} highscoreData.mapId - ID der Map * @param {string} highscoreData.mapName - Name der Map * @returns {Promise} Gespeicherter oder aktualisierter Highscore-Eintrag */ async createHighscore(highscoreData) { try { // Prüfen ob bereits ein Eintrag für diesen User und diese Map existiert const existingHighscore = await this.model.findOne({ where: { userId: highscoreData.userId, mapId: highscoreData.mapId } }); if (existingHighscore) { // Nur aktualisieren wenn der neue Score besser ist (mehr Punkte) if (highscoreData.points > existingHighscore.points) { await existingHighscore.update({ nickname: highscoreData.nickname, passengersDelivered: highscoreData.passengersDelivered, playtime: highscoreData.playtime, points: highscoreData.points, mapName: highscoreData.mapName }); return existingHighscore; } else { // Neuer Score ist nicht besser, existierenden zurückgeben return existingHighscore; } } else { // Kein existierender Eintrag, neuen erstellen const highscore = await this.model.create({ userId: highscoreData.userId, nickname: highscoreData.nickname, passengersDelivered: highscoreData.passengersDelivered, playtime: highscoreData.playtime, points: highscoreData.points, mapId: highscoreData.mapId, mapName: highscoreData.mapName }); return highscore; } } catch (error) { console.error('Fehler beim Erstellen/Aktualisieren des Highscores:', error); throw error; } } /** * Holt die Top-Highscores für eine bestimmte Map * @param {number} mapId - ID der Map (optional) * @param {number} limit - Anzahl der Einträge (Standard: 10) * @param {string} orderBy - Sortierung ('points', 'passengersDelivered', 'playtime') * @returns {Promise} Array der Highscore-Einträge */ async getTopHighscores(mapId = null, limit = 10, orderBy = 'points') { try { const whereClause = mapId ? { mapId } : {}; const highscores = await this.model.findAll({ where: whereClause, include: [ { model: User, as: 'user', attributes: ['id', 'username'], required: false // LEFT JOIN, da User gelöscht sein könnte }, { model: TaxiMap, as: 'map', attributes: ['id', 'name'], required: false } ], order: [[orderBy, 'DESC']], limit: parseInt(limit) }); return highscores; } catch (error) { console.error('Fehler beim Laden der Highscores:', error); throw error; } } /** * Holt die persönlichen Bestleistungen eines Users * @param {number} userId - ID des Users * @param {number} mapId - ID der Map (optional) * @returns {Promise} Bestleistungen des Users */ async getUserBestScores(userId, mapId = null) { try { const whereClause = { userId }; if (mapId) { whereClause.mapId = mapId; } const [bestPoints, bestPassengers, bestPlaytime] = await Promise.all([ this.model.findOne({ where: whereClause, order: [['points', 'DESC']] }), this.model.findOne({ where: whereClause, order: [['passengersDelivered', 'DESC']] }), this.model.findOne({ where: whereClause, order: [['playtime', 'DESC']] }) ]); return { bestPoints, bestPassengers, bestPlaytime }; } catch (error) { console.error('Fehler beim Laden der User-Bestleistungen:', error); throw error; } } /** * Holt alle Highscores eines Users * @param {number} userId - ID des Users * @param {number} limit - Anzahl der Einträge (Standard: 20) * @returns {Promise} Array der Highscore-Einträge des Users */ async getUserHighscores(userId, limit = 20) { try { const highscores = await this.model.findAll({ where: { userId }, include: [ { model: TaxiMap, as: 'map', attributes: ['id', 'name'] } ], order: [['createdAt', 'DESC']], limit: parseInt(limit) }); return highscores; } catch (error) { console.error('Fehler beim Laden der User-Highscores:', error); throw error; } } /** * Holt die Rangliste-Position eines Users für eine bestimmte Map * @param {number} userId - ID des Users * @param {number} mapId - ID der Map (optional) * @param {string} orderBy - Sortierung ('points', 'passengersDelivered', 'playtime') * @returns {Promise} Rangliste-Position (1-basiert) */ async getUserRank(userId, mapId = null, orderBy = 'points') { try { const whereClause = mapId ? { mapId } : {}; const userHighscore = await this.model.findOne({ where: { userId, ...whereClause }, order: [[orderBy, 'DESC']] }); if (!userHighscore) { return null; // User hat noch keinen Highscore } const rank = await this.model.count({ where: { ...whereClause, [orderBy]: { [this.model.sequelize.Sequelize.Op.gt]: userHighscore[orderBy] } } }); return rank + 1; // 1-basierte Position } catch (error) { console.error('Fehler beim Berechnen der User-Rangliste:', error); throw error; } } /** * Löscht alle Highscores eines Users (z.B. bei Account-Löschung) * @param {number} userId - ID des Users * @returns {Promise} Anzahl der gelöschten Einträge */ async deleteUserHighscores(userId) { try { const deletedCount = await this.model.destroy({ where: { userId } }); return deletedCount; } catch (error) { console.error('Fehler beim Löschen der User-Highscores:', error); throw error; } } /** * Holt Statistiken über die Highscores * @returns {Promise} Statistiken */ async getHighscoreStats() { try { const [ totalHighscores, totalPlayers, averagePoints, totalPassengersDelivered, totalPlaytime ] = await Promise.all([ this.model.count(), this.model.count({ distinct: true, col: 'userId' }), this.model.findOne({ attributes: [ [this.model.sequelize.fn('AVG', this.model.sequelize.col('points')), 'avg'] ], raw: true }), this.model.sum('passengersDelivered'), this.model.sum('playtime') ]); return { totalHighscores, totalPlayers, averagePoints: averagePoints ? parseFloat(averagePoints.avg).toFixed(2) : 0, totalPassengersDelivered: totalPassengersDelivered || 0, totalPlaytime: totalPlaytime || 0 }; } catch (error) { console.error('Fehler beim Laden der Highscore-Statistiken:', error); throw error; } } } export default new TaxiHighscoreService();