Initial commit: Flutter app + PHP/MySQL backend on Hostinger
Replaces Firebase with a self-hosted PHP/MySQL API served from winded.prymsolutions.com. Includes full backend (schema, auth, events, teams, brackets, suggestions, stats, media, file upload) and updated Flutter repositories and domain models. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
Header always set Access-Control-Allow-Origin "*"
|
||||
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
|
||||
Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') json_err('Method not allowed', 405);
|
||||
|
||||
$b = body();
|
||||
$email = trim($b['email'] ?? '');
|
||||
$password = $b['password'] ?? '';
|
||||
|
||||
if ($email === '' || $password === '') json_err('Email and password required');
|
||||
|
||||
$db = db();
|
||||
$stmt = $db->prepare('SELECT * FROM users WHERE email = ?');
|
||||
$stmt->execute([$email]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
if (!$row || !password_verify($password, $row['password_hash'])) {
|
||||
json_err('Invalid email or password', 401);
|
||||
}
|
||||
|
||||
$role = resolve_role($row['email'], $row['role']);
|
||||
$token = JWT::encode(['uid' => $row['id'], 'email' => $row['email'], 'role' => $role]);
|
||||
|
||||
json_ok([
|
||||
'token' => $token,
|
||||
'user' => [
|
||||
'id' => $row['id'],
|
||||
'email' => $row['email'],
|
||||
'display_name' => $row['display_name'],
|
||||
'role' => $role,
|
||||
'bio' => $row['bio'],
|
||||
'photo_url' => $row['photo_url'],
|
||||
'position' => $row['position'],
|
||||
'team_id' => $row['team_id'],
|
||||
'created_at' => $row['created_at'],
|
||||
],
|
||||
]);
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'PUT') {
|
||||
$payload = require_auth();
|
||||
$uid = $payload['uid'];
|
||||
$b = body();
|
||||
|
||||
$fields = [];
|
||||
$params = [];
|
||||
foreach (['display_name', 'bio', 'photo_url', 'position'] as $f) {
|
||||
if (array_key_exists($f, $b)) {
|
||||
$fields[] = "$f = ?";
|
||||
$params[] = $b[$f];
|
||||
}
|
||||
}
|
||||
if (empty($fields)) json_err('Nothing to update');
|
||||
|
||||
$params[] = $uid;
|
||||
db()->prepare('UPDATE users SET ' . implode(', ', $fields) . ' WHERE id = ?')
|
||||
->execute($params);
|
||||
}
|
||||
|
||||
$payload = require_auth();
|
||||
$stmt = db()->prepare('SELECT * FROM users WHERE id = ?');
|
||||
$stmt->execute([$payload['uid']]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) json_err('User not found', 404);
|
||||
|
||||
$role = resolve_role($row['email'], $row['role']);
|
||||
|
||||
json_ok([
|
||||
'id' => $row['id'],
|
||||
'email' => $row['email'],
|
||||
'display_name' => $row['display_name'],
|
||||
'role' => $role,
|
||||
'bio' => $row['bio'],
|
||||
'photo_url' => $row['photo_url'],
|
||||
'position' => $row['position'],
|
||||
'team_id' => $row['team_id'],
|
||||
'created_at' => $row['created_at'],
|
||||
]);
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') json_err('Method not allowed', 405);
|
||||
|
||||
$b = body();
|
||||
$email = trim($b['email'] ?? '');
|
||||
$password = $b['password'] ?? '';
|
||||
$displayName = trim($b['display_name'] ?? '');
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) json_err('Invalid email');
|
||||
if (strlen($password) < 6) json_err('Password must be at least 6 characters');
|
||||
|
||||
$db = db();
|
||||
|
||||
$stmt = $db->prepare('SELECT id FROM users WHERE email = ?');
|
||||
$stmt->execute([$email]);
|
||||
if ($stmt->fetch()) json_err('Email already registered', 409);
|
||||
|
||||
$id = uuid();
|
||||
$hash = password_hash($password, PASSWORD_BCRYPT);
|
||||
$role = resolve_role($email, 'player');
|
||||
|
||||
$db->prepare(
|
||||
'INSERT INTO users (id, email, password_hash, display_name, role) VALUES (?, ?, ?, ?, ?)'
|
||||
)->execute([$id, $email, $hash, $displayName, $role]);
|
||||
|
||||
$token = JWT::encode(['uid' => $id, 'email' => $email, 'role' => $role]);
|
||||
|
||||
json_ok([
|
||||
'token' => $token,
|
||||
'user' => [
|
||||
'id' => $id,
|
||||
'email' => $email,
|
||||
'display_name' => $displayName,
|
||||
'role' => $role,
|
||||
'bio' => '',
|
||||
'photo_url' => null,
|
||||
'position' => null,
|
||||
'team_id' => null,
|
||||
'created_at' => date('c'),
|
||||
],
|
||||
], 201);
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$id = $_GET['id'] ?? '';
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
if ($id === '') json_err('Missing id');
|
||||
|
||||
function load_bracket(PDO $db, string $id): ?array {
|
||||
$stmt = $db->prepare('SELECT * FROM brackets WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) return null;
|
||||
$row['rounds'] = $row['rounds_json'] ? json_decode($row['rounds_json'], true) : [];
|
||||
unset($row['rounds_json']);
|
||||
return $row;
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
$b = load_bracket($db, $id);
|
||||
if (!$b) json_err('Not found', 404);
|
||||
json_ok($b);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
require_admin();
|
||||
$body = body();
|
||||
$fields = []; $params = [];
|
||||
foreach (['name','event_id','status'] as $f) {
|
||||
if (array_key_exists($f, $body)) { $fields[] = "$f = ?"; $params[] = $body[$f]; }
|
||||
}
|
||||
if (array_key_exists('rounds', $body)) {
|
||||
$fields[] = 'rounds_json = ?';
|
||||
$params[] = json_encode($body['rounds']);
|
||||
}
|
||||
if (empty($fields)) json_err('Nothing to update');
|
||||
$params[] = $id;
|
||||
$db->prepare('UPDATE brackets SET ' . implode(', ', $fields) . ' WHERE id = ?')->execute($params);
|
||||
json_ok(load_bracket($db, $id));
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
require_admin();
|
||||
$db->prepare('DELETE FROM brackets WHERE id = ?')->execute([$id]);
|
||||
json_ok(['deleted' => true]);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
if ($method === 'GET') {
|
||||
$rows = $db->query('SELECT * FROM brackets ORDER BY created_at DESC')->fetchAll();
|
||||
$rows = array_map(function ($r) {
|
||||
$r['rounds'] = $r['rounds_json'] ? json_decode($r['rounds_json'], true) : [];
|
||||
unset($r['rounds_json']);
|
||||
return $r;
|
||||
}, $rows);
|
||||
json_ok(['brackets' => $rows]);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
require_admin();
|
||||
$b = body();
|
||||
$id = uuid();
|
||||
$db->prepare(
|
||||
'INSERT INTO brackets (id, name, event_id, status, rounds_json) VALUES (?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$id,
|
||||
$b['name'] ?? 'New Bracket',
|
||||
$b['event_id'] ?? null,
|
||||
$b['status'] ?? 'draft',
|
||||
json_encode($b['rounds'] ?? []),
|
||||
]);
|
||||
json_ok(['id' => $id], 201);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
// Copy this file to config.php and fill in your Hostinger MySQL credentials.
|
||||
// Never commit config.php to version control.
|
||||
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_NAME', 'u595523489_wndd'); // e.g. u123456789_winded
|
||||
define('DB_USER', 'u595523489_wnddusr'); // e.g. u123456789_winded
|
||||
define('DB_PASS', '@>fnr0E7eS');
|
||||
define('DB_CHARSET', 'utf8mb4');
|
||||
|
||||
function db(): PDO {
|
||||
static $pdo = null;
|
||||
if ($pdo === null) {
|
||||
$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=' . DB_CHARSET;
|
||||
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/database.php';
|
||||
require_once __DIR__ . '/jwt.php';
|
||||
|
||||
// Admin emails that always get admin role regardless of DB role column.
|
||||
const ADMIN_EMAILS = ['philip@theguzmanfamily.com'];
|
||||
|
||||
function cors(): void {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
function json_ok(array $data, int $code = 200): void {
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
function json_err(string $msg, int $code = 400): void {
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => $msg]);
|
||||
exit;
|
||||
}
|
||||
|
||||
function require_auth(): array {
|
||||
$h = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
if (!str_starts_with($h, 'Bearer ')) json_err('Unauthorized', 401);
|
||||
$payload = JWT::decode(substr($h, 7));
|
||||
if ($payload === null) json_err('Unauthorized', 401);
|
||||
return $payload;
|
||||
}
|
||||
|
||||
function require_admin(): array {
|
||||
$p = require_auth();
|
||||
if (($p['role'] ?? '') !== 'admin') json_err('Forbidden', 403);
|
||||
return $p;
|
||||
}
|
||||
|
||||
function require_manager_or_admin(): array {
|
||||
$p = require_auth();
|
||||
$r = $p['role'] ?? '';
|
||||
if ($r !== 'admin' && $r !== 'manager') json_err('Forbidden', 403);
|
||||
return $p;
|
||||
}
|
||||
|
||||
function uuid(): string {
|
||||
$b = random_bytes(16);
|
||||
$b[6] = chr(ord($b[6]) & 0x0f | 0x40);
|
||||
$b[8] = chr(ord($b[8]) & 0x3f | 0x80);
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));
|
||||
}
|
||||
|
||||
function body(): array {
|
||||
return json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
}
|
||||
|
||||
function resolve_role(string $email, string $dbRole): string {
|
||||
return in_array(strtolower(trim($email)), ADMIN_EMAILS) ? 'admin' : $dbRole;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
// Change JWT_SECRET to a long random string before deploying.
|
||||
define('JWT_SECRET', 'f0gHGG23#5j9gaGg90asndfa9gtybama89G#');
|
||||
define('JWT_TTL', 60 * 60 * 24 * 30); // 30 days
|
||||
|
||||
class JWT {
|
||||
public static function encode(array $payload): string {
|
||||
$header = self::b64e(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
|
||||
$payload['iat'] = time();
|
||||
$payload['exp'] = time() + JWT_TTL;
|
||||
$payload = self::b64e(json_encode($payload));
|
||||
$sig = self::b64e(hash_hmac('sha256', "$header.$payload", JWT_SECRET, true));
|
||||
return "$header.$payload.$sig";
|
||||
}
|
||||
|
||||
public static function decode(string $token): ?array {
|
||||
$parts = explode('.', $token);
|
||||
if (count($parts) !== 3) return null;
|
||||
[$header, $payload, $sig] = $parts;
|
||||
$expected = self::b64e(hash_hmac('sha256', "$header.$payload", JWT_SECRET, true));
|
||||
if (!hash_equals($expected, $sig)) return null;
|
||||
$data = json_decode(self::b64d($payload), true);
|
||||
if (!$data || ($data['exp'] ?? 0) < time()) return null;
|
||||
return $data;
|
||||
}
|
||||
|
||||
private static function b64e(string $v): string {
|
||||
return rtrim(strtr(base64_encode($v), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
private static function b64d(string $v): string {
|
||||
return base64_decode(strtr($v, '-_', '+/') . str_repeat('=', (4 - strlen($v) % 4) % 4));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$id = $_GET['id'] ?? '';
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($id === '') json_err('Missing id');
|
||||
|
||||
$db = db();
|
||||
|
||||
if ($method === 'GET') {
|
||||
$stmt = $db->prepare('SELECT * FROM events WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) json_err('Not found', 404);
|
||||
$s = $db->prepare('SELECT COUNT(*) as cnt FROM event_registrations WHERE event_id = ?');
|
||||
$s->execute([$id]);
|
||||
$row['teams_registered'] = (int)$s->fetch()['cnt'];
|
||||
json_ok($row);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
require_admin();
|
||||
$b = body();
|
||||
$fields = []; $params = [];
|
||||
foreach (['title','description','category','event_date','location',
|
||||
'registration_deadline','max_teams','is_cancelled','image_url'] as $f) {
|
||||
if (array_key_exists($f, $b)) { $fields[] = "$f = ?"; $params[] = $b[$f]; }
|
||||
}
|
||||
if (empty($fields)) json_err('Nothing to update');
|
||||
$params[] = $id;
|
||||
$db->prepare('UPDATE events SET ' . implode(', ', $fields) . ' WHERE id = ?')->execute($params);
|
||||
json_ok(['updated' => true]);
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
require_admin();
|
||||
$db->prepare('DELETE FROM events WHERE id = ?')->execute([$id]);
|
||||
$db->prepare('DELETE FROM event_registrations WHERE event_id = ?')->execute([$id]);
|
||||
json_ok(['deleted' => true]);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'GET') {
|
||||
$rows = db()->query('SELECT * FROM events ORDER BY event_date ASC')->fetchAll();
|
||||
// Attach registration count per event
|
||||
$db = db();
|
||||
$result = array_map(function ($row) use ($db) {
|
||||
$stmt = $db->prepare('SELECT COUNT(*) as cnt FROM event_registrations WHERE event_id = ?');
|
||||
$stmt->execute([$row['id']]);
|
||||
$row['teams_registered'] = (int)$stmt->fetch()['cnt'];
|
||||
return $row;
|
||||
}, $rows);
|
||||
json_ok(['events' => $result]);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
require_admin();
|
||||
$b = body();
|
||||
$id = uuid();
|
||||
db()->prepare(
|
||||
'INSERT INTO events (id, title, description, category, event_date, location,
|
||||
registration_deadline, max_teams, is_cancelled, image_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$id,
|
||||
$b['title'] ?? '',
|
||||
$b['description'] ?? '',
|
||||
$b['category'] ?? 'pickup',
|
||||
$b['event_date'] ?? date('Y-m-d H:i:s'),
|
||||
$b['location'] ?? '',
|
||||
$b['registration_deadline'] ?? null,
|
||||
(int)($b['max_teams'] ?? 0),
|
||||
(int)($b['is_cancelled'] ?? 0),
|
||||
$b['image_url'] ?? null,
|
||||
]);
|
||||
json_ok(['id' => $id], 201);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$payload = require_auth();
|
||||
$uid = $payload['uid'];
|
||||
$event_id = $_GET['event_id'] ?? (body()['event_id'] ?? '');
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($event_id === '') json_err('Missing event_id');
|
||||
|
||||
$db = db();
|
||||
|
||||
if ($method === 'POST') {
|
||||
try {
|
||||
$db->prepare(
|
||||
'INSERT INTO event_registrations (id, event_id, user_id) VALUES (?, ?, ?)'
|
||||
)->execute([uuid(), $event_id, $uid]);
|
||||
} catch (PDOException $e) {
|
||||
// Unique constraint: already registered — treat as success
|
||||
}
|
||||
json_ok(['registered' => true]);
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
$db->prepare(
|
||||
'DELETE FROM event_registrations WHERE event_id = ? AND user_id = ?'
|
||||
)->execute([$event_id, $uid]);
|
||||
json_ok(['unregistered' => true]);
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
$stmt = $db->prepare(
|
||||
'SELECT * FROM event_registrations WHERE event_id = ? AND user_id = ?'
|
||||
);
|
||||
$stmt->execute([$event_id, $uid]);
|
||||
json_ok(['registered' => (bool)$stmt->fetch()]);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
if ($method === 'GET') {
|
||||
$links = $db->query('SELECT * FROM media_links ORDER BY sort_order')->fetchAll();
|
||||
$highlights = $db->query('SELECT * FROM highlights ORDER BY sort_order DESC')->fetchAll();
|
||||
json_ok(['links' => $links, 'highlights' => $highlights]);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
require_admin();
|
||||
$b = body();
|
||||
$type = $b['type'] ?? '';
|
||||
|
||||
if ($type === 'link') {
|
||||
$id = uuid();
|
||||
$db->prepare(
|
||||
'INSERT INTO media_links (id, platform, handle, url, display_name, sort_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$id, $b['platform'] ?? '', $b['handle'] ?? '',
|
||||
$b['url'] ?? '', $b['display_name'] ?? '', (int)($b['sort_order'] ?? 0),
|
||||
]);
|
||||
json_ok(['id' => $id], 201);
|
||||
}
|
||||
|
||||
if ($type === 'highlight') {
|
||||
$id = uuid();
|
||||
$db->prepare(
|
||||
'INSERT INTO highlights (id, title, description, youtube_url, thumbnail_url, published_at, sort_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$id, $b['title'] ?? '', $b['description'] ?? '',
|
||||
$b['youtube_url'] ?? '', $b['thumbnail_url'] ?? null,
|
||||
$b['published_at'] ?? null, (int)($b['sort_order'] ?? 0),
|
||||
]);
|
||||
json_ok(['id' => $id], 201);
|
||||
}
|
||||
|
||||
json_err('type must be link or highlight');
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$uid = $_GET['uid'] ?? '';
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
if ($uid === '') json_err('Missing uid');
|
||||
|
||||
if ($method === 'GET') {
|
||||
$stmt = $db->prepare('SELECT * FROM users WHERE id = ?');
|
||||
$stmt->execute([$uid]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) json_err('Not found', 404);
|
||||
unset($row['password_hash']);
|
||||
$row['role'] = resolve_role($row['email'], $row['role']);
|
||||
json_ok($row);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
$payload = require_auth();
|
||||
// Users can only update themselves; admins can update anyone.
|
||||
if ($payload['role'] !== 'admin' && $payload['uid'] !== $uid) json_err('Forbidden', 403);
|
||||
|
||||
$b = body();
|
||||
$fields = []; $params = [];
|
||||
foreach (['display_name','bio','photo_url','position','team_id','role'] as $f) {
|
||||
if (array_key_exists($f, $b)) { $fields[] = "$f = ?"; $params[] = $b[$f]; }
|
||||
}
|
||||
if (empty($fields)) json_err('Nothing to update');
|
||||
$params[] = $uid;
|
||||
$db->prepare('UPDATE users SET ' . implode(', ', $fields) . ' WHERE id = ?')->execute($params);
|
||||
|
||||
$stmt = $db->prepare('SELECT * FROM users WHERE id = ?');
|
||||
$stmt->execute([$uid]);
|
||||
$row = $stmt->fetch();
|
||||
unset($row['password_hash']);
|
||||
$row['role'] = resolve_role($row['email'], $row['role']);
|
||||
json_ok($row);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') json_err('Method not allowed', 405);
|
||||
|
||||
$db = db();
|
||||
|
||||
// Top scorers across all players on approved teams
|
||||
$players = $db->query(
|
||||
"SELECT p.id, p.name, p.position, p.goals_scored, p.assists, p.team_id,
|
||||
t.name AS team_name
|
||||
FROM players p
|
||||
JOIN teams t ON t.id = p.team_id
|
||||
WHERE t.status = 'approved'
|
||||
ORDER BY p.goals_scored DESC, p.assists DESC
|
||||
LIMIT 50"
|
||||
)->fetchAll();
|
||||
|
||||
// Team leaderboard
|
||||
$teams = $db->query(
|
||||
"SELECT id, name, wins, draws, losses,
|
||||
(wins + draws + losses) AS total_games,
|
||||
CASE WHEN (wins+draws+losses)=0 THEN 0
|
||||
ELSE ROUND(wins/(wins+draws+losses)*100,1) END AS win_pct
|
||||
FROM teams WHERE status='approved'
|
||||
ORDER BY wins DESC, win_pct DESC
|
||||
LIMIT 30"
|
||||
)->fetchAll();
|
||||
|
||||
json_ok(['players' => $players, 'teams' => $teams]);
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$id = $_GET['id'] ?? '';
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
if ($id === '') json_err('Missing id');
|
||||
|
||||
if ($method === 'PUT') {
|
||||
require_admin();
|
||||
$b = body();
|
||||
$status = $b['status'] ?? '';
|
||||
if (!in_array($status, ['pending','reviewed','implemented'])) json_err('Invalid status');
|
||||
$db->prepare('UPDATE suggestions SET status = ? WHERE id = ?')->execute([$status, $id]);
|
||||
json_ok(['updated' => true]);
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
require_admin();
|
||||
$db->prepare('DELETE FROM suggestions WHERE id = ?')->execute([$id]);
|
||||
json_ok(['deleted' => true]);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
if ($method === 'GET') {
|
||||
$payload = require_auth();
|
||||
if ($payload['role'] === 'admin') {
|
||||
$rows = $db->query('SELECT * FROM suggestions ORDER BY submitted_at DESC')->fetchAll();
|
||||
} else {
|
||||
$stmt = $db->prepare(
|
||||
"SELECT * FROM suggestions WHERE user_id = ? AND is_anonymous = 0
|
||||
ORDER BY submitted_at DESC"
|
||||
);
|
||||
$stmt->execute([$payload['uid']]);
|
||||
$rows = $stmt->fetchAll();
|
||||
}
|
||||
json_ok(['suggestions' => $rows]);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
$payload = require_auth();
|
||||
$b = body();
|
||||
$text = trim($b['text'] ?? '');
|
||||
$anon = !empty($b['is_anonymous']);
|
||||
|
||||
if ($text === '') json_err('Text required');
|
||||
|
||||
$id = uuid();
|
||||
$db->prepare(
|
||||
'INSERT INTO suggestions (id, user_id, display_name, text, is_anonymous)
|
||||
VALUES (?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$id,
|
||||
$anon ? null : $payload['uid'],
|
||||
$anon ? null : ($b['display_name'] ?? ''),
|
||||
$text,
|
||||
$anon ? 1 : 0,
|
||||
]);
|
||||
json_ok(['id' => $id], 201);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$id = $_GET['id'] ?? '';
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
if ($id === '') json_err('Missing id');
|
||||
|
||||
function load_team(PDO $db, string $id): ?array {
|
||||
$stmt = $db->prepare('SELECT * FROM teams WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
if (!$row) return null;
|
||||
$ps = $db->prepare('SELECT * FROM players WHERE team_id = ? ORDER BY name');
|
||||
$ps->execute([$id]);
|
||||
$row['players'] = $ps->fetchAll();
|
||||
return $row;
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
$team = load_team($db, $id);
|
||||
if (!$team) json_err('Not found', 404);
|
||||
json_ok($team);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
$payload = require_auth();
|
||||
$b = body();
|
||||
|
||||
// Allow admin or the team's own manager
|
||||
$stmt = $db->prepare('SELECT manager_id FROM teams WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$t = $stmt->fetch();
|
||||
if (!$t) json_err('Not found', 404);
|
||||
if ($payload['role'] !== 'admin' && $payload['uid'] !== $t['manager_id']) {
|
||||
json_err('Forbidden', 403);
|
||||
}
|
||||
|
||||
// Update scalar fields
|
||||
$allowed = ['name','description','logo_url','primary_color','manager_email',
|
||||
'manager_phone','wins','draws','losses','status'];
|
||||
$fields = []; $params = [];
|
||||
foreach ($allowed as $f) {
|
||||
if (array_key_exists($f, $b)) { $fields[] = "$f = ?"; $params[] = $b[$f]; }
|
||||
}
|
||||
if (!empty($fields)) {
|
||||
$params[] = $id;
|
||||
$db->prepare('UPDATE teams SET ' . implode(', ', $fields) . ' WHERE id = ?')->execute($params);
|
||||
}
|
||||
|
||||
// Sync players if provided
|
||||
if (isset($b['players']) && is_array($b['players'])) {
|
||||
$db->prepare('DELETE FROM players WHERE team_id = ?')->execute([$id]);
|
||||
foreach ($b['players'] as $p) {
|
||||
$pid = $p['id'] ?? uuid();
|
||||
$db->prepare(
|
||||
'INSERT INTO players (id, team_id, user_id, name, number, position, goals_scored, assists)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$pid, $id,
|
||||
$p['user_id'] ?? null,
|
||||
$p['name'] ?? '',
|
||||
$p['number'] ?? null,
|
||||
$p['position'] ?? null,
|
||||
(int)($p['goals_scored'] ?? 0),
|
||||
(int)($p['assists'] ?? 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
json_ok(load_team($db, $id));
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
require_admin();
|
||||
$db->prepare('DELETE FROM players WHERE team_id = ?')->execute([$id]);
|
||||
$db->prepare('DELETE FROM join_requests WHERE team_id = ?')->execute([$id]);
|
||||
$db->prepare('DELETE FROM teams WHERE id = ?')->execute([$id]);
|
||||
json_ok(['deleted' => true]);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
function team_with_players(PDO $db, array $row): array {
|
||||
$stmt = $db->prepare('SELECT * FROM players WHERE team_id = ? ORDER BY name');
|
||||
$stmt->execute([$row['id']]);
|
||||
$row['players'] = $stmt->fetchAll();
|
||||
return $row;
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
$admin = isset($_GET['all']);
|
||||
if ($admin) require_admin();
|
||||
$sql = $admin
|
||||
? 'SELECT * FROM teams ORDER BY name'
|
||||
: "SELECT * FROM teams WHERE status = 'approved' ORDER BY name";
|
||||
$rows = $db->query($sql)->fetchAll();
|
||||
$rows = array_map(fn($r) => team_with_players($db, $r), $rows);
|
||||
json_ok(['teams' => $rows]);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
$payload = require_auth();
|
||||
$b = body();
|
||||
$id = uuid();
|
||||
$role = $payload['role'];
|
||||
$status = ($role === 'admin') ? 'approved' : 'pending';
|
||||
|
||||
$db->prepare(
|
||||
'INSERT INTO teams (id, name, description, logo_url, primary_color, status,
|
||||
manager_id, manager_email, manager_phone)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$id,
|
||||
$b['name'] ?? '',
|
||||
$b['description'] ?? null,
|
||||
$b['logo_url'] ?? null,
|
||||
$b['primary_color'] ?? null,
|
||||
$status,
|
||||
$payload['uid'],
|
||||
$b['manager_email'] ?? $payload['email'],
|
||||
$b['manager_phone'] ?? null,
|
||||
]);
|
||||
|
||||
// Stamp team on manager profile
|
||||
$db->prepare('UPDATE users SET team_id = ?, role = ? WHERE id = ?')
|
||||
->execute([$id, 'manager', $payload['uid']]);
|
||||
|
||||
json_ok(['id' => $id, 'status' => $status], 201);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = db();
|
||||
|
||||
if ($method === 'GET') {
|
||||
$payload = require_auth();
|
||||
if (isset($_GET['team_id'])) {
|
||||
// Manager or admin fetching a team's requests
|
||||
$stmt = $db->prepare(
|
||||
'SELECT * FROM join_requests WHERE team_id = ? ORDER BY requested_at DESC'
|
||||
);
|
||||
$stmt->execute([$_GET['team_id']]);
|
||||
} elseif (isset($_GET['player_id'])) {
|
||||
$stmt = $db->prepare(
|
||||
'SELECT * FROM join_requests WHERE player_id = ? ORDER BY requested_at DESC'
|
||||
);
|
||||
$stmt->execute([$_GET['player_id']]);
|
||||
} else {
|
||||
json_err('Provide team_id or player_id');
|
||||
}
|
||||
json_ok(['requests' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
$payload = require_auth();
|
||||
$b = body();
|
||||
$team_id = $b['team_id'] ?? '';
|
||||
$player_id = $payload['uid'];
|
||||
$player_name = $b['player_name'] ?? '';
|
||||
$player_email= $b['player_email']?? $payload['email'];
|
||||
$team_name = $b['team_name'] ?? '';
|
||||
|
||||
if ($team_id === '') json_err('team_id required');
|
||||
|
||||
// Idempotent: return existing pending request if one exists
|
||||
$stmt = $db->prepare(
|
||||
"SELECT id FROM join_requests WHERE team_id=? AND player_id=? AND status='pending'"
|
||||
);
|
||||
$stmt->execute([$team_id, $player_id]);
|
||||
$existing = $stmt->fetch();
|
||||
if ($existing) json_ok(['id' => $existing['id']]);
|
||||
|
||||
$id = uuid();
|
||||
$db->prepare(
|
||||
'INSERT INTO join_requests (id, team_id, team_name, player_id, player_name, player_email)
|
||||
VALUES (?, ?, ?, ?, ?, ?)'
|
||||
)->execute([$id, $team_id, $team_name, $player_id, $player_name, $player_email]);
|
||||
json_ok(['id' => $id], 201);
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
$payload = require_auth();
|
||||
$b = body();
|
||||
$request_id= $_GET['id'] ?? ($b['id'] ?? '');
|
||||
$status = $b['status'] ?? '';
|
||||
|
||||
if ($request_id === '' || $status === '') json_err('id and status required');
|
||||
if (!in_array($status, ['approved','rejected'])) json_err('Invalid status');
|
||||
|
||||
$db->prepare('UPDATE join_requests SET status = ? WHERE id = ?')
|
||||
->execute([$status, $request_id]);
|
||||
|
||||
if ($status === 'approved') {
|
||||
// Stamp team_id on the player's profile
|
||||
$stmt = $db->prepare('SELECT * FROM join_requests WHERE id = ?');
|
||||
$stmt->execute([$request_id]);
|
||||
$req = $stmt->fetch();
|
||||
if ($req) {
|
||||
$db->prepare('UPDATE users SET team_id = ? WHERE id = ?')
|
||||
->execute([$req['team_id'], $req['player_id']]);
|
||||
// Add player to players table
|
||||
$exists = $db->prepare('SELECT id FROM players WHERE team_id=? AND user_id=?');
|
||||
$exists->execute([$req['team_id'], $req['player_id']]);
|
||||
if (!$exists->fetch()) {
|
||||
$db->prepare(
|
||||
'INSERT INTO players (id, team_id, user_id, name) VALUES (?, ?, ?, ?)'
|
||||
)->execute([uuid(), $req['team_id'], $req['player_id'], $req['player_name']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_ok(['updated' => true]);
|
||||
}
|
||||
|
||||
json_err('Method not allowed', 405);
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/helpers.php';
|
||||
cors();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') json_err('Method not allowed', 405);
|
||||
|
||||
require_auth();
|
||||
|
||||
$file = $_FILES['file'] ?? null;
|
||||
$context = $_POST['context'] ?? 'general'; // e.g. 'avatar', 'team_logo'
|
||||
|
||||
if (!$file || $file['error'] !== UPLOAD_ERR_OK) json_err('No file uploaded');
|
||||
|
||||
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
$mime = mime_content_type($file['tmp_name']);
|
||||
if (!in_array($mime, $allowed)) json_err('Only JPEG, PNG, GIF, and WebP images are allowed');
|
||||
|
||||
$maxBytes = 5 * 1024 * 1024; // 5 MB
|
||||
if ($file['size'] > $maxBytes) json_err('File exceeds 5 MB limit');
|
||||
|
||||
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
$filename = uuid() . '.' . strtolower($ext);
|
||||
$uploadDir= __DIR__ . '/../../uploads/' . $context . '/';
|
||||
|
||||
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
|
||||
|
||||
$dest = $uploadDir . $filename;
|
||||
if (!move_uploaded_file($file['tmp_name'], $dest)) json_err('Upload failed', 500);
|
||||
|
||||
// Build public URL — adjust the base URL to match your Hostinger domain.
|
||||
$baseUrl = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
|
||||
$url = $baseUrl . '/uploads/' . $context . '/' . $filename;
|
||||
|
||||
json_ok(['url' => $url]);
|
||||
Reference in New Issue
Block a user