'Database connection failed']); exit; } // Route the request try { switch ($path) { case '/auth/telegram': if ($method !== 'POST') methodNotAllowed(); handleTelegramAuth($db); break; case '/auth/send-code': if ($method !== 'POST') methodNotAllowed(); handleSendCode($db); break; case '/auth/verify-code': if ($method !== 'POST') methodNotAllowed(); handleVerifyCode($db); break; case '/auth/register': if ($method !== 'POST') methodNotAllowed(); handleRegister($db); break; case '/plans': $auth = authenticateRequest(); if (!$auth) unauthorized(); if ($method === 'GET') { handleGetPlans($db, $auth['userId']); } elseif ($method === 'POST') { handleCreatePlan($db, $auth['userId']); } else { methodNotAllowed(); } break; case (preg_match('/^\/plans\/(.+)$/', $path, $matches) ? true : false): $auth = authenticateRequest(); if (!$auth) unauthorized(); if ($method === 'GET') { handleGetPlan($db, $auth['userId'], $matches[1]); } else { methodNotAllowed(); } break; case '/reports': $auth = authenticateRequest(); if (!$auth) unauthorized(); if ($method === 'POST') { handleCreateReport($db, $auth['userId']); } else { methodNotAllowed(); } break; case (preg_match('/^\/reports\/(.+)$/', $path, $matches) ? true : false): $auth = authenticateRequest(); if (!$auth) unauthorized(); if ($method === 'GET') { handleGetReport($db, $auth['userId'], $matches[1]); } else { methodNotAllowed(); } break; default: http_response_code(404); echo json_encode(['error' => 'Not found']); break; } } catch (Exception $e) { error_log('API Error: ' . $e->getMessage()); http_response_code(500); echo json_encode(['error' => 'Internal server error']); } // Handler functions function handleTelegramAuth($db) { $data = getJsonInput(); if (!isset($data['telegramUser'])) { badRequest('Missing telegramUser data'); } $telegramUser = $data['telegramUser']; // Verify Telegram auth if (!verifyTelegramAuth($telegramUser)) { http_response_code(401); echo json_encode(['error' => 'Invalid authentication data']); return; } // Check if user exists $userId = 'tg_' . $telegramUser['id']; $user = $db->getUser($userId); if ($user) { // Generate token $token = generateJWT($userId, $telegramUser['id']); $user['authToken'] = $token; echo json_encode(['user' => $user]); } else { echo json_encode(['needsRegistration' => true]); } } function handleSendCode($db) { $data = getJsonInput(); if (!isset($data['phone'])) { badRequest('Missing phone number'); } $phone = $data['phone']; $code = sprintf('%04d', rand(1000, 9999)); // Store code in database $db->storeVerificationCode($phone, $code); // Try to send code via Telegram // In production, you'd need to have users register their phone numbers with the bot first $telegram = new TelegramBot(); // For demo purposes, just log the code error_log("Verification code for $phone: $code"); echo json_encode(['success' => true]); } function handleVerifyCode($db) { $data = getJsonInput(); if (!isset($data['phone']) || !isset($data['code'])) { badRequest('Missing phone or code'); } $phone = $data['phone']; $code = $data['code']; // Verify code if (!$db->verifyCode($phone, $code)) { http_response_code(401); echo json_encode(['error' => 'Invalid or expired code']); return; } // Clear used code $db->clearVerificationCode($phone); // Check if user exists $user = $db->getUser($phone); if ($user) { // Generate token $token = generateJWT($phone, null); $user['authToken'] = $token; echo json_encode(['user' => $user]); } else { echo json_encode(['needsRegistration' => true]); } } function handleRegister($db) { $data = getJsonInput(); // Validate required fields $required = ['firstName', 'lastName', 'position']; foreach ($required as $field) { if (!isset($data[$field]) || empty($data[$field])) { badRequest("Missing required field: $field"); } } // Create user ID if (isset($data['telegramId'])) { $userId = 'tg_' . $data['telegramId']; } else if (isset($data['phone'])) { $userId = $data['phone']; } else { badRequest('Missing phone or telegramId'); } // Create user $user = [ 'userId' => $userId, 'phone' => $data['phone'] ?? null, 'telegramId' => $data['telegramId'] ?? null, 'firstName' => $data['firstName'], 'lastName' => $data['lastName'], 'position' => $data['position'] ]; $db->createUser($user); // Generate token $token = generateJWT($userId, $data['telegramId'] ?? null); $user['authToken'] = $token; // Send welcome message if Telegram user if (isset($data['telegramId'])) { $telegram = new TelegramBot(); $message = "🎉 Добро пожаловать в систему KPI, {$data['firstName']}!\n\n"; $message .= "Теперь вы можете:\n"; $message .= "• Планировать задачи на неделю\n"; $message .= "• Отправлять отчеты о выполнении\n"; $message .= "• Отслеживать свой прогресс"; $telegram->sendMessage($data['telegramId'], $message); } echo json_encode(['user' => $user]); } function handleGetPlans($db, $userId) { $weeks = $db->getUserPlanWeeks($userId); echo json_encode(['weeks' => $weeks]); } function handleGetPlan($db, $userId, $weekKey) { $plan = $db->getPlan($userId, $weekKey); echo json_encode(['plan' => $plan]); } function handleCreatePlan($db, $userId) { $data = getJsonInput(); if (!isset($data['weekKey']) || !isset($data['tasks'])) { badRequest('Missing weekKey or tasks'); } $db->savePlan($userId, $data['weekKey'], $data['tasks']); // Send notification $user = $db->getUser($userId); if ($user && $user['telegramId']) { $telegram = new TelegramBot(); $message = "✅ План на неделю сохранен!\n\n"; $message .= "Запланировано задач: " . count($data['tasks']) . "\n"; $message .= "Период: " . $data['weekKey']; $telegram->sendMessage($user['telegramId'], $message); } echo json_encode(['success' => true]); } function handleGetReport($db, $userId, $weekKey) { $report = $db->getReport($userId, $weekKey); echo json_encode(['report' => $report]); } function handleCreateReport($db, $userId) { $data = getJsonInput(); if (!isset($data['weekKey']) || !isset($data['results']) || !isset($data['summary'])) { badRequest('Missing required fields'); } $db->saveReport($userId, $data['weekKey'], $data['results'], $data['summary']); // Calculate stats $total = count($data['results']); $completed = 0; foreach ($data['results'] as $result) { if ($result['status'] === 'completed') { $completed++; } } $completionRate = round(($completed / $total) * 100); // Send notification $user = $db->getUser($userId); if ($user && $user['telegramId']) { $telegram = new TelegramBot(); $message = "📊 Отчет отправлен!\n\n"; $message .= "Выполнение: {$completionRate}%\n"; $message .= "Выполнено задач: {$completed}/{$total}\n"; $message .= "Новых лидов: " . $data['summary']['newLeads'] . "\n"; $message .= "Завершенных сделок: " . $data['summary']['completedDeals']; $telegram->sendMessage($user['telegramId'], $message); } echo json_encode(['success' => true]); } // Helper functions function getJsonInput() { $input = file_get_contents('php://input'); $data = json_decode($input, true); if (json_last_error() !== JSON_ERROR_NONE) { badRequest('Invalid JSON'); } return $data; } function methodNotAllowed() { http_response_code(405); echo json_encode(['error' => 'Method not allowed']); exit; } function badRequest($message) { http_response_code(400); echo json_encode(['error' => $message]); exit; } function unauthorized() { http_response_code(401); echo json_encode(['error' => 'Authentication required']); exit; } ?>
pdo = new PDO( "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); } catch (PDOException $e) { throw new Exception("Database connection failed: " . $e->getMessage()); } } // User operations public function getUser($userId) { $stmt = $this->pdo->prepare(" SELECT user_id as userId, phone, telegram_id as telegramId, first_name as firstName, last_name as lastName, position FROM users WHERE user_id = ? "); $stmt->execute([$userId]); return $stmt->fetch(); } public function createUser($userData) { $stmt = $this->pdo->prepare(" INSERT INTO users (user_id, phone, telegram_id, first_name, last_name, position) VALUES (?, ?, ?, ?, ?, ?) "); $stmt->execute([ $userData['userId'], $userData['phone'], $userData['telegramId'], $userData['firstName'], $userData['lastName'], $userData['position'] ]); } // Verification codes public function storeVerificationCode($phone, $code) { // Delete any existing codes for this phone $this->pdo->prepare("DELETE FROM verification_codes WHERE phone = ?")->execute([$phone]); // Insert new code with 5 minute expiry $stmt = $this->pdo->prepare(" INSERT INTO verification_codes (phone, code, expires_at) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE)) "); $stmt->execute([$phone, $code]); } public function verifyCode($phone, $code) { $stmt = $this->pdo->prepare(" SELECT 1 FROM verification_codes WHERE phone = ? AND code = ? AND expires_at > NOW() "); $stmt->execute([$phone, $code]); return $stmt->rowCount() > 0; } public function clearVerificationCode($phone) { $stmt = $this->pdo->prepare("DELETE FROM verification_codes WHERE phone = ?"); $stmt->execute([$phone]); } // Plans public function getUserPlanWeeks($userId) { $stmt = $this->pdo->prepare(" SELECT week_key FROM plans WHERE user_id = ? ORDER BY week_key DESC "); $stmt->execute([$userId]); return array_column($stmt->fetchAll(), 'week_key'); } public function getPlan($userId, $weekKey) { $stmt = $this->pdo->prepare(" SELECT tasks, created_at as date FROM plans WHERE user_id = ? AND week_key = ? "); $stmt->execute([$userId, $weekKey]); $result = $stmt->fetch(); if ($result) { return [ 'tasks' => json_decode($result['tasks'], true), 'date' => $result['date'] ]; } return null; } public function savePlan($userId, $weekKey, $tasks) { $stmt = $this->pdo->prepare(" INSERT INTO plans (user_id, week_key, tasks) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE tasks = VALUES(tasks), created_at = NOW() "); $stmt->execute([$userId, $weekKey, json_encode($tasks)]); } // Reports public function getReport($userId, $weekKey) { $stmt = $this->pdo->prepare(" SELECT results, summary, created_at as date FROM reports WHERE user_id = ? AND week_key = ? "); $stmt->execute([$userId, $weekKey]); $result = $stmt->fetch(); if ($result) { return [ 'results' => json_decode($result['results'], true), 'summary' => json_decode($result['summary'], true), 'date' => $result['date'] ]; } return null; } public function saveReport($userId, $weekKey, $results, $summary) { $stmt = $this->pdo->prepare(" INSERT INTO reports (user_id, week_key, results, summary) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE results = VALUES(results), summary = VALUES(summary), created_at = NOW() "); $stmt->execute([ $userId, $weekKey, json_encode($results), json_encode($summary) ]); } // Clean up expired codes (run periodically) public function cleanupExpiredCodes() { $this->pdo->exec("DELETE FROM verification_codes WHERE expires_at < NOW()"); } } ?>
$value) { $data_check_array[] = $key . '=' . $value; } sort($data_check_array); $data_check_string = implode("\n", $data_check_array); // Calculate hash $secret_key = hash('sha256', BOT_TOKEN, true); $hash = hash_hmac('sha256', $data_check_string, $secret_key); // Check if hashes match and auth date is recent (within 1 day) if (strcmp($hash, $check_hash) !== 0) { return false; } // Check auth date (optional, for extra security) if ((time() - $auth_data['auth_date']) > 86400) { return false; } return true; } function generateJWT($userId, $telegramId = null) { $header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']); $payload = json_encode([ 'userId' => $userId, 'telegramId' => $telegramId, 'iat' => time(), 'exp' => time() + JWT_EXPIRY ]); $base64Header = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header)); $base64Payload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload)); $signature = hash_hmac('sha256', $base64Header . "." . $base64Payload, JWT_SECRET, true); $base64Signature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature)); return $base64Header . "." . $base64Payload . "." . $base64Signature; } function verifyJWT($jwt) { $tokenParts = explode('.', $jwt); if (count($tokenParts) !== 3) { return false; } $header = base64_decode($tokenParts[0]); $payload = base64_decode($tokenParts[1]); $signatureProvided = $tokenParts[2]; $base64Header = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header)); $base64Payload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload)); $signature = hash_hmac('sha256', $base64Header . "." . $base64Payload, JWT_SECRET, true); $base64Signature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature)); if ($base64Signature !== $signatureProvided) { return false; } $payloadData = json_decode($payload, true); // Check expiration if (isset($payloadData['exp']) && $payloadData['exp'] < time()) { return false; } return $payloadData; } function authenticateRequest() { $headers = getallheaders(); if (!isset($headers['Authorization'])) { return false; } $authHeader = $headers['Authorization']; $token = str_replace('Bearer ', '', $authHeader); return verifyJWT($token); } ?>
baseUrl = "https://api.telegram.org/bot" . BOT_TOKEN; } public function sendMessage($chatId, $text, $parseMode = 'HTML') { $data = [ 'chat_id' => $chatId, 'text' => $text, 'parse_mode' => $parseMode ]; return $this->makeRequest('/sendMessage', $data); } public function sendCode($chatId, $code) { $message = "🔐 Ваш код подтверждения:\n\n"; $message .= "{$code}\n\n"; $message .= "Код действителен в течение 5 минут."; return $this->sendMessage($chatId, $message); } private function makeRequest($endpoint, $data) { $url = $this->baseUrl . $endpoint; $options = [ 'http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/json', 'content' => json_encode($data), 'timeout' => 10 ] ]; $context = stream_context_create($options); $result = @file_get_contents($url, false, $context); if ($result === false) { error_log("Telegram API request failed: " . error_get_last()['message']); return false; } $response = json_decode($result, true); if (!$response['ok']) { error_log("Telegram API error: " . json_encode($response)); return false; } return $response['result']; } } ?>
-- /security/setup.sql - Database setup script CREATE DATABASE IF NOT EXISTS kpi_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE kpi_system; -- Users table CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(255) UNIQUE NOT NULL, phone VARCHAR(20), telegram_id BIGINT, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, position VARCHAR(50) NOT NULL, registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_telegram_id (telegram_id), INDEX idx_phone (phone) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Plans table CREATE TABLE IF NOT EXISTS plans ( id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(255) NOT NULL, week_key VARCHAR(10) NOT NULL, tasks JSON NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_user_week (user_id, week_key), INDEX idx_user_week (user_id, week_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Reports table CREATE TABLE IF NOT EXISTS reports ( id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(255) NOT NULL, week_key VARCHAR(10) NOT NULL, results JSON NOT NULL, summary JSON NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_user_week (user_id, week_key), INDEX idx_user_week (user_id, week_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Verification codes table CREATE TABLE IF NOT EXISTS verification_codes ( phone VARCHAR(20) PRIMARY KEY, code VARCHAR(4) NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Create event to clean up expired codes automatically DELIMITER $ CREATE EVENT IF NOT EXISTS cleanup_expired_codes ON SCHEDULE EVERY 1 HOUR DO BEGIN DELETE FROM verification_codes WHERE expires_at < NOW(); END$ DELIMITER ;