api/config/Config.php:
<?php

// Configurações gerais da aplicação
date_default_timezone_set('America/Sao_Paulo');

define('APP_NAME', 'PHP-ShiftHub API');
define('APP_VERSION', '1.0.0');
?>

api/controllers/AuthController.php:
<?php

require_once __DIR__ . '/../lib/Connection.php';
require_once __DIR__ . '/../lib/Response.php';

class AuthController
{
    /**
     * Método para realizar o Login
     */
    public static function login()
    {
        $input = json_decode(file_get_contents('php://input'), true);

        if (!is_array($input)) {
            return Response::error('Dados da requisição inválidos.', 400);
        }

        $email    = trim($input['email'] ?? '');
        $password = $input['password'] ?? '';

        if (empty($email) || empty($password)) {
            return Response::error('Informe o e-mail e a senha.', 422);
        }

        $connection = new Connection();
        $db = $connection->connectDB();

        $stmt = $db->prepare("SELECT id_user, username, email, password_user FROM users WHERE email = ?");
        $stmt->bind_param('s', $email);
        $stmt->execute();

        $result = $stmt->get_result();
        $user = $result->fetch_assoc();

        if (!$user || !password_verify($password, $user['password_user'])) {
            $stmt->close();
            $connection->disconnectDB();
            return Response::error('E-mail ou senha incorretos.', 401);
        }

        // Salva as credenciais básicas na sessão
        $_SESSION['user'] = $user['username'];
        $_SESSION['user_id'] = $user['id_user'];

        $stmt->close();
        $connection->disconnectDB();

        return Response::success(
            'Login realizado com sucesso.',
            [
                'id_user'  => $user['id_user'],
                'username' => $user['username'],
                'email'    => $user['email']
            ]
        );
    }
}
?>


api/controllers/RegisterController.php:
<?php

require_once __DIR__ . '/../lib/Connection.php';
require_once __DIR__ . '/../lib/Response.php';

class RegisterController
{
    /**
     * Método para cadastrar novos usuários
     */
    public static function register()
    {
        $input = json_decode(file_get_contents('php://input'), true);

        if (!is_array($input)) {
            return Response::error('Dados da requisição inválidos.', 400);
        }

        $username = trim($input['username'] ?? '');
        $email    = trim($input['email'] ?? '');
        $password = $input['password'] ?? '';

        // Validações básicas
        if (empty($username) || empty($email) || empty($password)) {
            return Response::error('Preencha todos os campos obrigatórios (username, email, password).', 422);
        }

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return Response::error('O formato do e-mail informado é inválido.', 422);
        }

        if (strlen($password) < 6) {
            return Response::error('A senha deve ter no mínimo 6 caracteres.', 422);
        }

        $connection = new Connection();
        $db = $connection->connectDB();

        // 1. Verifica se o e-mail já está cadastrado
        $stmtCheck = $db->prepare("SELECT id_user FROM users WHERE email = ? LIMIT 1");
        $stmtCheck->bind_param('s', $email);
        $stmtCheck->execute();
        $resCheck = $stmtCheck->get_result();

        if ($resCheck->num_rows > 0) {
            $stmtCheck->close();
            $connection->disconnectDB();
            return Response::error('Este e-mail já está cadastrado.', 409);
        }
        $stmtCheck->close();

        // 2. Hash da senha para salvar com segurança
        $passwordHash = password_hash($password, PASSWORD_DEFAULT);

        // 3. Insere o novo usuário
        $stmtInsert = $db->prepare("INSERT INTO users (username, email, password_user) VALUES (?, ?, ?)");
        $stmtInsert->bind_param('sss', $username, $email, $passwordHash);

        if (!$stmtInsert->execute()) {
            $stmtInsert->close();
            $connection->disconnectDB();
            return Response::error('Erro ao cadastrar o usuário no banco de dados.', 500);
        }

        $newUserId = $stmtInsert->insert_id;
        $stmtInsert->close();
        $connection->disconnectDB();

        return Response::success(
            'Usuário cadastrado com sucesso!',
            [
                'id_user'  => $newUserId,
                'username' => $username,
                'email'    => $email
            ],
            201
        );
    }
}
?>


api/controllers/StatusController.php:
<?php

require_once __DIR__ . '/../lib/Connection.php';

class StatusController
{
    public static function check()
    {
        $connection = new Connection();
        $dbName = $connection->getDbName();

        return [
            'success'   => true,
            'status'    => 'online',
            'database'  => $dbName,
            'timestamp' => date('Y-m-d H:i:s')
        ];
    }
}
?>

api/lib/Middlewares/Authentication.php:
<?php

require_once __DIR__ . '/../Response.php';

class AuthenticationMiddleware
{
    public static function handle(): void
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        if (empty($_SESSION['user_id'])) {
            Response::send(Response::error('Acesso negado. Usuário não autenticado.', 401), 401);
        }
    }
}
?>

api/lib/Middlewares/Log.php:
<?php

class LogMiddleware
{
    public static function handle(): void
    {
        // Exemplo simples de log
        $log = date('Y-m-d H:i:s') . " | " . ($_SERVER['REQUEST_METHOD'] ?? 'GET') . " | " . ($_SERVER['REQUEST_URI'] ?? '/') . "\n";
        @file_put_contents(__DIR__ . '/../../logs.txt', $log, FILE_APPEND);
    }
}
?>

api/lib/Middlewares/Validation.php:
<?php

class ValidationMiddleware
{
    public static function handle(): void
    {
        // Middleware genérico para validações
    }
}
?>


api/lib/Connection.php:
<?php

require_once __DIR__ . '/Env.php';

class Connection
{
    private ?mysqli $connect = null;

    private string $host;
    private string $user;
    private string $pass;
    private string $dbname;

    public function __construct()
    {
        // Carrega configurações dinamicamente do .env.local (ou fallback para teste_api)
        $this->host   = Env::get('DB_HOST', 'localhost');
        $this->user   = Env::get('DB_USER', 'root');
        $this->pass   = Env::get('DB_PASS', '');
        $this->dbname = Env::get('DB_NAME', 'teste_api');
    }

    public function getDbName(): string
    {
        return $this->dbname;
    }

    public function connectDB(): mysqli
    {
        $this->connect = new mysqli($this->host, $this->user, $this->pass, $this->dbname);

        if ($this->connect->connect_error) {
            require_once __DIR__ . '/Response.php';
            Response::send(
                Response::error('Erro ao conectar ao Banco de Dados.', 500, [
                    'details' => $this->connect->connect_error,
                    'database_attempted' => $this->dbname
                ]),
                500
            );
        }

        $this->connect->set_charset('utf8mb4');

        return $this->connect;
    }

    public function disconnectDB(): void
    {
        if ($this->connect) {
            $this->connect->close();
            $this->connect = null;
        }
    }
}
?>


api/lib/Env.php:
<?php

class Env
{
    private static ?array $variables = null;

    public static function load(): void
    {
        if (self::$variables !== null) {
            return;
        }

        self::$variables = [];

        // Múltiplos caminhos de busca para localizar o .env.local no XAMPP ou Hostinger
        $possiblePaths = [
            dirname(__DIR__) . '/.env.local',
            dirname(__DIR__) . '/.env',
            dirname(__DIR__, 2) . '/.env.local',
            dirname(__DIR__, 2) . '/.env',
            __DIR__ . '/../.env.local',
            __DIR__ . '/../.env',
        ];

        // Se SCRIPT_FILENAME estiver disponível (execução web)
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
            $scriptDir = dirname($_SERVER['SCRIPT_FILENAME']);
            $possiblePaths[] = $scriptDir . '/.env.local';
            $possiblePaths[] = $scriptDir . '/.env';
            $possiblePaths[] = dirname($scriptDir) . '/.env.local';
            $possiblePaths[] = dirname($scriptDir) . '/.env';
        }

        foreach ($possiblePaths as $filePath) {
            if (file_exists($filePath) && is_readable($filePath)) {
                $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
                foreach ($lines as $line) {
                    $line = trim($line);
                    
                    // Ignora comentários e linhas vazias
                    if (empty($line) || strpos($line, '#') === 0) {
                        continue;
                    }

                    if (strpos($line, '=') !== false) {
                        list($key, $value) = explode('=', $line, 2);
                        $key   = trim($key);
                        $value = trim($value);
                        // Remove aspas
                        $value = trim($value, '"\'');

                        if (!array_key_exists($key, self::$variables)) {
                            self::$variables[$key] = $value;
                        }
                    }
                }
            }
        }
    }

    public static function get(string $key, mixed $default = null): mixed
    {
        self::load();

        if (array_key_exists($key, self::$variables)) {
            return self::$variables[$key];
        }

        $envVal = getenv($key);
        if ($envVal !== false) {
            return $envVal;
        }

        return $default;
    }
}
?>


api/lib/Response.php:
<?php

class Response
{
    /**
     * Envia uma resposta JSON e encerra a execução do script
     */
    public static function send(array $response, int $statusCode = 200): void
    {
        // Garante que nenhum conteúdo extra/warning interfira na resposta JSON
        if (ob_get_length()) {
            ob_clean();
        }

        $code = $response['statusCode'] ?? $statusCode;

        http_response_code($code);
        header('Content-Type: application/json; charset=utf-8');

        echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit();
    }

    /**
     * Estrutura de resposta de sucesso
     */
    public static function success(string $message = 'Sucesso', mixed $data = null, int $statusCode = 200): array
    {
        return [
            'success' => true,
            'statusCode' => $statusCode,
            'message' => $message,
            'data' => $data
        ];
    }

    /**
     * Estrutura de resposta de erro
     */
    public static function error(string $message = 'Ocorreu um erro', int $statusCode = 400, mixed $errors = null): array
    {
        return [
            'success' => false,
            'statusCode' => $statusCode,
            'message' => $message,
            'errors' => $errors
        ];
    }

    /**
     * Converte um array comum para o formato padronizado do formato JSON da API
     */
    public static function json(array $data, int $statusCode = 200): array
    {
        if (isset($data['success']) && isset($data['statusCode'])) {
            return $data;
        }

        return [
            'success' => true,
            'statusCode' => $statusCode,
            'data' => $data
        ];
    }
}
?>

api/lib/Router.php:
<?php

require_once __DIR__ . '/Response.php';

class Route
{
    private static array $routes = [];

    public static function get(string $path, callable|array $handler, array $middlewares = []): void
    {
        self::addRoute('GET', $path, $handler, $middlewares);
    }

    public static function post(string $path, callable|array $handler, array $middlewares = []): void
    {
        self::addRoute('POST', $path, $handler, $middlewares);
    }

    public static function put(string $path, callable|array $handler, array $middlewares = []): void
    {
        self::addRoute('PUT', $path, $handler, $middlewares);
    }

    public static function delete(string $path, callable|array $handler, array $middlewares = []): void
    {
        self::addRoute('DELETE', $path, $handler, $middlewares);
    }

    private static function addRoute(string $method, string $path, callable|array $handler, array $middlewares): void
    {
        $normalizedPath = '/' . trim(str_replace('\\', '/', $path), '/');
        if ($normalizedPath === '//') {
            $normalizedPath = '/';
        }

        self::$routes[] = [
            'method'      => strtoupper($method),
            'path'        => strtolower($normalizedPath),
            'handler'     => $handler,
            'middlewares' => $middlewares,
        ];
    }

    public static function dispatch(): void
    {
        $requestMethod = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
        $rawUri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/';
        $rawUri = str_replace('\\', '/', $rawUri);

        // Obtém o diretório onde o script index.php da API reside (ex: /projetos/teste/api)
        $scriptName = str_replace('\\', '/', $_SERVER['SCRIPT_NAME'] ?? '');
        $baseDir    = str_replace('\\', '/', dirname($scriptName));

        if ($baseDir === '.' || $baseDir === '/' || $baseDir === '\\') {
            $baseDir = '';
        }

        // Remove o diretório base da URI para isolar o endpoint
        $uri = $rawUri;
        if (!empty($baseDir) && strpos(strtolower($uri), strtolower($baseDir)) === 0) {
            $uri = substr($uri, strlen($baseDir));
        }

        // Normaliza a URI tratada (ex: /register)
        $uri = '/' . trim($uri, '/');
        if ($uri === '//') {
            $uri = '/';
        }
        $uri = strtolower($uri);

        foreach (self::$routes as $route) {
            if ($route['method'] === $requestMethod && $route['path'] === $uri) {
                
                // Executa middlewares cadastrados para a rota
                foreach ($route['middlewares'] as $middlewareName) {
                    $middlewareClass = ucfirst($middlewareName) . 'Middleware';
                    $middlewareFile  = __DIR__ . '/Middlewares/' . ucfirst($middlewareName) . '.php';

                    if (file_exists($middlewareFile)) {
                        require_once $middlewareFile;
                        if (class_exists($middlewareClass) && method_exists($middlewareClass, 'handle')) {
                            call_user_func([$middlewareClass, 'handle']);
                        }
                    }
                }

                // Executa o Handler (Controller ou Função Anônima)
                $handler = $route['handler'];

                if (is_array($handler)) {
                    list($controllerName, $methodName) = $handler;
                    $controllerFile = __DIR__ . '/../controllers/' . $controllerName . '.php';

                    if (!file_exists($controllerFile)) {
                        Response::send(Response::error("Controller '{$controllerName}' não encontrado.", 500), 500);
                    }

                    require_once $controllerFile;

                    if (!class_exists($controllerName) || !method_exists($controllerName, $methodName)) {
                        Response::send(Response::error("Método '{$methodName}' não encontrado no Controller '{$controllerName}'.", 500), 500);
                    }

                    $response = call_user_func([$controllerName, $methodName]);
                } else {
                    $response = call_user_func($handler);
                }

                // Envia a resposta final
                if (is_array($response)) {
                    Response::send($response);
                }
                return;
            }
        }

        // Rota não encontrada
        Response::send(Response::error('Rota não encontrada.', 404), 404);
    }
}
?>

api/src/routes/Routes.php:
<?php

require_once __DIR__ . '/../../lib/Router.php';
require_once __DIR__ . '/../../controllers/StatusController.php';
require_once __DIR__ . '/../../controllers/RegisterController.php';
require_once __DIR__ . '/../../controllers/AuthController.php';

// Rota de verificação do status da API
Route::get('/status', ['StatusController', 'check']);

// Rota de cadastro de usuário
Route::post('/register', ['RegisterController', 'register']);

// Rota de login
Route::post('/login', ['AuthController', 'login']);
?>

api/.env.example:
DB_HOST=localhost
DB_USER=root
DB_PASS=
DB_NAME=php_shifthub_teste

api/.htaccess:
<IfModule mod_rewrite.c>
    RewriteEngine On

    # Se a requisição não for para um arquivo físico ou diretório existente, redireciona para index.php
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ index.php [QSA,L]
</IfModule>

# Desativa a listagem de diretórios por segurança
Options -Indexes

api/index.php:
<?php

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// ============================================================================
// TRATAMENTO DE CORS (CROSS-ORIGIN RESOURCE SHARING)
// ============================================================================
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With");

// Trata requisições Preflight (OPTIONS) enviadas por navegadores/Postman
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit();
}

require_once __DIR__ . '/src/routes/Routes.php';

// Dispara o roteador da API
Route::dispatch();
?>

Fora da api - só exemplo para a tabela usada no banco de dados MySQL/MariaDB
schema.sql:
-- Script de criação do banco e da tabela de usuários
-- Importe este arquivo no phpMyAdmin ou no seu gerenciador MySQL

CREATE TABLE IF NOT EXISTS `users` (
  `id_user` INT AUTO_INCREMENT PRIMARY KEY,
  `username` VARCHAR(80) NOT NULL,
  `email` VARCHAR(150) NOT NULL UNIQUE,
  `password_user` VARCHAR(255) NOT NULL,
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
