add notifications logic and add support of both JWT and Sanctum
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\Notification;
|
||||
|
||||
class NotificationActiveService
|
||||
{
|
||||
public function list(?string $targetGroup): array
|
||||
{
|
||||
$rows = Notification::getActiveNotifications($targetGroup);
|
||||
$now = time();
|
||||
|
||||
$notifications = array_map(static function ($row) use ($now) {
|
||||
$expiresAt = $row['expires_at'] ?? null;
|
||||
$expiryTs = $expiresAt ? strtotime((string) $expiresAt) : false;
|
||||
$isExpired = $expiryTs !== false && $expiryTs <= $now;
|
||||
|
||||
return [
|
||||
'id' => $row['id'] ?? null,
|
||||
'title' => $row['title'] ?? null,
|
||||
'message' => $row['message'] ?? null,
|
||||
'priority' => $row['priority'] ?? null,
|
||||
'scheduled_at' => $row['scheduled_at'] ?? null,
|
||||
'expires_at' => $expiresAt,
|
||||
'created_at' => $row['created_at'] ?? null,
|
||||
'target_group' => $row['target_group'] ?? null,
|
||||
'isExpired' => $isExpired,
|
||||
];
|
||||
}, $rows ?? []);
|
||||
|
||||
return ['notifications' => $notifications];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\Notification;
|
||||
|
||||
class NotificationCleanupService
|
||||
{
|
||||
public function cleanupExpired(): int
|
||||
{
|
||||
return Notification::deleteExpiredNotifications();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\Notification;
|
||||
|
||||
class NotificationDeletedService
|
||||
{
|
||||
public function list(): array
|
||||
{
|
||||
$rows = Notification::getDeletedNotifications();
|
||||
|
||||
$notifications = array_map(static function ($row) {
|
||||
return [
|
||||
'id' => $row['id'] ?? null,
|
||||
'title' => $row['title'] ?? null,
|
||||
'message' => $row['message'] ?? null,
|
||||
'priority' => $row['priority'] ?? null,
|
||||
'scheduled_at' => $row['scheduled_at'] ?? null,
|
||||
'expires_at' => $row['expires_at'] ?? null,
|
||||
'deleted_at' => $row['deleted_at'] ?? null,
|
||||
];
|
||||
}, $rows ?? []);
|
||||
|
||||
return ['notifications' => $notifications];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class NotificationDispatchService
|
||||
{
|
||||
public function sendEmail(string $to, string $subject, string $message): void
|
||||
{
|
||||
Log::info('Notification email queued.', [
|
||||
'to' => $to,
|
||||
'subject' => $subject,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendSms(string $phone, string $message): void
|
||||
{
|
||||
Log::info('Notification SMS queued.', [
|
||||
'phone' => $phone,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\Notification;
|
||||
|
||||
class NotificationManagementService
|
||||
{
|
||||
public function update(int $notificationId, array $payload): array
|
||||
{
|
||||
$notification = Notification::query()->find($notificationId);
|
||||
if (!$notification) {
|
||||
return ['ok' => false, 'message' => 'Notification not found.'];
|
||||
}
|
||||
|
||||
$notification->update([
|
||||
'title' => $payload['title'] ?? $notification->title,
|
||||
'message' => $payload['message'] ?? $notification->message,
|
||||
'target_group' => $payload['target_group'] ?? $notification->target_group,
|
||||
'delivery_channels' => $payload['channels'] ?? $notification->delivery_channels,
|
||||
'priority' => $payload['priority'] ?? $notification->priority,
|
||||
'status' => $payload['status'] ?? $notification->status,
|
||||
'action_url' => $payload['action_url'] ?? $notification->action_url,
|
||||
'attachment_path' => $payload['attachment_path'] ?? $notification->attachment_path,
|
||||
'scheduled_at' => $payload['scheduled_at'] ?? $notification->scheduled_at,
|
||||
'expires_at' => $payload['expires_at'] ?? $notification->expires_at,
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
|
||||
return ['ok' => true, 'notification' => $notification];
|
||||
}
|
||||
|
||||
public function delete(int $notificationId): bool
|
||||
{
|
||||
$notification = Notification::query()->find($notificationId);
|
||||
if (!$notification) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $notification->delete();
|
||||
}
|
||||
|
||||
public function restore(int $notificationId): bool
|
||||
{
|
||||
return Notification::restoreNotification($notificationId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\UserNotification;
|
||||
|
||||
class NotificationReadService
|
||||
{
|
||||
public function markRead(int $userId, int $notificationId): bool
|
||||
{
|
||||
$row = UserNotification::query()
|
||||
->where('user_id', $userId)
|
||||
->where('notification_id', $notificationId)
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$row->is_read = true;
|
||||
$row->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class NotificationRecipientService
|
||||
{
|
||||
public function getRecipients(string $targetGroup): array
|
||||
{
|
||||
$rows = User::getUsersByRole($targetGroup);
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\Notification;
|
||||
use App\Models\UserNotification;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class NotificationSendService
|
||||
{
|
||||
public function __construct(
|
||||
private NotificationRecipientService $recipients,
|
||||
private NotificationDispatchService $dispatcher
|
||||
) {
|
||||
}
|
||||
|
||||
public function send(array $payload, int $actorId): array
|
||||
{
|
||||
$channels = $payload['channels'] ?? ['in_app'];
|
||||
$channels = array_values(array_unique(array_map('strtolower', (array) $channels)));
|
||||
if (empty($channels)) {
|
||||
$channels = ['in_app'];
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($payload, $channels, $actorId) {
|
||||
$notification = Notification::query()->create([
|
||||
'title' => $payload['title'],
|
||||
'message' => $payload['message'],
|
||||
'target_group' => $payload['target_group'],
|
||||
'delivery_channels' => $channels,
|
||||
'priority' => $payload['priority'] ?? null,
|
||||
'status' => $payload['status'] ?? null,
|
||||
'action_url' => $payload['action_url'] ?? null,
|
||||
'attachment_path' => $payload['attachment_path'] ?? null,
|
||||
'scheduled_at' => $payload['scheduled_at'] ?? now(),
|
||||
'sent_at' => in_array('in_app', $channels, true) ? now() : null,
|
||||
'expires_at' => $payload['expires_at'] ?? null,
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
'created_at' => utc_now(),
|
||||
'updated_at' => utc_now(),
|
||||
]);
|
||||
|
||||
$users = $this->recipients->getRecipients((string) $payload['target_group']);
|
||||
$created = 0;
|
||||
|
||||
foreach ($users as $user) {
|
||||
UserNotification::query()->create([
|
||||
'notification_id' => (int) $notification->id,
|
||||
'user_id' => (int) ($user['id'] ?? 0),
|
||||
'is_read' => false,
|
||||
'delivered' => in_array('in_app', $channels, true),
|
||||
'delivered_at' => in_array('in_app', $channels, true) ? now() : null,
|
||||
'school_year' => $payload['school_year'] ?? null,
|
||||
'semester' => $payload['semester'] ?? null,
|
||||
]);
|
||||
$created++;
|
||||
|
||||
if (in_array('email', $channels, true) && !empty($user['email'])) {
|
||||
$this->dispatcher->sendEmail((string) $user['email'], (string) $payload['title'], (string) $payload['message']);
|
||||
}
|
||||
|
||||
if (in_array('sms', $channels, true) && !empty($user['phone'])) {
|
||||
$this->dispatcher->sendSms((string) $user['phone'], (string) $payload['message']);
|
||||
}
|
||||
}
|
||||
|
||||
Log::info('Notification dispatched.', [
|
||||
'notification_id' => (int) $notification->id,
|
||||
'target_group' => $payload['target_group'],
|
||||
'channels' => $channels,
|
||||
'recipients' => $created,
|
||||
'actor_id' => $actorId,
|
||||
]);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'notification' => $notification,
|
||||
'recipient_count' => $created,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\UserNotification;
|
||||
|
||||
class NotificationShowService
|
||||
{
|
||||
public function getForUser(int $userId, int $notificationId): ?array
|
||||
{
|
||||
$row = UserNotification::query()
|
||||
->select([
|
||||
'user_notifications.*',
|
||||
'notifications.title',
|
||||
'notifications.message',
|
||||
'notifications.priority',
|
||||
'notifications.scheduled_at',
|
||||
'notifications.expires_at',
|
||||
'notifications.target_group',
|
||||
])
|
||||
->join('notifications', 'notifications.id', '=', 'user_notifications.notification_id')
|
||||
->where('user_notifications.user_id', $userId)
|
||||
->where('user_notifications.notification_id', $notificationId)
|
||||
->first();
|
||||
|
||||
return $row ? $row->toArray() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class NotificationTriggerService
|
||||
{
|
||||
public static function trigger(string $eventType, array $payload): void
|
||||
{
|
||||
Event::dispatch($eventType, $payload);
|
||||
}
|
||||
|
||||
public static function toUser(int $userId, string $title, string $message, array $channels = ['in_app']): void
|
||||
{
|
||||
self::trigger('customNotification', [
|
||||
'user_id' => $userId,
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'channels' => $channels,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\UserNotification;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
class NotificationUserListService
|
||||
{
|
||||
public function listForUser(int $userId, array $filters): array
|
||||
{
|
||||
$perPage = (int) ($filters['per_page'] ?? 25);
|
||||
$page = (int) ($filters['page'] ?? 1);
|
||||
$sortBy = (string) ($filters['sort_by'] ?? 'created_at');
|
||||
$sortDir = strtolower((string) ($filters['sort_dir'] ?? 'desc')) === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
$allowedSorts = ['created_at', 'scheduled_at', 'priority'];
|
||||
if (!in_array($sortBy, $allowedSorts, true)) {
|
||||
$sortBy = 'created_at';
|
||||
}
|
||||
|
||||
$query = UserNotification::query()
|
||||
->select([
|
||||
'user_notifications.*',
|
||||
'notifications.title',
|
||||
'notifications.message',
|
||||
'notifications.priority',
|
||||
'notifications.scheduled_at',
|
||||
'notifications.expires_at',
|
||||
'notifications.target_group',
|
||||
])
|
||||
->join('notifications', 'notifications.id', '=', 'user_notifications.notification_id')
|
||||
->where('user_notifications.user_id', $userId);
|
||||
|
||||
if (isset($filters['read'])) {
|
||||
$read = filter_var($filters['read'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
if ($read !== null) {
|
||||
$query->where('user_notifications.is_read', $read ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
$paginator = $query
|
||||
->orderBy('notifications.' . $sortBy, $sortDir)
|
||||
->paginate($perPage, ['*'], 'page', $page);
|
||||
|
||||
return [
|
||||
'notifications' => $paginator,
|
||||
'pagination' => $this->paginationPayload($paginator),
|
||||
];
|
||||
}
|
||||
|
||||
private function paginationPayload(LengthAwarePaginator $paginator): array
|
||||
{
|
||||
return [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
'total_pages' => $paginator->lastPage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Notifications;
|
||||
|
||||
use App\Models\Configuration;
|
||||
use App\Models\Notification;
|
||||
use App\Models\UserNotification;
|
||||
|
||||
class UserNotificationDispatchService
|
||||
{
|
||||
public function notifyUser(
|
||||
int $userId,
|
||||
string $title,
|
||||
string $message,
|
||||
array $channels = ['in_app'],
|
||||
string $topic = 'general',
|
||||
?string $targetGroup = null
|
||||
): array {
|
||||
if ($userId <= 0) {
|
||||
return ['ok' => false, 'message' => 'Invalid user id.'];
|
||||
}
|
||||
|
||||
$channels = array_values(array_unique(array_map('strtolower', (array) $channels)));
|
||||
if (empty($channels)) {
|
||||
$channels = ['in_app'];
|
||||
}
|
||||
|
||||
$schoolYear = Configuration::getConfigValueByKey('school_year');
|
||||
$semester = Configuration::getConfigValueByKey('semester');
|
||||
|
||||
$notification = Notification::query()->create([
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'target_group' => $targetGroup ?: 'everyone',
|
||||
'delivery_channels' => $channels,
|
||||
'priority' => 'normal',
|
||||
'status' => 'sent',
|
||||
'scheduled_at' => now(),
|
||||
'sent_at' => now(),
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
]);
|
||||
|
||||
UserNotification::query()->create([
|
||||
'notification_id' => (int) $notification->id,
|
||||
'user_id' => $userId,
|
||||
'is_read' => 0,
|
||||
'delivered' => in_array('in_app', $channels, true) ? 1 : 0,
|
||||
'delivered_at' => in_array('in_app', $channels, true) ? now() : null,
|
||||
'school_year' => $schoolYear,
|
||||
'semester' => $semester,
|
||||
]);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'notification_id' => (int) $notification->id,
|
||||
'topic' => $topic,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user