File: /home/xedaptot/ai.naniguide.com/app/Model/EmailVerificationServer.php
<?php
namespace App\Model;
use Illuminate\Database\Eloquent\Model;
use App\Library\Traits\HasUid;
use Exception;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;
use App\Library\RateTracker;
use App\Library\DynamicRateTracker;
use App\Library\RateLimit;
use App\Library\RateTrackerInterface;
use App\Library\CreditTracker;
use App\Library\Contracts\CreditTrackerInterface;
use App\Verification\DriverRegistry;
use App\Verification\Drivers\VerificationDriver;
class EmailVerificationServer extends Model
{
use HasUid;
protected $connection = 'mysql';
// set the table name
protected $table = 'email_verification_servers';
// Logger
protected $logger;
protected $service;
private ?VerificationDriver $driverInstance = null;
public const STATUS_ACTIVE = 'active';
public const STATUS_INACTIVE = 'inactive';
public const WAIT = 30;
protected $fillable = ['type', 'name', 'options', 'status', 'admin_id', 'customer_id'];
protected $casts = [
'options' => 'array',
];
/**
* Driver registry accessor — single source of truth for verification vendor logic.
* Single source of truth for vendor logic — see docs/verification/COMPREHENSIVE-DESIGN.md.
*/
public function driver(): VerificationDriver
{
return $this->driverInstance ??= DriverRegistry::resolve($this);
}
public function newFromBuilder($attributes = [], $connection = null)
{
$model = parent::newFromBuilder($attributes, $connection);
$model->driverInstance = null;
return $model;
}
private const OTHERWISE = '*';
/**
* Associations.
*
* @var object | collect
*/
public function customer()
{
return $this->belongsTo('App\Model\Customer');
}
public function admin()
{
return $this->belongsTo('App\Model\Admin');
}
/**
* Verify a single email — delegates to the registry-resolved driver.
*/
public function verify(string $email): array
{
return $this->driver()->verify($email);
}
/**
* Items per page.
*
* @var array
*/
public static $itemsPerPage = 25;
/**
* Filter items.
*
* @return collect
*/
public static function filter($request)
{
$user = $request->user();
$query = self::select('email_verification_servers.*');
// Keyword
if (!empty(trim($request->keyword))) {
foreach (explode(' ', trim($request->keyword)) as $keyword) {
$query = $query->where(function ($q) use ($keyword) {
$q->orwhere('email_verification_servers.name', 'like', '%'.$keyword.'%')
->orWhere('email_verification_servers.type', 'like', '%'.$keyword.'%');
});
}
}
// filters
$filters = $request->all();
if (!empty($filters)) {
if (!empty($filters['type'])) {
$query = $query->where('email_verification_servers.type', '=', $filters['type']);
}
}
// Other filter
if (!empty($request->customer_id)) {
$query = $query->where('email_verification_servers.customer_id', '=', $request->customer_id);
}
if (!empty($request->admin_id)) {
$query = $query->where('email_verification_servers.admin_id', '=', $request->admin_id);
}
// remove customer sending servers
if (!empty($request->no_customer)) {
$query = $query->whereNull('customer_id');
}
return $query;
}
/**
* Search items.
*
* @return collect
*/
public static function search($request)
{
$query = self::filter($request);
if (!empty($request->sort_order)) {
$query = $query->orderBy($request->sort_order, $request->sort_direction);
}
return $query;
}
/**
* Get campaign validation rules — base rules + per-vendor rules from driver class.
*/
public function rules()
{
$rules = array(
'name' => 'required',
'type' => 'required',
'options.limit_value' => 'required',
'options.limit_base' => 'required',
'options.limit_unit' => 'required',
);
if ($this->type && DriverRegistry::has($this->type)) {
$registry = DriverRegistry::all();
$driverClass = $registry[$this->type];
$vendorRules = $driverClass::validationRules()['cols'] ?? [];
foreach ($vendorRules as $field => $rule) {
$rules['options.' . $field] = $rule;
}
}
return $rules;
}
/**
* Frequency time unit options.
*
* @return array
*/
public static function quotaTimeUnitOptions()
{
return [
['value' => 'minute', 'text' => trans('messages.minute')],
['value' => 'hour', 'text' => trans('messages.hour')],
['value' => 'day', 'text' => trans('messages.day')],
];
}
/**
* Server status select options.
*
* @return array
*/
public static function statusSelectOptions()
{
return [
['value' => self::STATUS_ACTIVE, 'text' => trans('messages.email_verification_server_status_'.self::STATUS_ACTIVE)],
['value' => self::STATUS_INACTIVE, 'text' => trans('messages.email_verification_server_status_'.self::STATUS_INACTIVE)],
];
}
/**
* Get all items.
*
* @return collect
*/
public static function getAll()
{
return self::where('status', '=', 'active');
}
/**
* Disable verification server.
*
* @return array
*/
public function disable()
{
$this->status = self::STATUS_INACTIVE;
$this->save();
}
/**
* Enable verification server.
*
* @return array
*/
public function enable()
{
$this->status = self::STATUS_ACTIVE;
$this->save();
}
/**
* Get all active items.
*
* @return collect
*/
public static function getAllActive()
{
return self::where('status', '=', SendingServer::STATUS_ACTIVE);
}
/**
* Get all active system items.
*
* @return collect
*/
public static function getAllAdminActive()
{
return self::getAllActive()->fromSystem();
}
public static function scopeFromSystem($query)
{
return $query->whereNotNull('admin_id');
}
public function getSpeedLimitString()
{
$options = $this->options ?? [];
return number_format($options['limit_value']) . " / {$options['limit_base']} {$options['limit_unit']}";
}
public function getTypeName()
{
if (!DriverRegistry::has($this->type)) {
return 'Error: Driver not registered for type ' . $this->type;
}
$registry = DriverRegistry::all();
$driverClass = $registry[$this->type];
return $driverClass::displayName();
}
// For testing, do not save!
public function setLimit(int $value, int $periodValue, string $periodUnit)
{
$options = $this->options ?? [];
$options['limit_base'] = $periodValue;
$options['limit_unit'] = $periodUnit;
$options['limit_value'] = $value;
$this->options = $options;
}
public function getRateTracker(): RateTrackerInterface
{
// server-level admin-configured rate limit (distinct from vendor's intrinsic limit
// exposed via driver's HasRateLimit capability). Always 'verify' bucket.
$options = $this->options ?? [];
$limits = [];
if ($options['limit_value'] != RateLimit::UNLIMITED) {
$limits[] = new RateLimit(
$options['limit_value'],
$options['limit_base'],
$options['limit_unit'],
"Verification credits limit of {$options['limit_value']} per {$options['limit_base']} {$options['limit_unit']}",
);
}
if (config('custom.distributed_mode')) {
$key = 'verification-server-verify-email-rate-tracking-log-'.$this->uid;
$tracker = new DynamicRateTracker($key, $limits);
} else {
$file = storage_path('app/quota/verification-server-verify-email-rate-tracking-log-'.$this->uid);
$tracker = new RateTracker($file, $limits);
}
return $tracker;
}
public function getCreditTracker(): CreditTrackerInterface
{
$file = storage_path('app/quota/verification-server-credits-'.$this->uid);
return new CreditTracker($file, $createFile = true, "Verification server credits for server \"{$this->name}\"");
}
public function logger()
{
if (is_null($this->logger)) {
$formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message%\n");
$logfile = storage_path('logs/' . php_sapi_name() . "/everification-{$this->uid}-{$this->type}.log");
$stream = new RotatingFileHandler($logfile, 0, 'debug');
$stream->setFormatter($formatter);
$pid = getmypid();
$logger = new Logger($pid);
$logger->pushHandler($stream);
// Set
$this->logger = $logger;
}
return $this->logger;
}
public static function newDefault()
{
$server = new self();
$server->status = self::STATUS_ACTIVE;
return $server;
}
public function setOptions($options)
{
$this->options = json_encode($options);
}
}