From de5eb6b5d1f005e418792cf4dc1a5a40de477249 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 4 Nov 2025 16:30:37 +0000 Subject: [PATCH] Revert to version 577ca93 --- .perm_test_apache | 0 .perm_test_exec | 0 ai/LocalAIApi.php | 311 ----------------------------------------- ai/config.php | 52 ------- api/dummy_endpoint.php | 80 ----------- assets/css/custom.css | 23 --- assets/js/main.js | 41 ------ db/config.php | 2 +- db/setup.php | 101 ------------- index.php | 300 +++++++++++++++++++-------------------- login.php | 97 ------------- logout.php | 22 --- 12 files changed, 141 insertions(+), 888 deletions(-) delete mode 100644 .perm_test_apache delete mode 100644 .perm_test_exec delete mode 100644 ai/LocalAIApi.php delete mode 100644 ai/config.php delete mode 100644 api/dummy_endpoint.php delete mode 100644 assets/css/custom.css delete mode 100644 assets/js/main.js delete mode 100644 db/setup.php delete mode 100644 login.php delete mode 100644 logout.php diff --git a/.perm_test_apache b/.perm_test_apache deleted file mode 100644 index e69de29..0000000 diff --git a/.perm_test_exec b/.perm_test_exec deleted file mode 100644 index e69de29..0000000 diff --git a/ai/LocalAIApi.php b/ai/LocalAIApi.php deleted file mode 100644 index 00b1b00..0000000 --- a/ai/LocalAIApi.php +++ /dev/null @@ -1,311 +0,0 @@ - [ -// ['role' => 'system', 'content' => 'You are a helpful assistant.'], -// ['role' => 'user', 'content' => 'Tell me a bedtime story.'], -// ], -// ]); -// if (!empty($response['success'])) { -// $decoded = LocalAIApi::decodeJsonFromResponse($response); -// } - -class LocalAIApi -{ - /** @var array|null */ - private static ?array $configCache = null; - - /** - * Signature compatible with the OpenAI Responses API. - * - * @param array $params Request body (model, input, text, reasoning, metadata, etc.). - * @param array $options Extra options (timeout, verify_tls, headers, path, project_uuid). - * @return array{ - * success:bool, - * status?:int, - * data?:mixed, - * error?:string, - * response?:mixed, - * message?:string - * } - */ - public static function createResponse(array $params, array $options = []): array - { - $cfg = self::config(); - $payload = $params; - - if (empty($payload['input']) || !is_array($payload['input'])) { - return [ - 'success' => false, - 'error' => 'input_missing', - 'message' => 'Parameter "input" is required and must be an array.', - ]; - } - - if (!isset($payload['model']) || $payload['model'] === '') { - $payload['model'] = $cfg['default_model']; - } - - return self::request($options['path'] ?? null, $payload, $options); - } - - /** - * Snake_case alias for createResponse (matches the provided example). - * - * @param array $params - * @param array $options - * @return array - */ - public static function create_response(array $params, array $options = []): array - { - return self::createResponse($params, $options); - } - - /** - * Perform a raw request to the AI proxy. - * - * @param string $path Endpoint (may be an absolute URL). - * @param array $payload JSON payload. - * @param array $options Additional request options. - * @return array - */ - public static function request(?string $path = null, array $payload = [], array $options = []): array - { - if (!function_exists('curl_init')) { - return [ - 'success' => false, - 'error' => 'curl_missing', - 'message' => 'PHP cURL extension is missing. Install or enable it on the VM.', - ]; - } - - $cfg = self::config(); - - $projectUuid = $cfg['project_uuid']; - if (empty($projectUuid)) { - return [ - 'success' => false, - 'error' => 'project_uuid_missing', - 'message' => 'PROJECT_UUID is not defined; aborting AI request.', - ]; - } - - $defaultPath = $cfg['responses_path'] ?? null; - $resolvedPath = $path ?? ($options['path'] ?? $defaultPath); - if (empty($resolvedPath)) { - return [ - 'success' => false, - 'error' => 'project_id_missing', - 'message' => 'PROJECT_ID is not defined; cannot resolve AI proxy endpoint.', - ]; - } - - $url = self::buildUrl($resolvedPath, $cfg['base_url']); - $baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30; - $timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout; - if ($timeout <= 0) { - $timeout = 30; - } - - $baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true; - $verifyTls = array_key_exists('verify_tls', $options) - ? (bool) $options['verify_tls'] - : $baseVerifyTls; - - $projectHeader = $cfg['project_header']; - - $headers = [ - 'Content-Type: application/json', - 'Accept: application/json', - ]; - $headers[] = $projectHeader . ': ' . $projectUuid; - if (!empty($options['headers']) && is_array($options['headers'])) { - foreach ($options['headers'] as $header) { - if (is_string($header) && $header !== '') { - $headers[] = $header; - } - } - } - - if (!empty($projectUuid) && !array_key_exists('project_uuid', $payload)) { - $payload['project_uuid'] = $projectUuid; - } - - $body = json_encode($payload, JSON_UNESCAPED_UNICODE); - if ($body === false) { - return [ - 'success' => false, - 'error' => 'json_encode_failed', - 'message' => 'Failed to encode request body to JSON.', - ]; - } - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifyTls); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0); - curl_setopt($ch, CURLOPT_FAILONERROR, false); - - $responseBody = curl_exec($ch); - if ($responseBody === false) { - $error = curl_error($ch) ?: 'Unknown cURL error'; - curl_close($ch); - return [ - 'success' => false, - 'error' => 'curl_error', - 'message' => $error, - ]; - } - - $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - $decoded = null; - if ($responseBody !== '' && $responseBody !== null) { - $decoded = json_decode($responseBody, true); - if (json_last_error() !== JSON_ERROR_NONE) { - $decoded = null; - } - } - - if ($status >= 200 && $status < 300) { - return [ - 'success' => true, - 'status' => $status, - 'data' => $decoded ?? $responseBody, - ]; - } - - $errorMessage = 'AI proxy request failed'; - if (is_array($decoded)) { - $errorMessage = $decoded['error'] ?? $decoded['message'] ?? $errorMessage; - } elseif (is_string($responseBody) && $responseBody !== '') { - $errorMessage = $responseBody; - } - - return [ - 'success' => false, - 'status' => $status, - 'error' => $errorMessage, - 'response' => $decoded ?? $responseBody, - ]; - } - - /** - * Extract plain text from a Responses API payload. - * - * @param array $response Result of LocalAIApi::createResponse|request. - * @return string - */ - public static function extractText(array $response): string - { - $payload = $response['data'] ?? $response; - if (!is_array($payload)) { - return ''; - } - - if (!empty($payload['output']) && is_array($payload['output'])) { - $combined = ''; - foreach ($payload['output'] as $item) { - if (!isset($item['content']) || !is_array($item['content'])) { - continue; - } - foreach ($item['content'] as $block) { - if (is_array($block) && ($block['type'] ?? '') === 'output_text' && !empty($block['text'])) { - $combined .= $block['text']; - } - } - } - if ($combined !== '') { - return $combined; - } - } - - if (!empty($payload['choices'][0]['message']['content'])) { - return (string) $payload['choices'][0]['message']['content']; - } - - return ''; - } - - /** - * Attempt to decode JSON emitted by the model (handles markdown fences). - * - * @param array $response - * @return array|null - */ - public static function decodeJsonFromResponse(array $response): ?array - { - $text = self::extractText($response); - if ($text === '') { - return null; - } - - $decoded = json_decode($text, true); - if (is_array($decoded)) { - return $decoded; - } - - $stripped = preg_replace('/^```json|```$/m', '', trim($text)); - if ($stripped !== null && $stripped !== $text) { - $decoded = json_decode($stripped, true); - if (is_array($decoded)) { - return $decoded; - } - } - - return null; - } - - /** - * Load configuration from ai/config.php. - * - * @return array - */ - private static function config(): array - { - if (self::$configCache === null) { - $configPath = __DIR__ . '/config.php'; - if (!file_exists($configPath)) { - throw new RuntimeException('AI config file not found: ai/config.php'); - } - $cfg = require $configPath; - if (!is_array($cfg)) { - throw new RuntimeException('Invalid AI config format: expected array'); - } - self::$configCache = $cfg; - } - - return self::$configCache; - } - - /** - * Build an absolute URL from base_url and a path. - */ - private static function buildUrl(string $path, string $baseUrl): string - { - $trimmed = trim($path); - if ($trimmed === '') { - return $baseUrl; - } - if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) { - return $trimmed; - } - if ($trimmed[0] === '/') { - return $baseUrl . $trimmed; - } - return $baseUrl . '/' . $trimmed; - } -} - -// Legacy alias for backward compatibility with the previous class name. -if (!class_exists('OpenAIService')) { - class_alias(LocalAIApi::class, 'OpenAIService'); -} diff --git a/ai/config.php b/ai/config.php deleted file mode 100644 index 1ba1596..0000000 --- a/ai/config.php +++ /dev/null @@ -1,52 +0,0 @@ - $baseUrl, - 'responses_path' => $responsesPath, - 'project_id' => $projectId, - 'project_uuid' => $projectUuid, - 'project_header' => 'project-uuid', - 'default_model' => 'gpt-5', - 'timeout' => 30, - 'verify_tls' => true, -]; diff --git a/api/dummy_endpoint.php b/api/dummy_endpoint.php deleted file mode 100644 index 62ddda9..0000000 --- a/api/dummy_endpoint.php +++ /dev/null @@ -1,80 +0,0 @@ - 401, - 'statusMessage' => 'Unauthorized' - ]); - exit; -} - -// --- Request Method & Body --- -if ($_SERVER['REQUEST_METHOD'] !== 'POST') { - http_response_code(405); - echo json_encode([ - 'statusCode' => 405, - 'statusMessage' => 'Method Not Allowed' - ]); - exit; -} - -$json_payload = file_get_contents('php://input'); -$data = json_decode($json_payload, true); - -// --- Validation --- -$required_fields = ['name', 'accountNumber', 'accountName', 'amount']; -$missing_fields = []; -foreach ($required_fields as $field) { - if (empty($data[$field])) { - $missing_fields[] = $field; - } -} - -if (!empty($missing_fields)) { - http_response_code(400); - echo json_encode([ - 'statusCode' => 400, - 'statusMessage' => 'Bad Request', - 'error' => 'Missing required fields: ' . implode(', ', $missing_fields) - ]); - exit; -} - -// --- Log Request (for debugging the dummy API itself) --- -// In a real app, you might not log to a file here, but it's useful for the dummy. -// The main app will log to its own DB. -$log_entry = [ - 'timestamp' => date('c'), - 'request' => $data, -]; - -// --- Process and Respond --- -// Simulate some random failures for realism -$should_fail = rand(1, 10) > 8; // 20% chance of failure - -if ($should_fail) { - http_response_code(500); - $response = [ - 'statusCode' => 500, - 'statusMessage' => 'Internal Server Error', - 'error' => 'A simulated random error occurred.' - ]; -} else { - http_response_code(200); - $response = [ - 'statusCode' => 200, - 'statusMessage' => 'sukses', - 'transactionId' => 'txn_' . uniqid() - ]; -} - -$log_entry['response'] = $response; -file_put_contents('dummy_api_log.txt', json_encode($log_entry) . PHP_EOL, FILE_APPEND); - -echo json_encode($response); \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css deleted file mode 100644 index fa7950a..0000000 --- a/assets/css/custom.css +++ /dev/null @@ -1,23 +0,0 @@ -body { - font-family: 'Inter', sans-serif; - background-color: #F8F9FA; -} - -.navbar-brand { - font-size: 1.5rem; -} - -.card { - border: none; - border-radius: 0.5rem; - box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.05); - transition: transform 0.2s ease-in-out; -} - -.card:hover { - transform: translateY(-5px); -} - -.table-hover tbody tr:hover { - background-color: rgba(0, 0, 0, 0.025); -} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js deleted file mode 100644 index 830d46e..0000000 --- a/assets/js/main.js +++ /dev/null @@ -1,41 +0,0 @@ -$(document).ready(function() { - const successToastEl = document.getElementById('successToast'); - const successToast = new bootstrap.Toast(successToastEl); - const previewModalEl = document.getElementById('previewModal'); - const previewModal = new bootstrap.Modal(previewModalEl); - - $('#uploadForm').on('submit', function(event) { - event.preventDefault(); - - const form = this; - const submitButton = $(form).find('button[type="submit"]'); - const spinner = submitButton.find('.spinner-border'); - - // Basic validation - if (form.checkValidity() === false) { - event.stopPropagation(); - $(form).addClass('was-validated'); - return; - } - - // Show loading state - submitButton.attr('disabled', true); - spinner.removeClass('d-none'); - - // Simulate async background process - setTimeout(function() { - // Show success toast - successToast.show(); - - // Show preview modal - previewModal.show(); - - // Reset form and button - form.reset(); - $(form).removeClass('was-validated'); - submitButton.attr('disabled', false); - spinner.addClass('d-none'); - - }, 1000); // Simulate a 1-second delay for effect - }); -}); \ No newline at end of file diff --git a/db/config.php b/db/config.php index d054803..f12ebaf 100644 --- a/db/config.php +++ b/db/config.php @@ -14,4 +14,4 @@ function db() { ]); } return $pdo; -} \ No newline at end of file +} diff --git a/db/setup.php b/db/setup.php deleted file mode 100644 index d9cd593..0000000 --- a/db/setup.php +++ /dev/null @@ -1,101 +0,0 @@ - PDO::ERRMODE_EXCEPTION, - ]); - $pdo_admin->exec("CREATE DATABASE IF NOT EXISTS `".DB_NAME."`"); - - // Now connect to the app DB - $pdo = db(); - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - - $statements = [ - // Users table for login - "CREATE TABLE IF NOT EXISTS users ( - id INT AUTO_INCREMENT PRIMARY KEY, - username VARCHAR(50) NOT NULL UNIQUE, - password VARCHAR(255) NOT NULL, - role ENUM('maker', 'approver') NOT NULL, - createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP - );", - - // Payroll batches table - "CREATE TABLE IF NOT EXISTS payrolls ( - id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - fileName VARCHAR(255) NOT NULL, - status ENUM('pending', 'approved', 'processing', 'delivered', 'failed') NOT NULL DEFAULT 'pending', - createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - createdBy INT, - approvedAt TIMESTAMP NULL, - approvedBy INT, - FOREIGN KEY (createdBy) REFERENCES users(id), - FOREIGN KEY (approvedBy) REFERENCES users(id) - );", - - // Payroll details (individual records from CSV) - "CREATE TABLE IF NOT EXISTS payroll_details ( - id INT AUTO_INCREMENT PRIMARY KEY, - payroll_id INT NOT NULL, - name VARCHAR(255) NOT NULL, - accountNumber VARCHAR(50) NOT NULL, - accountName VARCHAR(255) NOT NULL, - amount DECIMAL(15, 2) NOT NULL, - status ENUM('pending', 'processing', 'delivered', 'failed') NOT NULL DEFAULT 'pending', - createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - createdBy INT, - approvedAt TIMESTAMP NULL, - approvedBy INT, - api_response TEXT, - FOREIGN KEY (payroll_id) REFERENCES payrolls(id) ON DELETE CASCADE, - FOREIGN KEY (createdBy) REFERENCES users(id), - FOREIGN KEY (approvedBy) REFERENCES users(id) - );", - - // API logs - "CREATE TABLE IF NOT EXISTS api_logs ( - id INT AUTO_INCREMENT PRIMARY KEY, - payroll_detail_id INT, - request_payload TEXT, - response_payload TEXT, - status_code INT, - createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (payroll_detail_id) REFERENCES payroll_details(id) ON DELETE SET NULL - );" - ]; - - foreach ($statements as $statement) { - $pdo->exec($statement); - } - - echo "Database and tables created or already exist successfully." . PHP_EOL; - - // --- User Seeding --- - // To ensure a clean state, we'll remove existing dummy users and recreate them. - // This makes the script safe to re-run. - $pdo->exec("DELETE FROM users WHERE username IN ('maker', 'approver')"); - - // Use prepared statements for security and reliability - $stmt = $pdo->prepare("INSERT INTO users (username, password, role) VALUES (:username, :password, :role)"); - - $users_to_seed = [ - ['username' => 'maker', 'password' => password_hash('password123', PASSWORD_DEFAULT), 'role' => 'maker'], - ['username' => 'approver', 'password' => password_hash('password123', PASSWORD_DEFAULT), 'role' => 'approver'] - ]; - - foreach ($users_to_seed as $user) { - $stmt->execute($user); - } - - echo "Dummy users 'maker' and 'approver' created/reset successfully with password 'password123'." . PHP_EOL; - - -} catch (PDOException $e) { - die("Database setup failed: " . $e->getMessage()); -} \ No newline at end of file diff --git a/index.php b/index.php index 4b6d6b3..7205f3d 100644 --- a/index.php +++ b/index.php @@ -1,170 +1,150 @@ - + - - - Payroll - - - - - - - - - - - - - - + + + New Style + + + + + + + + + + + + + + + + + + + - - - -
-
-

Payroll Management

-
- -
-
-
-
-
Upload Payroll
-

Upload a CSV file to start a new payroll batch.

-
-
- - -
-
- - -
- -
-
-
-
-
-
-
-
Payroll Batches
-
- - - - - - - - - - - - - - - - -
IDNameFile NameCreated AtStatusActions
No payroll batches found.
-
-
-
-
-
-
- - -