dok/routes/gildenhalle.route.js
2026-04-14 18:42:30 +01:00

415 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ============================================================
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: 'Offizier', 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);
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,
});
} 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 26 Zeichen lang sein.' });
try {
const existing = await getPlayerGuild(userId);
if (existing) 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) 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;