config = $this->loadConfig(); $this->db = $db ?? $this->getDatabaseConnection(); } /** * Load configuration */ private function loadConfig(): array { return [ 'db_host' => $_ENV['DB_HOST'] ?? 'localhost', 'db_name' => $_ENV['DB_NAME'] ?? 'softedge_db', 'db_user' => $_ENV['DB_USER'] ?? 'root', 'db_pass' => $_ENV['DB_PASS'] ?? '', 'jwt_secret' => $_ENV['JWT_SECRET'] ?? 'your-jwt-secret-key', 'google_client_id' => $_ENV['GOOGLE_CLIENT_ID'] ?? '', 'google_client_secret' => $_ENV['GOOGLE_CLIENT_SECRET'] ?? '', 'github_client_id' => $_ENV['GITHUB_CLIENT_ID'] ?? '', 'github_client_secret' => $_ENV['GITHUB_CLIENT_SECRET'] ?? '' ]; } /** * Get database connection */ private function getDatabaseConnection(): PDO { try { $dsn = "mysql:host={$this->config['db_host']};dbname={$this->config['db_name']};charset=utf8mb4"; $pdo = new PDO($dsn, $this->config['db_user'], $this->config['db_pass']); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); return $pdo; } catch (PDOException $e) { error_log("Database connection failed: " . $e->getMessage()); throw new \RuntimeException('Database connection failed'); } } /** * Create users table if it doesn't exist */ public function createTables(): void { try { // Users table $this->db->exec(" CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password VARCHAR(255), avatar VARCHAR(500), provider VARCHAR(50) DEFAULT 'local', provider_id VARCHAR(255), role ENUM('user', 'admin') DEFAULT 'user', email_verified BOOLEAN DEFAULT FALSE, verification_token VARCHAR(255), reset_token VARCHAR(255), reset_expires DATETIME, last_login DATETIME, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci "); // Page visits table $this->db->exec(" CREATE TABLE IF NOT EXISTS page_visits ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, page VARCHAR(255) NOT NULL, ip_address VARCHAR(45), user_agent TEXT, referrer VARCHAR(500), session_id VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci "); // User sessions table $this->db->exec(" CREATE TABLE IF NOT EXISTS user_sessions ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, session_token VARCHAR(255) UNIQUE NOT NULL, ip_address VARCHAR(45), user_agent TEXT, expires_at DATETIME NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci "); } catch (PDOException $e) { error_log("Table creation failed: " . $e->getMessage()); throw new \RuntimeException('Failed to create database tables'); } } /** * Register a new user */ public function register(array $data): array { $this->validateRegistrationData($data); try { $this->db->beginTransaction(); // Check if email already exists $stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?"); $stmt->execute([$data['email']]); if ($stmt->fetch()) { throw new \InvalidArgumentException('Email já cadastrado'); } // Hash password $hashedPassword = password_hash($data['password'], PASSWORD_ARGON2ID); // Generate verification token $verificationToken = bin2hex(random_bytes(32)); // Insert user $stmt = $this->db->prepare(" INSERT INTO users (name, email, password, verification_token, created_at) VALUES (?, ?, ?, ?, NOW()) "); $stmt->execute([ $data['name'], $data['email'], $hashedPassword, $verificationToken ]); $userId = $this->db->lastInsertId(); $this->db->commit(); return [ 'success' => true, 'user_id' => $userId, 'verification_token' => $verificationToken, 'message' => 'Usuário registrado com sucesso. Verifique seu email.' ]; } catch (PDOException $e) { $this->db->rollBack(); error_log("Registration failed: " . $e->getMessage()); throw new \RuntimeException('Erro ao registrar usuário'); } } /** * Authenticate user */ public function login(string $email, string $password): array { try { $stmt = $this->db->prepare(" SELECT id, name, email, password, role, email_verified FROM users WHERE email = ? AND provider = 'local' "); $stmt->execute([$email]); $user = $stmt->fetch(); if (!$user || !password_verify($password, $user['password'])) { throw new \InvalidArgumentException('Email ou senha incorretos'); } if (!$user['email_verified']) { throw new \InvalidArgumentException('Email não verificado. Verifique sua caixa de entrada.'); } // Update last login $stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?"); $stmt->execute([$user['id']]); // Create session $sessionToken = $this->createSession($user['id']); // Log page visit $this->logPageVisit($user['id'], 'login', $_SERVER['HTTP_USER_AGENT'] ?? ''); return [ 'success' => true, 'user' => [ 'id' => $user['id'], 'name' => $user['name'], 'email' => $user['email'], 'role' => $user['role'] ], 'session_token' => $sessionToken ]; } catch (PDOException $e) { error_log("Login failed: " . $e->getMessage()); throw new \RuntimeException('Erro ao fazer login'); } } /** * Social login (Google, GitHub) */ public function socialLogin(string $provider, array $profile): array { try { $this->db->beginTransaction(); // Check if user exists $stmt = $this->db->prepare(" SELECT id, name, email, role, email_verified FROM users WHERE provider = ? AND provider_id = ? "); $stmt->execute([$provider, $profile['id']]); $user = $stmt->fetch(); if (!$user) { // Create new user $stmt = $this->db->prepare(" INSERT INTO users (name, email, avatar, provider, provider_id, email_verified, created_at) VALUES (?, ?, ?, ?, ?, TRUE, NOW()) "); $stmt->execute([ $profile['name'], $profile['email'], $profile['avatar'] ?? null, $provider, $profile['id'] ]); $userId = $this->db->lastInsertId(); $user = [ 'id' => $userId, 'name' => $profile['name'], 'email' => $profile['email'], 'role' => 'user', 'email_verified' => true ]; } // Update last login $stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?"); $stmt->execute([$user['id']]); // Create session $sessionToken = $this->createSession($user['id']); // Log page visit $this->logPageVisit($user['id'], 'social_login', $_SERVER['HTTP_USER_AGENT'] ?? ''); $this->db->commit(); return [ 'success' => true, 'user' => $user, 'session_token' => $sessionToken ]; } catch (PDOException $e) { $this->db->rollBack(); error_log("Social login failed: " . $e->getMessage()); throw new \RuntimeException('Erro ao fazer login social'); } } /** * Create user session */ private function createSession(int $userId): string { $sessionToken = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', strtotime('+24 hours')); $stmt = $this->db->prepare(" INSERT INTO user_sessions (user_id, session_token, ip_address, user_agent, expires_at, created_at) VALUES (?, ?, ?, ?, ?, NOW()) "); $stmt->execute([ $userId, $sessionToken, $_SERVER['REMOTE_ADDR'] ?? '', $_SERVER['HTTP_USER_AGENT'] ?? '', $expiresAt ]); return $sessionToken; } /** * Validate session */ public function validateSession(string $sessionToken): ?array { try { $stmt = $this->db->prepare(" SELECT u.id, u.name, u.email, u.role, u.email_verified, s.expires_at FROM user_sessions s JOIN users u ON s.user_id = u.id WHERE s.session_token = ? AND s.expires_at > NOW() "); $stmt->execute([$sessionToken]); $result = $stmt->fetch(); return $result ?: null; } catch (PDOException $e) { error_log("Session validation failed: " . $e->getMessage()); return null; } } /** * Log page visit */ public function logPageVisit(?int $userId, string $page, string $userAgent = ''): void { try { $stmt = $this->db->prepare(" INSERT INTO page_visits (user_id, page, ip_address, user_agent, referrer, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW()) "); $stmt->execute([ $userId, $page, $_SERVER['REMOTE_ADDR'] ?? '', $userAgent, $_SERVER['HTTP_REFERER'] ?? '', session_id() ]); } catch (PDOException $e) { error_log("Page visit logging failed: " . $e->getMessage()); } } /** * Get admin statistics */ public function getAdminStats(): array { try { // Total users $stmt = $this->db->query("SELECT COUNT(*) as total FROM users"); $totalUsers = $stmt->fetch()['total']; // Total page visits $stmt = $this->db->query("SELECT COUNT(*) as total FROM page_visits"); $totalVisits = $stmt->fetch()['total']; // Recent visits (last 30 days) $stmt = $this->db->prepare(" SELECT COUNT(*) as total FROM page_visits WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) "); $stmt->execute(); $recentVisits = $stmt->fetch()['total']; // Top pages $stmt = $this->db->prepare(" SELECT page, COUNT(*) as visits FROM page_visits GROUP BY page ORDER BY visits DESC LIMIT 10 "); $stmt->execute(); $topPages = $stmt->fetchAll(); // Recent users $stmt = $this->db->prepare(" SELECT id, name, email, created_at FROM users ORDER BY created_at DESC LIMIT 10 "); $stmt->execute(); $recentUsers = $stmt->fetchAll(); return [ 'total_users' => $totalUsers, 'total_visits' => $totalVisits, 'recent_visits' => $recentVisits, 'top_pages' => $topPages, 'recent_users' => $recentUsers ]; } catch (PDOException $e) { error_log("Admin stats failed: " . $e->getMessage()); return []; } } /** * Validate registration data */ private function validateRegistrationData(array $data): void { $required = ['name', 'email', 'password']; foreach ($required as $field) { if (empty($data[$field])) { throw new \InvalidArgumentException("Campo {$field} é obrigatório"); } } if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException('Email inválido'); } if (strlen($data['name']) < 2) { throw new \InvalidArgumentException('Nome deve ter pelo menos 2 caracteres'); } if (strlen($data['password']) < 8) { throw new \InvalidArgumentException('Senha deve ter pelo menos 8 caracteres'); } } /** * Check if user is admin */ public function isAdmin(int $userId): bool { try { $stmt = $this->db->prepare("SELECT role FROM users WHERE id = ?"); $stmt->execute([$userId]); $user = $stmt->fetch(); return $user && $user['role'] === 'admin'; } catch (PDOException $e) { error_log("Admin check failed: " . $e->getMessage()); return false; } } }