415 lines
16 KiB
JavaScript
415 lines
16 KiB
JavaScript
/* ============================================================
|
||
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 2–6 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;
|