diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..376799d --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,185 @@ +# YourPart Deployment Anleitung + +Diese Anleitung beschreibt, wie Sie die YourPart-Anwendung auf einem Ubuntu 22.04 Server deployen. + +## Voraussetzungen + +- Ubuntu 22.04 Server +- Node.js 18+ installiert +- MySQL/MariaDB installiert und konfiguriert +- Redis installiert +- Apache2 installiert +- SSL-Zertifikate von Let's Encrypt (bereits vorhanden) + +## Installation der Abhängigkeiten + +```bash +# Node.js installieren +curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - +sudo apt-get install -y nodejs + +# MySQL installieren +sudo apt install mysql-server + +# Redis installieren +sudo apt install redis-server + +# Apache2 installieren +sudo apt install apache2 + +# Apache-Module aktivieren +sudo a2enmod proxy +sudo a2enmod proxy_http +sudo a2enmod proxy_wstunnel +sudo a2enmod rewrite +sudo a2enmod ssl +``` + +## Konfiguration + +### 1. Datenbank einrichten + +```bash +sudo mysql_secure_installation +sudo mysql -u root -p +``` + +```sql +CREATE DATABASE yourpart CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE USER 'yourpart'@'localhost' IDENTIFIED BY 'your_secure_password'; +GRANT ALL PRIVILEGES ON yourpart.* TO 'yourpart'@'localhost'; +FLUSH PRIVILEGES; +EXIT; +``` + +### 2. Redis konfigurieren + +```bash +sudo nano /etc/redis/redis.conf +``` + +Stellen Sie sicher, dass Redis auf localhost läuft und ein Passwort gesetzt ist. + +### 3. Umgebungsvariablen konfigurieren + +Erstellen Sie eine `.env`-Datei im Backend-Verzeichnis: + +```bash +cd backend +cp .env.example .env +nano .env +``` + +Wichtige Variablen: +```env +NODE_ENV=production +PORT=2020 +DB_HOST=localhost +DB_USER=yourpart +DB_PASS=your_secure_password +DB_NAME=yourpart +REDIS_HOST=localhost +REDIS_PASS=your_redis_password +``` + +## Deployment + +### 1. Skripte ausführbar machen + +```bash +chmod +x deploy.sh deploy-backend.sh build-frontend.sh +``` + +### 2. Deployment ausführen + +```bash +./deploy.sh +``` + +Das Skript wird automatisch: +- Das Frontend bauen +- Den Backend-Service installieren +- Die Apache-Konfiguration einrichten +- Alle Services starten + +## Verifizierung + +### 1. Service-Status prüfen + +```bash +sudo systemctl status yourpart.service +sudo systemctl status apache2 +``` + +### 2. Logs überprüfen + +```bash +# Backend-Logs +sudo journalctl -u yourpart.service -f + +# Apache-Logs +sudo tail -f /var/log/apache2/yourpart.*.log +``` + +### 3. Anwendung testen + +Öffnen Sie in Ihrem Browser: +- https://www.your-part.de + +## Wartung + +### Service neu starten + +```bash +sudo systemctl restart yourpart.service +sudo systemctl reload apache2 +``` + +### Updates deployen + +```bash +git pull origin main +./deploy.sh +``` + +### Logs rotieren + +```bash +sudo logrotate -f /etc/logrotate.d/apache2 +``` + +## Troubleshooting + +### Backend startet nicht + +```bash +sudo journalctl -u yourpart.service -n 50 +``` + +Prüfen Sie: +- Datenbankverbindung +- Redis-Verbindung +- Port 2020 ist verfügbar +- Berechtigungen in `/opt/yourpart/backend` + +### Apache-Fehler + +```bash +sudo apache2ctl configtest +sudo tail -f /var/log/apache2/error.log +``` + +### SSL-Probleme + +```bash +sudo certbot certificates +sudo certbot renew --dry-run +``` + +## Sicherheit + +- Firewall konfigurieren (nur Port 80, 443, 22 öffnen) +- Regelmäßige Updates +- Datenbank-Backups +- SSL-Zertifikate erneuern +- Logs überwachen diff --git a/build-frontend.sh b/build-frontend.sh new file mode 100644 index 0000000..fc75a46 --- /dev/null +++ b/build-frontend.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +echo "Building YourPart Frontend..." + +# Zum Frontend-Verzeichnis wechseln +cd frontend + +# Dependencies installieren +echo "Installing dependencies..." +npm ci --production + +# Frontend bauen +echo "Building frontend..." +npm run build + +# Build-Verzeichnis nach /opt/yourpart kopieren +echo "Copying build to /opt/yourpart/frontend/dist..." +sudo mkdir -p /opt/yourpart/frontend +sudo cp -r dist /opt/yourpart/frontend/ + +# Berechtigungen setzen +echo "Setting permissions..." +sudo chown -R www-data:www-data /opt/yourpart/frontend +sudo chmod -R 755 /opt/yourpart/frontend + +echo "Frontend build completed!" diff --git a/deploy-backend.sh b/deploy-backend.sh new file mode 100644 index 0000000..96b17a7 --- /dev/null +++ b/deploy-backend.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +echo "Deploying YourPart Backend..." + +# Zum Backend-Verzeichnis wechseln +cd backend + +# Dependencies installieren +echo "Installing dependencies..." +npm ci --production + +# Backend nach /opt/yourpart kopieren +echo "Copying backend to /opt/yourpart/backend..." +sudo mkdir -p /opt/yourpart/backend +sudo cp -r * /opt/yourpart/backend/ + +# Berechtigungen setzen +echo "Setting permissions..." +sudo chown -R www-data:www-data /opt/yourpart/backend +sudo chmod -R 755 /opt/yourpart/backend + +# .env-Datei kopieren (falls vorhanden) +if [ -f .env ]; then + echo "Copying .env file..." + sudo cp .env /opt/yourpart/backend/ + sudo chown www-data:www-data /opt/yourpart/backend/.env + sudo chmod 600 /opt/yourpart/backend/.env +fi + +echo "Backend deployment completed!" diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..0d241fb --- /dev/null +++ b/deploy.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +echo "=== YourPart Deployment Script ===" +echo "" + +# Prüfen ob wir im richtigen Verzeichnis sind +if [ ! -f "package.json" ]; then + echo "Error: Please run this script from the YourPart3 root directory" + exit 1 +fi + +# Prüfen ob sudo verfügbar ist +if ! command -v sudo &> /dev/null; then + echo "Error: sudo is required but not installed" + exit 1 +fi + +# Verzeichnisstruktur erstellen +echo "Creating directory structure..." +sudo mkdir -p /opt/yourpart/{frontend,backend} + +# Backend deployen +echo "" +echo "=== Deploying Backend ===" +./deploy-backend.sh + +# Frontend bauen und deployen +echo "" +echo "=== Building and Deploying Frontend ===" +./build-frontend.sh + +# Systemd Service installieren +echo "" +echo "=== Installing Systemd Service ===" +sudo cp yourpart.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable yourpart.service + +# Apache-Konfiguration installieren +echo "" +echo "=== Installing Apache Configuration ===" +sudo cp yourpart-http.conf /etc/apache2/sites-available/yourpart-http.conf +sudo cp yourpart-https.conf /etc/apache2/sites-available/yourpart-https.conf + +# Alte Konfiguration deaktivieren (falls vorhanden) +sudo a2dissite yourpart 2>/dev/null || true + +# Neue Konfiguration aktivieren +sudo a2ensite yourpart-http +sudo a2ensite yourpart-https + +# Apache-Module aktivieren +echo "Enabling required Apache modules..." +sudo a2enmod proxy +sudo a2enmod proxy_http +sudo a2enmod proxy_wstunnel +sudo a2enmod rewrite +sudo a2enmod ssl + +# Apache neu laden +echo "Reloading Apache..." +sudo systemctl reload apache2 + +# Backend-Service starten +echo "" +echo "=== Starting Backend Service ===" +sudo systemctl start yourpart.service + +# Status anzeigen +echo "" +echo "=== Deployment Status ===" +echo "Backend Service Status:" +sudo systemctl status yourpart.service --no-pager -l + +echo "" +echo "Apache Status:" +sudo systemctl status apache2 --no-pager -l + +echo "" +echo "=== Deployment Completed! ===" +echo "Your application should now be available at:" +echo " HTTP: http://your-part.de (redirects to HTTPS)" +echo " HTTPS: https://www.your-part.de" +echo "" +echo "To check logs:" +echo " Backend: sudo journalctl -u yourpart.service -f" +echo " Apache: sudo tail -f /var/log/apache2/yourpart.*.log" diff --git a/frontend/src/views/minigames/Match3Game.vue b/frontend/src/views/minigames/Match3Game.vue index 992d972..afc9f13 100644 --- a/frontend/src/views/minigames/Match3Game.vue +++ b/frontend/src/views/minigames/Match3Game.vue @@ -293,18 +293,18 @@ export default { movesLeft: 15, matchesMade: 0, // Neue: Zählt tatsächlich gemachte Matches - // Drag & drop - dragStartIndex: null, - dragStartX: null, - dragStartY: null, + // Drag & drop + dragStartIndex: null, + dragStartX: null, + dragStartY: null, originalTilePosition: null, - isDragging: false, + isDragging: false, draggedTileIndex: null, // Neuer: Index des aktuell gedraggten Tiles adjacentTilesForHover: [], // Neue: Liste der benachbarten Tiles für Hover currentlyAnimatingTile: null, // Neuer: Index des aktuell animierten Tiles - dragElement: null, - dragOffsetX: 0, - dragOffsetY: 0, + dragElement: null, + dragOffsetX: 0, + dragOffsetY: 0, boundMouseMoveHandler: null, boundTouchMoveHandler: null, @@ -505,10 +505,10 @@ export default { }); }, - // Neue Hilfsmethode ohne Rekursion - loadLevelDataInternal(levelData) { - return apiClient.get(`/api/match3/levels/${levelData.id}`) - .then(response => { + // Neue Hilfsmethode ohne Rekursion + loadLevelDataInternal(levelData) { + return apiClient.get(`/api/match3/levels/${levelData.id}`) + .then(response => { if (response.data && response.data.success && response.data.data) { const freshLevelData = response.data.data; @@ -652,14 +652,14 @@ export default { }) .catch(error => { // Fallback: Setze currentLevel basierend auf completedLevels - if (this.completedLevels > 0) { - this.currentLevel = this.completedLevels + 1; - } else { - this.currentLevel = 1; - } + if (this.completedLevels > 0) { + this.currentLevel = this.completedLevels + 1; + } else { + this.currentLevel = 1; + } // Lade das Level basierend auf dem Fallback currentLevel this.loadLevelData(this.currentLevel); - }); + }); }, async initializeLevel() { @@ -668,8 +668,8 @@ export default { return; } - // Safety check: ensure currentLevelData is loaded - if (!this.currentLevelData) { + // Safety check: ensure currentLevelData is loaded + if (!this.currentLevelData) { // KEINE REKURSION: Lade Kampagnendaten nur einmal this.loadCampaignData().then(async () => { // Nach dem Laden der Kampagne, initialisiere das Level direkt @@ -783,14 +783,14 @@ export default { } else { console.log('🔧 Keine initialen Matches gefunden, Level ist bereit'); } - + // WICHTIG: Nach der Korrektur der initialen Matches das Brett erneut überprüfen console.log('🔍 Überprüfe das Brett nach der Korrektur der initialen Matches...'); await this.checkBoardAfterSetup(); // WICHTIG: Setze das Spiel als aktiv, aber prüfe NICHT sofort die Level-Objekte - this.gameActive = true; - + this.gameActive = true; + // KEINE Prüfung der Level-Objekte beim Start - diese werden nur nach Spielzügen geprüft // WICHTIG: Reset Flag für Level-Initialisierung @@ -1107,7 +1107,7 @@ export default { const index = this.coordsToIndex(r, col); if (index && board[index] && board[index].type === centerType && !this.isPowerUpTile(board[index].type)) { verticalLength++; - } else { + } else { break; } } @@ -1118,7 +1118,7 @@ export default { const index = this.coordsToIndex(row, c); if (index && board[index] && board[index].type === centerType && !this.isPowerUpTile(board[index].type)) { horizontalLength++; - } else { + } else { break; } } @@ -1408,10 +1408,10 @@ export default { // Prüfe auf 3er-Match if (row + 2 < this.boardHeight) { - const index1 = this.coordsToIndex(row, col); - const index2 = this.coordsToIndex(row + 1, col); - const index3 = this.coordsToIndex(row + 2, col); - + const index1 = this.coordsToIndex(row, col); + const index2 = this.coordsToIndex(row + 1, col); + const index3 = this.coordsToIndex(row + 2, col); + // Prüfe ob alle drei Positionen bereits Teil eines höherwertigen Matches sind if (!usedIndices.has(index1) && !usedIndices.has(index2) && !usedIndices.has(index3) && this.isValidMatch(index1, index2, index3, board)) { @@ -1679,7 +1679,7 @@ export default { document.removeEventListener('mousemove', this.boundMouseMoveHandler); this.boundMouseMoveHandler = null; } - if (this.boundTouchMoveHandler) { + if (this.boundTouchMoveHandler) { document.removeEventListener('touchmove', this.boundTouchMoveHandler); this.boundTouchMoveHandler = null; } @@ -1709,7 +1709,7 @@ export default { if (this.isFalling) { return; } - + // WICHTIG: Prüfe auf Regenbogen-Tile Tausch (vor dem Tausch) // Prüfe beide Kombinationen der ursprünglichen Tiles const rainbowSwapResult = await this.handleRainbowSwap(this.board[index1], this.board[index2]); @@ -1758,11 +1758,11 @@ export default { this.moves--; this.movesLeft++; - // Wichtig: Auch ohne Matches Level-Objekte prüfen + // Wichtig: Auch ohne Matches Level-Objekte prüfen // (falls der Spieler bereits alle Ziele erreicht hat) // Nur prüfen wenn der Spieler bereits gespielt hat UND das Level nicht initialisiert wird if (this.moves > 0 && !this.isInitializingLevel) { - this.checkLevelObjectives(); + this.checkLevelObjectives(); } } }, @@ -1864,7 +1864,7 @@ export default { // WICHTIG: Erhöhe den Match-Zähler für Level-Objekte if (isPlayerMove && !this.isInitializingLevel) { - this.matchesMade += 1; + this.matchesMade += 1; console.log(`🎯 Match-Zähler erhöht: ${this.matchesMade} Matches gemacht`); } @@ -1969,8 +1969,8 @@ export default { console.log('🔧 Debug: Alle Power-ups nach dem Fall-Down:'); this.debugPowerUps(); - // Fülle leere Positionen mit neuen Tiles auf - await this.fillEmptyPositions(); + // Fülle leere gültige Felder mit neuen Tiles auf + await this.checkAndFillEmptyValidFields(); // Debug: Zeige alle Power-ups nach dem Füllen console.log('🔧 Debug: Alle Power-ups nach dem Füllen:'); @@ -2003,7 +2003,7 @@ export default { // WICHTIG: Prüfe Level-Objekte nach dem Verarbeiten der Matches if (isPlayerMove && !this.isInitializingLevel) { console.log('🎯 Prüfe Level-Objekte nach Match-Verarbeitung...'); - this.checkLevelObjectives(); + this.checkLevelObjectives(); } } @@ -2015,94 +2015,127 @@ export default { let hasChanges = true; let iteration = 0; + const maxIterations = 10; // Verhindert Endlosschleifen - // Wiederhole bis keine weiteren Änderungen mehr auftreten - while (hasChanges && iteration < this.boardHeight) { - hasChanges = false; + while (hasChanges && iteration < maxIterations) { iteration++; + console.log(`🔧 Fall-Down Iteration ${iteration}...`); - console.log(`🔧 Fall-Down Iteration ${iteration}`); + hasChanges = false; - // Sammle alle Tiles, die fallen sollen - const tilesToFall = []; - - // Gehe von unten nach oben durch jede Spalte + // 1. Von unten nach oben leere Felder finden und Tiles nach unten verschieben for (let col = 0; col < this.boardWidth; col++) { - for (let row = this.boardHeight - 2; row >= 0; row--) { // Von vorletzter Zeile nach oben - const currentIndex = this.coordsToIndex(row, col); - const belowIndex = this.coordsToIndex(row + 1, col); + console.log(`🔧 Prüfe Spalte ${col}...`); + + // Finde alle leeren Positionen in dieser Spalte von unten nach oben + for (let row = this.boardHeight - 1; row >= 0; row--) { + const index = this.coordsToIndex(row, col); - // Wenn aktuelles Tile existiert und das darunter leer ist - if (this.board[currentIndex] && !this.board[belowIndex] && !this.isPowerUpTile(this.board[currentIndex].type)) { - // Prüfe, ob das Zielfeld im board_layout gültig ist (nicht 'o') - const targetRow = row + 1; - const targetCol = col; - - // Hole das board_layout für das aktuelle Level + // Wenn Position leer ist, prüfe ob sie im Layout gültig ist + if (!this.board[index]) { if (this.currentLevelData && this.currentLevelData.boardLayout) { - const layout = this.currentLevelData.boardLayout; - const layoutRows = layout.split('\n'); - - // Prüfe, ob das Zielfeld im Layout gültig ist - if (targetRow < layoutRows.length && targetCol < layoutRows[targetRow].length) { - const targetChar = layoutRows[targetRow][targetCol]; - - // Nur fallen lassen, wenn das Zielfeld 'x' ist (gültig) - if (targetChar === 'x') { - tilesToFall.push({ - fromIndex: currentIndex, - toIndex: belowIndex, - fromRow: row, - toRow: row + 1, - col: col - }); - - hasChanges = true; - } else { - console.log(`🔧 Tile kann nicht auf Position [${targetRow}, ${targetCol}] fallen - Layout zeigt '${targetChar}'`); + const layoutRows = this.currentLevelData.boardLayout.split('\n'); + if (row < layoutRows.length && col < layoutRows[row].length) { + const targetChar = layoutRows[row][col]; + if (targetChar !== 'x') { + console.log(`🔧 Position [${row}, ${col}] ist ungültig im Layout (${targetChar}) - überspringe`); + continue; // Überspringe ungültige Positionen } } - } else { - // Fallback: Wenn kein Layout vorhanden, normale Logik verwenden - tilesToFall.push({ - fromIndex: currentIndex, - toIndex: belowIndex, - fromRow: row, - toRow: row + 1, - col: col - }); + } + + console.log(`🔧 Leere Position gefunden: [${row}, ${col}] -> Index ${index}`); + + // Suche nach oben nach einem Tile + let tileFound = false; + for (let searchRow = row - 1; searchRow >= 0; searchRow--) { + const searchIndex = this.coordsToIndex(searchRow, col); - hasChanges = true; + if (this.board[searchIndex] && !this.isPowerUpTile(this.board[searchIndex].type)) { + console.log(`🔧 Tile ${this.board[searchIndex].type} gefunden an [${searchRow}, ${col}] -> verschiebe nach [${row}, ${col}]`); + + // Verschiebe Tile nach unten + this.board[index] = this.board[searchIndex]; + this.board[searchIndex] = null; + + // Aktualisiere DOM + this.$forceUpdate(); + + // Warte kurz für Animation + await this.wait(500); + + hasChanges = true; + tileFound = true; + break; + } + } + + if (!tileFound) { + console.log(`🔧 Kein Tile über Position [${row}, ${col}] gefunden`); } } } } - // Führe Fall-Animation für alle Tiles aus - if (tilesToFall.length > 0) { - console.log(`🔧 ${tilesToFall.length} Tiles fallen in Iteration ${iteration}`); + // 2. Oberste Zeile nach fehlenden Feldern untersuchen und auffüllen + if (hasChanges) { + console.log('🔧 Fülle oberste Zeile auf...'); - // Führe Fall-Animation aus, BEVOR die Tiles im Board verschoben werden - await this.animateTilesFalling(tilesToFall); + for (let col = 0; col < this.boardWidth; col++) { + const index = this.coordsToIndex(0, col); + + // Wenn oberste Position leer ist, prüfe ob sie im Layout gültig ist + if (!this.board[index]) { + if (this.currentLevelData && this.currentLevelData.boardLayout) { + const layoutRows = this.currentLevelData.boardLayout.split('\n'); + if (0 < layoutRows.length && col < layoutRows[0].length) { + const targetChar = layoutRows[0][col]; + if (targetChar !== 'x') { + console.log(`🔧 Oberste Position [0, ${col}] ist ungültig im Layout (${targetChar}) - überspringe`); + continue; + } + } + } + + // Erstelle neues Tile + const newTile = this.createRandomTile(); + this.board[index] = newTile; + console.log(`🔧 Neues Tile ${newTile.type} an oberster Position [0, ${col}] hinzugefügt`); + hasChanges = true; + } + } - // Bewege Tiles im Board NACH der Animation - tilesToFall.forEach(fallData => { - this.board[fallData.toIndex] = this.board[fallData.fromIndex]; - this.board[fallData.fromIndex] = null; - }); - - // Aktualisiere die Anzeige nach dem Fallen + // Aktualisiere DOM nach dem Auffüllen this.$forceUpdate(); + await this.wait(100); } - // Kürzere Pause zwischen den Iterationen für bessere Performance - await this.wait(50); + console.log(`🔧 Iteration ${iteration} abgeschlossen - Änderungen: ${hasChanges}`); } - console.log(`🔧 Fall-Down abgeschlossen nach ${iteration} Iterationen`); + if (iteration >= maxIterations) { + console.log('⚠️ Maximale Anzahl von Fall-Down Iterationen erreicht'); + } - // Nach dem Fallen: Prüfe ob alle gültigen Felder ein Tile enthalten - await this.checkAndFillEmptyValidFields(); + console.log('🔧 Fall-Down-Logik abgeschlossen'); + }, + + // Hilfsfunktion: Zeige den aktuellen Board-Zustand in der Konsole + printBoardState() { + console.log('🔧 Board-Zustand:'); + for (let row = 0; row < this.boardHeight; row++) { + let rowStr = ''; + for (let col = 0; col < this.boardWidth; col++) { + const index = this.coordsToIndex(row, col); + const tile = this.board[index]; + if (tile) { + rowStr += `[${tile.type}] `; + } else { + rowStr += '[ ] '; + } + } + console.log(` Zeile ${row}: ${rowStr}`); + } }, // Fülle leere Positionen mit neuen Tiles auf @@ -2458,34 +2491,28 @@ export default { console.log(`🎬 Schrumpf-Animation abgeschlossen`); }, - // Animierte Fall-Animation für Tiles + // Animierte Fall-Animation für Tiles (einfache Animation nach dem Verschieben im Board) async animateTilesFalling(tilesToFall) { console.log(`🎬 Starte Fall-Animation für ${tilesToFall.length} Tiles...`); - // Sammle alle DOM-Elemente der fallenden Tiles (an ihren ursprünglichen Positionen) + // Sammle alle DOM-Elemente der fallenden Tiles (an ihren neuen Positionen) const tileElements = tilesToFall.map(fallData => { - const element = document.querySelector(`[data-index="${fallData.fromIndex}"]`); + const element = document.querySelector(`[data-index="${fallData.toIndex}"]`); if (element) { - // Berechne die Fall-Distanz (sollte immer 1 Zeile sein) - const fallDistance = fallData.toRow - fallData.fromRow; - if (fallDistance !== 1) { - console.warn(`⚠️ Unerwartete Fall-Distanz: ${fallDistance} Zeilen für Tile ${fallData.fromIndex}`); - } + // Berechne die Fall-Distanz + const fallDistance = fallData.fallDistance || (fallData.toRow - fallData.fromRow); const tileHeight = 60; // Höhe eines Tiles in Pixeln const fallPixels = fallDistance * tileHeight; - // Setze das Tile an seine ursprüngliche Position (oben) - element.style.transform = `translateY(0px)`; - element.style.transition = 'transform 0.3s ease-out'; + // Setze das Tile an seine ursprüngliche Position (oben) mit transform + element.style.transform = `translateY(-${fallPixels}px)`; + element.style.transition = 'transform 0.4s ease-out'; // Füge CSS-Klasse für die Fall-Animation hinzu element.classList.add('falling'); - // Speichere die Fall-Pixel für später - element.dataset.fallPixels = fallPixels; - - console.log(`🎬 Fall-Animation für Tile ${fallData.fromIndex}: ${fallDistance} Zeile(n) (${fallPixels}px)`); + console.log(`🎬 Fall-Animation für Tile ${fallData.toIndex}: ${fallDistance} Zeile(n) (${fallPixels}px)`); } return element; }).filter(element => element !== null); @@ -2500,17 +2527,16 @@ export default { // Warte kurz, damit die transform-Eigenschaft gesetzt wird await this.wait(50); - // Bewege alle Tiles nach unten (Fall-Animation) + // Bewege alle Tiles zu ihrer finalen Position (Fall-Animation) tileElements.forEach(element => { - const fallPixels = parseInt(element.dataset.fallPixels); - element.style.transform = `translateY(${fallPixels}px)`; + element.style.transform = 'translateY(0px)'; }); // Spiele Fall-Sound ab this.playSound('falling'); - // Warte auf die Fall-Animation (0,3 Sekunden) - await this.wait(300); + // Warte auf die Fall-Animation (0,4 Sekunden) + await this.wait(400); // Entferne die CSS-Klassen und transform-Eigenschaften tileElements.forEach(element => { @@ -2749,8 +2775,8 @@ export default { // Führe Fall-Down-Logik aus await this.fallTilesDown(); - // Fülle leere Positionen mit neuen Tiles auf - await this.fillEmptyPositions(); + // Fülle leere gültige Felder mit neuen Tiles auf + await this.checkAndFillEmptyValidFields(); console.log('🔧 Fall-Prozess abgeschlossen'); }, @@ -2916,8 +2942,8 @@ export default { // Führe Fall-Down-Logik aus await this.fallTilesDown(); - // Fülle leere Positionen mit neuen Tiles auf - await this.fillEmptyPositions(); + // Fülle leere gültige Felder mit neuen Tiles auf + await this.checkAndFillEmptyValidFields(); // Erhöhe den Zug-Zähler this.countPowerUpMove(); @@ -2961,8 +2987,8 @@ export default { // Führe Fall-Down-Logik aus await this.fallTilesDown(); - // Fülle leere Positionen mit neuen Tiles auf - await this.fillEmptyPositions(); + // Fülle leere gültige Felder mit neuen Tiles auf + await this.checkAndFillEmptyValidFields(); // Erhöhe den Zug-Zähler this.countPowerUpMove(); @@ -3026,8 +3052,8 @@ export default { // Führe Fall-Down-Logik aus await this.fallTilesDown(); - // Fülle leere Positionen mit neuen Tiles auf - await this.fillEmptyPositions(); + // Fülle leere gültige Felder mit neuen Tiles auf + await this.checkAndFillEmptyValidFields(); // Erhöhe den Zug-Zähler this.countPowerUpMove(); @@ -3090,8 +3116,8 @@ export default { // Führe Fall-Down-Logik aus await this.fallTilesDown(); - // Fülle leere Positionen mit neuen Tiles auf - await this.fillEmptyPositions(); + // Fülle leere gültige Felder mit neuen Tiles auf + await this.checkAndFillEmptyValidFields(); // Erhöhe den Zug-Zähler this.countPowerUpMove(); @@ -3643,7 +3669,7 @@ export default { draggedElement.style.transition = 'all 0.3s ease-out'; // Warte kurz und setze dann alle Styles komplett zurück - setTimeout(() => { + setTimeout(() => { draggedElement.style.transition = ''; }, 300); } @@ -4781,8 +4807,8 @@ export default { // Führe Fall-Down-Logik aus await this.fallTilesDown(); - // Fülle leere Positionen mit neuen Tiles auf - await this.fillEmptyPositions(); + // Fülle leere gültige Felder mit neuen Tiles auf + await this.checkAndFillEmptyValidFields(); // Erhöhe den Zug-Zähler this.countPowerUpMove(); @@ -4918,8 +4944,8 @@ export default { // Führe Fall-Down-Logik aus await this.fallTilesDown(); - // Fülle leere Positionen mit neuen Tiles auf - await this.fillEmptyPositions(); + // Fülle leere gültige Felder mit neuen Tiles auf + await this.checkAndFillEmptyValidFields(); // WICHTIG: Zug-Zähler wird bereits in handleRainbowSwap erhöht, nicht hier nochmal! // this.countPowerUpMove(); // ENTFERNT - wird bereits in handleRainbowSwap aufgerufen diff --git a/rollback.sh b/rollback.sh new file mode 100644 index 0000000..bcca7fb --- /dev/null +++ b/rollback.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +echo "=== YourPart Rollback Script ===" +echo "" + +# Bestätigung anfordern +read -p "Are you sure you want to rollback? This will stop the new service and restore the old configuration. (y/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Rollback cancelled." + exit 1 +fi + +echo "Starting rollback process..." + +# Neuen Service stoppen und deaktivieren +echo "Stopping new YourPart service..." +sudo systemctl stop yourpart.service +sudo systemctl disable yourpart.service + +# Alte Apache-Konfiguration wiederherstellen +echo "Restoring old Apache configuration..." + +# Neue Konfigurationen deaktivieren +sudo a2dissite yourpart-http +sudo a2dissite yourpart-https + +# Alte Konfiguration wieder aktivieren (falls vorhanden) +if [ -f "/etc/apache2/sites-available/yourpart" ]; then + echo "Re-enabling old configuration..." + sudo a2ensite yourpart +fi + +# Apache neu laden +echo "Reloading Apache..." +sudo systemctl reload apache2 + +# Neue Dateien entfernen +echo "Removing new files..." +sudo rm -rf /opt/yourpart/backend +sudo rm -rf /opt/yourpart/frontend +sudo rm -f /etc/systemd/system/yourpart.service +sudo rm -f /etc/apache2/sites-available/yourpart-http.conf +sudo rm -f /etc/apache2/sites-available/yourpart-https.conf + +# Systemd neu laden +sudo systemctl daemon-reload + +echo "" +echo "=== Rollback Completed ===" +echo "The old configuration has been restored." +echo "" +echo "To verify:" +echo " - Check if the old service is running on port 2030" +echo " - Check Apache status: sudo systemctl status apache2" +echo " - Check if the old site is accessible" diff --git a/yourpart-http.conf b/yourpart-http.conf new file mode 100644 index 0000000..95bf5cc --- /dev/null +++ b/yourpart-http.conf @@ -0,0 +1,40 @@ + + ServerAdmin webmaster@your-part.de + ServerName your-part.de + ServerAlias www.your-part.de + + DocumentRoot /opt/yourpart/frontend/dist + + DirectoryIndex index.html + + # Frontend statische Dateien + + AllowOverride None + Options -Indexes +FollowSymLinks + Require all granted + + # Fallback für Vue Router + FallbackResource /index.html + + + # API-Requests an Backend weiterleiten + ProxyPass "/api/" "http://localhost:2020/api/" + ProxyPassReverse "/api/" "http://localhost:2020/api/" + + # WebSocket-Requests an Backend weiterleiten + ProxyPass "/socket.io/" "http://localhost:2020/socket.io/" + ProxyPassReverse "/socket.io/" "http://localhost:2020/socket.io/" + + ErrorLog /var/log/apache2/yourpart.error.log + CustomLog /var/log/apache2/yourpart.access.log combined + + HostnameLookups Off + UseCanonicalName Off + ServerSignature On + + # SSL Redirect + RewriteEngine on + RewriteCond %{SERVER_NAME} =www.your-part.de [OR] + RewriteCond %{SERVER_NAME} =your-part.de + RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] + diff --git a/yourpart-https.conf b/yourpart-https.conf new file mode 100644 index 0000000..afb62d3 --- /dev/null +++ b/yourpart-https.conf @@ -0,0 +1,46 @@ + + + ServerAdmin webmaster@your-part.de + ServerName your-part.de + ServerAlias www.your-part.de + + DocumentRoot /opt/yourpart/frontend/dist + + DirectoryIndex index.html + + # Frontend statische Dateien + + AllowOverride None + Options -Indexes +FollowSymLinks + Require all granted + + # Fallback für Vue Router + FallbackResource /index.html + + + # API-Requests an Backend weiterleiten + ProxyPass "/api/" "http://localhost:2020/api/" + ProxyPassReverse "/api/" "http://localhost:2020/api/" + + # WebSocket-Requests an Backend weiterleiten + ProxyPass "/socket.io/" "http://localhost:2020/socket.io/" + ProxyPassReverse "/socket.io/" "http://localhost:2020/socket.io/" + + ErrorLog /var/log/apache2/yourpart.error.log + CustomLog /var/log/apache2/yourpart.access.log combined + + HostnameLookups Off + UseCanonicalName Off + ServerSignature On + + # SSL-Konfiguration + Include /etc/letsencrypt/options-ssl-apache.conf + SSLCertificateFile /etc/letsencrypt/live/www.your-part.de/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/www.your-part.de/privkey.pem + + # www Redirect + RewriteEngine on + RewriteCond %{SERVER_NAME} =your-part.de + RewriteRule ^ https://www.%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] + + diff --git a/yourpart.service b/yourpart.service new file mode 100644 index 0000000..e3e0bb5 --- /dev/null +++ b/yourpart.service @@ -0,0 +1,22 @@ +[Unit] +Description=YourPart Backend Server +After=network.target mysql.service redis-server.service +Wants=mysql.service redis-server.service + +[Service] +Type=simple +User=www-data +Group=www-data +WorkingDirectory=/opt/yourpart/backend +Environment=NODE_ENV=production +Environment=PORT=2020 +ExecStart=/usr/bin/node server.js +ExecReload=/bin/kill -HUP $MAINPID +Restart=always +RestartSec=10 +StandardOutput=journal +StandardError=journal +SyslogIdentifier=yourpart-backend + +[Install] +WantedBy=multi-user.target