/** * Server Routes * API endpoints for servers (guilds), channels, and members */ const express = require('express'); const router = express.Router(); const { authenticateUser } = require('../middleware/auth'); const db = require('../database/db'); const { v4: uuidv4 } = require('uuid'); /** * GET /api/servers * Get all servers the current user is a member of */ router.get('/', authenticateUser, async (req, res) => { try { const result = await db.query(` SELECT s.*, sm.role, sm.nickname, sm.joined_at as member_joined_at FROM servers s INNER JOIN server_members sm ON s.id = sm.server_id WHERE sm.user_id = $1 ORDER BY sm.joined_at ASC `, [req.user.id]); res.json({ success: true, servers: result.rows }); } catch (error) { console.error('Failed to get servers:', error); res.status(500).json({ success: false, error: error.message }); } }); /** * POST /api/servers * Create a new server */ router.post('/', authenticateUser, async (req, res) => { try { const { name, description, icon_url, is_public } = req.body; if (!name || name.trim().length === 0) { return res.status(400).json({ success: false, error: 'Server name is required' }); } // Generate invite code const invite_code = uuidv4().substring(0, 8).toUpperCase(); // Create server const serverResult = await db.query(` INSERT INTO servers (name, description, icon_url, owner_id, invite_code, is_public) VALUES ($1, $2, $3, $4, $5, $6) RETURNING * `, [name.trim(), description || null, icon_url || null, req.user.id, invite_code, is_public || false]); const server = serverResult.rows[0]; // Add owner as member await db.query(` INSERT INTO server_members (server_id, user_id, role) VALUES ($1, $2, 'owner') `, [server.id, req.user.id]); // Create default channels await db.query(` INSERT INTO channels (server_id, name, channel_type, position) VALUES ($1, 'general', 'text', 0), ($1, 'voice', 'voice', 1) `, [server.id]); res.json({ success: true, server }); } catch (error) { console.error('Failed to create server:', error); res.status(500).json({ success: false, error: error.message }); } }); /** * GET /api/servers/:id * Get server details */ router.get('/:id', authenticateUser, async (req, res) => { try { // Check if user is a member const memberCheck = await db.query(` SELECT * FROM server_members WHERE server_id = $1 AND user_id = $2 `, [req.params.id, req.user.id]); if (memberCheck.rows.length === 0) { return res.status(403).json({ success: false, error: 'Not a member of this server' }); } const result = await db.query(` SELECT * FROM servers WHERE id = $1 `, [req.params.id]); if (result.rows.length === 0) { return res.status(404).json({ success: false, error: 'Server not found' }); } res.json({ success: true, server: result.rows[0] }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); /** * POST /api/servers/join/:inviteCode * Join a server by invite code */ router.post('/join/:inviteCode', authenticateUser, async (req, res) => { try { const server = await db.query(` SELECT * FROM servers WHERE invite_code = $1 `, [req.params.inviteCode.toUpperCase()]); if (server.rows.length === 0) { return res.status(404).json({ success: false, error: 'Invalid invite code' }); } // Check if already a member const existingMember = await db.query(` SELECT * FROM server_members WHERE server_id = $1 AND user_id = $2 `, [server.rows[0].id, req.user.id]); if (existingMember.rows.length > 0) { return res.json({ success: true, server: server.rows[0], message: 'Already a member' }); } // Add as member await db.query(` INSERT INTO server_members (server_id, user_id, role) VALUES ($1, $2, 'member') `, [server.rows[0].id, req.user.id]); // Update member count await db.query(` UPDATE servers SET member_count = member_count + 1 WHERE id = $1 `, [server.rows[0].id]); res.json({ success: true, server: server.rows[0] }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); /** * GET /api/servers/:id/channels * Get channels for a server */ router.get('/:id/channels', authenticateUser, async (req, res) => { try { // Check membership const memberCheck = await db.query(` SELECT * FROM server_members WHERE server_id = $1 AND user_id = $2 `, [req.params.id, req.user.id]); if (memberCheck.rows.length === 0) { return res.status(403).json({ success: false, error: 'Not a member of this server' }); } const channels = await db.query(` SELECT * FROM channels WHERE server_id = $1 ORDER BY position ASC `, [req.params.id]); res.json({ success: true, channels: channels.rows }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); /** * POST /api/servers/:id/channels * Create a new channel in a server */ router.post('/:id/channels', authenticateUser, async (req, res) => { try { const { name, description, channel_type } = req.body; // Check if user is admin/owner const memberCheck = await db.query(` SELECT * FROM server_members WHERE server_id = $1 AND user_id = $2 AND role IN ('owner', 'admin') `, [req.params.id, req.user.id]); if (memberCheck.rows.length === 0) { return res.status(403).json({ success: false, error: 'Permission denied' }); } // Get next position const posResult = await db.query(` SELECT COALESCE(MAX(position), -1) + 1 as next_pos FROM channels WHERE server_id = $1 `, [req.params.id]); const channel = await db.query(` INSERT INTO channels (server_id, name, description, channel_type, position) VALUES ($1, $2, $3, $4, $5) RETURNING * `, [req.params.id, name, description || null, channel_type || 'text', posResult.rows[0].next_pos]); res.json({ success: true, channel: channel.rows[0] }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); /** * GET /api/servers/:id/members * Get members of a server */ router.get('/:id/members', authenticateUser, async (req, res) => { try { // Check membership const memberCheck = await db.query(` SELECT * FROM server_members WHERE server_id = $1 AND user_id = $2 `, [req.params.id, req.user.id]); if (memberCheck.rows.length === 0) { return res.status(403).json({ success: false, error: 'Not a member of this server' }); } const members = await db.query(` SELECT u.id, u.username, u.display_name, u.avatar_url, u.status, u.custom_status, sm.role, sm.nickname, sm.joined_at FROM server_members sm INNER JOIN users u ON sm.user_id = u.id WHERE sm.server_id = $1 ORDER BY CASE sm.role WHEN 'owner' THEN 0 WHEN 'admin' THEN 1 WHEN 'moderator' THEN 2 ELSE 3 END, sm.joined_at ASC `, [req.params.id]); res.json({ success: true, members: members.rows }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); /** * GET /api/channels/:channelId/messages * Get messages from a channel */ router.get('/channels/:channelId/messages', authenticateUser, async (req, res) => { try { const { limit = 50, before } = req.query; // Get channel and check membership const channel = await db.query(`SELECT server_id FROM channels WHERE id = $1`, [req.params.channelId]); if (channel.rows.length === 0) { return res.status(404).json({ success: false, error: 'Channel not found' }); } const memberCheck = await db.query(` SELECT * FROM server_members WHERE server_id = $1 AND user_id = $2 `, [channel.rows[0].server_id, req.user.id]); if (memberCheck.rows.length === 0) { return res.status(403).json({ success: false, error: 'Not a member' }); } let query = ` SELECT m.*, u.username, u.display_name, u.avatar_url FROM messages m INNER JOIN users u ON m.user_id = u.id WHERE m.channel_id = $1 `; const params = [req.params.channelId]; if (before) { query += ` AND m.created_at < $2`; params.push(before); } query += ` ORDER BY m.created_at DESC LIMIT $${params.length + 1}`; params.push(parseInt(limit)); const messages = await db.query(query, params); res.json({ success: true, messages: messages.rows.reverse() }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); /** * POST /api/channels/:channelId/messages * Send a message to a channel */ router.post('/channels/:channelId/messages', authenticateUser, async (req, res) => { try { const { content } = req.body; if (!content || content.trim().length === 0) { return res.status(400).json({ success: false, error: 'Message content is required' }); } // Get channel and check membership const channel = await db.query(`SELECT server_id FROM channels WHERE id = $1`, [req.params.channelId]); if (channel.rows.length === 0) { return res.status(404).json({ success: false, error: 'Channel not found' }); } const memberCheck = await db.query(` SELECT * FROM server_members WHERE server_id = $1 AND user_id = $2 `, [channel.rows[0].server_id, req.user.id]); if (memberCheck.rows.length === 0) { return res.status(403).json({ success: false, error: 'Not a member' }); } const message = await db.query(` INSERT INTO messages (channel_id, user_id, content) VALUES ($1, $2, $3) RETURNING * `, [req.params.channelId, req.user.id, content.trim()]); // Get user info const user = await db.query(`SELECT username, display_name, avatar_url FROM users WHERE id = $1`, [req.user.id]); res.json({ success: true, message: { ...message.rows[0], ...user.rows[0] } }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); module.exports = router;