Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions api/app/Console/Commands/SendSystemNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Notifications\SystemNotification;
use App\Services\Notification\NotificationService;
use Illuminate\Console\Command;

final class SendSystemNotification extends Command
{
protected $signature = 'notification:system
{--title-key= : Translation key for the notification title}
{--message-key= : Translation key for the notification message}';

protected $description = 'Send a system notification to all users';

public function handle(NotificationService $notificationService): int
{
$titleKey = $this->option('title-key');
$messageKey = $this->option('message-key');

if (empty($titleKey) || empty($messageKey)) {
$this->error('Both --title-key and --message-key are required.');

return self::FAILURE;
}

$notification = new SystemNotification($titleKey, $messageKey);

$this->info('Sending system notification to all users...');

$notificationService->notifyAllUsers($notification);

$this->info('System notification dispatched successfully.');

return self::SUCCESS;
}
}
145 changes: 145 additions & 0 deletions api/app/Http/Controllers/API/Notification/NotificationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\API\Notification;

use App\Http\Controllers\Controller;
use App\Http\Resources\Notification\NotificationResource;
use App\Services\Notification\NotificationService;
use Illuminate\Http\Request;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use OpenApi\Attributes as OA;

final class NotificationController extends Controller implements HasMiddleware
{
public static function middleware(): array
{
return [
new Middleware('auth:sanctum'),
];
}

public function __construct(
private NotificationService $notificationService
) {}

#[OA\Get(
operationId: 'listNotifications',
path: '/api/notifications',
summary: 'List authenticated user notifications',
security: [['bearerAuth' => []]],
tags: ['Notifications'],
parameters: [
new OA\Parameter(
name: 'page',
in: 'query',
required: false,
schema: new OA\Schema(type: 'integer'),
description: 'Page number'
),
],
responses: [
new OA\Response(
response: 200,
description: 'Notifications retrieved successfully',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(
property: 'data',
type: 'array',
items: new OA\Items(ref: '#/components/schemas/NotificationResource')
),
new OA\Property(
property: 'meta',
type: 'object',
properties: [
new OA\Property(property: 'unreadCount', type: 'integer'),
]
),
]
)
),
]
)]
public function index(Request $request)
{
$user = $request->user();
$notifications = $this->notificationService->listForUser($user);
$unreadCount = $this->notificationService->unreadCount($user);

return NotificationResource::collection($notifications)
->additional(['meta' => ['unreadCount' => $unreadCount]]);
}

#[OA\Post(
operationId: 'markNotificationAsRead',
path: '/api/notifications/{id}/read',
summary: 'Mark a notification as read',
security: [['bearerAuth' => []]],
tags: ['Notifications'],
parameters: [
new OA\Parameter(
name: 'id',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', format: 'uuid'),
description: 'Notification ID'
),
],
responses: [
new OA\Response(response: 204, description: 'Notification marked as read'),
]
)]
public function markAsRead(Request $request, string $id)
{
$this->notificationService->markAsRead($request->user(), $id);

return response()->noContent();
}

#[OA\Post(
operationId: 'markAllNotificationsAsRead',
path: '/api/notifications/read-all',
summary: 'Mark all notifications as read',
security: [['bearerAuth' => []]],
tags: ['Notifications'],
responses: [
new OA\Response(response: 204, description: 'All notifications marked as read'),
]
)]
public function markAllAsRead(Request $request)
{
$this->notificationService->markAllAsRead($request->user());

return response()->noContent();
}

#[OA\Delete(
operationId: 'deleteNotification',
path: '/api/notifications/{id}',
summary: 'Delete a notification',
security: [['bearerAuth' => []]],
tags: ['Notifications'],
parameters: [
new OA\Parameter(
name: 'id',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string', format: 'uuid'),
description: 'Notification ID'
),
],
responses: [
new OA\Response(response: 204, description: 'Notification deleted'),
]
)]
public function destroy(Request $request, string $id)
{
$this->notificationService->delete($request->user(), $id);

return response()->noContent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Events\DonationPaid;
use App\Http\Controllers\Controller;
use App\Models\Donation;
use App\Notifications\DonationReceived;
use Illuminate\Http\Request;
use OpenPix\PhpSdk\Client;

Expand Down Expand Up @@ -80,6 +81,8 @@ public function handleChargePaidWebhook(Request $request)

event(new DonationPaid($donation->external_reference));

$donation->campaign->user->notify(new DonationReceived($donation->load('campaign')));

return response()->json(['message' => 'Success.']);
}

Expand Down
1 change: 1 addition & 0 deletions api/app/Http/Controllers/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#[OA\Tag(name: 'Withdrawals', description: 'Withdrawal management endpoints')]
#[OA\Tag(name: 'Reports', description: 'Report management endpoints')]
#[OA\Tag(name: 'Leaderboard', description: 'Leaderboard endpoints')]
#[OA\Tag(name: 'Notifications', description: 'Notification management endpoints')]
abstract class Controller
{
//
Expand Down
22 changes: 22 additions & 0 deletions api/app/Http/Middleware/SetLocale.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Symfony\Component\HttpFoundation\Response;

final class SetLocale
{
public function handle(Request $request, Closure $next): Response
{
$locale = $request->getPreferredLanguage(['en', 'es', 'pt']);

App::setLocale($locale ?? 'pt');

return $next($request);
}
}
36 changes: 36 additions & 0 deletions api/app/Http/Resources/Notification/NotificationResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace App\Http\Resources\Notification;

use Illuminate\Http\Resources\Json\JsonResource;
use OpenApi\Attributes as OA;

#[OA\Schema(
schema: 'NotificationResource',
properties: [
new OA\Property(property: 'id', type: 'string', format: 'uuid'),
new OA\Property(property: 'type', type: 'string', enum: ['donation', 'campaign', 'withdrawal', 'system']),
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'message', type: 'string'),
new OA\Property(property: 'timestamp', type: 'string', format: 'date-time'),
new OA\Property(property: 'isRead', type: 'boolean'),
]
)]
class NotificationResource extends JsonResource
{
public function toArray($request): array
{
$data = $this->data;

return [
'id' => $this->id,
'type' => $data['type'],
'title' => __($data['titleKey'], $data['params'] ?? []),
'message' => __($data['messageKey'], $data['params'] ?? []),
'timestamp' => $this->created_at->toIso8601String(),
'isRead' => $this->read_at !== null,
];
}
}
37 changes: 37 additions & 0 deletions api/app/Notifications/CampaignApproved.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace App\Notifications;

use App\Models\Campaign;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

final class CampaignApproved extends Notification implements ShouldQueue
{
use Queueable;

public function __construct(
private Campaign $campaign
) {}

public function via(object $notifiable): array
{
return ['database'];
}

public function toArray(object $notifiable): array
{
return [
'type' => 'campaign',
'titleKey' => 'notifications.campaign.title',
'messageKey' => 'notifications.campaign.message',
'params' => [
'campaign' => $this->campaign->title,
'campaignSlug' => $this->campaign->slug,
],
];
}
}
38 changes: 38 additions & 0 deletions api/app/Notifications/DonationReceived.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace App\Notifications;

use App\Models\Donation;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

final class DonationReceived extends Notification implements ShouldQueue
{
use Queueable;

public function __construct(
private Donation $donation
) {}

public function via(object $notifiable): array
{
return ['database'];
}

public function toArray(object $notifiable): array
{
return [
'type' => 'donation',
'titleKey' => 'notifications.donation.title',
'messageKey' => 'notifications.donation.message',
'params' => [
'amount' => 'R$ '.number_format($this->donation->amount_cents / 100, 2, ',', '.'),
'campaign' => $this->donation->campaign->title,
'campaignSlug' => $this->donation->campaign->slug,
],
];
}
}
35 changes: 35 additions & 0 deletions api/app/Notifications/SystemNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

final class SystemNotification extends Notification implements ShouldQueue
{
use Queueable;

public function __construct(
private string $titleKey,
private string $messageKey,
private array $params = []
) {}

public function via(object $notifiable): array
{
return ['database'];
}

public function toArray(object $notifiable): array
{
return [
'type' => 'system',
'titleKey' => $this->titleKey,
'messageKey' => $this->messageKey,
'params' => $this->params,
];
}
}
Loading
Loading