373 lines
13 KiB
JavaScript
373 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', 'http://localhost:3000'],
|
|
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 tylko w production
|
|
if (process.env.NODE_ENV === 'production') {
|
|
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 - tylko w production
|
|
if (process.env.NODE_ENV === 'production') {
|
|
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 };
|