Skip to content

Commit bb8e3ff

Browse files
authored
feat: implement tenant scoped database notifications (EC-200)
* feat(notifications): implement tenant-scoped broadcast notifications * chore(notifications): add import * chore(notifications): add comment * fix: add uncommited file * refactor: move tenant related code into core package * refactor: refactor User model * feat: implement tenant scoped database notifications * fix: remove duplicate import * chore: use native Context
1 parent a9aa716 commit bb8e3ff

File tree

5 files changed

+90
-0
lines changed

5 files changed

+90
-0
lines changed

database/migrations/2025_04_28_090019_create_notifications_table.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ public function up(): void
1515
$table->uuid('id')->primary();
1616
$table->string('type');
1717
$table->morphs('notifiable');
18+
$table->foreignId('site_id')
19+
->nullable()
20+
->constrained('sites')
21+
->cascadeOnUpdate()
22+
->cascadeOnDelete();
1823
$table->text('data');
1924
$table->timestamp('read_at')->nullable();
2025
$table->timestamps();

src/EclipseServiceProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Eclipse\Core\Models\User;
1717
use Eclipse\Core\Models\User\Permission;
1818
use Eclipse\Core\Models\User\Role;
19+
use Eclipse\Core\Notifications\Channels\SiteDatabaseChannel;
1920
use Eclipse\Core\Policies\User\RolePolicy;
2021
use Eclipse\Core\Providers\AdminPanelProvider;
2122
use Eclipse\Core\Providers\HorizonServiceProvider;
@@ -28,6 +29,7 @@
2829
use Illuminate\Auth\Events\Login;
2930
use Illuminate\Database\Eloquent\Model;
3031
use Illuminate\Mail\Events\MessageSent;
32+
use Illuminate\Notifications\Channels\DatabaseChannel;
3133
use Illuminate\Support\Facades\Config;
3234
use Illuminate\Support\Facades\DB;
3335
use Illuminate\Support\Facades\Event;
@@ -108,6 +110,8 @@ public function register(): self
108110
return new Registry;
109111
});
110112

113+
$this->app->bind(DatabaseChannel::class, SiteDatabaseChannel::class);
114+
111115
return $this;
112116
}
113117

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Eclipse\Core\Models;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Illuminate\Notifications\DatabaseNotification as BaseDatabaseNotification;
7+
use Illuminate\Support\Facades\Context;
8+
9+
class DatabaseNotification extends BaseDatabaseNotification
10+
{
11+
/**
12+
* Allow storing tenant/site identifier alongside the notification payload.
13+
*/
14+
protected $fillable = [
15+
'id',
16+
'type',
17+
'notifiable_type',
18+
'notifiable_id',
19+
'data',
20+
'read_at',
21+
'site_id',
22+
];
23+
24+
protected static function booted(): void
25+
{
26+
static::addGlobalScope('site', function (Builder $builder): void {
27+
$siteId = Context::get('site');
28+
29+
if ($siteId !== null) {
30+
$builder->where($builder->getModel()->getTable().'.site_id', $siteId);
31+
}
32+
});
33+
}
34+
}

src/Models/User.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Illuminate\Database\Eloquent\Model;
1616
use Illuminate\Database\Eloquent\Relations\BelongsTo;
1717
use Illuminate\Database\Eloquent\Relations\HasMany;
18+
use Illuminate\Database\Eloquent\Relations\MorphMany;
1819
use Illuminate\Database\Eloquent\SoftDeletes;
1920
use Illuminate\Foundation\Auth\User as Authenticatable;
2021
use Illuminate\Notifications\Notifiable;
@@ -184,6 +185,15 @@ public function getSettings(string $settingsClass = UserSettings::class): Settin
184185
return $settingsClass::forUser($this->id);
185186
}
186187

188+
/**
189+
* Override notifications relation to use site-aware notification model.
190+
*/
191+
public function notifications(): MorphMany
192+
{
193+
return $this->morphMany(DatabaseNotification::class, 'notifiable')
194+
->orderBy('created_at', 'desc');
195+
}
196+
187197
/**
188198
* The channels the user receives notification broadcasts on.
189199
*/
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Eclipse\Core\Notifications\Channels;
4+
5+
use Illuminate\Notifications\Channels\DatabaseChannel;
6+
use Illuminate\Notifications\Notification;
7+
use Illuminate\Support\Facades\Context;
8+
9+
class SiteDatabaseChannel extends DatabaseChannel
10+
{
11+
/**
12+
* Build the payload stored in the notifications table and
13+
* append the current site id so rows are tenant-aware.
14+
*/
15+
protected function buildPayload($notifiable, Notification $notification): array
16+
{
17+
$payload = parent::buildPayload($notifiable, $notification);
18+
$fromNotification = $this->resolveSiteIdFromNotification($notification);
19+
$resolved = $fromNotification ?? Context::get('site');
20+
$payload['site_id'] = $resolved;
21+
22+
return $payload;
23+
}
24+
25+
/**
26+
* Prefer an explicit site id coming from the notification instance
27+
* (useful when dispatching from queues) before resolving ambient context.
28+
*/
29+
protected function resolveSiteIdFromNotification(Notification $notification): ?int
30+
{
31+
if (method_exists($notification, 'getSiteId')) {
32+
return $notification->getSiteId();
33+
}
34+
35+
return null;
36+
}
37+
}

0 commit comments

Comments
 (0)