initial: bootstrap from BukidBountyApp base
This commit is contained in:
565
app/Http/Controllers/Market/UltimateController.php
Normal file
565
app/Http/Controllers/Market/UltimateController.php
Normal file
@@ -0,0 +1,565 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Enums\UserActions;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\User;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\GlobalTransaction;
|
||||
use App\Models\FileContent;
|
||||
use App\Models\DbBackup;
|
||||
use Hyperf\Stringable\Str;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\DB;
|
||||
use Hypervel\Support\Facades\Redis;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
|
||||
class UltimateController
|
||||
{
|
||||
/**
|
||||
* Common check for Ultimate access.
|
||||
*/
|
||||
private function checkAccess()
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(0, UserActions::UltimateConsole)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system-wide statistics for the dashboard.
|
||||
*/
|
||||
public function getSystemStats()
|
||||
{
|
||||
if (!$this->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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user