forked from admin/services_core
Giant Update
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Notification;
|
||||
|
||||
use App\Http\Controllers\ApiController;
|
||||
use App\Http\Requests\Notification\IndexNotificationRequest;
|
||||
use App\Http\Requests\Notification\SaveNotificationPreferencesRequest;
|
||||
use App\Http\Requests\Notification\StoreCustomerContactRequest;
|
||||
use App\Http\Requests\Notification\StoreNotificationBroadcastRequest;
|
||||
use App\Http\Requests\Notification\StoreNotificationChannelRequest;
|
||||
use App\Http\Requests\Notification\StoreNotificationTemplateRequest;
|
||||
use App\Http\Resources\Notification\NotificationDeliveryResource;
|
||||
use App\Models\Customer;
|
||||
use App\Models\CustomerContact;
|
||||
use App\Models\NotificationBroadcast;
|
||||
use App\Models\NotificationChannel;
|
||||
use App\Models\NotificationEvent;
|
||||
use App\Models\NotificationTemplate;
|
||||
use App\Services\Notification\NotificationService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NotificationController extends ApiController
|
||||
{
|
||||
public function __construct(protected NotificationService $service) {}
|
||||
|
||||
public function events()
|
||||
{
|
||||
return $this->success(NotificationEvent::where('enabled', true)->orderBy('category')->orderBy('name')->get(), 'Daftar event notifikasi berhasil diambil');
|
||||
}
|
||||
|
||||
public function channels(Request $request)
|
||||
{
|
||||
return $this->success($this->service->channels($this->channel($request), $request->user(), $this->tenantId($request)), 'Konfigurasi channel berhasil diambil');
|
||||
}
|
||||
|
||||
public function storeChannel(StoreNotificationChannelRequest $request)
|
||||
{
|
||||
abort_unless($request->validated('channel') === $this->channel($request), 422);
|
||||
|
||||
return $this->created($this->service->saveChannel($request->validated(), $request->user(), $this->tenantId($request)), 'Konfigurasi channel berhasil dibuat');
|
||||
}
|
||||
|
||||
public function updateChannel(StoreNotificationChannelRequest $request, NotificationChannel $notificationChannel)
|
||||
{
|
||||
$this->authorizeTenant($request, $notificationChannel->tenant_id);
|
||||
abort_unless($notificationChannel->channel === $this->channel($request), 404);
|
||||
|
||||
return $this->updated($this->service->saveChannel($request->validated(), $request->user(), $this->tenantId($request), $notificationChannel), 'Konfigurasi channel berhasil diperbarui');
|
||||
}
|
||||
|
||||
public function testChannel(Request $request, NotificationChannel $notificationChannel)
|
||||
{
|
||||
$this->authorizeTenant($request, $notificationChannel->tenant_id);
|
||||
abort_unless($notificationChannel->channel === $this->channel($request), 404);
|
||||
|
||||
return $this->success($this->service->testChannel($notificationChannel), 'Pengujian channel selesai');
|
||||
}
|
||||
|
||||
public function templates(Request $request)
|
||||
{
|
||||
return $this->success($this->service->templates($this->channel($request), $request->user(), $this->tenantId($request)), 'Daftar template berhasil diambil');
|
||||
}
|
||||
|
||||
public function storeTemplate(StoreNotificationTemplateRequest $request)
|
||||
{
|
||||
abort_unless($request->validated('channel') === $this->channel($request), 422);
|
||||
|
||||
return $this->created($this->service->saveTemplate($request->validated(), $request->user(), $this->tenantId($request)), 'Template berhasil dibuat');
|
||||
}
|
||||
|
||||
public function updateTemplate(StoreNotificationTemplateRequest $request, NotificationTemplate $notificationTemplate)
|
||||
{
|
||||
if ($notificationTemplate->tenant_id) {
|
||||
$this->authorizeTenant($request, $notificationTemplate->tenant_id);
|
||||
}
|
||||
abort_unless($notificationTemplate->channel === $this->channel($request), 404);
|
||||
|
||||
return $this->updated($this->service->saveTemplate($request->validated(), $request->user(), $this->tenantId($request), $notificationTemplate), 'Template berhasil diperbarui');
|
||||
}
|
||||
|
||||
public function preferences(Request $request)
|
||||
{
|
||||
$tenantId = $request->user()->isMasterAdmin() && $request->integer('tenant_id')
|
||||
? $request->integer('tenant_id')
|
||||
: $this->tenantId($request);
|
||||
|
||||
return $this->success($this->service->preferences($request->user(), $tenantId), 'Preference notifikasi berhasil diambil');
|
||||
}
|
||||
|
||||
public function savePreferences(SaveNotificationPreferencesRequest $request)
|
||||
{
|
||||
$this->service->savePreferences($request->validated('preferences'), $request->user(), $this->tenantId($request));
|
||||
|
||||
return $this->updated(null, 'Preference notifikasi berhasil disimpan');
|
||||
}
|
||||
|
||||
public function deliveries(IndexNotificationRequest $request)
|
||||
{
|
||||
return $this->successPaginated(
|
||||
$this->service->deliveries($this->channel($request), $request->validated(), $request->user(), $this->tenantId($request)),
|
||||
NotificationDeliveryResource::class,
|
||||
'Log pesan berhasil diambil',
|
||||
);
|
||||
}
|
||||
|
||||
public function broadcasts(IndexNotificationRequest $request)
|
||||
{
|
||||
return $this->success($this->service->broadcasts($request->validated(), $request->user(), $this->tenantId($request)), 'Daftar pesan siaran berhasil diambil');
|
||||
}
|
||||
|
||||
public function previewBroadcast(StoreNotificationBroadcastRequest $request)
|
||||
{
|
||||
return $this->success($this->service->previewBroadcast($request->validated(), $request->user(), $this->tenantId($request)), 'Preview penerima berhasil dihitung');
|
||||
}
|
||||
|
||||
public function storeBroadcast(StoreNotificationBroadcastRequest $request)
|
||||
{
|
||||
return $this->created($this->service->createBroadcast($request->validated(), $request->user(), $this->tenantId($request)), 'Pesan siaran masuk antrean');
|
||||
}
|
||||
|
||||
public function showBroadcast(Request $request, NotificationBroadcast $notificationBroadcast)
|
||||
{
|
||||
$this->authorizeTenant($request, $notificationBroadcast->tenant_id);
|
||||
|
||||
return $this->success($notificationBroadcast->load(['template', 'channelConfig', 'recipients.customer:id,customer_code,name']), 'Detail siaran berhasil diambil');
|
||||
}
|
||||
|
||||
public function approveBroadcast(Request $request, NotificationBroadcast $notificationBroadcast)
|
||||
{
|
||||
$this->authorizeTenant($request, $notificationBroadcast->tenant_id);
|
||||
|
||||
return $this->updated($this->service->approveBroadcast($notificationBroadcast, $request->user()), 'Pesan siaran berhasil disetujui');
|
||||
}
|
||||
|
||||
public function myNotifications(IndexNotificationRequest $request)
|
||||
{
|
||||
return $this->success($this->service->userNotifications($request->user(), $request->validated()), 'Notifikasi aplikasi berhasil diambil');
|
||||
}
|
||||
|
||||
public function markRead(Request $request, string $uuid)
|
||||
{
|
||||
$notification = $request->user()->userNotifications()->where('uuid', $uuid)->firstOrFail();
|
||||
$notification->update(['read_at' => now()]);
|
||||
|
||||
return $this->updated($notification, 'Notifikasi ditandai sudah dibaca');
|
||||
}
|
||||
|
||||
public function contacts(Request $request, Customer $customer)
|
||||
{
|
||||
$this->authorizeTenant($request, $customer->tenant_id);
|
||||
|
||||
return $this->success($customer->contacts()->orderBy('type')->orderByDesc('is_primary')->get(), 'Kontak notifikasi berhasil diambil');
|
||||
}
|
||||
|
||||
public function storeContact(StoreCustomerContactRequest $request, Customer $customer)
|
||||
{
|
||||
$this->authorizeTenant($request, $customer->tenant_id);
|
||||
|
||||
return $this->created($this->service->saveContact($customer, $request->validated()), 'Kontak notifikasi berhasil dibuat');
|
||||
}
|
||||
|
||||
public function updateContact(StoreCustomerContactRequest $request, Customer $customer, CustomerContact $contact)
|
||||
{
|
||||
$this->authorizeTenant($request, $customer->tenant_id);
|
||||
|
||||
return $this->updated($this->service->saveContact($customer, $request->validated(), $contact), 'Kontak notifikasi berhasil diperbarui');
|
||||
}
|
||||
|
||||
public function destroyContact(Request $request, Customer $customer, CustomerContact $contact)
|
||||
{
|
||||
$this->authorizeTenant($request, $customer->tenant_id);
|
||||
abort_unless((int) $contact->customer_id === (int) $customer->id, 404);
|
||||
$contact->delete();
|
||||
|
||||
return $this->deleted('Kontak notifikasi berhasil dihapus');
|
||||
}
|
||||
|
||||
private function channel(Request $request): string
|
||||
{
|
||||
return $request->route()->defaults['notification_channel'];
|
||||
}
|
||||
|
||||
private function tenantId(Request $request): ?int
|
||||
{
|
||||
return $request->attributes->get('tenant_id');
|
||||
}
|
||||
|
||||
private function authorizeTenant(Request $request, int $tenantId): void
|
||||
{
|
||||
abort_unless($request->user()->isMasterAdmin() || $tenantId === $this->tenantId($request), 403);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Notification;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\NotificationChannel;
|
||||
use App\Models\NotificationDelivery;
|
||||
use App\Models\NotificationWebhookLog;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NotificationWebhookController extends Controller
|
||||
{
|
||||
public function verify(Request $request, NotificationChannel $notificationChannel)
|
||||
{
|
||||
abort_unless(
|
||||
$request->query('hub.mode') === 'subscribe'
|
||||
&& hash_equals((string) data_get($notificationChannel->credentials, 'webhook_verify_token'), (string) $request->query('hub.verify_token')),
|
||||
403,
|
||||
);
|
||||
|
||||
return response($request->query('hub.challenge'), 200);
|
||||
}
|
||||
|
||||
public function handle(Request $request, NotificationChannel $notificationChannel)
|
||||
{
|
||||
$valid = $this->validSignature($request, $notificationChannel);
|
||||
$payload = $request->all();
|
||||
$updates = $this->updates($payload);
|
||||
$log = NotificationWebhookLog::create([
|
||||
'notification_channel_id' => $notificationChannel->id,
|
||||
'provider' => $notificationChannel->provider,
|
||||
'event_type' => data_get($payload, 'entry.0.changes.0.field') ?? data_get($payload, 'event'),
|
||||
'provider_message_id' => data_get($updates, '0.id'),
|
||||
'signature_valid' => $valid,
|
||||
'payload' => $payload,
|
||||
]);
|
||||
abort_unless($valid, 401);
|
||||
|
||||
foreach ($updates as $update) {
|
||||
$delivery = NotificationDelivery::where('provider_message_id', $update['id'] ?? null)->first();
|
||||
if (! $delivery) {
|
||||
continue;
|
||||
}
|
||||
$status = match ($update['status'] ?? null) {
|
||||
'delivered' => 'delivered',
|
||||
'read' => 'read',
|
||||
'failed' => 'failed',
|
||||
default => 'sent',
|
||||
};
|
||||
$delivery->update([
|
||||
'status' => $status,
|
||||
'delivered_at' => $status === 'delivered' ? now() : $delivery->delivered_at,
|
||||
'read_at' => $status === 'read' ? now() : $delivery->read_at,
|
||||
'failed_at' => $status === 'failed' ? now() : $delivery->failed_at,
|
||||
'error_code' => data_get($update, 'errors.0.code'),
|
||||
'error_message' => data_get($update, 'errors.0.title'),
|
||||
]);
|
||||
}
|
||||
$log->update(['processed_at' => now()]);
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
private function validSignature(Request $request, NotificationChannel $channel): bool
|
||||
{
|
||||
if ($channel->channel === 'whatsapp_official') {
|
||||
$secret = data_get($channel->credentials, 'app_secret');
|
||||
$signature = $request->header('X-Hub-Signature-256');
|
||||
|
||||
return $secret && $signature && hash_equals('sha256='.hash_hmac('sha256', $request->getContent(), $secret), $signature);
|
||||
}
|
||||
$secret = data_get($channel->credentials, 'webhook_secret');
|
||||
|
||||
return $secret && hash_equals((string) $secret, (string) $request->header('X-Webhook-Secret'));
|
||||
}
|
||||
|
||||
private function updates(array $payload): array
|
||||
{
|
||||
$statuses = data_get($payload, 'entry.0.changes.0.value.statuses');
|
||||
if (is_array($statuses)) {
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
return [[
|
||||
'id' => data_get($payload, 'message_id') ?? data_get($payload, 'id'),
|
||||
'status' => data_get($payload, 'status') ?? data_get($payload, 'event'),
|
||||
'errors' => data_get($payload, 'errors', []),
|
||||
]];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user