- is_read and read_at columns are now obsolete since notification_reads tracks per-user read receipts - Removed UPDATE statements in markAsRead/markAllAsRead that touched the old columns - API response still includes computed is_read field via SQL CASE in getForUser() JOIN with notification_reads, maintaining Android compatibility - Migration 009 drops both columns
147 lines
4.5 KiB
PHP
147 lines
4.5 KiB
PHP
<?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 n
|
|
WHERE (n.user_id IS NULL OR n.user_id = ?)",
|
|
[$userId]
|
|
);
|
|
|
|
$items = $this->db->fetchAll(
|
|
"SELECT n.*,
|
|
CASE
|
|
WHEN nr.id IS NOT NULL THEN 1
|
|
ELSE 0
|
|
END as is_read,
|
|
nr.read_at as read_at,
|
|
(SELECT COUNT(*) FROM notification_reads WHERE notification_id = n.id) as total_reads,
|
|
(SELECT COUNT(*) FROM users WHERE status = 'active') as total_users
|
|
FROM notifications n
|
|
LEFT JOIN notification_reads nr ON nr.notification_id = n.id AND nr.user_id = ?
|
|
WHERE n.user_id IS NULL OR n.user_id = ?
|
|
ORDER BY n.created_at DESC LIMIT ? OFFSET ?",
|
|
[$userId, $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 n
|
|
WHERE (n.user_id IS NULL OR n.user_id = ?)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM notification_reads nr
|
|
WHERE nr.notification_id = n.id AND nr.user_id = ?
|
|
)",
|
|
[$userId, $userId]
|
|
);
|
|
return (int) ($result['total'] ?? 0);
|
|
}
|
|
|
|
public function markAsRead(int $id, int $userId): void
|
|
{
|
|
try {
|
|
$this->db->insert('notification_reads', [
|
|
'notification_id' => $id,
|
|
'user_id' => $userId,
|
|
'read_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
// Already read, ignore
|
|
}
|
|
}
|
|
|
|
public function markAllAsRead(int $userId): void
|
|
{
|
|
$unread = $this->db->fetchAll(
|
|
"SELECT n.id FROM notifications n
|
|
WHERE (n.user_id IS NULL OR n.user_id = ?)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM notification_reads nr
|
|
WHERE nr.notification_id = n.id AND nr.user_id = ?
|
|
)",
|
|
[$userId, $userId]
|
|
);
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
foreach ($unread as $note) {
|
|
try {
|
|
$this->db->insert('notification_reads', [
|
|
'notification_id' => (int) $note['id'],
|
|
'user_id' => $userId,
|
|
'read_at' => $now,
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
// Duplicate entry — already read, skip
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
(SELECT COUNT(*) FROM notification_reads nr WHERE nr.notification_id = n.id) as read_count
|
|
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),
|
|
];
|
|
}
|
|
|
|
public function getTotalActiveUsers(): int
|
|
{
|
|
$result = $this->db->fetch("SELECT COUNT(*) as total FROM users WHERE status = 'active'");
|
|
return (int) ($result['total'] ?? 0);
|
|
}
|
|
}
|