Files
SkrzynkaImpostora/bot/backend/web/server.js
2025-07-21 00:45:28 +02:00

369 lines
13 KiB
JavaScript

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 };