/* ============================================================ routes/gildenhalle.route.js ============================================================ */ 'use strict'; const express = require('express'); const router = express.Router(); const db = require('../database/database'); function requireLogin(req, res, next) { if (!req.session?.user) return res.status(401).json({ error: 'Nicht eingeloggt' }); next(); } /* ── Hilfsfunktion: Spieler-Gilde laden ────────────────── */ async function getPlayerGuild(userId) { const [[row]] = await db.query( `SELECT g.*, gm.rank_id, gr.name AS rank_name, gr.sort_order, gr.can_invite, gr.can_kick, gr.can_manage_ranks, gr.can_manage_tasks FROM guild_members gm JOIN guilds g ON g.id = gm.guild_id LEFT JOIN guild_ranks gr ON gr.id = gm.rank_id WHERE gm.user_id = ?`, [userId] ); return row || null; } /* ── Standard-Ränge für neue Gilde anlegen ─────────────── */ async function createDefaultRanks(guildId) { const ranks = [ { name: 'Gildenmeister', sort_order: 1, can_invite: 1, can_kick: 1, can_manage_ranks: 1, can_manage_tasks: 1 }, { name: 'Stellv. Gildenmeister', sort_order: 2, can_invite: 1, can_kick: 1, can_manage_ranks: 0, can_manage_tasks: 1 }, { name: 'Veteran', sort_order: 3, can_invite: 1, can_kick: 0, can_manage_ranks: 0, can_manage_tasks: 0 }, { name: 'Mitglied', sort_order: 99, can_invite: 0, can_kick: 0, can_manage_ranks: 0, can_manage_tasks: 0 }, ]; const ids = []; for (const r of ranks) { const [res] = await db.query( `INSERT INTO guild_ranks (guild_id, name, sort_order, can_invite, can_kick, can_manage_ranks, can_manage_tasks) VALUES (?, ?, ?, ?, ?, ?, ?)`, [guildId, r.name, r.sort_order, r.can_invite, r.can_kick, r.can_manage_ranks, r.can_manage_tasks] ); ids.push({ name: r.name, id: res.insertId }); } return ids; } /* ── Tagesaufgaben für Gilde generieren ────────────────── */ async function ensureDailyTasks(guildId) { const today = new Date().toISOString().slice(0, 10); const [existing] = await db.query( 'SELECT id FROM guild_tasks WHERE guild_id = ? AND expires_at = ?', [guildId, today] ); if (existing.length > 0) return; const [templates] = await db.query( 'SELECT * FROM guild_task_templates WHERE active = 1' ); for (const t of templates) { await db.query( `INSERT IGNORE INTO guild_tasks (guild_id, task_key, label, target_amount, reward_type, reward_amount, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, [guildId, t.task_key, t.label, t.target_amount, t.reward_type, t.reward_amount, today] ); } } /* ════════════════════════════════════════════ GET /api/gildenhalle/list?search=&page=1 Alle Gilden auflisten ════════════════════════════════════════════ */ router.get('/gildenhalle/list', requireLogin, async (req, res) => { const search = req.query.search?.trim() || ''; const page = Math.max(1, parseInt(req.query.page) || 1); const limit = 12; const offset = (page - 1) * limit; const userId = req.session.user.id; try { const searchParam = search ? `%${search}%` : '%'; const [guilds] = await db.query( `SELECT g.id, g.name, g.tag, g.description, g.level, g.open, g.max_members, COUNT(gm.id) AS member_count, a.ingame_name AS leader_name FROM guilds g LEFT JOIN guild_members gm ON gm.guild_id = g.id LEFT JOIN accounts a ON a.id = g.leader_id WHERE g.name LIKE ? OR g.tag LIKE ? GROUP BY g.id ORDER BY g.level DESC, member_count DESC LIMIT ? OFFSET ?`, [searchParam, searchParam, limit, offset] ); const [[{ total }]] = await db.query( 'SELECT COUNT(*) AS total FROM guilds WHERE name LIKE ? OR tag LIKE ?', [searchParam, searchParam] ); // Offene Anfrage des Spielers prüfen const [myRequests] = await db.query( "SELECT guild_id FROM guild_join_requests WHERE user_id = ? AND status = 'pending'", [userId] ); const pendingIds = new Set(myRequests.map(r => r.guild_id)); const playerGuild = await getPlayerGuild(userId); // Gildenmeister und Stellv. Gildenmeister dürfen keine weitere Gilde gründen const leaderRanks = ['Gildenmeister', 'Stellv. Gildenmeister']; const isGuildLeader = playerGuild ? (playerGuild.leader_id === userId || leaderRanks.includes(playerGuild.rank_name)) : false; res.json({ guilds: guilds.map(g => ({ ...g, has_pending_request: pendingIds.has(g.id), })), total, totalPages: Math.ceil(total / limit), page, playerGuildId: playerGuild?.id || null, isGuildLeader, }); } catch (err) { console.error('[gildenhalle/list]', err); res.status(500).json({ error: 'DB Fehler' }); } }); /* ════════════════════════════════════════════ POST /api/gildenhalle/create Neue Gilde gründen ════════════════════════════════════════════ */ router.post('/gildenhalle/create', requireLogin, async (req, res) => { const userId = req.session.user.id; const { name, tag, description, open } = req.body; if (!name?.trim() || !tag?.trim()) return res.status(400).json({ error: 'Name und Tag sind Pflichtfelder.' }); if (tag.length < 2 || tag.length > 6) return res.status(400).json({ error: 'Tag muss 2–6 Zeichen lang sein.' }); try { const existing = await getPlayerGuild(userId); if (existing) { // Gildenmeister oder Rang mit can_manage_ranks dürfen keine neue Gilde gründen const leaderRanks = ['Gildenmeister', 'Stellv. Gildenmeister']; if (existing.leader_id === userId || leaderRanks.includes(existing.rank_name)) { return res.status(403).json({ error: 'Gildenmeister und Stellv. Gildenmeister können keine weitere Gilde gründen.' }); } return res.status(400).json({ error: 'Du bist bereits in einer Gilde.' }); } const [result] = await db.query( `INSERT INTO guilds (name, tag, description, leader_id, open) VALUES (?, ?, ?, ?, ?)`, [name.trim(), tag.trim().toUpperCase(), description?.trim() || null, userId, open ? 1 : 0] ); const guildId = result.insertId; // Standard-Ränge anlegen const rankIds = await createDefaultRanks(guildId); const leaderRankId = rankIds.find(r => r.name === 'Gildenmeister')?.id; // Gründer als Mitglied mit höchstem Rang eintragen await db.query( 'INSERT INTO guild_members (guild_id, user_id, rank_id) VALUES (?, ?, ?)', [guildId, userId, leaderRankId] ); // Erste Tagesaufgaben generieren await ensureDailyTasks(guildId); res.json({ success: true, guildId }); } catch (err) { if (err.code === 'ER_DUP_ENTRY') return res.status(400).json({ error: 'Gildenname oder Tag bereits vergeben.' }); console.error('[gildenhalle/create]', err); res.status(500).json({ error: 'DB Fehler' }); } }); /* ════════════════════════════════════════════ POST /api/gildenhalle/join/:id Gilde beitreten oder Anfrage stellen ════════════════════════════════════════════ */ router.post('/gildenhalle/join/:id', requireLogin, async (req, res) => { const userId = req.session.user.id; const guildId = parseInt(req.params.id); try { const existing = await getPlayerGuild(userId); if (existing) { // Gildenmeister oder Rang mit can_manage_ranks dürfen keine neue Gilde gründen const leaderRanks = ['Gildenmeister', 'Stellv. Gildenmeister']; if (existing.leader_id === userId || leaderRanks.includes(existing.rank_name)) { return res.status(403).json({ error: 'Gildenmeister und Stellv. Gildenmeister können keine weitere Gilde gründen.' }); } return res.status(400).json({ error: 'Du bist bereits in einer Gilde.' }); } const [[guild]] = await db.query('SELECT * FROM guilds WHERE id = ?', [guildId]); if (!guild) return res.status(404).json({ error: 'Gilde nicht gefunden.' }); const [[memberCount]] = await db.query( 'SELECT COUNT(*) AS c FROM guild_members WHERE guild_id = ?', [guildId] ); if (memberCount.c >= guild.max_members) return res.status(400).json({ error: 'Gilde ist voll.' }); if (guild.open) { // Standard-Rang (Mitglied = höchste sort_order) const [[defaultRank]] = await db.query( 'SELECT id FROM guild_ranks WHERE guild_id = ? ORDER BY sort_order DESC LIMIT 1', [guildId] ); await db.query( 'INSERT INTO guild_members (guild_id, user_id, rank_id) VALUES (?, ?, ?)', [guildId, userId, defaultRank?.id || null] ); res.json({ success: true, joined: true }); } else { // Anfrage stellen await db.query( `INSERT IGNORE INTO guild_join_requests (guild_id, user_id, message) VALUES (?, ?, ?)`, [guildId, userId, req.body.message?.trim() || null] ); res.json({ success: true, joined: false, requested: true }); } } catch (err) { if (err.code === 'ER_DUP_ENTRY') return res.status(400).json({ error: 'Anfrage bereits gestellt.' }); console.error('[gildenhalle/join]', err); res.status(500).json({ error: 'DB Fehler' }); } }); /* ════════════════════════════════════════════ POST /api/gildenhalle/leave Gilde verlassen ════════════════════════════════════════════ */ router.post('/gildenhalle/leave', requireLogin, async (req, res) => { const userId = req.session.user.id; try { const guild = await getPlayerGuild(userId); if (!guild) return res.status(400).json({ error: 'Du bist in keiner Gilde.' }); if (guild.leader_id === userId) return res.status(400).json({ error: 'Als Gildenleiterin kannst du nicht austreten. Übertrage zuerst die Leitung.' }); await db.query('DELETE FROM guild_members WHERE user_id = ? AND guild_id = ?', [userId, guild.id]); res.json({ success: true }); } catch (err) { console.error('[gildenhalle/leave]', err); res.status(500).json({ error: 'DB Fehler' }); } }); /* ════════════════════════════════════════════ GET /api/gildenhalle/my Eigene Gilde + Mitglieder laden ════════════════════════════════════════════ */ router.get('/gildenhalle/my', requireLogin, async (req, res) => { const userId = req.session.user.id; try { const guild = await getPlayerGuild(userId); if (!guild) return res.json({ guild: null }); await ensureDailyTasks(guild.id); const [members] = await db.query( `SELECT a.id, a.ingame_name AS name, a.level, gr.id AS rank_id, gr.name AS rank_name, gr.sort_order, gm.joined_at FROM guild_members gm JOIN accounts a ON a.id = gm.user_id LEFT JOIN guild_ranks gr ON gr.id = gm.rank_id WHERE gm.guild_id = ? ORDER BY gr.sort_order ASC, gm.joined_at ASC`, [guild.id] ); const [ranks] = await db.query( 'SELECT * FROM guild_ranks WHERE guild_id = ? ORDER BY sort_order ASC', [guild.id] ); const [requests] = await db.query( `SELECT gjr.id, gjr.user_id, a.ingame_name AS name, gjr.message, gjr.created_at FROM guild_join_requests gjr JOIN accounts a ON a.id = gjr.user_id WHERE gjr.guild_id = ? AND gjr.status = 'pending'`, [guild.id] ); res.json({ guild, members, ranks, requests }); } catch (err) { console.error('[gildenhalle/my]', err); res.status(500).json({ error: 'DB Fehler' }); } }); /* ════════════════════════════════════════════ GET /api/gildenhalle/tasks Gilden-Tagesaufgaben laden ════════════════════════════════════════════ */ router.get('/gildenhalle/tasks', requireLogin, async (req, res) => { const userId = req.session.user.id; try { const guild = await getPlayerGuild(userId); if (!guild) return res.status(400).json({ error: 'Du bist in keiner Gilde.' }); await ensureDailyTasks(guild.id); const today = new Date().toISOString().slice(0, 10); const [tasks] = await db.query( `SELECT t.*, COALESCE(SUM(c.amount), 0) AS contributed_total, (SELECT COALESCE(SUM(c2.amount),0) FROM guild_task_contributions c2 WHERE c2.task_id = t.id AND c2.user_id = ?) AS my_contribution FROM guild_tasks t LEFT JOIN guild_task_contributions c ON c.task_id = t.id WHERE t.guild_id = ? AND t.expires_at = ? GROUP BY t.id`, [userId, guild.id, today] ); res.json({ tasks, guildName: guild.name }); } catch (err) { console.error('[gildenhalle/tasks]', err); res.status(500).json({ error: 'DB Fehler' }); } }); /* ════════════════════════════════════════════ POST /api/gildenhalle/requests/:id/accept POST /api/gildenhalle/requests/:id/reject Beitrittsanfragen verwalten ════════════════════════════════════════════ */ router.post('/gildenhalle/requests/:id/:action', requireLogin, async (req, res) => { const userId = req.session.user.id; const requestId = parseInt(req.params.id); const action = req.params.action; // accept | reject if (!['accept', 'reject'].includes(action)) return res.status(400).json({ error: 'Ungültige Aktion.' }); try { const guild = await getPlayerGuild(userId); if (!guild?.can_invite) return res.status(403).json({ error: 'Keine Berechtigung.' }); const [[request]] = await db.query( "SELECT * FROM guild_join_requests WHERE id = ? AND guild_id = ? AND status = 'pending'", [requestId, guild.id] ); if (!request) return res.status(404).json({ error: 'Anfrage nicht gefunden.' }); await db.query( 'UPDATE guild_join_requests SET status = ? WHERE id = ?', [action === 'accept' ? 'accepted' : 'rejected', requestId] ); if (action === 'accept') { const [[defaultRank]] = await db.query( 'SELECT id FROM guild_ranks WHERE guild_id = ? ORDER BY sort_order DESC LIMIT 1', [guild.id] ); await db.query( 'INSERT IGNORE INTO guild_members (guild_id, user_id, rank_id) VALUES (?, ?, ?)', [guild.id, request.user_id, defaultRank?.id || null] ); } res.json({ success: true }); } catch (err) { console.error('[gildenhalle/requests]', err); res.status(500).json({ error: 'DB Fehler' }); } }); /* ════════════════════════════════════════════ POST /api/gildenhalle/member/:userId/rank Rang eines Mitglieds ändern ════════════════════════════════════════════ */ router.post('/gildenhalle/member/:targetId/rank', requireLogin, async (req, res) => { const userId = req.session.user.id; const targetId = parseInt(req.params.targetId); const { rank_id } = req.body; try { const guild = await getPlayerGuild(userId); if (!guild?.can_manage_ranks) return res.status(403).json({ error: 'Keine Berechtigung.' }); if (targetId === guild.leader_id) return res.status(403).json({ error: 'Rang des Gildenmeisters kann nicht geändert werden.' }); await db.query( 'UPDATE guild_members SET rank_id = ? WHERE user_id = ? AND guild_id = ?', [rank_id, targetId, guild.id] ); res.json({ success: true }); } catch (err) { console.error('[gildenhalle/member/rank]', err); res.status(500).json({ error: 'DB Fehler' }); } }); /* ════════════════════════════════════════════ POST /api/gildenhalle/member/:userId/kick Mitglied entfernen ════════════════════════════════════════════ */ router.post('/gildenhalle/member/:targetId/kick', requireLogin, async (req, res) => { const userId = req.session.user.id; const targetId = parseInt(req.params.targetId); try { const guild = await getPlayerGuild(userId); if (!guild?.can_kick) return res.status(403).json({ error: 'Keine Berechtigung.' }); if (targetId === guild.leader_id) return res.status(403).json({ error: 'Gildenmeister kann nicht gekickt werden.' }); await db.query( 'DELETE FROM guild_members WHERE user_id = ? AND guild_id = ?', [targetId, guild.id] ); res.json({ success: true }); } catch (err) { console.error('[gildenhalle/member/kick]', err); res.status(500).json({ error: 'DB Fehler' }); } }); module.exports = router;