checkAccess()) return ResponseHelper::returnUnauthorized(); $globalMessage = Redis::get('system:global_message'); $redisStatus = ['connected' => false, 'ping_ms' => null, 'used_memory_human' => null, 'version' => null, 'error' => null]; try { $start = microtime(true); $pong = Redis::ping(); $redisStatus['ping_ms'] = round((microtime(true) - $start) * 1000, 2); $redisStatus['connected'] = $pong === true || $pong === 'PONG' || $pong === '+PONG' || (is_string($pong) && stripos($pong, 'PONG') !== false); $info = Redis::info(); if (is_array($info)) { $flat = isset($info['Memory']) && is_array($info['Memory']) ? $info['Memory'] : $info; $redisStatus['used_memory_human'] = $flat['used_memory_human'] ?? null; $serverInfo = isset($info['Server']) && is_array($info['Server']) ? $info['Server'] : $info; $redisStatus['version'] = $serverInfo['redis_version'] ?? null; } } catch (\Throwable $e) { $redisStatus['error'] = $e->getMessage(); } $stats = [ 'users' => User::count(), 'active_users' => User::where('active', true)->count(), 'stores' => Store::count(), 'active_stores' => Store::where('is_active', true)->count(), 'products' => Product::count(), 'transactions' => GlobalTransaction::count(), 'total_balance' => GlobalTransaction::sum('amount'), 'php_version' => PHP_VERSION, 'server_time' => date('Y-m-d H:i:s'), 'maintenance_mode' => Redis::get('system:maintenance_mode') === 'true', 'global_message' => $globalMessage ? json_decode($globalMessage, true) : null, 'logs_count' => DB::table('logs')->count(), 'table_logs_count' => DB::table('table_logs')->count(), 'pos_sessions_count' => DB::table('pos_sessions')->count(), 'cooperatives_count' => DB::table('organizations')->where('type', 'COOPERATIVE')->count(), 'carts_count' => DB::table('carts')->count(), 'farmer_profiles_count' => DB::table('farmer_profiles')->count(), 'redis' => $redisStatus, ]; return Response::json(['success' => true, 'data' => $stats]); } /** * Execute a raw SQL query. */ public function runQuery(Request $request) { if (Auth::user()->acct_type !== \App\Enums\UserTypes::ULTIMATE || !UserPermissions::isActionPermitted(0, UserActions::UltimateQuery)) { return ResponseHelper::returnUnauthorized(); } $query = $request->input('query'); if (empty($query)) return ResponseHelper::returnError('Query cannot be empty'); try { $queryLower = strtolower(trim($query)); if (str_starts_with($queryLower, 'select') || str_starts_with($queryLower, 'show') || str_starts_with($queryLower, 'describe')) { $results = DB::select($query); return Response::json(['success' => true, 'data' => $results]); } else { $affected = DB::statement($query); return Response::json(['success' => true, 'affected' => $affected]); } } catch (\Throwable $th) { return ResponseHelper::returnError($th->getMessage()); } } /** * Toggle maintenance mode system-wide. */ public function toggleMaintenance(Request $request) { if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateMaintenance)) { return ResponseHelper::returnUnauthorized(); } $enabled = (bool) $request->input('enabled'); Redis::set('system:maintenance_mode', $enabled ? 'true' : 'false'); return Response::json(['success' => true, 'maintenance_mode' => $enabled]); } /** * Send a global message / broadcast. */ public function sendGlobalMessage(Request $request) { if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateGlobalMessage)) { return ResponseHelper::returnUnauthorized(); } $message = $request->input('message'); $type = $request->input('type', 'info'); // info, success, warning, danger if (empty($message)) { Redis::del('system:global_message'); return Response::json(['success' => true, 'message' => 'Global message cleared']); } Redis::set('system:global_message', json_encode([ 'text' => $message, 'type' => $type, 'timestamp' => time() ])); return Response::json(['success' => true]); } /** * Flush / Truncate specific tables. */ public function flushData(Request $request) { if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateFlush)) { return ResponseHelper::returnUnauthorized(); } $target = $request->input('target'); try { $affected = 0; switch ($target) { case 'transactions': $affected = GlobalTransaction::count(); DB::statement('SET FOREIGN_KEY_CHECKS = 0'); GlobalTransaction::truncate(); DB::statement('SET FOREIGN_KEY_CHECKS = 1'); break; case 'pos_sessions': $affected = DB::table('pos_sessions')->count(); DB::table('pos_sessions')->truncate(); break; case 'cache': Redis::flushDB(); break; case 'stores': $affected = DB::table('str')->count(); DB::statement('SET FOREIGN_KEY_CHECKS = 0'); DB::table('prd_str')->truncate(); DB::table('store_managers')->truncate(); DB::table('str')->truncate(); DB::statement('SET FOREIGN_KEY_CHECKS = 1'); break; case 'products': $affected = DB::table('prd_items')->count(); DB::statement('SET FOREIGN_KEY_CHECKS = 0'); DB::table('prd_str')->truncate(); DB::table('prd_items')->truncate(); DB::statement('SET FOREIGN_KEY_CHECKS = 1'); break; case 'cooperatives': $affected = DB::table('organizations')->where('type', 'COOPERATIVE')->count(); DB::statement('SET FOREIGN_KEY_CHECKS = 0'); DB::table('cooperative_votes')->truncate(); DB::table('cooperative_resolutions')->truncate(); DB::table('cooperative_documents')->truncate(); DB::table('cooperative_members')->truncate(); DB::table('organizations')->where('type', 'COOPERATIVE')->delete(); DB::statement('SET FOREIGN_KEY_CHECKS = 1'); break; case 'carts': $affected = DB::table('carts')->count(); DB::statement('SET FOREIGN_KEY_CHECKS = 0'); DB::table('cart_items')->truncate(); DB::table('carts')->truncate(); DB::statement('SET FOREIGN_KEY_CHECKS = 1'); break; case 'farmer_profiles': $affected = DB::table('farmer_profiles')->count(); DB::statement('SET FOREIGN_KEY_CHECKS = 0'); DB::table('farmer_profiles')->truncate(); DB::statement('SET FOREIGN_KEY_CHECKS = 1'); break; default: return ResponseHelper::returnError('Invalid flush target'); } return Response::json(['success' => true, 'message' => "Flushed $target successfully", 'affected' => $affected]); } catch (\Throwable $th) { return ResponseHelper::returnError($th->getMessage()); } } /** * Trigger a test notification for a specific user. */ public function testNotification(Request $request) { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); $userHash = $request->input('user_hash'); $user = User::where('hashkey', $userHash)->first(); if (!$user) return ResponseHelper::returnError('User not found'); // Setting exec_command which SSEController picks up to notify client $user->exec_command = 'toast:success:Test Notification from Ultimate Console: ' . date('H:i:s'); $user->save(); return Response::json(['success' => true]); } /** * Batch management for various entities. */ public function batchManage(Request $request) { if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateBatch)) { return ResponseHelper::returnUnauthorized(); } $action = $request->input('action'); $ids = $request->input('ids', []); $data = $request->input('data', []); if (empty($ids) && !in_array($action, ['cleanup_sessions'])) { return ResponseHelper::returnError('No IDs provided'); } try { switch($action) { case 'activate_users': User::whereIn('id', $ids)->update(['active' => true]); break; case 'deactivate_users': User::whereIn('id', $ids)->update(['active' => false]); break; case 'cleanup_sessions': DB::table('pos_sessions')->where('status', 'VOIDED')->delete(); break; case 'mass_transfer_points': $amount = (float)($data['amount'] ?? 0); if ($amount <= 0) return ResponseHelper::returnError('Invalid amount'); foreach ($ids as $id) { GlobalTransaction::create([ 'user_id' => $id, 'amount' => $amount, 'type' => 'REWARD', 'description' => 'Mass points adjustment via Ultimate Console', 'is_active' => true, ]); } break; default: return ResponseHelper::returnError('Invalid batch action'); } return Response::json(['success' => true]); } catch (\Throwable $th) { return ResponseHelper::returnError($th->getMessage()); } } /** * Run a system command (Artisan wrapper). */ public function runCommand(Request $request) { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); $command = $request->input('command'); if (empty($command)) return ResponseHelper::returnError('Command cannot be empty'); // Normalize command: strip 'php artisan ' if present $command = preg_replace('/^php artisan\s+/', '', trim($command)); // Mapping for user-friendly commands if ($command === 'reset-app all users') { $command = 'app:reset-users'; } if ($command === 'db seed') { $command = 'db:seed'; } // For security, only allow specific commands $allowedCommands = [ 'cache:clear', 'view:clear', 'config:clear', 'route:clear', 'migrate', 'migrate:rollback', 'migrate:fresh', 'db:seed', 'app:reset-users', 'optimize', 'optimize:clear' ]; $baseCommand = explode(' ', trim($command))[0]; if (!in_array($baseCommand, $allowedCommands)) { return ResponseHelper::returnError("Command '{$baseCommand}' not allowed for security reasons."); } try { // In Hyperf, running commands from HTTP request context is tricky. // We'll use shell_exec in this local environment demo as a fallback. $output = shell_exec("php artisan $command 2>&1"); return Response::json(['success' => true, 'output' => $output]); } catch (\Throwable $th) { return ResponseHelper::returnError($th->getMessage()); } } /** * Run `php artisan migrate --force` (non-interactive). */ public function runMigrate(Request $request) { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); try { $output = shell_exec('cd ' . escapeshellarg(BASE_PATH) . ' && php artisan migrate --force 2>&1'); return Response::json(['success' => true, 'output' => $output]); } catch (\Throwable $th) { return ResponseHelper::returnError($th->getMessage()); } } /** * Download a full database backup. * Puts system in maintenance mode during the process. */ public function downloadBackup() { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); // 1. Enable maintenance mode & Notify Redis::set('system:maintenance_mode', 'true'); Redis::set('system:global_message', json_encode(['text' => 'System backup in progress. Transactions temporarily disabled.', 'type' => 'warning'])); try { $filename = 'backup_' . date('Y-m-d_H-i-s') . '.sql'; $path = BASE_PATH . '/storage/app/backups/' . $filename; if (!is_dir(dirname($path))) { mkdir(dirname($path), 0755, true); } $dbHost = env('DB_HOST', '127.0.0.1'); $dbPort = env('DB_PORT', '3306'); $dbName = env('DB_DATABASE', 'bukid'); $dbUser = env('DB_USERNAME', 'root'); $dbPass = env('DB_PASSWORD', ''); $dump = new \Ifsnop\Mysqldump\Mysqldump( "mysql:host={$dbHost};port={$dbPort};dbname={$dbName}", $dbUser, $dbPass, [ 'add-drop-table' => true, 'exclude-tables' => ['db_backups'] // Exclude the backups table ] ); $dump->start($path); if (!file_exists($path) || filesize($path) === 0) { throw new \Exception('Backup file was not created or is empty.'); } // Compress into 7z Ultra $sevenZFilename = 'backup_' . date('Y-m-d_H-i-s') . '.7z'; $sevenZPath = BASE_PATH . '/storage/app/backups/' . $sevenZFilename; // -mx=9 for Ultra compression $path_escaped = escapeshellarg($path); $sevenZPath_escaped = escapeshellarg($sevenZPath); $command = "7z a -t7z -m0=lzma2 -mx=9 {$sevenZPath_escaped} {$path_escaped} 2>&1"; shell_exec($command); if (!file_exists($sevenZPath)) { throw new \Exception('Failed to create 7z archive.'); } // Save to database $fileContentRaw = file_get_contents($sevenZPath); $fileHash = hash('sha256', $fileContentRaw); $fileContent = new FileContent(); $fileContent->filehash = $fileHash; $fileContent->titlename = $sevenZFilename; $fileContent->description = 'System database backup'; $fileContent->size_in_bytes = filesize($sevenZPath); $fileContent->content = base64_encode($fileContentRaw); $fileContent->mimetype = 'application/x-7z-compressed'; $fileContent->created_by = Auth::id(); $fileContent->updated_by = Auth::id(); $fileContent->save(); $dbBackup = new DbBackup(); $dbBackup->file_content_hashkey = $fileContent->hashkey; $dbBackup->filename = $sevenZFilename; $dbBackup->size_in_bytes = filesize($sevenZPath); $dbBackup->created_by = Auth::id(); $dbBackup->updated_by = Auth::id(); $dbBackup->save(); // Clean up the temporary files from filesystem @unlink($path); @unlink($sevenZPath); // 2. Disable maintenance mode & Clear Notify Redis::set('system:maintenance_mode', 'false'); Redis::del('system:global_message'); // 3. Return the binary content for download return Response::make($fileContentRaw, 200, [ 'Content-Type' => 'application/x-7z-compressed', 'Content-Disposition' => 'attachment; filename="' . $sevenZFilename . '"', 'Content-Length' => strlen($fileContentRaw), ]); } catch (\Throwable $th) { Redis::set('system:maintenance_mode', 'false'); return ResponseHelper::returnError($th->getMessage()); } } /** * Get recently created backups. */ public function getBackups() { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); $backups = DbBackup::with(['creator']) ->orderBy('created_at', 'desc') ->limit(50) ->get(); return Response::json(['success' => true, 'data' => $backups]); } /** * Rename a specific backup. */ public function renameBackup(Request $request) { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); $hash = $request->input('hash'); $newName = $request->input('name'); if (empty($newName)) return ResponseHelper::returnError('Name cannot be empty'); $backup = DbBackup::where('hashkey', $hash)->first(); if (!$backup) return ResponseHelper::returnError('Backup not found'); $backup->name = $newName; $backup->save(); return Response::json(['success' => true]); } /** * Delete a specific backup. */ public function deleteBackup(Request $request) { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); $hash = $request->input('hash'); $backup = DbBackup::where('hashkey', $hash)->first(); if (!$backup) return ResponseHelper::returnError('Backup not found'); // Delete associated file content FileContent::where('hashkey', $backup->file_content_hashkey)->delete(); // Delete backup record $backup->delete(); return Response::json(['success' => true]); } /** * Download a specific backup from the database. */ public function downloadBackupByHash(Request $request) { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); $hash = $request->input('hash'); $backup = DbBackup::where('hashkey', $hash)->first(); if (!$backup) return ResponseHelper::returnError('Backup not found'); $fileContent = FileContent::where('hashkey', $backup->file_content_hashkey)->first(); if (!$fileContent) return ResponseHelper::returnError('File content not found'); $content = base64_decode($fileContent->content); return Response::make($content, 200, [ 'Content-Type' => $fileContent->mimetype, 'Content-Disposition' => 'attachment; filename="' . $backup->filename . '"', 'Content-Length' => strlen($content), ]); } /** * Get system-wide logs from file and database. */ public function getSystemLogs(Request $request) { if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); $type = $request->input('type', 'database'); if ($type === 'file') { $logPath = BASE_PATH . '/storage/logs/hypervel.log'; if (!file_exists($logPath)) { return Response::json(['success' => true, 'data' => 'No file logs found.']); } $logs = shell_exec("tail -n 1000 " . escapeshellarg($logPath)); return Response::json(['success' => true, 'data' => $logs]); } // Database logs (audit) if ($type === 'audit') { $logs = DB::table('table_logs')->orderBy('id', 'desc')->limit(500)->get(); return Response::json(['success' => true, 'data' => $logs]); } // Database logs (system) $logs = DB::table('logs')->orderBy('uid', 'desc')->limit(500)->get(); return Response::json(['success' => true, 'data' => $logs]); } }