first commit
This commit is contained in:
368
bot/backend/web/server.js
Normal file
368
bot/backend/web/server.js
Normal file
@@ -0,0 +1,368 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user