File: /home/xedaptot/ai.naniguide.com/database/seeders/Demo/Stage05_AdminInfraSeeder.php
<?php
namespace Database\Seeders\Demo;
use App\Model\Blacklist;
use App\Model\BounceHandler;
use App\Model\EmailVerificationServer;
use App\Model\FeedbackLoopHandler;
use App\Model\PaymentGateway;
use App\Model\Plan;
use App\Model\SendingServer;
use App\Model\WarmupStrategy;
/**
* Stage 05 — admin-level infrastructure.
*
* Creates:
* - Bounce handlers (1 per major sending server backend)
* - Feedback loop handlers (1)
* - System sending servers (Sendmail, SMTP, Mailgun-SMTP, SendGrid-SMTP,
* Amazon-SMTP, Elastic-SMTP) — associated with all plans
* - Payment gateways (offline, stripe, stripe-subscription)
* - System blacklist (~25 sample bounced/spam emails)
* - Admin email-verification server (1)
*
* All entries use placeholder credentials — they will not actually send mail
* but they make the admin UI fully populated for demos and tests.
*/
class Stage05_AdminInfraSeeder extends AbstractDemoSeeder
{
protected int $fakerOffset = 5;
public static array $sendingServers = [];
public static array $bounceHandlers = [];
public static array $paymentGateways = [];
public function run(): void
{
$superAdmin = Stage01_AdminsSeeder::$created['super']['admin'] ?? null;
if (!$superAdmin) {
throw new \RuntimeException('Stage05 needs Stage01 admins seeded first.');
}
$this->seedBounceHandlers($superAdmin);
$this->seedFeedbackLoopHandlers($superAdmin);
$this->seedSendingServers();
$this->seedPaymentGateways();
$this->seedBlacklist();
$this->seedAdminEvServers($superAdmin);
$this->seedWarmupStrategies();
$this->log('admin infra seeding complete');
}
/**
* Seed 4 warmup strategies covering the 3 presets + 1 custom long-haul
* config so the admin warmup page is populated for demos.
*
* Mirrors the defaults that `WarmupStrategy::newDefault()` uses, then
* tweaks the 3 preset variants to match the names users see in the UI
* picker (cautious / balanced / aggressive). Strategies are admin-level
* reference data — they have no customer_id column.
*/
protected function seedWarmupStrategies(): void
{
if (!class_exists(WarmupStrategy::class)) {
$this->log('WarmupStrategy model missing — skipping');
return;
}
if (WarmupStrategy::count() > 0) {
$this->log('warmup_strategies already populated — skipping');
return;
}
$presets = [
[
'name' => 'Cautious — New SMTP Server',
'description' => 'Slow ramp for brand-new SMTP servers with no prior reputation. Recommended starting point for cold IPs.',
'preset' => WarmupStrategy::PRESET_CAUTIOUS,
'growth' => WarmupStrategy::GROWTH_STRATEGY_LINEAR,
'starting' => 10,
'increment' => 10,
'cap' => 500,
'target' => 5000,
'days' => 45,
'weekends' => false,
'increment_pct' => 10,
'sending_count' => 10,
'sending_limit' => 500,
],
[
'name' => 'Balanced — Recommended Default',
'description' => 'Sensible middle-ground for most senders. Linear ramp + safety pause if bounces or complaints spike.',
'preset' => WarmupStrategy::PRESET_BALANCED,
'growth' => WarmupStrategy::GROWTH_STRATEGY_LINEAR,
'starting' => 20,
'increment' => 15,
'cap' => 1000,
'target' => 10000,
'days' => 30,
'weekends' => false,
'increment_pct' => 15,
'sending_count' => 30,
'sending_limit' => 1000,
],
[
'name' => 'Aggressive — Established Domain',
'description' => 'Faster exponential ramp for senders with existing reputation on a different IP. Use only if you know your bounce rate is low.',
'preset' => WarmupStrategy::PRESET_AGGRESSIVE,
'growth' => WarmupStrategy::GROWTH_STRATEGY_EXPONENTIAL,
'starting' => 50,
'increment' => 30,
'cap' => 5000,
'target' => 50000,
'days' => 21,
'weekends' => true,
'increment_pct' => 25,
'sending_count' => 100,
'sending_limit' => 5000,
],
[
'name' => 'Long-Haul Volume — Enterprise',
'description' => 'Custom 60-day exponential ramp targeting 100k+/day sends. Built for high-volume enterprise senders.',
'preset' => WarmupStrategy::PRESET_BALANCED,
'growth' => WarmupStrategy::GROWTH_STRATEGY_EXPONENTIAL,
'starting' => 100,
'increment' => 50,
'cap' => 10000,
'target' => 100000,
'days' => 60,
'weekends' => true,
'increment_pct' => 20,
'sending_count' => 200,
'sending_limit' => 10000,
],
];
foreach ($presets as $i => $cfg) {
$w = WarmupStrategy::newDefault();
$w->name = $cfg['name'];
$w->description = $cfg['description'];
$w->preset = $cfg['preset'];
$w->growth_strategy = $cfg['growth'];
$w->starting_volume = $cfg['starting'];
$w->daily_increment = $cfg['increment'];
$w->limit_per_day_cap = $cfg['cap'];
$w->limit_target_volume = $cfg['target'];
$w->limit_stop_after_days = $cfg['days'];
$w->send_on_weekends = $cfg['weekends'];
$w->increment_percentage = $cfg['increment_pct'];
$w->sending_count = $cfg['sending_count'];
$w->sending_limit = $cfg['sending_limit'];
// Last strategy is inactive so the picker shows both states
$w->status = $i === count($presets) - 1
? WarmupStrategy::STATUS_INACTIVE
: WarmupStrategy::STATUS_ACTIVE;
$w->save();
}
$this->log('seeded ' . count($presets) . ' warmup strategies (3 active + 1 inactive)');
}
protected function seedBounceHandlers($admin): void
{
$handlers = [
['name' => 'IMAP Bounce Handler', 'host' => 'imap.acelle-demo.com', 'username' => '[email protected]', 'port' => '993', 'protocol' => 'imap', 'encryption' => 'ssl'],
['name' => 'POP3 Bounce Handler', 'host' => 'pop3.acelle-demo.com', 'username' => '[email protected]', 'port' => '995', 'protocol' => 'pop3', 'encryption' => 'ssl'],
];
foreach ($handlers as $cfg) {
$h = new BounceHandler();
$h->admin_id = $admin->id;
$h->name = $cfg['name'];
$h->host = $cfg['host'];
$h->username = $cfg['username'];
$h->password = 'demo-password';
$h->port = $cfg['port'];
$h->protocol = $cfg['protocol'];
$h->encryption = $cfg['encryption'];
$h->status = 'active';
$h->email = $cfg['username'];
$h->save();
self::$bounceHandlers[] = $h;
}
$this->log('seeded ' . count($handlers) . ' bounce handlers');
}
protected function seedFeedbackLoopHandlers($admin): void
{
if (!class_exists(FeedbackLoopHandler::class)) {
$this->log('FeedbackLoopHandler model missing — skipping');
return;
}
$h = new FeedbackLoopHandler();
$h->admin_id = $admin->id;
$h->name = 'Default FBL Handler';
$h->host = 'imap.acelle-demo.com';
$h->username = '[email protected]';
$h->password = 'demo-password';
$h->port = '993';
$h->protocol = 'imap';
$h->encryption = 'ssl';
$h->status = 'active';
$h->email = '[email protected]';
$h->save();
$this->log('seeded 1 feedback loop handler');
}
protected function seedSendingServers(): void
{
// DatabaseInit already seeds a default sending server and associates
// it with plans. Only create demo servers if none exist yet.
if (SendingServer::count() > 0) {
// Populate $sendingServers from existing records so later stages can use them
foreach (SendingServer::all() as $srv) {
self::$sendingServers[$srv->type] = $srv;
}
$this->log('sending servers already exist (' . count(self::$sendingServers) . ') — skipping creation');
return;
}
$bounce = self::$bounceHandlers[0] ?? null;
$servers = [
[
'name' => 'Sendmail (Local)',
'type' => SendingServer::TYPE_SENDMAIL,
'sendmail_path' => '/usr/sbin/sendmail',
],
[
'name' => 'SMTP (Generic)',
'type' => SendingServer::TYPE_SMTP,
'host' => 'smtp.acelle-demo.com',
'smtp_username' => '[email protected]',
'smtp_password' => 'demo-password',
'smtp_port' => '587',
'smtp_protocol' => 'tls',
],
[
'name' => 'Mailgun SMTP',
'type' => SendingServer::TYPE_MAILGUN_SMTP,
'host' => 'smtp.mailgun.org',
'domain' => 'mg.acelle-demo.com',
'api_key' => 'demo-api-key',
'smtp_username' => '[email protected]',
'smtp_password' => 'demo-password',
'smtp_port' => '587',
'smtp_protocol' => 'tls',
],
[
'name' => 'SendGrid SMTP',
'type' => SendingServer::TYPE_SENDGRID_SMTP,
'host' => 'smtp.sendgrid.net',
'api_key' => 'SG.demo-api-key',
'smtp_username' => 'apikey',
'smtp_password' => 'SG.demo-api-key',
'smtp_port' => '587',
'smtp_protocol' => 'tls',
],
[
'name' => 'Amazon SES SMTP',
'type' => SendingServer::TYPE_AMAZON_SMTP,
'host' => 'email-smtp.us-east-1.amazonaws.com',
'aws_access_key_id' => 'AKIA-DEMO-KEY',
'aws_secret_access_key' => 'demo-secret-key',
'aws_region' => 'us-east-1',
'smtp_port' => '587',
'smtp_protocol' => 'tls',
],
[
'name' => 'Elastic Email SMTP',
'type' => SendingServer::TYPE_ELASTICEMAIL_SMTP,
'host' => 'smtp.elasticemail.com',
'smtp_username' => '[email protected]',
'smtp_password' => 'demo-password',
'smtp_port' => '2525',
'smtp_protocol' => 'tls',
'api_key' => 'demo-api-key',
],
];
foreach ($servers as $cfg) {
$s = new SendingServer();
// fill() routes vendor keys (host, smtp_*, aws_*, sendmail_path, api_key, …)
// through VENDOR_CONFIG_KEYS into the JSON config column. The legacy
// typed columns no longer exist — direct $s->host = ... would fail.
$s->fill($cfg);
$s->status = SendingServer::STATUS_ACTIVE;
$s->quota_value = 10000;
$s->quota_base = 1;
$s->quota_unit = 'hour';
if ($bounce) $s->bounce_handler_id = $bounce->id;
$s->save();
self::$sendingServers[$cfg['type']] = $s;
}
// Associate every sending server with every plan, mark first as primary
$first = reset(self::$sendingServers);
foreach (Plan::all() as $plan) {
foreach (self::$sendingServers as $srv) {
if (!$plan->plansSendingServers()->where('sending_server_id', $srv->id)->exists()) {
$plan->addSendingServerByUid($srv->uid);
}
}
if ($first) {
$plan->setPrimarySendingServer($first->uid);
}
}
$this->log('seeded ' . count($servers) . ' system sending servers + linked to ' . Plan::count() . ' plans');
}
protected function seedPaymentGateways(): void
{
// Only gateways with live service classes post-refactor.
// type → [name, description, gatewayData]
$gateways = [
'offline' => [
'name' => 'Offline',
'description' => 'Pay manually via bank transfer or other offline methods.',
'data' => ['payment_instruction' => "Wire to:\nBank: Demo Bank\nAccount: 1234-5678-9012\nReference: order #"],
],
'stripe' => [
'name' => 'Stripe (Card)',
'description' => 'Pay with credit card via Stripe. Demo test card: 4242 4242 4242 4242, any future date, any CVC.',
'data' => [
// Real Stripe test-mode keys — usable for e2e against the live test API.
'publishable_key' => 'pk_test_fqKGTiyNkzQB2muKmYG6HzOM',
'secret_key' => 'sk_test_rQwQAGrsSGiqFJywxeMLBRy8',
],
],
'stripe-subscription' => [
'name' => 'Stripe Subscription',
'description' => 'Recurring subscriptions managed by Stripe. Stripe handles billing cycles + retries.',
'data' => [
'publishable_key' => 'pk_test_fqKGTiyNkzQB2muKmYG6HzOM',
'secret_key' => 'sk_test_rQwQAGrsSGiqFJywxeMLBRy8',
'webhook_secret' => '',
],
],
];
foreach ($gateways as $type => $cfg) {
try {
$g = PaymentGateway::newDefault($type);
$g->name = $cfg['name'];
$g->description = $cfg['description'];
$g->gatewayData = json_encode($cfg['data']);
$g->status = PaymentGateway::STATUS_ACTIVE;
$g->save();
self::$paymentGateways[$type] = $g;
} catch (\Throwable $e) {
// newDefault uses Billing::getGateways() which may not have all types registered.
// Fall back to a manual instance.
$g = new PaymentGateway();
$g->type = $type;
$g->name = $cfg['name'];
$g->description = $cfg['description'];
$g->gatewayData = json_encode($cfg['data']);
$g->status = PaymentGateway::STATUS_ACTIVE;
$g->save();
self::$paymentGateways[$type] = $g;
}
}
$this->log('seeded ' . count(self::$paymentGateways) . ' payment gateways');
}
protected function seedBlacklist(): void
{
$f = $this->faker();
$reasons = [
'Hard bounce: mailbox does not exist',
'Spam complaint',
'Invalid mailbox',
'Recipient address rejected',
'Bounced multiple times',
'Marked as abuse',
];
$emails = [];
for ($i = 0; $i < 30; $i++) {
$emails[] = [
'email' => strtolower($f->firstName . '.' . $f->lastName . $i) . '@' . $f->randomElement(['baddomain.test', 'spamtrap.io', 'inactive.example', 'bounced.demo']),
'reason' => $f->randomElement($reasons),
'created_at' => now()->subDays($f->numberBetween(1, 365)),
'updated_at' => now(),
];
}
// De-dupe by email (DB has unique constraint).
$byEmail = [];
foreach ($emails as $row) $byEmail[$row['email']] = $row;
\DB::table('blacklists')->insert(array_values($byEmail));
$this->log('seeded ' . count($byEmail) . ' blacklist entries');
}
protected function seedAdminEvServers($admin): void
{
if (!class_exists(EmailVerificationServer::class)) {
$this->log('EmailVerificationServer model missing — skipping');
return;
}
$s = new EmailVerificationServer();
$s->admin_id = $admin->id;
$s->name = 'Default System Verifier';
$s->type = 'builtin';
$s->status = 'active';
$s->options = json_encode([]);
$s->save();
$this->log('seeded 1 admin email verification server');
}
}