feat: admin app version mgmt + notification system
- New tables: app_config, notifications - Admin views: App Version editor, Notification sender - API: GET /api/notifications, POST /api/notifications/:id/read, POST /api/notifications/read-all - App version now stored in DB, editable by super admin - Notifications: send to all users or specific user - Android notification models + API client - Sidebar: App Version + Notifications links for super_admin
This commit is contained in:
@@ -5,8 +5,10 @@ declare(strict_types=1);
|
||||
namespace ServerManager\Controllers;
|
||||
|
||||
use ServerManager\Core\App;
|
||||
use ServerManager\Core\Database;
|
||||
use ServerManager\Core\Session;
|
||||
use ServerManager\Core\Validator;
|
||||
use ServerManager\Models\Notification;
|
||||
use ServerManager\Models\User;
|
||||
use ServerManager\Services\AuditService;
|
||||
|
||||
@@ -210,4 +212,79 @@ class AdminController
|
||||
'title' => 'Security Settings - ServerManager',
|
||||
]);
|
||||
}
|
||||
|
||||
public function appVersion(): void
|
||||
{
|
||||
$this->requireSuperAdmin();
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$fields = ['app_version', 'apk_url', 'force_update', 'release_notes'];
|
||||
foreach ($fields as $field) {
|
||||
if (isset($_POST[$field])) {
|
||||
$db->update('app_config', ['value' => $_POST[$field]], '`key` = ?', [$field]);
|
||||
}
|
||||
}
|
||||
$this->auditService->log('app_version_updated', 'App version config updated');
|
||||
$this->view->json(['success' => true, 'message' => 'App version updated successfully.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$configs = $db->fetchAll('SELECT * FROM app_config');
|
||||
$config = [];
|
||||
foreach ($configs as $row) {
|
||||
$config[$row['key']] = $row['value'];
|
||||
}
|
||||
|
||||
$this->view->display('admin.app_version', [
|
||||
'title' => 'App Version - ServerManager',
|
||||
'config' => $config,
|
||||
]);
|
||||
}
|
||||
|
||||
public function notifications(): void
|
||||
{
|
||||
$this->requireSuperAdmin();
|
||||
|
||||
$notificationModel = new Notification();
|
||||
$page = (int) ($_GET['page'] ?? 1);
|
||||
$allNotifications = $notificationModel->getAll($page, 20);
|
||||
|
||||
$userModel = new User();
|
||||
$users = $userModel->getAll(1, 200);
|
||||
|
||||
$this->view->display('admin.notifications', [
|
||||
'title' => 'Notifications - ServerManager',
|
||||
'notifications' => $allNotifications,
|
||||
'users' => $users,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendNotification(): void
|
||||
{
|
||||
$this->requireSuperAdmin();
|
||||
|
||||
$title = $_POST['title'] ?? '';
|
||||
$message = $_POST['message'] ?? '';
|
||||
$type = $_POST['type'] ?? 'info';
|
||||
$userId = !empty($_POST['user_id']) ? (int) $_POST['user_id'] : null;
|
||||
|
||||
if (empty($title) || empty($message)) {
|
||||
$this->view->json(['success' => false, 'message' => 'Title and message are required.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$notificationModel = new Notification();
|
||||
$notificationModel->create([
|
||||
'user_id' => $userId,
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'type' => $type,
|
||||
]);
|
||||
|
||||
$this->auditService->log('notification_sent', "Notification: {$title}" . ($userId ? " to user #{$userId}" : ' (all users)'));
|
||||
|
||||
$this->view->json(['success' => true, 'message' => 'Notification sent successfully.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,17 +468,68 @@ class ApiController
|
||||
|
||||
public function appVersion(): void
|
||||
{
|
||||
$db = \ServerManager\Core\Database::getInstance();
|
||||
$configs = $db->fetchAll('SELECT * FROM app_config');
|
||||
$config = [];
|
||||
foreach ($configs as $row) {
|
||||
$config[$row['key']] = $row['value'];
|
||||
}
|
||||
|
||||
$this->view->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'version' => '1.0.1',
|
||||
'apk_url' => '/sysadmin.apk',
|
||||
'force_update' => false,
|
||||
'release_notes' => '• Gráficos de métricas en tiempo real\n• Nuevo diseño con animaciones\n• Modo oscuro\n• Perfil de usuario\n• Mejoras de rendimiento',
|
||||
'version' => $config['app_version'] ?? '1.0.0',
|
||||
'apk_url' => $config['apk_url'] ?? '/sysadmin.apk',
|
||||
'force_update' => ($config['force_update'] ?? '0') === '1',
|
||||
'release_notes' => $config['release_notes'] ?? '',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function notifications(): void
|
||||
{
|
||||
$user = $this->authenticateRequest();
|
||||
|
||||
$notificationModel = new \ServerManager\Models\Notification();
|
||||
$page = (int) ($_GET['page'] ?? 1);
|
||||
$perPage = (int) ($_GET['per_page'] ?? 20);
|
||||
|
||||
$result = $notificationModel->getForUser((int) $user['id'], $page, $perPage);
|
||||
$unreadCount = $notificationModel->getUnreadCount((int) $user['id']);
|
||||
|
||||
$this->view->json([
|
||||
'success' => true,
|
||||
'data' => $result['data'],
|
||||
'pagination' => [
|
||||
'page' => $result['page'],
|
||||
'per_page' => $result['per_page'],
|
||||
'total' => $result['total'],
|
||||
'total_pages' => $result['total_pages'],
|
||||
],
|
||||
'unread_count' => $unreadCount,
|
||||
]);
|
||||
}
|
||||
|
||||
public function markNotificationRead(int $id): void
|
||||
{
|
||||
$user = $this->authenticateRequest();
|
||||
|
||||
$notificationModel = new \ServerManager\Models\Notification();
|
||||
$notificationModel->markAsRead($id, (int) $user['id']);
|
||||
|
||||
$this->view->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function markAllNotificationsRead(): void
|
||||
{
|
||||
$user = $this->authenticateRequest();
|
||||
|
||||
$notificationModel = new \ServerManager\Models\Notification();
|
||||
$notificationModel->markAllAsRead((int) $user['id']);
|
||||
|
||||
$this->view->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function login(): void
|
||||
{
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
99
src/Models/Notification.php
Normal file
99
src/Models/Notification.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ServerManager\Models;
|
||||
|
||||
use ServerManager\Core\Database;
|
||||
|
||||
class Notification
|
||||
{
|
||||
private Database $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Database::getInstance();
|
||||
}
|
||||
|
||||
public function create(array $data): int
|
||||
{
|
||||
$data['created_at'] = date('Y-m-d H:i:s');
|
||||
return $this->db->insert('notifications', $data);
|
||||
}
|
||||
|
||||
public function findById(int $id): ?array
|
||||
{
|
||||
return $this->db->fetch('SELECT * FROM notifications WHERE id = ?', [$id]);
|
||||
}
|
||||
|
||||
public function getForUser(int $userId, int $page = 1, int $perPage = 20): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$total = $this->db->fetch(
|
||||
"SELECT COUNT(*) as total FROM notifications WHERE user_id IS NULL OR user_id = ?",
|
||||
[$userId]
|
||||
);
|
||||
$items = $this->db->fetchAll(
|
||||
"SELECT * FROM notifications WHERE user_id IS NULL OR user_id = ?
|
||||
ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
||||
[$userId, $perPage, $offset]
|
||||
);
|
||||
return [
|
||||
'data' => $items,
|
||||
'total' => (int) ($total['total'] ?? 0),
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total_pages' => (int) ceil((int) ($total['total'] ?? 0) / $perPage),
|
||||
];
|
||||
}
|
||||
|
||||
public function getUnreadCount(int $userId): int
|
||||
{
|
||||
$result = $this->db->fetch(
|
||||
"SELECT COUNT(*) as total FROM notifications
|
||||
WHERE (user_id IS NULL OR user_id = ?) AND is_read = 0",
|
||||
[$userId]
|
||||
);
|
||||
return (int) ($result['total'] ?? 0);
|
||||
}
|
||||
|
||||
public function markAsRead(int $id, int $userId): void
|
||||
{
|
||||
$this->db->update(
|
||||
'notifications',
|
||||
['is_read' => 1, 'read_at' => date('Y-m-d H:i:s')],
|
||||
'id = ? AND (user_id IS NULL OR user_id = ?)',
|
||||
[$id, $userId]
|
||||
);
|
||||
}
|
||||
|
||||
public function markAllAsRead(int $userId): void
|
||||
{
|
||||
$this->db->update(
|
||||
'notifications',
|
||||
['is_read' => 1, 'read_at' => date('Y-m-d H:i:s')],
|
||||
'(user_id IS NULL OR user_id = ?) AND is_read = 0',
|
||||
[$userId]
|
||||
);
|
||||
}
|
||||
|
||||
public function getAll(int $page = 1, int $perPage = 20): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$total = $this->db->fetch("SELECT COUNT(*) as total FROM notifications");
|
||||
$items = $this->db->fetchAll(
|
||||
"SELECT n.*, u.username as target_username
|
||||
FROM notifications n
|
||||
LEFT JOIN users u ON n.user_id = u.id
|
||||
ORDER BY n.created_at DESC LIMIT ? OFFSET ?",
|
||||
[$perPage, $offset]
|
||||
);
|
||||
return [
|
||||
'data' => $items,
|
||||
'total' => (int) ($total['total'] ?? 0),
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total_pages' => (int) ceil((int) ($total['total'] ?? 0) / $perPage),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user