Files
jabali-panel/app/Models/BackupSchedule.php

236 lines
5.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class BackupSchedule extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'destination_id',
'name',
'is_active',
'is_server_backup',
'frequency',
'time',
'day_of_week',
'day_of_month',
'include_files',
'include_databases',
'include_mailboxes',
'include_dns',
'domains',
'databases',
'mailboxes',
'users',
'retention_count',
'last_run_at',
'next_run_at',
'last_status',
'last_error',
'metadata',
];
protected function casts(): array
{
return [
'is_active' => 'boolean',
'is_server_backup' => 'boolean',
'include_files' => 'boolean',
'include_databases' => 'boolean',
'include_mailboxes' => 'boolean',
'include_dns' => 'boolean',
'domains' => 'array',
'databases' => 'array',
'mailboxes' => 'array',
'users' => 'array',
'metadata' => 'array',
'retention_count' => 'integer',
'day_of_week' => 'integer',
'day_of_month' => 'integer',
'last_run_at' => 'datetime',
'next_run_at' => 'datetime',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function destination(): BelongsTo
{
return $this->belongsTo(BackupDestination::class, 'destination_id');
}
public function backups(): HasMany
{
return $this->hasMany(Backup::class, 'schedule_id');
}
/**
* Check if the schedule should run now.
*/
public function shouldRun(): bool
{
if (! $this->is_active) {
return false;
}
if (! $this->next_run_at) {
return true;
}
return $this->next_run_at->isPast();
}
/**
* Calculate and set the next run time.
*/
public function calculateNextRun(): Carbon
{
$timezone = $this->getSystemTimezone();
$now = Carbon::now($timezone);
$time = explode(':', $this->time);
$hour = (int) ($time[0] ?? 2);
$minute = (int) ($time[1] ?? 0);
$next = $now->copy()->setTime($hour, $minute, 0);
// If time already passed today, start from tomorrow
if ($next->isPast()) {
$next->addDay();
}
switch ($this->frequency) {
case 'hourly':
$next = $now->copy()->addHour()->startOfHour();
break;
case 'daily':
// Already set to next occurrence
break;
case 'weekly':
$targetDay = $this->day_of_week ?? 0; // Default to Sunday
while ($next->dayOfWeek !== $targetDay) {
$next->addDay();
}
break;
case 'monthly':
$targetDay = $this->day_of_month ?? 1;
$next->day = min($targetDay, $next->daysInMonth);
if ($next->isPast()) {
$next->addMonth();
$next->day = min($targetDay, $next->daysInMonth);
}
break;
}
$nextUtc = $next->copy()->setTimezone('UTC');
$this->attributes['next_run_at'] = $nextUtc->format($this->getDateFormat());
return $nextUtc;
}
/**
* Get frequency label for UI.
*/
public function getFrequencyLabelAttribute(): string
{
$base = match ($this->frequency) {
'hourly' => 'Every hour',
'daily' => 'Daily at '.$this->time,
'weekly' => 'Weekly on '.$this->getDayName().' at '.$this->time,
'monthly' => 'Monthly on day '.($this->day_of_month ?? 1).' at '.$this->time,
default => ucfirst($this->frequency),
};
return $base;
}
/**
* Get day name for weekly schedules.
*/
protected function getDayName(): string
{
$days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
return $days[$this->day_of_week ?? 0];
}
protected function getSystemTimezone(): string
{
static $timezone = null;
if ($timezone === null) {
$timezone = trim((string) @file_get_contents('/etc/timezone'));
if ($timezone === '') {
$timezone = trim((string) @shell_exec('timedatectl show -p Timezone --value 2>/dev/null'));
}
if ($timezone === '') {
$timezone = 'UTC';
}
}
return $timezone;
}
/**
* Scope for active schedules.
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* Scope for due schedules.
*/
public function scopeDue($query)
{
return $query->active()
->where(function ($q) {
$q->whereNull('next_run_at')
->orWhere('next_run_at', '<=', now());
});
}
/**
* Scope for user schedules.
*/
public function scopeForUser($query, int $userId)
{
return $query->where('user_id', $userId);
}
/**
* Scope for server backup schedules.
*/
public function scopeServerBackups($query)
{
return $query->where('is_server_backup', true);
}
/**
* Get last status color for UI.
*/
public function getLastStatusColorAttribute(): string
{
return match ($this->last_status) {
'success' => 'success',
'failed' => 'danger',
default => 'gray',
};
}
}