File: /home/xedaptot/ai.naniguide.com/database/seeders/Demo/Stage18_NotificationsSeeder.php
<?php
namespace Database\Seeders\Demo;
use App\Model\AppNotification;
use App\Notifications\AppNotification as Event;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
/**
* Stage 18 — Notifications + activity logs.
*
* Per customer:
* - 8-15 direct notifications (audience='direct') with mixed severity / category
* - 20-50 activity log rows (mirrored to notifications by ActivityLog::booted)
*
* Plus one shared admin broadcast per seeded run (audience='admins') so the
* admin bell on /rui/admin/notifications shows realistic content.
*/
class Stage18_NotificationsSeeder extends AbstractDemoSeeder
{
protected int $fakerOffset = 18;
public function run(): void
{
$f = $this->faker();
$totalNotifs = 0;
$totalLogs = 0;
// [title, severity, category, body template, optional action]
$notifTemplates = [
['Campaign sent successfully', Event::SEVERITY_SUCCESS, Event::CATEGORY_INFO, 'Your campaign "{name}" has been delivered to {n} recipients.', null],
['New subscriber added', Event::SEVERITY_INFO, Event::CATEGORY_INFO, '{email} just joined your "{list}" list.', null],
['Sending server quota warning', Event::SEVERITY_WARNING, Event::CATEGORY_ALERT, 'Your sending server has used {p}% of its hourly quota.', null],
['Bounce rate threshold reached', Event::SEVERITY_WARNING, Event::CATEGORY_ALERT, 'Your bounce rate ({r}%) is above the recommended threshold.', null],
['Subscription renewed', Event::SEVERITY_SUCCESS, Event::CATEGORY_INFO, 'Your subscription has been renewed successfully.', null],
['Failed login attempt', Event::SEVERITY_ERROR, Event::CATEGORY_ALERT, 'Multiple failed login attempts detected from IP {ip}.', null],
['List import complete', Event::SEVERITY_INFO, Event::CATEGORY_INFO, 'Imported {n} subscribers into "{list}".', null],
['New form submission', Event::SEVERITY_INFO, Event::CATEGORY_INFO, 'You received a new submission from "{form}".', null],
['Trial ending soon', Event::SEVERITY_WARNING, Event::CATEGORY_ACTION, 'Your trial period ends in {days} days.',
['label' => 'Renew now', 'route' => 'refactor.account.billing', 'params' => []]],
['DKIM verification needed', Event::SEVERITY_WARNING, Event::CATEGORY_ACTION, 'Please verify DKIM for {domain}.', null],
];
$activityTypes = [
['login', 'auth', 'User logged in'],
['create', 'campaign', 'Created campaign'],
['update', 'campaign', 'Updated campaign'],
['delete', 'campaign', 'Deleted campaign'],
['create', 'list', 'Created mail list'],
['create', 'subscriber', 'Added subscriber'],
['import', 'subscriber', 'Imported subscribers'],
['export', 'subscriber', 'Exported subscribers'],
['create', 'form', 'Created signup form'],
['update', 'settings', 'Updated settings'],
];
foreach (Stage03_CustomersSeeder::$created as $key => $customer) {
try {
$count = $f->numberBetween(8, 15);
$rows = [];
for ($i = 0; $i < $count; $i++) {
[$title, $severity, $category, $tpl, $action] = $notifTemplates[array_rand($notifTemplates)];
$body = strtr($tpl, [
'{name}' => 'Campaign #' . $f->numberBetween(1, 50),
'{n}' => $f->numberBetween(50, 5000),
'{email}' => $f->safeEmail,
'{list}' => 'Newsletter',
'{p}' => $f->numberBetween(50, 95),
'{r}' => $f->randomFloat(1, 1, 8),
'{ip}' => $f->ipv4,
'{form}' => 'Newsletter Signup',
'{days}' => $f->numberBetween(1, 7),
'{domain}' => preg_replace('/[^a-z0-9]/', '', strtolower($customer->name)) . '.demo',
]);
$createdAt = now()->subDays($f->numberBetween(0, 90));
$isRead = $f->boolean(60);
$data = ['title' => $title, 'body' => $body];
if ($action) {
$data['action'] = $action;
}
$rows[] = [
'id' => (string) Str::uuid(),
'type' => 'demo.seed',
'audience' => Event::AUDIENCE_DIRECT,
'notifiable_type' => $customer->getMorphClass(),
'notifiable_id' => $customer->id,
'subject_type' => null,
'subject_id' => null,
'category' => $category,
'severity' => $severity,
'group_key' => null,
'data' => json_encode($data),
'read_at' => $isRead ? $createdAt->copy()->addHours($f->numberBetween(1, 24)) : null,
'read_by_type' => null,
'read_by_id' => null,
'dismissed_at' => null,
'dismissed_by_type' => null,
'dismissed_by_id' => null,
'created_at' => $createdAt,
'updated_at' => $createdAt,
];
$totalNotifs++;
}
DB::table('notifications')->insert($rows);
} catch (\Throwable $e) {
$this->log("notifications skip for {$key}: " . $e->getMessage());
}
// Activity logs (mirror happens automatically in ActivityLog::booted
// for SubscriptionEvent rows — demo activity types here are not in
// the registry, so they stay audit-trail-only).
try {
$count = $f->numberBetween(20, 50);
$logRows = [];
for ($i = 0; $i < $count; $i++) {
[$type, $subjectType, $subjectName] = $activityTypes[array_rand($activityTypes)];
$logRows[] = [
'uid' => Str::random(13),
'customer_id' => $customer->id,
'user_id' => $customer->user_id ?? null,
'type' => $type,
'subject_type' => 'App\\Model\\' . ucfirst($subjectType),
'subject_id' => $f->numberBetween(1, 100),
'subject_name' => $subjectName,
'data' => json_encode(['source' => 'demo seed']),
'created_at' => now()->subDays($f->numberBetween(0, 180)),
'updated_at' => now()->subDays($f->numberBetween(0, 180)),
];
}
DB::table('activity_logs')->insert($logRows);
$totalLogs += $count;
} catch (\Throwable $e) {
$this->log("activity_logs skip for {$key}: " . $e->getMessage());
}
}
// Shared admin broadcasts — each appears in EVERY admin's inbox via
// the 'admins' audience, illustrating the shared-row pattern.
$adminBroadcasts = [
[
'type' => 'demo.admin-shared',
'category' => Event::CATEGORY_ACTION,
'severity' => Event::SEVERITY_WARNING,
'group_key' => 'demo-shared-admin-banner',
'title' => 'Demo: invoice claimed paid',
'body' => 'A customer marked an offline invoice as paid. Any admin can review and approve.',
'action' => [
'label' => 'Review',
'route' => 'refactor.admin.notifications.index',
'params' => [],
],
'created_at' => now(),
],
[
'type' => 'plugin.installed',
'category' => Event::CATEGORY_ACTION,
'severity' => Event::SEVERITY_INFO,
'group_key' => 'plugin-installed-acelle-ai',
'title' => 'New plugin: Acelle AI installed',
'body' => 'The Acelle AI plugin has been installed. It helps automate your daily marketing tasks — configure the AI engine to get started.',
'action' => [
'label' => 'Configure AI engine',
'route' => 'refactor.admin.ai-settings.show',
'params' => ['section' => 'engines'],
],
'created_at' => now()->subHours(2),
],
];
foreach ($adminBroadcasts as $b) {
try {
DB::table('notifications')->insert([
'id' => (string) Str::uuid(),
'type' => $b['type'],
'audience' => Event::AUDIENCE_ADMINS,
'notifiable_type' => null,
'notifiable_id' => null,
'subject_type' => null,
'subject_id' => null,
'category' => $b['category'],
'severity' => $b['severity'],
'group_key' => $b['group_key'],
'data' => json_encode([
'title' => $b['title'],
'body' => $b['body'],
'action' => $b['action'],
]),
'read_at' => null,
'read_by_type' => null,
'read_by_id' => null,
'dismissed_at' => null,
'dismissed_by_type' => null,
'dismissed_by_id' => null,
'created_at' => $b['created_at'],
'updated_at' => $b['created_at'],
]);
$totalNotifs++;
} catch (\Throwable $e) {
$this->log("admin broadcast '{$b['type']}' skip: " . $e->getMessage());
}
}
$this->log("seeded {$totalNotifs} notifications + {$totalLogs} activity logs");
}
}