const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const morgan = require('morgan'); const compression = require('compression'); const jwt = require('jsonwebtoken'); const { body, validationResult } = require('express-validator'); require('dotenv').config(); class WebPanel { constructor(database) { this.db = database; this.app = express(); this.setupMiddleware(); this.setupRoutes(); } setupMiddleware() { // Bezpieczeństwo this.app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], scriptSrc: ["'self'"], fontSrc: ["'self'", "https://fonts.gstatic.com"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'", "https://discord.com", "https://discordapp.com"] } } })); // CORS this.app.use(cors({ origin: process.env.NODE_ENV === 'production' ? ['https://your-domain.com'] : ['http://localhost:3001', 'http://127.0.0.1:3001'], credentials: true })); // Middleware podstawowe this.app.use(compression()); this.app.use(morgan('combined')); this.app.use(express.json({ limit: '10mb' })); this.app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Serwowanie plików statycznych (dla built React app) this.app.use(express.static('frontend/build')); } setupRoutes() { // API Routes this.app.use('/api/auth', this.createAuthRoutes()); this.app.use('/api/guilds', this.createGuildRoutes()); this.app.use('/api/messages', this.createMessageRoutes()); // Health check this.app.get('/api/health', (req, res) => { res.json({ status: 'OK', timestamp: new Date().toISOString(), version: '1.0.0' }); }); // Catch all handler dla React Router this.app.get('*', (req, res) => { res.sendFile('index.html', { root: 'frontend/build' }); }); // Error handler this.app.use(this.errorHandler.bind(this)); } createAuthRoutes() { const router = express.Router(); // Discord OAuth2 login router.get('/discord', (req, res) => { const discordAuthUrl = `https://discord.com/api/oauth2/authorize?client_id=${process.env.DISCORD_CLIENT_ID}&redirect_uri=${encodeURIComponent(process.env.OAUTH2_REDIRECT_URI)}&response_type=code&scope=identify%20guilds`; res.redirect(discordAuthUrl); }); // Discord OAuth2 callback router.get('/discord/callback', async (req, res) => { const { code } = req.query; if (!code) { return res.status(400).json({ error: 'Brak kodu autoryzacji' }); } try { // Wymiana kodu na token const tokenResponse = await fetch('https://discord.com/api/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: process.env.DISCORD_CLIENT_ID, client_secret: process.env.DISCORD_CLIENT_SECRET, code, grant_type: 'authorization_code', redirect_uri: process.env.OAUTH2_REDIRECT_URI, }), }); const tokenData = await tokenResponse.json(); if (!tokenData.access_token) { throw new Error('Nie udało się uzyskać tokenu dostępu'); } // Pobierz dane użytkownika const userResponse = await fetch('https://discord.com/api/users/@me', { headers: { Authorization: `Bearer ${tokenData.access_token}`, }, }); const userData = await userResponse.json(); // Pobierz serwery użytkownika const guildsResponse = await fetch('https://discord.com/api/users/@me/guilds', { headers: { Authorization: `Bearer ${tokenData.access_token}`, }, }); const guildsData = await guildsResponse.json(); // Zapisz użytkownika w bazie const user = await this.db.saveUser(userData); // Generuj JWT token const jwtToken = jwt.sign( { userId: user.id, discordId: user.discord_id, username: user.username, role: user.role }, process.env.JWT_SECRET, { expiresIn: '7d' } ); // Przekieruj z tokenem res.redirect(`${process.env.NODE_ENV === 'production' ? 'https://your-domain.com' : 'http://localhost:3001'}/dashboard?token=${jwtToken}`); } catch (error) { console.error('Błąd podczas autoryzacji Discord:', error); res.status(500).json({ error: 'Błąd podczas autoryzacji' }); } }); // Wylogowanie router.post('/logout', (req, res) => { res.json({ message: 'Wylogowano pomyślnie' }); }); // Weryfikacja tokenu router.get('/verify', this.authenticateToken, (req, res) => { res.json({ user: req.user }); }); return router; } createGuildRoutes() { const router = express.Router(); // Pobierz serwery użytkownika router.get('/', this.authenticateToken, async (req, res) => { try { // Tu powinieneś pobrać serwery z Discord API i porównać z bazą // Na razie zwróć mockowane dane res.json([ { id: '123456789', name: 'Test Server', icon: null, hasBot: true, userPermissions: ['MANAGE_CHANNELS'] } ]); } catch (error) { console.error('Błąd podczas pobierania serwerów:', error); res.status(500).json({ error: 'Błąd podczas pobierania serwerów' }); } }); // Pobierz konfigurację serwera router.get('/:guildId/config', this.authenticateToken, async (req, res) => { try { const { guildId } = req.params; const welcomeChannel = await this.db.getWelcomeChannel(guildId); const welcomeMessage = await this.db.getWelcomeMessage(guildId); res.json({ welcomeChannel, welcomeMessage }); } catch (error) { console.error('Błąd podczas pobierania konfiguracji:', error); res.status(500).json({ error: 'Błąd podczas pobierania konfiguracji' }); } }); return router; } createMessageRoutes() { const router = express.Router(); // Pobierz wiadomość powitalną router.get('/:guildId', this.authenticateToken, async (req, res) => { try { const { guildId } = req.params; const message = await this.db.getWelcomeMessage(guildId); res.json(message || { content: this.getDefaultWelcomeMessage(), embed_data: null }); } catch (error) { console.error('Błąd podczas pobierania wiadomości:', error); res.status(500).json({ error: 'Błąd podczas pobierania wiadomości' }); } }); // Zapisz wiadomość powitalną router.post('/:guildId', this.authenticateToken, [ body('content').notEmpty().withMessage('Treść wiadomości nie może być pusta'), body('content').isLength({ max: 2000 }).withMessage('Treść wiadomości nie może przekraczać 2000 znaków') ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { guildId } = req.params; const { content, embed_data } = req.body; // Sprawdź czy wiadomość już istnieje const existingMessage = await this.db.getWelcomeMessage(guildId); let message; if (existingMessage) { // Zapisz rewizję przed aktualizacją await this.db.saveMessageRevision( existingMessage.id, existingMessage.content, existingMessage.embed_data, req.user.userId ); message = await this.db.updateWelcomeMessage(guildId, content, embed_data); } else { message = await this.db.saveWelcomeMessage(guildId, content, embed_data); } res.json({ message: 'Wiadomość została zapisana pomyślnie', data: message }); } catch (error) { console.error('Błąd podczas zapisywania wiadomości:', error); res.status(500).json({ error: 'Błąd podczas zapisywania wiadomości' }); } } ); // Historia wersji wiadomości router.get('/:guildId/revisions', this.authenticateToken, async (req, res) => { try { const { guildId } = req.params; const message = await this.db.getWelcomeMessage(guildId); if (!message) { return res.json([]); } const revisions = await this.db.getMessageRevisions(message.id); res.json(revisions); } catch (error) { console.error('Błąd podczas pobierania historii:', error); res.status(500).json({ error: 'Błąd podczas pobierania historii' }); } }); return router; } // Middleware autoryzacji authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Brak tokenu dostępu' }); } jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) { return res.status(403).json({ error: 'Nieprawidłowy token' }); } req.user = user; next(); }); } // Error handler errorHandler(error, req, res, next) { console.error('Błąd serwera:', error); if (error.type === 'entity.parse.failed') { return res.status(400).json({ error: 'Nieprawidłowy format JSON' }); } res.status(500).json({ error: 'Wewnętrzny błąd serwera', ...(process.env.NODE_ENV === 'development' && { details: error.message }) }); } getDefaultWelcomeMessage() { return `# 🎭 Witamy na naszym serwerze! Miło Cię tutaj widzieć! 👋 ## 📋 Najważniejsze informacje: • Przeczytaj regulamin serwera • Przedstaw się w odpowiednim kanale • Baw się dobrze i szanuj innych członków ## 🎮 Funkcje serwera: • Kanały tematyczne • System ról • Eventy i konkursy --- *Ta wiadomość może być edytowana przez panel administracyjny*`; } async start() { const port = process.env.API_PORT || 3000; this.server = this.app.listen(port, () => { console.log(`🌐 Panel web uruchomiony na porcie ${port}`); console.log(`📱 URL panelu: http://localhost:${port}`); }); return this.server; } async stop() { if (this.server) { this.server.close(); } } } module.exports = { WebPanel };