chore: snapshot notifications hardening and css/docs alignment
This commit is contained in:
@@ -3,58 +3,69 @@
|
||||
namespace MintyPHP\Module\Notifications\Service;
|
||||
|
||||
use MintyPHP\Module\Notifications\Repository\NotificationRepositoryInterface;
|
||||
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
private const DEDUPE_WINDOW_MINUTES = 30;
|
||||
private const DEDUPE_TYPES = [
|
||||
'user.created' => true,
|
||||
'user.deleted' => true,
|
||||
'user.activated' => true,
|
||||
'user.deactivated' => true,
|
||||
'user.assignment_changed' => true,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly NotificationRepositoryInterface $notificationRepository
|
||||
private readonly NotificationRepositoryInterface $notificationRepository,
|
||||
private readonly UserTenantRepositoryInterface $userTenantRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/** @return list<array<string, mixed>> */
|
||||
public function listForUser(int $userId, int $limit = 50): array
|
||||
public function listForUser(int $userId, ?int $tenantId = null, int $limit = 50): array
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->notificationRepository->listByUser($userId, $limit, 0);
|
||||
return $this->notificationRepository->listByUser($userId, $tenantId, $limit, 0);
|
||||
}
|
||||
|
||||
public function unreadCount(int $userId): int
|
||||
public function unreadCount(int $userId, ?int $tenantId = null): int
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $this->notificationRepository->countUnreadByUser($userId);
|
||||
return $this->notificationRepository->countUnreadByUser($userId, $tenantId);
|
||||
}
|
||||
|
||||
public function markAsRead(int $userId, int $notificationId): bool
|
||||
public function markAsRead(int $userId, int $notificationId, ?int $tenantId = null): bool
|
||||
{
|
||||
if ($userId <= 0 || $notificationId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->notificationRepository->markRead($notificationId, $userId);
|
||||
return $this->notificationRepository->markRead($notificationId, $userId, $tenantId);
|
||||
}
|
||||
|
||||
public function markAllAsRead(int $userId): int
|
||||
public function markAllAsRead(int $userId, ?int $tenantId = null): int
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $this->notificationRepository->markAllReadByUser($userId);
|
||||
return $this->notificationRepository->markAllReadByUser($userId, $tenantId);
|
||||
}
|
||||
|
||||
public function dismiss(int $userId, int $notificationId): bool
|
||||
public function dismiss(int $userId, int $notificationId, ?int $tenantId = null): bool
|
||||
{
|
||||
if ($userId <= 0 || $notificationId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->notificationRepository->delete($notificationId, $userId);
|
||||
return $this->notificationRepository->delete($notificationId, $userId, $tenantId);
|
||||
}
|
||||
|
||||
public function createForUser(
|
||||
@@ -70,6 +81,8 @@ class NotificationService
|
||||
return false;
|
||||
}
|
||||
|
||||
$dedupeMeta = $this->buildDedupeMeta($recipientUserId, $tenantId, $type, $data);
|
||||
|
||||
return $this->notificationRepository->create([
|
||||
'recipient_user_id' => $recipientUserId,
|
||||
'tenant_id' => $tenantId,
|
||||
@@ -78,6 +91,8 @@ class NotificationService
|
||||
'body' => $body,
|
||||
'link' => $link,
|
||||
'data' => $data ?: null,
|
||||
'dedupe_fingerprint' => $dedupeMeta['fingerprint'] ?? null,
|
||||
'dedupe_bucket' => $dedupeMeta['bucket'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -99,7 +114,7 @@ class NotificationService
|
||||
return 0;
|
||||
}
|
||||
|
||||
$userIds = $this->listUserIdsForTenant($tenantId);
|
||||
$userIds = $this->userTenantRepository->listActiveUserIdsByTenantId($tenantId);
|
||||
$created = 0;
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
@@ -136,7 +151,7 @@ class NotificationService
|
||||
return 0;
|
||||
}
|
||||
|
||||
$userIds = $this->listUserIdsForTenant($tenantId);
|
||||
$userIds = $this->userTenantRepository->listActiveUserIdsByTenantId($tenantId);
|
||||
$created = 0;
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
@@ -171,33 +186,55 @@ class NotificationService
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up active user IDs belonging to a tenant.
|
||||
*
|
||||
* @return list<int>
|
||||
* @param array<string, mixed> $data
|
||||
* @return array{fingerprint: string, bucket: int}|array{}
|
||||
*/
|
||||
private function listUserIdsForTenant(int $tenantId): array
|
||||
private function buildDedupeMeta(int $recipientUserId, ?int $tenantId, string $type, array $data): array
|
||||
{
|
||||
if ($tenantId <= 0) {
|
||||
if (!isset(self::DEDUPE_TYPES[$type])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = \MintyPHP\DB::select(
|
||||
'select ut.user_id from user_tenants ut join users u on u.id = ut.user_id and u.active = 1 where ut.tenant_id = ?',
|
||||
(string) $tenantId
|
||||
);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
$target = $this->dedupeTargetFromData($data);
|
||||
if ($target === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
foreach ($rows as $row) {
|
||||
$data = $row['user_tenants'] ?? $row;
|
||||
if (is_array($data) && isset($data['user_id'])) {
|
||||
$ids[] = (int) $data['user_id'];
|
||||
}
|
||||
}
|
||||
$tenantScope = $tenantId !== null && $tenantId > 0 ? $tenantId : 0;
|
||||
$raw = implode('|', [
|
||||
'recipient:' . $recipientUserId,
|
||||
'tenant:' . $tenantScope,
|
||||
'type:' . $type,
|
||||
'target:' . $target,
|
||||
]);
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
return [
|
||||
'fingerprint' => hash('sha256', $raw),
|
||||
'bucket' => intdiv(time(), self::DEDUPE_WINDOW_MINUTES * 60),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private function dedupeTargetFromData(array $data): string
|
||||
{
|
||||
$explicit = trim((string) ($data['dedupe_target'] ?? ''));
|
||||
if ($explicit !== '') {
|
||||
return $explicit;
|
||||
}
|
||||
|
||||
$targetUserId = (int) ($data['user_id'] ?? 0);
|
||||
if ($targetUserId > 0) {
|
||||
return 'user:' . $targetUserId;
|
||||
}
|
||||
|
||||
$targetUuid = trim((string) ($data['uuid'] ?? ''));
|
||||
if ($targetUuid !== '') {
|
||||
return 'uuid:' . $targetUuid;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace MintyPHP\Module\Notifications\Service;
|
||||
|
||||
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
|
||||
|
||||
class NotificationServicesFactory
|
||||
{
|
||||
private ?NotificationService $notificationService = null;
|
||||
@@ -11,10 +13,11 @@ class NotificationServicesFactory
|
||||
) {
|
||||
}
|
||||
|
||||
public function createNotificationService(): NotificationService
|
||||
public function createNotificationService(UserTenantRepositoryInterface $userTenantRepository): NotificationService
|
||||
{
|
||||
return $this->notificationService ??= new NotificationService(
|
||||
$this->notificationRepositoryFactory->createNotificationRepository()
|
||||
$this->notificationRepositoryFactory->createNotificationRepository(),
|
||||
$userTenantRepository
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user