diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/bot/.dockerignore b/bot/.dockerignore deleted file mode 100644 index 71eed03..0000000 --- a/bot/.dockerignore +++ /dev/null @@ -1,89 +0,0 @@ -# Git -.git -.gitignore - -# Documentation -README.md -*.md - -# Environment files -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - -# Dependencies -node_modules -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Build outputs -frontend/build -dist -build - -# Development tools -.vscode -.idea -*.swp -*.swo - -# Testing -coverage -.nyc_output - -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# Docker -Dockerfile* -docker-compose* -.dockerignore - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Temporary files -*.tmp -*.temp - -# Database files -*.db -*.sqlite - -# SSL certificates (for security) -ssl/ -*.pem -*.crt -*.key - -# Backup files -*.bak -*.backup diff --git a/bot/.env.example b/bot/.env.example deleted file mode 100644 index f1155a7..0000000 --- a/bot/.env.example +++ /dev/null @@ -1,37 +0,0 @@ -# Discord Bot Configuration -DISCORD_TOKEN=your_discord_bot_token_here -DISCORD_CLIENT_ID=your_discord_client_id_here -DISCORD_CLIENT_SECRET=your_discord_client_secret_here - -# Database Configuration -DATABASE_URL=postgresql://username:password@localhost:5432/skrzynka_impostora -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=skrzynka_impostora -DB_USER=username -DB_PASSWORD=password - -# Web Panel Configuration -JWT_SECRET=your_jwt_secret_here -SESSION_SECRET=your_session_secret_here -WEB_PORT=3001 -API_PORT=3000 - -# OAuth2 Configuration -OAUTH2_REDIRECT_URI=http://localhost:3001/auth/discord/callback - -# Environment -NODE_ENV=development - -# Logging -LOG_LEVEL=info - -# Redis Configuration (optional) -REDIS_URL=redis://localhost:6379 - -# Docker specific overrides (uncomment for Docker usage) -# DB_HOST=postgres -# REDIS_URL=redis://redis:6379 - -# Production domain (for OAuth2 and CORS) -# PRODUCTION_DOMAIN=https://your-domain.com diff --git a/bot/.github/copilot-instructions.md b/bot/.github/copilot-instructions.md deleted file mode 100644 index 2b2c10d..0000000 --- a/bot/.github/copilot-instructions.md +++ /dev/null @@ -1,85 +0,0 @@ - - -# Skrzynka Impostora Bot - Instrukcje dla Copilot - -## Opis projektu -To jest projekt Discord Bot o nazwie "Skrzynka Impostora Bot" służący do zarządzania wiadomościami powiatalnymi na serwerach Discord. Projekt składa się z: - -- **Backend**: Node.js + Express z biblioteką discord.js -- **Frontend**: React SPA (Single Page Application) -- **Baza danych**: PostgreSQL -- **Funkcjonalności**: Discord slash commands, panel web OAuth2, edytor wiadomości - -## Struktura projektu -``` -/backend/ - Serwer Node.js i bot Discord - /commands/ - Slash commands dla Discord - /database/ - Zarządzanie bazą danych - /web/ - API serwer dla panelu web -/frontend/ - React aplikacja (panel administracyjny) - /src/components/ - Komponenty React - /src/pages/ - Strony aplikacji - /src/services/ - Serwisy API - /src/hooks/ - React hooks -/database/ - Skrypty migracji i seedowania -/shared/ - Współdzielone utilities -``` - -## Kluczowe technologie -- **Discord.js v14** - Biblioteka Discord API -- **Express.js** - Web framework -- **PostgreSQL** - Baza danych relacyjna -- **React 18** - Frontend framework -- **JWT** - Autoryzacja -- **OAuth2** - Logowanie przez Discord - -## Główne funkcjonalności -1. **Slash Commands**: - - `/skrzynka` - komendy użytkownika - - `/skrzynka-adm` - komendy administracyjne - -2. **Panel Web**: - - Logowanie przez Discord OAuth2 - - Edytor wiadomości z podglądem - - Zarządzanie serwerami - - Historia zmian - -3. **Zarządzanie wiadomościami**: - - Wsparcie Discord Markdown - - Live preview - - Walidacja długości (2000 znaków) - - Automatyczna aktualizacja na Discord - -## Style kodowania -- Używaj ES6+ składni -- Preferuj async/await nad Promise.then() -- Zastosuj destructuring tam gdzie możliwe -- Używaj const/let zamiast var -- Komponenty React jako funkcyjne z hooks -- Nazwy plików: PascalCase dla komponentów, camelCase dla utilities - -## Bezpieczeństwo -- Wszystkie zapytania API wymagają JWT token -- Walidacja danych wejściowych przez express-validator -- Helmet.js dla zabezpieczeń HTTP -- CORS skonfigurowany dla specific origins -- SQL prepared statements (pg library) - -## Środowisko i konfiguracja -- Zmienne środowiskowe w pliku .env -- Development: localhost:3000 (API), localhost:3001 (React) -- Production: buildy React serwowane przez Express - -## Konwencje nazewnictwa -- Tabele bazy: snake_case (np. welcome_messages) -- Kolumny bazy: snake_case (np. guild_id) -- JavaScript: camelCase (np. guildId) -- Komponenty React: PascalCase (np. MessageEditor) -- CSS classes: kebab-case (np. discord-button) - -## Pomocne wskazówki -- Bot wymaga uprawnień MANAGE_CHANNELS do działania -- Discord API ma limit 2000 znaków na wiadomość -- Używaj PostgreSQL JSONB dla embed_data -- Zawsze dodawaj error handling dla Discord API calls -- Frontend używa Tailwind-like CSS classes w App.css diff --git a/bot/.gitignore b/bot/.gitignore deleted file mode 100644 index 4818346..0000000 --- a/bot/.gitignore +++ /dev/null @@ -1,73 +0,0 @@ -# Dependencies -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Production builds -frontend/build/ -dist/ - -# Environment variables -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - -# Database -*.db -*.sqlite -backup_*.sql - -# Logs -logs/ -*.log - -# Runtime data -pids/ -*.pid -*.seed -*.pid.lock - -# Coverage directory used by tools like istanbul -coverage/ - -# IDE -.vscode/settings.json -.vscode/launch.json -.idea/ -*.swp -*.swo - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Temporary files -*.tmp -*.temp - -# Discord bot specific -config.json -database/data/ - -# Docker -docker-compose.override.yml -.docker/ - -# SSL certificates -ssl/ -*.pem -*.crt -*.key -*.p12 - -# Backup files -*.bak -*.backup diff --git a/bot/.vscode/tasks.json b/bot/.vscode/tasks.json deleted file mode 100644 index 07f2605..0000000 --- a/bot/.vscode/tasks.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Start Development Server", - "type": "shell", - "command": "npm", - "args": [ - "run", - "dev" - ], - "group": "build", - "isBackground": true, - "problemMatcher": [], - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "new", - "showReuseMessage": true, - "clear": false - }, - "options": { - "cwd": "${workspaceFolder}" - } - }, - { - "label": "Docker: Start Development", - "type": "shell", - "command": "docker", - "args": [ - "compose", - "-f", - "docker-compose.dev.yml", - "up", - "--build" - ], - "group": "build", - "isBackground": true, - "problemMatcher": [], - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "new", - "showReuseMessage": true, - "clear": false - }, - "options": { - "cwd": "${workspaceFolder}" - } - }, - { - "label": "Docker: Stop Development", - "type": "shell", - "command": "docker", - "args": [ - "compose", - "-f", - "docker-compose.dev.yml", - "down" - ], - "group": "build", - "problemMatcher": [], - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "new", - "showReuseMessage": true, - "clear": false - }, - "options": { - "cwd": "${workspaceFolder}" - } - }, - { - "label": "Docker: Build Image", - "type": "shell", - "command": "docker", - "args": [ - "build", - "-t", - "skrzynka-impostora-bot:latest", - "." - ], - "group": "build", - "problemMatcher": [], - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "new", - "showReuseMessage": true, - "clear": false - }, - "options": { - "cwd": "${workspaceFolder}" - } - }, - { - "label": "Docker: View Logs", - "type": "shell", - "command": "docker", - "args": [ - "compose", - "-f", - "docker-compose.dev.yml", - "logs", - "-f" - ], - "group": "test", - "isBackground": true, - "problemMatcher": [], - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "new", - "showReuseMessage": true, - "clear": false - }, - "options": { - "cwd": "${workspaceFolder}" - } - }, - { - "label": "Deploy Commands to Discord", - "type": "shell", - "command": "npm", - "args": [ - "run", - "deploy" - ], - "group": "build", - "problemMatcher": [], - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "new", - "showReuseMessage": true, - "clear": false - }, - "options": { - "cwd": "${workspaceFolder}" - } - } - ] -} \ No newline at end of file diff --git a/bot/Dockerfile b/bot/Dockerfile deleted file mode 100644 index 79d387e..0000000 --- a/bot/Dockerfile +++ /dev/null @@ -1,61 +0,0 @@ -# Multi-stage build dla optymalizacji -FROM node:18-alpine AS base - -# Ustaw workdir -WORKDIR /app - -# Kopiuj package.json dla głównego projektu -COPY package*.json ./ - -# Zainstaluj zależności głównego projektu -RUN npm ci --only=production - -# Stage dla frontendu -FROM node:18-alpine AS frontend-build - -WORKDIR /app/frontend - -# Kopiuj package.json frontendu -COPY frontend/package*.json ./ -RUN npm ci - -# Kopiuj kod frontendu -COPY frontend/ ./ - -# Zbuduj frontend -RUN npm run build - -# Stage dla backend -FROM node:18-alpine AS backend - -WORKDIR /app - -# Kopiuj zależności z base stage -COPY --from=base /app/node_modules ./node_modules -COPY package*.json ./ - -# Kopiuj kod backend -COPY backend/ ./backend/ -COPY database/ ./database/ -COPY shared/ ./shared/ - -# Kopiuj zbudowany frontend -COPY --from=frontend-build /app/frontend/build ./frontend/build - -# Stwórz użytkownika non-root -RUN addgroup -g 1001 -S nodejs -RUN adduser -S nodejs -u 1001 - -# Zmień ownership plików -RUN chown -R nodejs:nodejs /app -USER nodejs - -# Expose port -EXPOSE 3000 - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD node -e "require('http').get('http://localhost:3000/api/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" - -# Uruchom aplikację -CMD ["node", "backend/index.js"] diff --git a/bot/Dockerfile.dev b/bot/Dockerfile.dev deleted file mode 100644 index d27b50c..0000000 --- a/bot/Dockerfile.dev +++ /dev/null @@ -1,38 +0,0 @@ -# Development Dockerfile z hot reload -FROM node:18-alpine - -# Zainstaluj nodemon globalnie -RUN npm install -g nodemon concurrently - -# Ustaw workdir -WORKDIR /app - -# Kopiuj package.json files -COPY package*.json ./ -COPY frontend/package*.json ./frontend/ - -# Zainstaluj wszystkie zależności (including dev dependencies) -RUN npm install - -# Zainstaluj zależności frontendu -WORKDIR /app/frontend -RUN npm install - -# Wróć do głównego katalogu -WORKDIR /app - -# Expose ports -EXPOSE 3000 3001 9229 - -# Kopiuj pozostałe pliki (będzie nadpisane przez volume w dev) -COPY . . - -# Stwórz katalogi dla logów -RUN mkdir -p logs - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD node -e "require('http').get('http://localhost:3000/api/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" || exit 1 - -# Default command (może być nadpisane w docker-compose) -CMD ["npm", "run", "dev"] diff --git a/bot/Makefile b/bot/Makefile deleted file mode 100644 index 73a486a..0000000 --- a/bot/Makefile +++ /dev/null @@ -1,102 +0,0 @@ -# Makefile dla Skrzynka Impostora Bot -# Użycie: make - -.PHONY: help dev dev-down prod prod-down build logs clean deploy-commands - -# Default target -help: ## Pokaż dostępne komendy - @echo "Dostępne komendy dla Skrzynka Impostora Bot:" - @echo "" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -dev: ## Uruchom środowisko development - @echo "🚀 Uruchamianie środowiska development..." - @if [ ! -f .env ]; then \ - echo "⚠️ Kopiuję .env.example do .env"; \ - cp .env.example .env; \ - echo "📝 Uzupełnij zmienne w pliku .env przed kontynuowaniem"; \ - fi - docker compose -f docker-compose.dev.yml up --build -d - @echo "✅ Środowisko uruchomione!" - @echo "📋 Dostępne usługi:" - @echo " 🤖 Bot + Panel: http://localhost:3000" - @echo " 📊 pgAdmin: http://localhost:8080" - -dev-down: ## Zatrzymaj środowisko development - @echo "🛑 Zatrzymywanie środowiska development..." - docker compose -f docker-compose.dev.yml down - -dev-rebuild: ## Przebuduj i uruchom development - @echo "🔄 Przebudowywanie środowiska development..." - docker compose -f docker-compose.dev.yml down - docker compose -f docker-compose.dev.yml up --build -d - -prod: ## Uruchom środowisko produkcyjne - @echo "🚀 Uruchamianie środowiska produkcyjnego..." - docker compose up --build -d - @echo "✅ Produkcja uruchomiona!" - -prod-down: ## Zatrzymaj środowisko produkcyjne - @echo "🛑 Zatrzymywanie środowiska produkcyjnego..." - docker compose down - -build: ## Zbuduj obraz Docker - @echo "🏗️ Budowanie obrazu Docker..." - docker build -t skrzynka-impostora-bot:latest . - -logs: ## Pokaż logi development - docker compose -f docker-compose.dev.yml logs -f - -logs-prod: ## Pokaż logi produkcji - docker compose logs -f - -clean: ## Wyczyść wszystkie kontenery i volumes - @echo "🧹 Czyszczenie kontenerów i volumes..." - docker compose -f docker-compose.dev.yml down -v - docker compose down -v - docker system prune -f - -clean-all: ## Wyczyść wszystko włącznie z obrazami - @echo "🧹 Czyszczenie wszystkiego..." - docker compose -f docker-compose.dev.yml down -v - docker compose down -v - docker rmi skrzynka-impostora-bot:latest 2>/dev/null || true - docker system prune -af - -deploy-commands: ## Deploy komend Discord (wymaga DISCORD_TOKEN) - @if [ -z "$(DISCORD_TOKEN)" ]; then \ - echo "❌ Brak DISCORD_TOKEN. Użyj: make deploy-commands DISCORD_TOKEN=twoj_token"; \ - exit 1; \ - fi - @echo "📡 Deployowanie komend Discord..." - docker run --rm \ - -e DISCORD_TOKEN="$(DISCORD_TOKEN)" \ - -e DISCORD_CLIENT_ID="$(DISCORD_CLIENT_ID)" \ - skrzynka-impostora-bot:latest \ - node backend/deploy-commands.js - -shell: ## Wejdź do kontenera bota (development) - docker compose -f docker-compose.dev.yml exec bot-dev sh - -shell-prod: ## Wejdź do kontenera bota (produkcja) - docker compose exec bot sh - -db-shell: ## Wejdź do bazy danych PostgreSQL - docker compose -f docker-compose.dev.yml exec postgres-dev psql -U dev_user -d skrzynka_impostora_dev - -backup-db: ## Stwórz backup bazy danych - @echo "💾 Tworzenie backupu bazy danych..." - docker compose -f docker-compose.dev.yml exec postgres-dev pg_dump -U dev_user skrzynka_impostora_dev > backup_$(shell date +%Y%m%d_%H%M%S).sql - @echo "✅ Backup utworzony!" - -status: ## Pokaż status kontenerów - @echo "📊 Status kontenerów development:" - docker compose -f docker-compose.dev.yml ps - @echo "" - @echo "📊 Status kontenerów produkcji:" - docker compose ps - -# Aliasy dla wygody -up: dev ## Alias dla dev -down: dev-down ## Alias dla dev-down -restart: dev-rebuild ## Alias dla dev-rebuild diff --git a/bot/README.md b/bot/README.md deleted file mode 100644 index b6a4a09..0000000 --- a/bot/README.md +++ /dev/null @@ -1,373 +0,0 @@ -# 🎭 Skrzynka Impostora Bot - -Kompleksowy bot Discord do zarządzania wiadomościami powaitalnymi z panelem web administracyjnym. - -## 📋 Funkcjonalności - -### ✨ Wersja 1.0 -- ✅ Integracja z Discord API (discord.js v14) -- ✅ Slash commands (`/skrzynka`, `/skrzynka-adm`) -- ✅ Panel web do zarządzania wiadomościami -- ✅ Edytor z podglądem Discord Markdown -- ✅ Autoryzacja OAuth2 Discord -- ✅ Historia zmian wiadomości -- ✅ Baza danych PostgreSQL - -### 🚀 Planowane funkcjonalności -- Wielojęzyczność -- Szablony wiadomości -- Harmonogramy wysyłki -- Statystyki i analizy -- System ról użytkowników - -## 🛠️ Technologie - -- **Backend**: Node.js, Express.js, discord.js -- **Frontend**: React 18, React Router -- **Baza danych**: PostgreSQL -- **Autoryzacja**: JWT, OAuth2 Discord -- **Styling**: Custom CSS (Discord-like) - -## 📦 Instalacja - -### Wymagania - -**Dla Docker (Rekomendowane):** -- Docker Desktop >= 4.0 -- Docker Compose v2 -- Konto Discord Developer - -**Dla lokalnego uruchomienia:** -- Node.js >= 18.0.0 -- PostgreSQL >= 12 -- npm >= 8.0.0 -- Konto Discord Developer - -### 1. Sklonuj repozytorium -```bash -git clone -cd skrzynka-impostora-bot -``` - -### 2. Zainstaluj zależności -```bash -# Backend dependencies -npm install - -# Frontend dependencies -cd frontend -npm install -cd .. -``` - -### 3. Konfiguracja Discord Bot - -1. Idź do [Discord Developer Portal](https://discord.com/developers/applications) -2. Utwórz nową aplikację -3. W sekcji "Bot" utwórz bota i skopiuj token -4. W sekcji "OAuth2" dodaj redirect URI: `http://localhost:3001/auth/discord/callback` - -### 4. Konfiguracja bazy danych - -```bash -# Utwórz bazę danych PostgreSQL -createdb skrzynka_impostora - -# Lub w psql: -CREATE DATABASE skrzynka_impostora; -``` - -### 5. Zmienne środowiskowe - -Skopiuj `.env.example` do `.env` i wypełnij: - -```bash -cp .env.example .env -``` - -Edytuj `.env`: -```env -# Discord Bot Configuration -DISCORD_TOKEN=your_discord_bot_token_here -DISCORD_CLIENT_ID=your_discord_client_id_here -DISCORD_CLIENT_SECRET=your_discord_client_secret_here - -# Database Configuration -DATABASE_URL=postgresql://username:password@localhost:5432/skrzynka_impostora -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=skrzynka_impostora -DB_USER=your_username -DB_PASSWORD=your_password - -# Web Panel Configuration -JWT_SECRET=your_secure_random_string_here -SESSION_SECRET=another_secure_random_string -WEB_PORT=3001 -API_PORT=3000 - -# OAuth2 Configuration -OAUTH2_REDIRECT_URI=http://localhost:3001/auth/discord/callback - -# Environment -NODE_ENV=development -``` - -### 6. Inicjalizacja bazy danych - -```bash -# Uruchom migracje -npm run db:migrate - -# Opcjonalnie: dodaj przykładowe dane -npm run db:seed -``` - -### 7. Deploy komend Discord - -```bash -npm run deploy -``` - -## 🚀 Uruchomienie - -### 🐳 Przez Docker (Rekomendowane) - -**Development:** -```bash -# Automatyczny skrypt (Linux/macOS) -./scripts/dev-start.sh - -# Automatyczny skrypt (Windows) -scripts\dev-start.bat - -# Lub ręcznie -docker compose -f docker-compose.dev.yml up --build -``` - -**Produkcja:** -```bash -# Skrypt deploy (Linux/macOS) -./scripts/prod-deploy.sh - -# Lub ręcznie -docker compose up --build -d -``` - -### 💻 Lokalnie (bez Docker) - -### Development (równoczesne uruchomienie backend + frontend) -```bash -npm run dev -``` - -### Osobno Backend i Frontend - -**Backend:** -```bash -npm run dev:backend -``` - -**Frontend:** -```bash -npm run dev:frontend -``` - -### Produkcja -```bash -# Zbuduj frontend -npm run build - -# Uruchom serwer -npm start -``` - -## 📖 Użytkowanie - -### 1. Zaproszenie bota na serwer - -Użyj linku z odpowiednimi uprawnieniami: -``` -https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=2147484672&scope=bot%20applications.commands -``` - -### 2. Konfiguracja kanału powitalnego - -Na serwerze Discord użyj komendy: -``` -/skrzynka-adm set-welcome channel:#witamy -``` - -### 3. Zarządzanie przez panel web - -1. Idź do `http://localhost:3001` (development) -2. Zaloguj się przez Discord -3. Wybierz serwer -4. Edytuj wiadomość powitalną - -### 4. Komendy Discord - -**Komendy użytkownika:** -- `/skrzynka info` - Informacje o bocie -- `/skrzynka help` - Pomoc - -**Komendy administracyjne:** -- `/skrzynka-adm set-welcome ` - Ustaw kanał powitalny -- `/skrzynka-adm welcome` - Wyślij wiadomość powitalną -- `/skrzynka-adm welcome-update` - Zaktualizuj wiadomość -- `/skrzynka-adm status` - Status konfiguracji - -## 🗄️ Struktura bazy danych - -```sql --- Serwery Discord -guilds (id, name, created_at, updated_at) - --- Kanały powitalne -welcome_channels (id, guild_id, channel_id, channel_name, is_active, created_at, updated_at) - --- Wiadomości powitalne -welcome_messages (id, guild_id, content, embed_data, message_id, is_active, created_at, updated_at) - --- Użytkownicy panelu -users (id, discord_id, username, discriminator, avatar, email, role, is_active, last_login, created_at, updated_at) - --- Historia zmian -message_revisions (id, message_id, content, embed_data, user_id, revision_number, created_at) - --- Uprawnienia użytkowników -user_guild_permissions (id, user_id, guild_id, role, granted_by, created_at) -``` - -## 🔧 Development - -### Dostępne skrypty - -```bash -npm run start # Uruchom produkcyjnie -npm run dev # Development (backend + frontend) -npm run dev:backend # Tylko backend -npm run dev:frontend # Tylko frontend -npm run build # Zbuduj frontend -npm run deploy # Deploy komend Discord -npm run db:migrate # Migracja bazy -npm run db:seed # Seed bazy danych - -# Docker skrypty -npm run docker:dev # Uruchom development w Docker -npm run docker:dev:down # Zatrzymaj development Docker -npm run docker:prod # Uruchom produkcję w Docker -npm run docker:build # Zbuduj obraz Docker -npm run docker:logs # Pokaż logi Docker -``` - -### 🐳 Docker Development - -**Dostępne usługi w trybie development:** -- **Bot + API**: http://localhost:3000 -- **Panel Web**: http://localhost:3001 -- **PostgreSQL**: localhost:5433 -- **pgAdmin**: http://localhost:8080 (admin@skrzynka.local / admin) -- **Redis**: localhost:6380 - -**Użyteczne komendy Docker:** -```bash -# Wyświetl logi konkretnej usługi -docker compose -f docker-compose.dev.yml logs -f bot-dev - -# Wejdź do kontenera bota -docker compose -f docker-compose.dev.yml exec bot-dev sh - -# Restart konkretnej usługi -docker compose -f docker-compose.dev.yml restart bot-dev - -# Wyczyść wszystko i rozpocznij od nowa -docker compose -f docker-compose.dev.yml down -v -docker compose -f docker-compose.dev.yml up --build -``` - -### 🔄 Hot Reload - -W trybie development Docker automatycznie: -- Monitoruje zmiany w kodzie (volume mount) -- Restartuje backend przy zmianach (nodemon) -- Przebudowuje frontend na żywo -- Automatycznie stosuje migracje bazy danych - -### Struktura projektu - -``` -├── backend/ # Serwer Node.js -│ ├── index.js # Główny plik bota -│ ├── deploy-commands.js # Deploy slash commands -│ ├── commands/ # Slash commands -│ ├── database/ # Zarządzanie bazą -│ └── web/ # API serwer -├── frontend/ # React aplikacja -│ ├── src/ -│ │ ├── components/ # Komponenty React -│ │ ├── pages/ # Strony aplikacji -│ │ ├── services/ # API client -│ │ └── hooks/ # React hooks -│ └── public/ # Pliki statyczne -├── database/ # Skrypty bazy danych -├── shared/ # Współdzielone utilities -└── .github/ # GitHub konfiguracja -``` - -## 🔒 Bezpieczeństwo - -- JWT tokens dla autoryzacji API -- OAuth2 Discord dla logowania -- Helmet.js dla zabezpieczeń HTTP -- CORS protection -- SQL injection protection (prepared statements) -- Input validation (express-validator) - -## 📊 Monitoring i Logi - -Bot loguje ważne zdarzenia: -- Startup/shutdown -- Command execution -- Database operations -- API requests -- Errors and warnings - -## 🤝 Contribucje - -1. Fork repository -2. Utwórz feature branch (`git checkout -b feature/amazing-feature`) -3. Commit changes (`git commit -m 'Add amazing feature'`) -4. Push branch (`git push origin feature/amazing-feature`) -5. Otwórz Pull Request - -## 📄 Licencja - -MIT License - zobacz plik `LICENSE` - -## 🆘 Pomoc i wsparcie - -- Sprawdź [Issues](../../issues) dla znanych problemów -- Utwórz nowy Issue dla bugów lub feature requests -- Przeczytaj dokumentację Discord.js: https://discord.js.org/ - -## 📈 Roadmap - -### v1.1 - Zarządzanie -- [ ] System ról użytkowników -- [ ] Bulk operations -- [ ] Advanced permissions - -### v1.2 - Funkcjonalności -- [ ] Message templates -- [ ] Scheduled messages -- [ ] Multiple languages - -### v1.3 - Analytics -- [ ] Usage statistics -- [ ] User engagement metrics -- [ ] Performance monitoring - ---- - -Stworzono z ❤️ dla społeczności Discord diff --git a/bot/backend/commands/index.js b/bot/backend/commands/index.js deleted file mode 100644 index 5eec057..0000000 --- a/bot/backend/commands/index.js +++ /dev/null @@ -1,440 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); - -class SlashCommands { - constructor(database) { - this.db = database; - this.commands = this.setupCommands(); - } - - setupCommands() { - return [ - // Komenda informacyjna dla użytkowników - new SlashCommandBuilder() - .setName('skrzynka') - .setDescription('Informacje o bocie Skrzynka Impostora') - .addSubcommand(subcommand => - subcommand - .setName('info') - .setDescription('Pokaż informacje o bocie') - ) - .addSubcommand(subcommand => - subcommand - .setName('help') - .setDescription('Pokaż dostępne komendy') - ), - - // Komendy administracyjne - new SlashCommandBuilder() - .setName('skrzynka-adm') - .setDescription('Komendy administracyjne dla bota Skrzynka Impostora') - .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) - .addSubcommand(subcommand => - subcommand - .setName('set-welcome') - .setDescription('Ustaw kanał powitalny') - .addChannelOption(option => - option - .setName('channel') - .setDescription('Kanał na którym będzie wyświetlana wiadomość powitalna') - .setRequired(true) - ) - ) - .addSubcommand(subcommand => - subcommand - .setName('welcome') - .setDescription('Wyślij wiadomość powitalną') - ) - .addSubcommand(subcommand => - subcommand - .setName('welcome-update') - .setDescription('Zaktualizuj wiadomość powitalną z bazy danych') - ) - .addSubcommand(subcommand => - subcommand - .setName('status') - .setDescription('Pokaż status konfiguracji bota') - ) - ]; - } - - getCommandsData() { - return this.commands.map(command => command.toJSON()); - } - - async handleCommand(interaction) { - const { commandName, options } = interaction; - - switch (commandName) { - case 'skrzynka': - await this.handleUserCommands(interaction, options); - break; - case 'skrzynka-adm': - await this.handleAdminCommands(interaction, options); - break; - default: - await interaction.reply({ - content: '❌ Nieznana komenda.', - ephemeral: true - }); - } - } - - async handleUserCommands(interaction, options) { - const subcommand = options.getSubcommand(); - - switch (subcommand) { - case 'info': - await this.showBotInfo(interaction); - break; - case 'help': - await this.showHelp(interaction); - break; - } - } - - async handleAdminCommands(interaction, options) { - const subcommand = options.getSubcommand(); - - // Sprawdź uprawnienia użytkownika - if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels)) { - await interaction.reply({ - content: '❌ Nie masz uprawnień do używania komend administracyjnych.', - ephemeral: true - }); - return; - } - - switch (subcommand) { - case 'set-welcome': - await this.setWelcomeChannel(interaction, options); - break; - case 'welcome': - await this.sendWelcomeMessage(interaction); - break; - case 'welcome-update': - await this.updateWelcomeMessage(interaction); - break; - case 'status': - await this.showStatus(interaction); - break; - } - } - - async showBotInfo(interaction) { - const embed = new EmbedBuilder() - .setColor(0x00AE86) - .setTitle('🎭 Skrzynka Impostora Bot') - .setDescription('Bot do zarządzania wiadomościami powaitalnymi na serwerze Discord.') - .addFields( - { name: '📝 Wersja', value: '1.0.0', inline: true }, - { name: '🔧 Panel Web', value: 'Dostępny dla administratorów', inline: true }, - { name: '⚡ Status', value: 'Online', inline: true }, - { name: '💻 Funkcje', value: '• Automatyczne wiadomości powitalne\n• Panel zarządzania web\n• Historia zmian\n• Konfiguracja kanałów', inline: false } - ) - .setThumbnail(interaction.client.user.displayAvatarURL()) - .setFooter({ - text: 'Skrzynka Impostora Bot', - iconURL: interaction.client.user.displayAvatarURL() - }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed] }); - } - - async showHelp(interaction) { - const embed = new EmbedBuilder() - .setColor(0x0099FF) - .setTitle('📚 Pomoc - Dostępne komendy') - .addFields( - { - name: '👥 Komendy użytkownika', - value: '`/skrzynka info` - Informacje o bocie\n`/skrzynka help` - Ta pomoc', - inline: false - }, - { - name: '🛠️ Komendy administracyjne', - value: '`/skrzynka-adm set-welcome` - Ustaw kanał powitalny\n`/skrzynka-adm welcome` - Wyślij wiadomość powitalną\n`/skrzynka-adm welcome-update` - Zaktualizuj wiadomość\n`/skrzynka-adm status` - Status konfiguracji', - inline: false - }, - { - name: '🌐 Panel Web', - value: 'Zarządzanie treścią wiadomości dostępne przez panel web.', - inline: false - } - ) - .setFooter({ - text: 'Potrzebujesz pomocy? Skontaktuj się z administratorem serwera.', - iconURL: interaction.client.user.displayAvatarURL() - }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed], ephemeral: true }); - } - - async setWelcomeChannel(interaction, options) { - const channel = options.getChannel('channel'); - const guildId = interaction.guild.id; - const guildName = interaction.guild.name; - - try { - // Sprawdź czy bot ma uprawnienia do pisania na kanale - if (!channel.permissionsFor(interaction.client.user).has(PermissionFlagsBits.SendMessages)) { - await interaction.reply({ - content: '❌ Bot nie ma uprawnień do pisania na wybranym kanale.', - ephemeral: true - }); - return; - } - - // Dodaj/zaktualizuj serwer w bazie - await this.db.addGuild(guildId, guildName); - - // Ustaw kanał powitalny - await this.db.setWelcomeChannel(guildId, channel.id, channel.name); - - const embed = new EmbedBuilder() - .setColor(0x00FF00) - .setTitle('✅ Kanał powitalny ustawiony') - .setDescription(`Kanał ${channel} został ustawiony jako kanał powitalny.`) - .addFields( - { name: 'Kanał', value: `${channel.name} (${channel.id})`, inline: false }, - { name: 'Następne kroki', value: 'Skonfiguruj treść wiadomości przez panel web lub użyj `/skrzynka-adm welcome` aby wysłać domyślną wiadomość.', inline: false } - ) - .setTimestamp(); - - await interaction.reply({ embeds: [embed] }); - - } catch (error) { - console.error('Błąd podczas ustawiania kanału powitalnego:', error); - await interaction.reply({ - content: '❌ Wystąpił błąd podczas ustawiania kanału powitalnego.', - ephemeral: true - }); - } - } - - async sendWelcomeMessage(interaction) { - const guildId = interaction.guild.id; - - try { - await interaction.deferReply({ ephemeral: true }); - - // Pobierz konfigurację kanału - const welcomeChannelConfig = await this.db.getWelcomeChannel(guildId); - if (!welcomeChannelConfig) { - await interaction.editReply({ - content: '❌ Kanał powitalny nie został skonfigurowany. Użyj `/skrzynka-adm set-welcome` aby go ustawić.' - }); - return; - } - - // Pobierz kanał - const channel = interaction.guild.channels.cache.get(welcomeChannelConfig.channel_id); - if (!channel) { - await interaction.editReply({ - content: '❌ Skonfigurowany kanał powitalny nie istnieje lub bot nie ma do niego dostępu.' - }); - return; - } - - // Pobierz wiadomość z bazy lub użyj domyślnej - let welcomeMessage = await this.db.getWelcomeMessage(guildId); - let messageContent, embedData; - - if (welcomeMessage) { - messageContent = welcomeMessage.content; - embedData = welcomeMessage.embed_data; - } else { - messageContent = this.getDefaultWelcomeMessage(interaction.guild); - embedData = null; - } - - // Wyślij wiadomość - const messageOptions = { content: messageContent }; - if (embedData) { - messageOptions.embeds = [new EmbedBuilder(embedData)]; - } - - const sentMessage = await channel.send(messageOptions); - - // Zapisz lub zaktualizuj wiadomość w bazie - if (welcomeMessage) { - await this.db.updateWelcomeMessage(guildId, messageContent, embedData, sentMessage.id); - } else { - await this.db.saveWelcomeMessage(guildId, messageContent, embedData, sentMessage.id); - } - - const successEmbed = new EmbedBuilder() - .setColor(0x00FF00) - .setTitle('✅ Wiadomość powitalna wysłana') - .setDescription(`Wiadomość została wysłana na kanał ${channel}.`) - .addFields( - { name: 'Kanał', value: `${channel.name}`, inline: true }, - { name: 'ID wiadomości', value: `${sentMessage.id}`, inline: true } - ) - .setTimestamp(); - - await interaction.editReply({ embeds: [successEmbed] }); - - } catch (error) { - console.error('Błąd podczas wysyłania wiadomości powitalnej:', error); - await interaction.editReply({ - content: '❌ Wystąpił błąd podczas wysyłania wiadomości powitalnej.' - }); - } - } - - async updateWelcomeMessage(interaction) { - const guildId = interaction.guild.id; - - try { - await interaction.deferReply({ ephemeral: true }); - - // Pobierz konfigurację i wiadomość - const welcomeChannelConfig = await this.db.getWelcomeChannel(guildId); - const welcomeMessage = await this.db.getWelcomeMessage(guildId); - - if (!welcomeChannelConfig || !welcomeMessage) { - await interaction.editReply({ - content: '❌ Kanał powitalny lub wiadomość nie zostały skonfigurowane.' - }); - return; - } - - const channel = interaction.guild.channels.cache.get(welcomeChannelConfig.channel_id); - if (!channel) { - await interaction.editReply({ - content: '❌ Skonfigurowany kanał powitalny nie istnieje.' - }); - return; - } - - // Znajdź i zaktualizuj wiadomość - if (welcomeMessage.message_id) { - try { - const message = await channel.messages.fetch(welcomeMessage.message_id); - - const messageOptions = { content: welcomeMessage.content }; - if (welcomeMessage.embed_data) { - messageOptions.embeds = [new EmbedBuilder(welcomeMessage.embed_data)]; - } - - await message.edit(messageOptions); - - const successEmbed = new EmbedBuilder() - .setColor(0x00FF00) - .setTitle('✅ Wiadomość zaktualizowana') - .setDescription(`Wiadomość powitalna została zaktualizowana na kanale ${channel}.`) - .setTimestamp(); - - await interaction.editReply({ embeds: [successEmbed] }); - - } catch (fetchError) { - // Jeśli nie można znaleźć wiadomości, wyślij nową - await this.sendWelcomeMessage(interaction); - } - } else { - // Jeśli nie ma ID wiadomości, wyślij nową - await this.sendWelcomeMessage(interaction); - } - - } catch (error) { - console.error('Błąd podczas aktualizacji wiadomości powitalnej:', error); - await interaction.editReply({ - content: '❌ Wystąpił błąd podczas aktualizacji wiadomości powitalnej.' - }); - } - } - - async showStatus(interaction) { - const guildId = interaction.guild.id; - - try { - const welcomeChannelConfig = await this.db.getWelcomeChannel(guildId); - const welcomeMessage = await this.db.getWelcomeMessage(guildId); - - const embed = new EmbedBuilder() - .setColor(0x0099FF) - .setTitle('📊 Status konfiguracji bota') - .setDescription(`Status dla serwera: **${interaction.guild.name}**`); - - if (welcomeChannelConfig) { - const channel = interaction.guild.channels.cache.get(welcomeChannelConfig.channel_id); - embed.addFields({ - name: '📍 Kanał powitalny', - value: channel ? `${channel.name} (${channel.id})` : `❌ Kanał niedostępny (${welcomeChannelConfig.channel_id})`, - inline: false - }); - } else { - embed.addFields({ - name: '📍 Kanał powitalny', - value: '❌ Nie skonfigurowany', - inline: false - }); - } - - if (welcomeMessage) { - embed.addFields( - { - name: '💬 Wiadomość powitalna', - value: '✅ Skonfigurowana w bazie danych', - inline: true - }, - { - name: '🆔 ID wiadomości', - value: welcomeMessage.message_id || 'Brak', - inline: true - }, - { - name: '📅 Ostatnia aktualizacja', - value: new Date(welcomeMessage.updated_at).toLocaleString('pl-PL'), - inline: true - } - ); - } else { - embed.addFields({ - name: '💬 Wiadomość powitalna', - value: '❌ Nie skonfigurowana (będzie użyta domyślna)', - inline: false - }); - } - - embed.setFooter({ - text: 'Użyj panelu web aby zarządzać treścią wiadomości', - iconURL: interaction.client.user.displayAvatarURL() - }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed], ephemeral: true }); - - } catch (error) { - console.error('Błąd podczas pobierania statusu:', error); - await interaction.reply({ - content: '❌ Wystąpił błąd podczas pobierania statusu.', - ephemeral: true - }); - } - } - - getDefaultWelcomeMessage(guild) { - return `# 🎭 Witamy na serwerze ${guild.name}! - -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ść została wygenerowana przez Skrzynka Impostora Bot* -*Administratorzy mogą edytować treść przez panel web*`; - } -} - -module.exports = { SlashCommands }; diff --git a/bot/backend/database/DatabaseManager.js b/bot/backend/database/DatabaseManager.js deleted file mode 100644 index 562cc50..0000000 --- a/bot/backend/database/DatabaseManager.js +++ /dev/null @@ -1,234 +0,0 @@ -const { Pool } = require('pg'); -require('dotenv').config(); - -class DatabaseManager { - constructor() { - this.pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false - }); - } - - async initialize() { - try { - await this.createTables(); - console.log('Tabele bazy danych utworzone pomyślnie'); - } catch (error) { - console.error('Błąd podczas inicjalizacji bazy danych:', error); - throw error; - } - } - - async createTables() { - const createTablesSQL = ` - -- Tabela serwerów Discord - CREATE TABLE IF NOT EXISTS guilds ( - id VARCHAR(20) PRIMARY KEY, - name VARCHAR(100) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - -- Tabela kanałów powitalnych - CREATE TABLE IF NOT EXISTS welcome_channels ( - id SERIAL PRIMARY KEY, - guild_id VARCHAR(20) REFERENCES guilds(id) ON DELETE CASCADE, - channel_id VARCHAR(20) NOT NULL, - channel_name VARCHAR(100), - is_active BOOLEAN DEFAULT true, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(guild_id) - ); - - -- Tabela wiadomości powitalnych - CREATE TABLE IF NOT EXISTS welcome_messages ( - id SERIAL PRIMARY KEY, - guild_id VARCHAR(20) REFERENCES guilds(id) ON DELETE CASCADE, - content TEXT NOT NULL, - embed_data JSONB, - message_id VARCHAR(20), - is_active BOOLEAN DEFAULT true, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - -- Tabela użytkowników panelu web - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - discord_id VARCHAR(20) UNIQUE NOT NULL, - username VARCHAR(32) NOT NULL, - discriminator VARCHAR(4), - avatar VARCHAR(255), - email VARCHAR(255), - role VARCHAR(20) DEFAULT 'viewer', - is_active BOOLEAN DEFAULT true, - last_login TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - -- Tabela historii wersji wiadomości - CREATE TABLE IF NOT EXISTS message_revisions ( - id SERIAL PRIMARY KEY, - message_id INTEGER REFERENCES welcome_messages(id) ON DELETE CASCADE, - content TEXT NOT NULL, - embed_data JSONB, - user_id INTEGER REFERENCES users(id), - revision_number INTEGER NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - -- Tabela uprawnień użytkowników do serwerów - CREATE TABLE IF NOT EXISTS user_guild_permissions ( - id SERIAL PRIMARY KEY, - user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, - guild_id VARCHAR(20) REFERENCES guilds(id) ON DELETE CASCADE, - role VARCHAR(20) DEFAULT 'viewer', - granted_by INTEGER REFERENCES users(id), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(user_id, guild_id) - ); - - -- Indeksy dla wydajności - CREATE INDEX IF NOT EXISTS idx_guilds_id ON guilds(id); - CREATE INDEX IF NOT EXISTS idx_welcome_channels_guild_id ON welcome_channels(guild_id); - CREATE INDEX IF NOT EXISTS idx_welcome_messages_guild_id ON welcome_messages(guild_id); - CREATE INDEX IF NOT EXISTS idx_users_discord_id ON users(discord_id); - CREATE INDEX IF NOT EXISTS idx_message_revisions_message_id ON message_revisions(message_id); - CREATE INDEX IF NOT EXISTS idx_user_guild_permissions_user_guild ON user_guild_permissions(user_id, guild_id); - `; - - await this.pool.query(createTablesSQL); - } - - // Metody dla zarządzania serwerami - async addGuild(guildId, guildName) { - const query = ` - INSERT INTO guilds (id, name) - VALUES ($1, $2) - ON CONFLICT (id) DO UPDATE SET - name = EXCLUDED.name, - updated_at = CURRENT_TIMESTAMP - `; - await this.pool.query(query, [guildId, guildName]); - } - - async getGuild(guildId) { - const query = 'SELECT * FROM guilds WHERE id = $1'; - const result = await this.pool.query(query, [guildId]); - return result.rows[0]; - } - - // Metody dla kanałów powitalnych - async setWelcomeChannel(guildId, channelId, channelName) { - const query = ` - INSERT INTO welcome_channels (guild_id, channel_id, channel_name) - VALUES ($1, $2, $3) - ON CONFLICT (guild_id) DO UPDATE SET - channel_id = EXCLUDED.channel_id, - channel_name = EXCLUDED.channel_name, - is_active = true, - updated_at = CURRENT_TIMESTAMP - `; - await this.pool.query(query, [guildId, channelId, channelName]); - } - - async getWelcomeChannel(guildId) { - const query = 'SELECT * FROM welcome_channels WHERE guild_id = $1 AND is_active = true'; - const result = await this.pool.query(query, [guildId]); - return result.rows[0]; - } - - // Metody dla wiadomości powitalnych - async saveWelcomeMessage(guildId, content, embedData = null, messageId = null) { - const query = ` - INSERT INTO welcome_messages (guild_id, content, embed_data, message_id) - VALUES ($1, $2, $3, $4) - RETURNING * - `; - const result = await this.pool.query(query, [guildId, content, embedData, messageId]); - return result.rows[0]; - } - - async updateWelcomeMessage(guildId, content, embedData = null, messageId = null) { - const query = ` - UPDATE welcome_messages - SET content = $2, embed_data = $3, message_id = $4, updated_at = CURRENT_TIMESTAMP - WHERE guild_id = $1 AND is_active = true - RETURNING * - `; - const result = await this.pool.query(query, [guildId, content, embedData, messageId]); - return result.rows[0]; - } - - async getWelcomeMessage(guildId) { - const query = 'SELECT * FROM welcome_messages WHERE guild_id = $1 AND is_active = true ORDER BY created_at DESC LIMIT 1'; - const result = await this.pool.query(query, [guildId]); - return result.rows[0]; - } - - // Metody dla użytkowników - async saveUser(discordUser) { - const query = ` - INSERT INTO users (discord_id, username, discriminator, avatar, email) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (discord_id) DO UPDATE SET - username = EXCLUDED.username, - discriminator = EXCLUDED.discriminator, - avatar = EXCLUDED.avatar, - email = EXCLUDED.email, - last_login = CURRENT_TIMESTAMP, - updated_at = CURRENT_TIMESTAMP - RETURNING * - `; - const result = await this.pool.query(query, [ - discordUser.id, - discordUser.username, - discordUser.discriminator || '0000', - discordUser.avatar, - discordUser.email - ]); - return result.rows[0]; - } - - async getUser(discordId) { - const query = 'SELECT * FROM users WHERE discord_id = $1'; - const result = await this.pool.query(query, [discordId]); - return result.rows[0]; - } - - // Metody dla historii wersji - async saveMessageRevision(messageId, content, embedData, userId) { - // Pobierz aktualny numer rewizji - const revisionQuery = 'SELECT COALESCE(MAX(revision_number), 0) + 1 as next_revision FROM message_revisions WHERE message_id = $1'; - const revisionResult = await this.pool.query(revisionQuery, [messageId]); - const nextRevision = revisionResult.rows[0].next_revision; - - const query = ` - INSERT INTO message_revisions (message_id, content, embed_data, user_id, revision_number) - VALUES ($1, $2, $3, $4, $5) - RETURNING * - `; - const result = await this.pool.query(query, [messageId, content, embedData, userId, nextRevision]); - return result.rows[0]; - } - - async getMessageRevisions(messageId) { - const query = ` - SELECT mr.*, u.username - FROM message_revisions mr - LEFT JOIN users u ON mr.user_id = u.id - WHERE mr.message_id = $1 - ORDER BY mr.revision_number DESC - `; - const result = await this.pool.query(query, [messageId]); - return result.rows; - } - - async close() { - await this.pool.end(); - } -} - -module.exports = { DatabaseManager }; diff --git a/bot/backend/deploy-commands.js b/bot/backend/deploy-commands.js deleted file mode 100644 index fa2c578..0000000 --- a/bot/backend/deploy-commands.js +++ /dev/null @@ -1,43 +0,0 @@ -const { REST, Routes } = require('discord.js'); -const { SlashCommands } = require('./commands'); -require('dotenv').config(); - -async function deployCommands() { - if (!process.env.DISCORD_TOKEN || !process.env.DISCORD_CLIENT_ID) { - console.error('❌ Brak wymaganych zmiennych środowiskowych (DISCORD_TOKEN, DISCORD_CLIENT_ID)'); - process.exit(1); - } - - const commands = new SlashCommands(); - const commandsData = commands.getCommandsData(); - - const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN); - - try { - console.log(`🚀 Rozpoczęto deploy ${commandsData.length} komend slash...`); - - // Deploy globalnych komend - const data = await rest.put( - Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), - { body: commandsData } - ); - - console.log(`✅ Pomyślnie zdeployowano ${data.length} komend slash globalnie.`); - - // Wyświetl listę komend - console.log('\n📋 Zdeployowane komendy:'); - data.forEach(command => { - console.log(` • /${command.name} - ${command.description}`); - }); - - } catch (error) { - console.error('❌ Błąd podczas deployowania komend:', error); - } -} - -// Jeśli skrypt jest uruchamiany bezpośrednio -if (require.main === module) { - deployCommands(); -} - -module.exports = { deployCommands }; diff --git a/bot/backend/index.js b/bot/backend/index.js deleted file mode 100644 index f29ffa4..0000000 --- a/bot/backend/index.js +++ /dev/null @@ -1,113 +0,0 @@ -const { Client, GatewayIntentBits, REST, Routes, EmbedBuilder } = require('discord.js'); -const express = require('express'); -const cors = require('cors'); -const helmet = require('helmet'); -const morgan = require('morgan'); -const compression = require('compression'); -require('dotenv').config(); - -const { DatabaseManager } = require('./database/DatabaseManager'); -const { SlashCommands } = require('./commands'); -const { WebPanel } = require('./web/server'); - -class SkrzynkaImpostoraBot { - constructor() { - this.client = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - GatewayIntentBits.GuildMembers - ] - }); - - this.db = new DatabaseManager(); - this.commands = new SlashCommands(this.db); - this.webPanel = new WebPanel(this.db); - - this.setupEventHandlers(); - } - - setupEventHandlers() { - this.client.once('ready', () => { - console.log(`✅ Bot zalogowany jako ${this.client.user.tag}`); - this.client.user.setActivity('Zarządzanie wiadomościami powiatalnymi', { type: 'WATCHING' }); - }); - - this.client.on('guildCreate', async (guild) => { - console.log(`Dodano do nowego serwera: ${guild.name} (${guild.id})`); - await this.db.addGuild(guild.id, guild.name); - }); - - this.client.on('interactionCreate', async (interaction) => { - if (!interaction.isChatInputCommand()) return; - - try { - await this.commands.handleCommand(interaction); - } catch (error) { - console.error('Błąd podczas wykonywania komendy:', error); - - const errorEmbed = new EmbedBuilder() - .setColor(0xFF0000) - .setTitle('❌ Błąd') - .setDescription('Wystąpił błąd podczas wykonywania komendy.') - .setTimestamp(); - - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ embeds: [errorEmbed], ephemeral: true }); - } else { - await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); - } - } - }); - - this.client.on('error', console.error); - } - - async start() { - try { - // Inicjalizacja bazy danych - await this.db.initialize(); - console.log('✅ Baza danych zainicjalizowana'); - - // Uruchomienie panelu web - await this.webPanel.start(); - console.log('✅ Panel web uruchomiony'); - - // Logowanie bota - await this.client.login(process.env.DISCORD_TOKEN); - console.log('✅ Bot Discord uruchomiony'); - - } catch (error) { - console.error('❌ Błąd podczas uruchamiania bota:', error); - process.exit(1); - } - } - - async deployCommands() { - const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN); - - try { - console.log('Rozpoczęto odświeżanie komend slash...'); - - const commands = this.commands.getCommandsData(); - - await rest.put( - Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), - { body: commands } - ); - - console.log('✅ Komendy slash zostały pomyślnie odświeżone.'); - } catch (error) { - console.error('❌ Błąd podczas odświeżania komend:', error); - } - } -} - -// Uruchomienie bota -if (require.main === module) { - const bot = new SkrzynkaImpostoraBot(); - bot.start(); -} - -module.exports = { SkrzynkaImpostoraBot }; diff --git a/bot/backend/web/server.js b/bot/backend/web/server.js deleted file mode 100644 index 46962fc..0000000 --- a/bot/backend/web/server.js +++ /dev/null @@ -1,372 +0,0 @@ -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 }; diff --git a/bot/database/migrate.js b/bot/database/migrate.js deleted file mode 100644 index 6ef90cc..0000000 --- a/bot/database/migrate.js +++ /dev/null @@ -1,24 +0,0 @@ -const { DatabaseManager } = require('../backend/database/DatabaseManager'); -require('dotenv').config(); - -async function migrate() { - console.log('🚀 Rozpoczynam migrację bazy danych...'); - - const db = new DatabaseManager(); - - try { - await db.initialize(); - console.log('✅ Migracja zakończona pomyślnie'); - } catch (error) { - console.error('❌ Błąd podczas migracji:', error); - process.exit(1); - } finally { - await db.close(); - } -} - -if (require.main === module) { - migrate(); -} - -module.exports = { migrate }; diff --git a/bot/database/seed.js b/bot/database/seed.js deleted file mode 100644 index c70ae7f..0000000 --- a/bot/database/seed.js +++ /dev/null @@ -1,54 +0,0 @@ -const { DatabaseManager } = require('../backend/database/DatabaseManager'); -require('dotenv').config(); - -async function seed() { - console.log('🌱 Rozpoczynam seedowanie bazy danych...'); - - const db = new DatabaseManager(); - - try { - await db.initialize(); - - // Dodaj przykładowy serwer - await db.addGuild('123456789', 'Test Server'); - console.log('✅ Dodano przykładowy serwer'); - - // Dodaj przykładowy kanał powitalny - await db.setWelcomeChannel('123456789', '987654321', 'witamy'); - console.log('✅ Dodano przykładowy kanał powitalny'); - - // Dodaj przykładową wiadomość - const defaultMessage = `# 🎭 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ść została wygenerowana przez Skrzynka Impostora Bot*`; - - await db.saveWelcomeMessage('123456789', defaultMessage); - console.log('✅ Dodano przykładową wiadomość powitalną'); - - console.log('✅ Seedowanie zakończone pomyślnie'); - } catch (error) { - console.error('❌ Błąd podczas seedowania:', error); - process.exit(1); - } finally { - await db.close(); - } -} - -if (require.main === module) { - seed(); -} - -module.exports = { seed }; diff --git a/bot/docker-compose.dev.yml b/bot/docker-compose.dev.yml deleted file mode 100644 index 8e24084..0000000 --- a/bot/docker-compose.dev.yml +++ /dev/null @@ -1,126 +0,0 @@ -version: '3.8' - -services: - # PostgreSQL Database dla development - postgres-dev: - image: postgres:15-alpine - container_name: skrzynka-postgres-dev - restart: unless-stopped - environment: - POSTGRES_DB: skrzynka_impostora_dev - POSTGRES_USER: dev_user - POSTGRES_PASSWORD: dev_password - PGDATA: /var/lib/postgresql/data/pgdata - volumes: - - postgres_dev_data:/var/lib/postgresql/data - - ./database/init:/docker-entrypoint-initdb.d - ports: - - "5433:5432" # Inny port żeby nie kolidować z lokalnym PostgreSQL - networks: - - skrzynka-dev-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U dev_user -d skrzynka_impostora_dev"] - interval: 10s - timeout: 5s - retries: 5 - - # Redis dla development - redis-dev: - image: redis:7-alpine - container_name: skrzynka-redis-dev - restart: unless-stopped - ports: - - "6380:6379" # Inny port - volumes: - - redis_dev_data:/data - networks: - - skrzynka-dev-network - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - - # Bot w trybie development z hot reload - bot-dev: - build: - context: . - dockerfile: Dockerfile.dev - container_name: skrzynka-bot-dev - restart: unless-stopped - environment: - # Database - DATABASE_URL: postgresql://dev_user:dev_password@postgres-dev:5432/skrzynka_impostora_dev - DB_HOST: postgres-dev - DB_PORT: 5432 - DB_NAME: skrzynka_impostora_dev - DB_USER: dev_user - DB_PASSWORD: dev_password - - # Discord Bot - DISCORD_TOKEN: ${DISCORD_TOKEN} - DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID} - DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET} - - # Web Panel - JWT_SECRET: dev_jwt_secret_key - SESSION_SECRET: dev_session_secret_key - WEB_PORT: 3001 - API_PORT: 3000 - - # OAuth2 - OAUTH2_REDIRECT_URI: http://localhost:3001/auth/discord/callback # Environment - NODE_ENV: development - LOG_LEVEL: debug - - # Redis - REDIS_URL: redis://redis-dev:6379 - ports: - - "3000:3000" - - "3001:3001" - - "9229:9229" # Node.js debugger port - depends_on: - postgres-dev: - condition: service_healthy - redis-dev: - condition: service_healthy - networks: - - skrzynka-dev-network - volumes: - - .:/app - - /app/node_modules - - /app/frontend/node_modules - - ./logs:/app/logs - command: npm run dev - - # pgAdmin dla zarządzania bazą danych - pgadmin: - image: dpage/pgadmin4:latest - container_name: skrzynka-pgadmin - restart: unless-stopped - environment: - PGADMIN_DEFAULT_EMAIL: admin@skrzynka.local - PGADMIN_DEFAULT_PASSWORD: admin - PGADMIN_CONFIG_SERVER_MODE: 'False' - ports: - - "8080:80" - depends_on: - - postgres-dev - networks: - - skrzynka-dev-network - volumes: - - pgadmin_data:/var/lib/pgadmin - -# Volumes -volumes: - postgres_dev_data: - driver: local - redis_dev_data: - driver: local - pgadmin_data: - driver: local - -# Networks -networks: - skrzynka-dev-network: - driver: bridge diff --git a/bot/docker-compose.yml b/bot/docker-compose.yml deleted file mode 100644 index bcccc5a..0000000 --- a/bot/docker-compose.yml +++ /dev/null @@ -1,131 +0,0 @@ -version: '3.8' - -services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: skrzynka-postgres - restart: unless-stopped - environment: - POSTGRES_DB: skrzynka_impostora - POSTGRES_USER: skrzynka_user - POSTGRES_PASSWORD: skrzynka_password - PGDATA: /var/lib/postgresql/data/pgdata - volumes: - - postgres_data:/var/lib/postgresql/data - - ./database/init:/docker-entrypoint-initdb.d - ports: - - "5432:5432" - networks: - - skrzynka-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U skrzynka_user -d skrzynka_impostora"] - interval: 10s - timeout: 5s - retries: 5 - - # Redis for caching (optional) - redis: - image: redis:7-alpine - container_name: skrzynka-redis - restart: unless-stopped - ports: - - "6379:6379" - volumes: - - redis_data:/data - networks: - - skrzynka-network - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - - # Discord Bot Application - bot: - build: - context: . - dockerfile: Dockerfile - container_name: skrzynka-bot - restart: unless-stopped - environment: - # Database - DATABASE_URL: postgresql://skrzynka_user:skrzynka_password@postgres:5432/skrzynka_impostora - DB_HOST: postgres - DB_PORT: 5432 - DB_NAME: skrzynka_impostora - DB_USER: skrzynka_user - DB_PASSWORD: skrzynka_password - - # Discord Bot (należy ustawić w .env lub przez docker secrets) - DISCORD_TOKEN: ${DISCORD_TOKEN} - DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID} - DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET} - - # Web Panel - JWT_SECRET: ${JWT_SECRET:-super_secret_jwt_key_change_in_production} - SESSION_SECRET: ${SESSION_SECRET:-super_secret_session_key_change_in_production} - WEB_PORT: 3001 - API_PORT: 3000 - - # OAuth2 - OAUTH2_REDIRECT_URI: ${OAUTH2_REDIRECT_URI:-http://localhost:3001/auth/discord/callback} - - # Environment - NODE_ENV: ${NODE_ENV:-production} - LOG_LEVEL: ${LOG_LEVEL:-info} - - # Redis (optional) - REDIS_URL: redis://redis:6379 - ports: - - "3000:3000" - - "3001:3001" - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_healthy - networks: - - skrzynka-network - volumes: - - ./logs:/app/logs - healthcheck: - test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s - - # Nginx Reverse Proxy (production) - nginx: - image: nginx:alpine - container_name: skrzynka-nginx - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - - ./nginx/conf.d:/etc/nginx/conf.d:ro - - ./ssl:/etc/nginx/ssl:ro - - nginx_logs:/var/log/nginx - depends_on: - - bot - networks: - - skrzynka-network - profiles: - - production - -# Volumes -volumes: - postgres_data: - driver: local - redis_data: - driver: local - nginx_logs: - driver: local - -# Networks -networks: - skrzynka-network: - driver: bridge diff --git a/bot/frontend/package.json b/bot/frontend/package.json deleted file mode 100644 index 44ce8c0..0000000 --- a/bot/frontend/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "skrzynka-impostora-panel", - "version": "1.0.0", - "private": true, - "homepage": ".", - "dependencies": { - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.8.1", - "react-scripts": "5.0.1", - "axios": "^1.3.4", - "react-markdown": "^8.0.5", - "react-syntax-highlighter": "^15.5.0", - "@monaco-editor/react": "^4.4.6", - "lucide-react": "^0.321.0", - "clsx": "^1.2.1" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "proxy": "http://localhost:3000" -} diff --git a/bot/frontend/public/index.html b/bot/frontend/public/index.html deleted file mode 100644 index c0e1575..0000000 --- a/bot/frontend/public/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - Skrzynka Impostora - Panel Administracyjny - - - -
- - diff --git a/bot/frontend/public/manifest.json b/bot/frontend/public/manifest.json deleted file mode 100644 index f94f46d..0000000 --- a/bot/frontend/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "Skrzynka Impostora", - "name": "Skrzynka Impostora Bot Panel", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#5865f2", - "background_color": "#ffffff" -} diff --git a/bot/frontend/src/App.css b/bot/frontend/src/App.css deleted file mode 100644 index 226acbd..0000000 --- a/bot/frontend/src/App.css +++ /dev/null @@ -1,708 +0,0 @@ -/* Discord-inspired CSS for Skrzynka Impostora Bot Panel */ - -/* CSS Variables */ -:root { - --discord-blue: #5865f2; - --discord-green: #57f287; - --discord-red: #ed4245; - --discord-yellow: #fee75c; - --discord-purple: #eb459e; - - --discord-dark: #2c2f33; - --discord-darker: #23272a; - --discord-light: #36393f; - --discord-lighter: #40444b; - - --text-primary: #ffffff; - --text-secondary: #b9bbbe; - --text-muted: #72767d; - - --background-primary: #36393f; - --background-secondary: #2f3136; - --background-tertiary: #202225; - - --border-color: #202225; - --hover-color: rgba(79, 84, 92, 0.16); -} - -/* Base styles */ -* { - box-sizing: border-box; -} - -body { - margin: 0; - padding: 0; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background-color: var(--background-primary); - color: var(--text-primary); - line-height: 1.6; -} - -/* Layout */ -.flex { - display: flex; -} - -.min-h-screen { - min-height: 100vh; -} - -/* Sidebar */ -.sidebar { - width: 260px; - min-height: 100vh; - background-color: var(--background-secondary); - padding: 1.5rem; - display: flex; - flex-direction: column; - position: fixed; - left: 0; - top: 0; - z-index: 100; -} - -.sidebar h2 { - color: var(--text-primary); - font-size: 1.25rem; - margin: 0 0 0.5rem 0; -} - -.sidebar p { - color: var(--text-secondary); - font-size: 0.875rem; - margin: 0 0 2rem 0; -} - -/* Navigation */ -.nav-link { - display: flex; - align-items: center; - padding: 0.5rem 0.75rem; - margin-bottom: 0.25rem; - border-radius: 4px; - color: var(--text-secondary); - text-decoration: none; - transition: all 0.15s ease; -} - -.nav-link:hover { - background-color: var(--hover-color); - color: var(--text-primary); -} - -.nav-link.active { - background-color: var(--discord-blue); - color: white; -} - -/* Main content */ -.main-content { - flex: 1; - margin-left: 260px; - padding: 2rem; - background-color: var(--background-primary); - min-height: 100vh; -} - -/* Cards */ -.discord-card { - background-color: var(--background-secondary); - border: 1px solid var(--border-color); - border-radius: 8px; - padding: 1.5rem; - margin-bottom: 1rem; -} - -.guild-card { - background-color: var(--background-secondary); - border: 1px solid var(--border-color); - border-radius: 8px; - padding: 1.5rem; - transition: all 0.15s ease; -} - -.guild-card:hover { - border-color: var(--discord-blue); - transform: translateY(-2px); -} - -/* Guild icon */ -.guild-icon { - width: 48px; - height: 48px; - border-radius: 50%; - background-color: var(--discord-blue); - display: flex; - align-items: center; - justify-content: center; - color: white; - font-weight: bold; - margin-right: 1rem; - font-size: 1rem; -} - -/* Buttons */ -.discord-button { - background-color: var(--discord-blue); - color: white; - border: none; - border-radius: 4px; - padding: 0.5rem 1rem; - font-size: 0.875rem; - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; - text-decoration: none; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.discord-button:hover { - background-color: #4752c4; - transform: translateY(-1px); -} - -.discord-button:disabled { - background-color: var(--text-muted); - cursor: not-allowed; - transform: none; -} - -.discord-button.secondary { - background-color: var(--background-lighter); - color: var(--text-primary); -} - -.discord-button.secondary:hover { - background-color: var(--hover-color); -} - -.discord-button.danger { - background-color: var(--discord-red); -} - -.discord-button.danger:hover { - background-color: #c73e41; -} - -.discord-button.success { - background-color: var(--discord-green); -} - -.discord-button.success:hover { - background-color: #4ac486; -} - -/* Forms */ -.discord-textarea { - width: 100%; - min-height: 200px; - background-color: var(--background-tertiary); - border: 1px solid var(--border-color); - border-radius: 4px; - padding: 0.75rem; - color: var(--text-primary); - font-family: 'Consolas', 'Monaco', 'Courier New', monospace; - font-size: 0.875rem; - line-height: 1.5; - resize: vertical; -} - -.discord-textarea:focus { - outline: none; - border-color: var(--discord-blue); -} - -.discord-textarea::placeholder { - color: var(--text-muted); -} - -/* Preview area */ -.preview-area { - background-color: var(--background-tertiary); - border: 1px solid var(--border-color); - border-radius: 4px; - padding: 1rem; - overflow-y: auto; -} - -.preview-area h1 { - font-size: 1.5rem; - font-weight: bold; - margin: 0 0 1rem 0; - color: var(--text-primary); -} - -.preview-area h2 { - font-size: 1.25rem; - font-weight: bold; - margin: 0 0 0.75rem 0; - color: var(--text-primary); -} - -.preview-area h3 { - font-size: 1.125rem; - font-weight: bold; - margin: 0 0 0.5rem 0; - color: var(--text-primary); -} - -.preview-area p { - margin: 0 0 1rem 0; - color: var(--text-secondary); -} - -.preview-area strong { - color: var(--text-primary); - font-weight: bold; -} - -.preview-area em { - font-style: italic; -} - -.preview-area code { - background-color: var(--background-secondary); - color: var(--discord-yellow); - padding: 0.125rem 0.25rem; - border-radius: 3px; - font-family: 'Consolas', 'Monaco', 'Courier New', monospace; - font-size: 0.8em; -} - -.preview-area pre { - background-color: var(--background-secondary); - border: 1px solid var(--border-color); - border-radius: 4px; - padding: 0.75rem; - overflow-x: auto; - margin: 1rem 0; -} - -.preview-area pre code { - background: none; - color: var(--discord-green); - padding: 0; -} - -/* Grid system */ -.grid { - display: grid; -} - -.grid-cols-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); -} - -.grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - -.grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - -.grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); -} - -.gap-4 { - gap: 1rem; -} - -.gap-6 { - gap: 1.5rem; -} - -/* Spacing */ -.mb-2 { margin-bottom: 0.5rem; } -.mb-4 { margin-bottom: 1rem; } -.mb-6 { margin-bottom: 1.5rem; } -.mb-8 { margin-bottom: 2rem; } -.mt-4 { margin-top: 1rem; } -.mt-6 { margin-top: 1.5rem; } -.mt-8 { margin-top: 2rem; } -.mr-2 { margin-right: 0.5rem; } -.mr-3 { margin-right: 0.75rem; } -.mr-4 { margin-right: 1rem; } -.ml-4 { margin-left: 1rem; } - -/* Text utilities */ -.text-center { text-align: center; } -.text-sm { font-size: 0.875rem; } -.text-lg { font-size: 1.125rem; } -.text-xl { font-size: 1.25rem; } -.text-2xl { font-size: 1.5rem; } -.text-3xl { font-size: 1.875rem; } - -.font-medium { font-weight: 500; } -.font-semibold { font-weight: 600; } -.font-bold { font-weight: 700; } - -.text-gray-900 { color: var(--text-primary); } -.text-gray-600 { color: var(--text-secondary); } -.text-gray-500 { color: var(--text-muted); } - -/* Status indicators */ -.loading { - display: flex; - align-items: center; - justify-content: center; - padding: 2rem; - color: var(--text-secondary); -} - -.error { - background-color: rgba(237, 66, 69, 0.1); - border: 1px solid var(--discord-red); - border-radius: 4px; - padding: 1rem; - color: var(--discord-red); - margin-bottom: 1rem; -} - -.success { - background-color: rgba(87, 242, 135, 0.1); - border: 1px solid var(--discord-green); - border-radius: 4px; - padding: 1rem; - color: var(--discord-green); - margin-bottom: 1rem; -} - -/* Responsive */ -@media (max-width: 768px) { - .sidebar { - width: 100%; - height: auto; - position: relative; - } - - .main-content { - margin-left: 0; - } - - .grid-cols-2, - .grid-cols-3, - .grid-cols-4 { - grid-template-columns: repeat(1, minmax(0, 1fr)); - } -} - -@media (max-width: 1024px) { - .grid-cols-4 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} - -/* Animations */ -@keyframes fade-in { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.discord-card, -.guild-card { - animation: fade-in 0.3s ease-out; -} - -/* Custom scrollbar */ -::-webkit-scrollbar { - width: 8px; -} - -::-webkit-scrollbar-track { - background: var(--background-secondary); -} - -::-webkit-scrollbar-thumb { - background: var(--text-muted); - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--text-secondary); -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} - -.App { - min-height: 100vh; -} - -/* Discord-like styling */ -.discord-card { - background: #ffffff; - border-radius: 8px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); - padding: 20px; - margin-bottom: 20px; -} - -.discord-button { - background: #5865f2; - color: white; - border: none; - border-radius: 4px; - padding: 10px 16px; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s; -} - -.discord-button:hover { - background: #4752c4; -} - -.discord-button:disabled { - background: #a5a5a5; - cursor: not-allowed; -} - -.discord-button.secondary { - background: #4f545c; -} - -.discord-button.secondary:hover { - background: #5d6269; -} - -.discord-button.danger { - background: #ed4245; -} - -.discord-button.danger:hover { - background: #c03537; -} - -.discord-input { - background: #f2f3f5; - border: 1px solid #e3e5e8; - border-radius: 4px; - padding: 10px; - font-size: 16px; - width: 100%; - box-sizing: border-box; -} - -.discord-input:focus { - outline: none; - border-color: #5865f2; -} - -.discord-textarea { - background: #f2f3f5; - border: 1px solid #e3e5e8; - border-radius: 4px; - padding: 10px; - font-size: 14px; - width: 100%; - min-height: 200px; - resize: vertical; - font-family: 'Consolas', 'Monaco', 'Courier New', monospace; - box-sizing: border-box; -} - -.discord-textarea:focus { - outline: none; - border-color: #5865f2; -} - -.sidebar { - width: 250px; - background: #2f3136; - color: white; - height: 100vh; - position: fixed; - left: 0; - top: 0; - padding: 20px; - box-sizing: border-box; -} - -.main-content { - margin-left: 250px; - padding: 20px; - min-height: 100vh; -} - -.nav-link { - color: #b9bbbe; - text-decoration: none; - padding: 8px 12px; - border-radius: 4px; - display: block; - margin-bottom: 4px; - transition: background-color 0.2s; -} - -.nav-link:hover { - background: #393c43; - color: white; -} - -.nav-link.active { - background: #5865f2; - color: white; -} - -.preview-area { - background: #36393f; - color: white; - padding: 20px; - border-radius: 8px; - min-height: 200px; - font-family: 'Whitney', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -.preview-area h1, .preview-area h2, .preview-area h3 { - margin-top: 0; -} - -.loading { - display: flex; - justify-content: center; - align-items: center; - height: 200px; - font-size: 18px; - color: #666; -} - -.error { - background: #fef2f2; - border: 1px solid #fecaca; - color: #dc2626; - padding: 12px; - border-radius: 4px; - margin-bottom: 16px; -} - -.success { - background: #f0f9ff; - border: 1px solid #bae6fd; - color: #0369a1; - padding: 12px; - border-radius: 4px; - margin-bottom: 16px; -} - -.guild-card { - background: white; - border: 1px solid #e3e5e8; - border-radius: 8px; - padding: 16px; - margin-bottom: 12px; - cursor: pointer; - transition: all 0.2s; -} - -.guild-card:hover { - border-color: #5865f2; - box-shadow: 0 2px 8px rgba(88, 101, 242, 0.1); -} - -.guild-icon { - width: 48px; - height: 48px; - border-radius: 50%; - background: #5865f2; - color: white; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 18px; - margin-right: 12px; -} - -.flex { - display: flex; -} - -.items-center { - align-items: center; -} - -.justify-between { - justify-content: space-between; -} - -.text-sm { - font-size: 14px; -} - -.text-gray-500 { - color: #6b7280; -} - -.text-green-600 { - color: #059669; -} - -.text-red-600 { - color: #dc2626; -} - -.font-medium { - font-weight: 500; -} - -.font-bold { - font-weight: 700; -} - -.mb-2 { - margin-bottom: 8px; -} - -.mb-4 { - margin-bottom: 16px; -} - -.mt-4 { - margin-top: 16px; -} - -.editor-container { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 20px; - height: 600px; -} - -.editor-panel { - display: flex; - flex-direction: column; -} - -.editor-panel h3 { - margin-top: 0; - margin-bottom: 12px; - color: #4f545c; -} - -@media (max-width: 768px) { - .sidebar { - transform: translateX(-100%); - } - - .main-content { - margin-left: 0; - } - - .editor-container { - grid-template-columns: 1fr; - height: auto; - } -} diff --git a/bot/frontend/src/App.js b/bot/frontend/src/App.js deleted file mode 100644 index 8b4c356..0000000 --- a/bot/frontend/src/App.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; -import { AuthProvider } from './hooks/useAuth'; -import Layout from './components/Layout'; -import Login from './pages/Login'; -import Dashboard from './pages/Dashboard'; -import MessageEditor from './pages/MessageEditor'; -import ServerSelect from './pages/ServerSelect'; -import './App.css'; - -function App() { - return ( - - -
- - } /> - }> - } /> - } /> - } /> - } /> - - -
-
-
- ); -} - -export default App; diff --git a/bot/frontend/src/components/Layout.js b/bot/frontend/src/components/Layout.js deleted file mode 100644 index 1018181..0000000 --- a/bot/frontend/src/components/Layout.js +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import { Outlet, NavLink, useNavigate } from 'react-router-dom'; -import { useAuth } from '../hooks/useAuth'; -import { LogOut, Home, Server, Settings } from 'lucide-react'; - -function Layout() { - const { user, logout, isAuthenticated } = useAuth(); - const navigate = useNavigate(); - - React.useEffect(() => { - if (!isAuthenticated) { - navigate('/login'); - } - }, [isAuthenticated, navigate]); - - const handleLogout = () => { - logout(); - navigate('/login'); - }; - - if (!isAuthenticated) { - return null; - } - - return ( -
- - -
- -
-
- ); -} - -export default Layout; diff --git a/bot/frontend/src/hooks/useAuth.js b/bot/frontend/src/hooks/useAuth.js deleted file mode 100644 index acec8bd..0000000 --- a/bot/frontend/src/hooks/useAuth.js +++ /dev/null @@ -1,67 +0,0 @@ -import React, { createContext, useContext, useState, useEffect } from 'react'; -import { api } from '../services/api'; - -const AuthContext = createContext(); - -export function useAuth() { - const context = useContext(AuthContext); - if (!context) { - throw new Error('useAuth must be used within an AuthProvider'); - } - return context; -} - -export function AuthProvider({ children }) { - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - const [token, setToken] = useState(localStorage.getItem('auth_token')); - - useEffect(() => { - if (token) { - api.setAuthToken(token); - verifyToken(); - } else { - setLoading(false); - } - }, [token]); - - const verifyToken = async () => { - try { - const response = await api.verifyToken(); - setUser(response.data.user); - } catch (error) { - console.error('Token verification failed:', error); - logout(); - } finally { - setLoading(false); - } - }; - - const login = (authToken) => { - setToken(authToken); - localStorage.setItem('auth_token', authToken); - api.setAuthToken(authToken); - }; - - const logout = () => { - setToken(null); - setUser(null); - localStorage.removeItem('auth_token'); - api.setAuthToken(null); - }; - - const value = { - user, - token, - loading, - login, - logout, - isAuthenticated: !!user - }; - - return ( - - {children} - - ); -} diff --git a/bot/frontend/src/index.css b/bot/frontend/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/bot/frontend/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/bot/frontend/src/index.js b/bot/frontend/src/index.js deleted file mode 100644 index 2cb1087..0000000 --- a/bot/frontend/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - -); diff --git a/bot/frontend/src/pages/Dashboard.js b/bot/frontend/src/pages/Dashboard.js deleted file mode 100644 index e847c88..0000000 --- a/bot/frontend/src/pages/Dashboard.js +++ /dev/null @@ -1,213 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Link } from 'react-router-dom'; -import { api } from '../services/api'; -import { Server, MessageSquare, Users, Activity } from 'lucide-react'; - -function Dashboard() { - const [stats, setStats] = useState({ - totalServers: 0, - totalMessages: 0, - totalUsers: 0, - botStatus: 'online' - }); - const [recentActivity, setRecentActivity] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - loadDashboardData(); - }, []); - - const loadDashboardData = async () => { - try { - // Mock data - w prawdziwej aplikacji pobierz z API - setStats({ - totalServers: 5, - totalMessages: 23, - totalUsers: 1, - botStatus: 'online' - }); - - setRecentActivity([ - { - id: 1, - type: 'message_updated', - server: 'Test Server', - timestamp: new Date(), - description: 'Zaktualizowano wiadomość powitalną' - }, - { - id: 2, - type: 'channel_configured', - server: 'Test Server', - timestamp: new Date(Date.now() - 3600000), - description: 'Skonfigurowano kanał #witamy' - } - ]); - } catch (error) { - console.error('Error loading dashboard data:', error); - } finally { - setLoading(false); - } - }; - - if (loading) { - return
Ładowanie dashboard...
; - } - - return ( -
-
-

Dashboard

-

- Przegląd stanu bota i ostatnich aktywności -

-
- - {/* Stats Cards */} -
-
-
-
- -
-
-

Serwery

-

{stats.totalServers}

-
-
-
- -
-
-
- -
-
-

Wiadomości

-

{stats.totalMessages}

-
-
-
- -
-
-
- -
-
-

Użytkownicy

-

{stats.totalUsers}

-
-
-
- -
-
-
- -
-
-

Status Bota

-
-
-

- {stats.botStatus === 'online' ? 'Online' : 'Offline'} -

-
-
-
-
-
- -
- {/* Quick Actions */} -
-

Szybkie akcje

-
- -
- -
-

Zarządzaj serwerami

-

Konfiguruj kanały i wiadomości

-
-
- - -
-
- -
-

Szablony wiadomości

-

Wkrótce dostępne

-
-
-
- -
-
- -
-

Statystyki

-

Wkrótce dostępne

-
-
-
-
-
- - {/* Recent Activity */} -
-

Ostatnia aktywność

-
- {recentActivity.length > 0 ? ( - recentActivity.map((activity) => ( -
-
-
-

- {activity.description} -

-

- {activity.server} • {activity.timestamp.toLocaleString('pl-PL')} -

-
-
- )) - ) : ( -

- Brak ostatnich aktywności -

- )} -
-
-
- - {/* Bot Info */} -
-

Informacje o bocie

-
-
-

Wersja

-

1.0.0

-
-
-

Czas działania

-

99.5%

-
-
-

Ostatnia aktualizacja

-

Dzisiaj

-
-
-
-
- ); -} - -export default Dashboard; diff --git a/bot/frontend/src/pages/Login.js b/bot/frontend/src/pages/Login.js deleted file mode 100644 index aef91d8..0000000 --- a/bot/frontend/src/pages/Login.js +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useEffect } from 'react'; -import { useAuth } from '../hooks/useAuth'; -import { useNavigate, useSearchParams } from 'react-router-dom'; - -function Login() { - const { login, isAuthenticated } = useAuth(); - const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - - useEffect(() => { - // Sprawdź czy w URL jest token (po przekierowaniu z OAuth) - const token = searchParams.get('token'); - if (token) { - login(token); - navigate('/dashboard'); - return; - } - - // Jeśli już zalogowany, przekieruj na dashboard - if (isAuthenticated) { - navigate('/dashboard'); - } - }, [searchParams, login, navigate, isAuthenticated]); - - const handleDiscordLogin = () => { - const apiUrl = process.env.NODE_ENV === 'production' - ? '/api/auth/discord' - : 'http://localhost:3000/api/auth/discord'; - - window.location.href = apiUrl; - }; - - return ( -
-
-
-
🎭
-

- Skrzynka Impostora Bot -

-

- Panel zarządzania wiadomościami powaitalnymi -

-
- -
-
-

- Zaloguj się za pomocą Discord -

-

- Aby zarządzać wiadomościami powaitalnymi, musisz się zalogować przez Discord. -

- - -
-
- -
-

- Potrzebujesz uprawnień administratora na serwerze Discord, - aby zarządzać wiadomościami powaitalnymi. -

-
-
-
- ); -} - -export default Login; diff --git a/bot/frontend/src/pages/MessageEditor.js b/bot/frontend/src/pages/MessageEditor.js deleted file mode 100644 index 1033175..0000000 --- a/bot/frontend/src/pages/MessageEditor.js +++ /dev/null @@ -1,303 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { api } from '../services/api'; -import { Save, Eye, History, ArrowLeft, RefreshCw } from 'lucide-react'; -import ReactMarkdown from 'react-markdown'; - -function MessageEditor() { - const { guildId } = useParams(); - const navigate = useNavigate(); - - const [message, setMessage] = useState({ - content: '', - embed_data: null - }); - const [originalMessage, setOriginalMessage] = useState(null); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [error, setError] = useState(null); - const [success, setSuccess] = useState(null); - const [previewMode, setPreviewMode] = useState('discord'); - const [isDirty, setIsDirty] = useState(false); - - useEffect(() => { - loadMessage(); - }, [guildId]); - - useEffect(() => { - if (originalMessage) { - setIsDirty(message.content !== originalMessage.content); - } - }, [message.content, originalMessage]); - - const loadMessage = async () => { - try { - const response = await api.getMessage(guildId); - const loadedMessage = response.data; - - setMessage(loadedMessage); - setOriginalMessage(loadedMessage); - } catch (error) { - console.error('Error loading message:', error); - setError('Nie udało się załadować wiadomości'); - } finally { - setLoading(false); - } - }; - - const saveMessage = async () => { - if (!message.content.trim()) { - setError('Treść wiadomości nie może być pusta'); - return; - } - - if (message.content.length > 2000) { - setError('Treść wiadomości nie może przekraczać 2000 znaków'); - return; - } - - setSaving(true); - setError(null); - - try { - await api.saveMessage(guildId, message.content, message.embed_data); - setSuccess('Wiadomość została zapisana pomyślnie'); - setOriginalMessage({ ...message }); - setIsDirty(false); - - // Ukryj komunikat po 3 sekundach - setTimeout(() => setSuccess(null), 3000); - } catch (error) { - console.error('Error saving message:', error); - setError('Nie udało się zapisać wiadomości'); - } finally { - setSaving(false); - } - }; - - const resetMessage = () => { - if (originalMessage) { - setMessage({ ...originalMessage }); - setIsDirty(false); - } - }; - - const renderDiscordPreview = (content) => { - // Podstawowa konwersja Discord Markdown do HTML - let html = content - // Headers - .replace(/^# (.*$)/gim, '

$1

') - .replace(/^## (.*$)/gim, '

$1

') - .replace(/^### (.*$)/gim, '

$1

') - // Bold - .replace(/\*\*(.*?)\*\*/gim, '$1') - // Italic - .replace(/\*(.*?)\*/gim, '$1') - // Underline - .replace(/__(.*?)__/gim, '$1') - // Strikethrough - .replace(/~~(.*?)~~/gim, '$1') - // Code blocks - .replace(/```([\s\S]*?)```/gim, '
$1
') - // Inline code - .replace(/`(.*?)`/gim, '$1') - // Lists - .replace(/^\• (.*$)/gim, '
  • • $1
  • ') - // Line breaks - .replace(/\n/gim, '
    '); - - return { __html: html }; - }; - - if (loading) { - return
    Ładowanie edytora wiadomości...
    ; - } - - return ( -
    -
    - - -
    -

    Edytor wiadomości

    -

    - Edytuj wiadomość powitalną dla serwera -

    -
    -
    - - {error && ( -
    - {error} -
    - )} - - {success && ( -
    - {success} -
    - )} - -
    - {/* Editor Panel */} -
    -
    -

    Edytor

    -
    - {message.content.length}/2000 - {isDirty && ( - - • Niezapisane zmiany - - )} -
    -
    - -