This commit is contained in:
torsten 2026-10-03 16:27:22 +02:00
parent 366935846c
commit 4bf3bbb978
8 changed files with 348 additions and 2 deletions

10
composer.json Normal file
View file

@ -0,0 +1,10 @@
{
"name": "torsten/tippspiel2027",
"description": "Nachfolgeprojekt von tippspiel in Pure PHP",
"type": "project",
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}

View file

@ -1,13 +1,56 @@
<?php <?php
// Einfaches Routing (Microframework / Pure PHP) für tippspiel2027 // Lade den minimalistischen Autoloader
// Dies stellt sicher, dass der Code maximal lesbar und zukunftssicher bleibt. require_once __DIR__ . '/../src/autoload.php';
use App\Core\Database;
use App\Models\Game;
use App\Models\Tipp;
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($uri === '/' || $uri === '/index.php') { if ($uri === '/' || $uri === '/index.php') {
echo "<h1>Willkommen bei Tippspiel 2027</h1>"; echo "<h1>Willkommen bei Tippspiel 2027</h1>";
echo "<p>Dies ist die Nachfolgeversion des alten Tippspiels (Pure PHP Variante).</p>"; echo "<p>Dies ist die Nachfolgeversion des alten Tippspiels (Pure PHP Variante).</p>";
try {
$db = Database::getConnection();
$userCount = $db->query("SELECT COUNT(*) FROM users")->fetchColumn();
echo "<p>Datenbank verbunden! Aktuell <strong>$userCount</strong> User im System.</p>";
echo "<h2>Portierte Backend-Logik (Test)</h2>";
// 1. Test der Score-Berechnung (getScoreLocal Logik aus Tipp.php)
echo "<h3>Punkteberechnung</h3>";
$scoreTest = Tipp::getScoreLocal(2, 1, 2, 1, true); // Ergebnis: 2:1, Tipp: 2:1 (mit Joker)
echo "<p>Test: Spiel endet 2:1, getippt wurde 2:1 mit Joker. Erwartet: 4 Punkte. <strong>Berechnet: $scoreTest Punkte</strong>.</p>";
// 2. Test der Spiel-Logiken
echo "<h3>Spiele der letzten gespielten Runde</h3>";
// Finde zunächst die ID der letzten gespielten Runde (wo es Ergebnisse gibt)
$lastRoundId = $db->query("SELECT round_id FROM games WHERE score1 > -1 ORDER BY id DESC LIMIT 1")->fetchColumn();
if ($lastRoundId) {
// Hole alle Spiele exakt dieser Runde
$games = Game::getGamesByRound($lastRoundId);
if ($games) {
echo "<ul>";
foreach ($games as $g) {
$status = Game::getResultString($g['score1'], $g['score2'], $g['kickoff']);
$locked = Game::isLocked($g['kickoff']) ? "Gesperrt" : "Offen";
echo "<li>" . htmlspecialchars($g['team1'] . " vs " . $g['team2']) . " | Status: $status | $locked</li>";
}
echo "</ul>";
}
} else {
echo "<p>Keine gespielten Spiele gefunden.</p>";
}
} catch (Exception $e) {
echo "<p style='color:red;'>Datenbankfehler: " . $e->getMessage() . "</p>";
}
} elseif ($uri === '/api/status') { } elseif ($uri === '/api/status') {
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode(['status' => 'ok', 'version' => '2027.1']); echo json_encode(['status' => 'ok', 'version' => '2027.1']);

View file

@ -1,3 +1,4 @@
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` ( CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(201) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(201) COLLATE utf8mb4_unicode_ci NOT NULL,
@ -19,6 +20,7 @@ CREATE TABLE `users` (
UNIQUE KEY `users_email_unique` (`email`) UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `tipps`;
CREATE TABLE `tipps` ( CREATE TABLE `tipps` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL, `user_id` int(11) NOT NULL,
@ -31,6 +33,7 @@ CREATE TABLE `tipps` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `games`;
CREATE TABLE `games` ( CREATE TABLE `games` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`round_id` int(11) NOT NULL, `round_id` int(11) NOT NULL,
@ -44,6 +47,7 @@ CREATE TABLE `games` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `rounds`;
CREATE TABLE `rounds` ( CREATE TABLE `rounds` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
@ -54,6 +58,7 @@ CREATE TABLE `rounds` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `seasons`;
CREATE TABLE `seasons` ( CREATE TABLE `seasons` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
@ -63,3 +68,58 @@ CREATE TABLE `seasons` (
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `settings`;
CREATE TABLE `settings` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`current_season_id` int(11) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `payments`;
CREATE TABLE `payments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`season_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`paid_date` datetime DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `quotas`;
CREATE TABLE `quotas` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`season_id` int(11) NOT NULL DEFAULT '0',
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`order` int(11) NOT NULL DEFAULT '0',
`prc` double NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `roundmails`;
CREATE TABLE `roundmails` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`send_date` date NOT NULL,
`recipients` text COLLATE utf8mb4_unicode_ci NOT NULL,
`topic` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
`message` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `manual_payouts`;
CREATE TABLE `manual_payouts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`season_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`quota_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

39
src/Core/Database.php Normal file
View file

@ -0,0 +1,39 @@
<?php
namespace App\Core;
use PDO;
use PDOException;
class Database {
private static $instance = null;
private $pdo;
private function __construct() {
$host = getenv('DB_HOST') ?: '127.0.0.1';
$user = getenv('DB_USER') ?: 'root';
$pass = getenv('DB_PASS') ?: '';
$dbName = getenv('DB_NEW_NAME') ?: 'tippspiel2027';
$dsn = "mysql:host=$host;dbname=$dbName;charset=utf8mb4";
try {
$this->pdo = new PDO($dsn, $user, $pass);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("Datenbankverbindung fehlgeschlagen: " . $e->getMessage());
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public static function getConnection() {
return self::getInstance()->pdo;
}
}

57
src/Models/Game.php Normal file
View file

@ -0,0 +1,57 @@
<?php
namespace App\Models;
use App\Core\Database;
use DateTime;
use DateInterval;
class Game {
/**
* Prüft ob ein Spiel bereits gelockt (Anpfiff steht kurz bevor) ist.
* Portiert aus dem Laravel Game Modell: Lock war 1 Minute vor Anpfiff.
*/
public static function isLocked($kickoffTime) {
$now = new DateTime();
$kickoff = new DateTime($kickoffTime);
// 1 Minute vor Anpfiff wird gelockt
$kickoff->sub(new DateInterval('PT1M'));
return $now >= $kickoff;
}
/**
* Gibt einen formatierten String für das Ergebnis eines Spiels zurück
*/
public static function getResultString($score1, $score2, $kickoffTime) {
if ($score1 > -1 && $score2 > -1) {
return "$score1 : $score2";
}
$now = new DateTime();
$start = new DateTime($kickoffTime);
$end = (clone $start)->add(new DateInterval('PT2H')); // ca. 2 Stunden nach Anpfiff
if ($start > $now) {
return "-";
}
if ($now > $start && $now < $end) {
return "Läuft";
}
return "(Eingeben)";
}
/**
* Holt alle Spiele einer Runde aus der Datenbank
*/
public static function getGamesByRound($roundId) {
$db = Database::getConnection();
$stmt = $db->prepare("SELECT * FROM games WHERE round_id = ? ORDER BY kickoff ASC");
$stmt->execute([$roundId]);
return $stmt->fetchAll();
}
}

28
src/Models/Round.php Normal file
View file

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use App\Core\Database;
class Round {
/**
* Holt eine Runde anhand der ID
*/
public static function getById($id) {
$db = Database::getConnection();
$stmt = $db->prepare("SELECT * FROM rounds WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
/**
* Holt alle Runden einer Season
*/
public static function getBySeason($seasonId) {
$db = Database::getConnection();
$stmt = $db->prepare("SELECT * FROM rounds WHERE season_id = ? ORDER BY id ASC");
$stmt->execute([$seasonId]);
return $stmt->fetchAll();
}
}

83
src/Models/Tipp.php Normal file
View file

@ -0,0 +1,83 @@
<?php
namespace App\Models;
class Tipp {
/**
* Berechnet die Punkte für einen Tipp im Vergleich zum echten Ergebnis.
* Portiert aus der alten Laravel-Anwendung.
*
* @param int $goals1 Tore Team 1
* @param int $goals2 Tore Team 2
* @param int $tipp1 Getippte Tore Team 1
* @param int $tipp2 Getippte Tore Team 2
* @param bool $joker Ob der Joker für diesen Tipp gesetzt wurde
* @return int Die erreichten Punkte
*/
public static function getScoreLocal($goals1, $goals2, $tipp1, $tipp2, $joker) {
$score = 0;
// Wenn noch kein Ergebnis vorliegt (-1 bedeutet in der DB meistens noch nicht gespielt)
if ($tipp1 == -1 || $tipp2 == -1 || $goals1 == -1 || $goals2 == -1) {
return 0;
}
$tippTendenz = $tipp1 - $tipp2;
if ($tippTendenz > 0) { $tippTendenz = 1; }
if ($tippTendenz < 0) { $tippTendenz = -1; }
$gameTendenz = $goals1 - $goals2;
if ($gameTendenz < 0) { $gameTendenz = -1; }
if ($gameTendenz > 0) { $gameTendenz = 1; }
// Tendenz richtig
if ($tippTendenz == $gameTendenz) {
$score = 1;
}
// Genaues Ergebnis richtig (gibt 2 Grundpunkte + Bonus)
if (($tipp1 == $goals1) && ($tipp2 == $goals2)) {
$bonus = max(0, $goals1 + $goals2 - 3);
$score = 2 + $bonus;
}
// Joker verdoppelt die Punktzahl
if ($joker == true) {
$score = $score * 2;
}
return $score;
}
/**
* Gibt das Styling/die Farbe für die Tendenz zurück (fürs Frontend)
*/
public static function getCol($goals1, $goals2, $tipp1, $tipp2, $joker) {
if ($tipp1 == -1 || $tipp2 == -1 || $goals1 == -1 || $goals2 == -1) {
return "";
}
$tippTendenz = $tipp1 - $tipp2;
if ($tippTendenz > 0) { $tippTendenz = 1; }
if ($tippTendenz < 0) { $tippTendenz = -1; }
$gameTendenz = $goals1 - $goals2;
if ($gameTendenz < 0) { $gameTendenz = -1; }
if ($gameTendenz > 0) { $gameTendenz = 1; }
if ($tippTendenz != $gameTendenz) {
return "";
}
$styling = 'background-color:grey;';
if (($tipp1 == $goals1) && ($tipp2 == $goals2)) {
$styling = 'background-color:orange;';
if ($joker) {
$styling = 'background-color:red';
}
}
return $styling;
}
}

26
src/autoload.php Normal file
View file

@ -0,0 +1,26 @@
<?php
spl_autoload_register(function ($class) {
// Projekt-Namespace Prefix
$prefix = 'App\\';
// Basisverzeichnis für den Namespace Prefix
$base_dir = __DIR__ . '/';
// Prüfen ob die Klasse das Prefix verwendet
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return; // Gehört nicht zu diesem Autoloader
}
// Den relativen Klassennamen extrahieren
$relative_class = substr($class, $len);
// Namespace-Trenner (\) durch Verzeichnis-Trenner (/) ersetzen und .php anhängen
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// Wenn die Datei existiert, einbinden
if (file_exists($file)) {
require $file;
}
});