411 lines
10 KiB
JavaScript
411 lines
10 KiB
JavaScript
/**
|
|
* 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;
|