Ctrl K
ComunidadGuías…

app/Models/Monitor.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php

namespace App\Models;

use App\Enums\AssertionType;
use App\Enums\CheckType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Monitor extends Model
{
    /** @use HasFactory<\Database\Factories\MonitorFactory> */
    use HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'user_id',
        'team_id',
        'project_id',
        'name',
        'url',
        'check_type',
        'assertion_type',
        'assertion_value',
        'is_active',
        'status',
        'check_interval',
        'last_checked_at',
        'next_run_at',
        'last_error',
        'consecutive_failures',
        'failure_threshold',
        'last_decided_round_id',
        'cert_expires_at',
        'cert_issuer',
        'cert_alerted_threshold_days',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'is_active' => 'boolean',
            'last_checked_at' => 'datetime',
            'next_run_at' => 'datetime',
            'cert_expires_at' => 'datetime',
            'check_type' => CheckType::class,
            'assertion_type' => AssertionType::class,
        ];
    }

    /**
     * Whether a response-body assertion is configured on this monitor.
     */
    public function hasAssertion(): bool
    {
        return $this->check_type === CheckType::Http
            && $this->assertion_type !== null
            && $this->assertion_type !== AssertionType::None
            && $this->assertion_value !== null
            && $this->assertion_value !== '';
    }

    /**
     * Days until the TLS certificate expires (negative when already expired),
     * or null when no certificate has been observed.
     */
    public function certDaysRemaining(): ?int
    {
        if ($this->cert_expires_at === null) {
            return null;
        }

        return (int) floor(now()->diffInDays($this->cert_expires_at, false));
    }

    /**
     * The notification channels alerts for this monitor should go to:
     * the monitor's attached active channels, falling back to the owner's
     * default channel (legacy monitors with none attached).
     *
     * @return \Illuminate\Support\Collection<int, NotificationChannel>
     */
    public function alertChannels(): \Illuminate\Support\Collection
    {
        $channels = $this->notificationChannels()
            ->where('is_active', true)
            ->get()
            ->filter(fn (NotificationChannel $channel) => $channel->isConfigured())
            ->values();

        if ($channels->isNotEmpty()) {
            return $channels->toBase();
        }

        $default = $this->user?->defaultNotificationChannel();

        return $default && $default->isConfigured()
            ? collect([$default])
            : collect();
    }

    /**
     * Get the user that owns the monitor
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * Get the team that owns the monitor (optional)
     */
    public function team(): BelongsTo
    {
        return $this->belongsTo(Team::class);
    }

    /**
     * Get the project this monitor belongs to (optional)
     */
    public function project(): BelongsTo
    {
        return $this->belongsTo(Project::class);
    }

    /**
     * Return the effective team for access control.
     *
     * When a monitor is inside a project, the project's team wins over
     * the monitor's own team_id. Standalone monitors fall back to team_id.
     */
    public function effectiveTeam(): ?Team
    {
        if ($this->project_id) {
            return $this->project?->team;
        }

        return $this->team;
    }

    /**
     * Return the effective owner user_id for access control.
     *
     * When a monitor is inside a project, the project owner governs.
     */
    public function effectiveUserId(): int
    {
        if ($this->project_id && $this->project) {
            return $this->project->user_id;
        }

        return $this->user_id;
    }

    /**
     * Check if the monitor is currently up
     */
    public function isUp(): bool
    {
        return $this->status === 'up';
    }

    /**
     * Check if the monitor is currently down
     */
    public function isDown(): bool
    {
        return $this->status === 'down';
    }

    /**
     * Check if the monitor is pending first check
     */
    public function isPending(): bool
    {
        return $this->status === 'pending';
    }

    /**
     * Get all check results for this monitor
     */
    public function checkResults(): HasMany
    {
        return $this->hasMany(CheckResult::class);
    }

    /**
     * Get the latest check result for this monitor
     */
    public function latestCheckResult(): HasOne
    {
        return $this->hasOne(CheckResult::class)->latestOfMany();
    }

    /**
     * Get all incidents (downtimes) for this monitor
     */
    public function incidents(): HasMany
    {
        return $this->hasMany(Incident::class);
    }

    /**
     * Get the currently ongoing incident, if any
     */
    public function ongoingIncident(): HasOne
    {
        return $this->hasOne(Incident::class)->whereNull('ended_at')->latestOfMany('started_at');
    }

    /**
     * Notification channels selected for this monitor
     */
    public function notificationChannels(): BelongsToMany
    {
        return $this->belongsToMany(NotificationChannel::class);
    }
}