HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
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');
    }
}