app/Models/Monitor.php
Rama main · réplica de lectura del repositorio original.
<?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);
}
}