feat(actions): add ScheduledAction model and migration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8d0b2b64ec
commit
ace48d57c2
3 changed files with 222 additions and 0 deletions
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('scheduled_actions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('action_class')->unique();
|
||||
$table->string('frequency', 100);
|
||||
$table->string('timezone', 50)->nullable();
|
||||
$table->boolean('without_overlapping')->default(true);
|
||||
$table->boolean('run_in_background')->default(true);
|
||||
$table->boolean('is_enabled')->default(true);
|
||||
$table->timestamp('last_run_at')->nullable();
|
||||
$table->timestamp('next_run_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('scheduled_actions');
|
||||
}
|
||||
};
|
||||
100
src/Core/Actions/ScheduledAction.php
Normal file
100
src/Core/Actions/ScheduledAction.php
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Core PHP Framework
|
||||
*
|
||||
* Licensed under the European Union Public Licence (EUPL) v1.2.
|
||||
* See LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Actions;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Represents a scheduled action persisted in the database.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $action_class
|
||||
* @property string $frequency
|
||||
* @property string|null $timezone
|
||||
* @property bool $without_overlapping
|
||||
* @property bool $run_in_background
|
||||
* @property bool $is_enabled
|
||||
* @property \Illuminate\Support\Carbon|null $last_run_at
|
||||
* @property \Illuminate\Support\Carbon|null $next_run_at
|
||||
* @property \Illuminate\Support\Carbon $created_at
|
||||
* @property \Illuminate\Support\Carbon $updated_at
|
||||
*/
|
||||
class ScheduledAction extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'action_class',
|
||||
'frequency',
|
||||
'timezone',
|
||||
'without_overlapping',
|
||||
'run_in_background',
|
||||
'is_enabled',
|
||||
'last_run_at',
|
||||
'next_run_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'without_overlapping' => 'boolean',
|
||||
'run_in_background' => 'boolean',
|
||||
'is_enabled' => 'boolean',
|
||||
'last_run_at' => 'datetime',
|
||||
'next_run_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only enabled actions.
|
||||
*/
|
||||
public function scopeEnabled(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_enabled', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the frequency string and return the method name.
|
||||
*
|
||||
* 'dailyAt:09:00' → 'dailyAt'
|
||||
* 'everyMinute' → 'everyMinute'
|
||||
*/
|
||||
public function frequencyMethod(): string
|
||||
{
|
||||
return explode(':', $this->frequency, 2)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the frequency string and return the arguments.
|
||||
*
|
||||
* 'dailyAt:09:00' → ['09:00']
|
||||
* 'weeklyOn:1,09:00' → ['1', '09:00']
|
||||
* 'everyMinute' → []
|
||||
*/
|
||||
public function frequencyArgs(): array
|
||||
{
|
||||
$parts = explode(':', $this->frequency, 2);
|
||||
|
||||
if (! isset($parts[1])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return explode(',', $parts[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record that this action has just run.
|
||||
*/
|
||||
public function markRun(): void
|
||||
{
|
||||
$this->update(['last_run_at' => now()]);
|
||||
}
|
||||
}
|
||||
91
tests/Feature/ScheduledActionModelTest.php
Normal file
91
tests/Feature/ScheduledActionModelTest.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Tests\Feature;
|
||||
|
||||
use Core\Actions\ScheduledAction;
|
||||
use Core\Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
class ScheduledActionModelTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function defineDatabaseMigrations(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(__DIR__.'/../../database/migrations');
|
||||
}
|
||||
|
||||
public function test_model_can_be_created(): void
|
||||
{
|
||||
$action = ScheduledAction::create([
|
||||
'action_class' => 'App\\Actions\\TestAction',
|
||||
'frequency' => 'dailyAt:09:00',
|
||||
'timezone' => 'Europe/London',
|
||||
'without_overlapping' => true,
|
||||
'run_in_background' => true,
|
||||
'is_enabled' => true,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('scheduled_actions', [
|
||||
'action_class' => 'App\\Actions\\TestAction',
|
||||
'frequency' => 'dailyAt:09:00',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_enabled_scope(): void
|
||||
{
|
||||
ScheduledAction::create([
|
||||
'action_class' => 'App\\Actions\\Enabled',
|
||||
'frequency' => 'hourly',
|
||||
'is_enabled' => true,
|
||||
]);
|
||||
ScheduledAction::create([
|
||||
'action_class' => 'App\\Actions\\Disabled',
|
||||
'frequency' => 'hourly',
|
||||
'is_enabled' => false,
|
||||
]);
|
||||
|
||||
$enabled = ScheduledAction::enabled()->get();
|
||||
$this->assertCount(1, $enabled);
|
||||
$this->assertSame('App\\Actions\\Enabled', $enabled->first()->action_class);
|
||||
}
|
||||
|
||||
public function test_frequency_method_parses_simple_frequency(): void
|
||||
{
|
||||
$action = new ScheduledAction(['frequency' => 'everyMinute']);
|
||||
$this->assertSame('everyMinute', $action->frequencyMethod());
|
||||
$this->assertSame([], $action->frequencyArgs());
|
||||
}
|
||||
|
||||
public function test_frequency_method_parses_frequency_with_args(): void
|
||||
{
|
||||
$action = new ScheduledAction(['frequency' => 'dailyAt:09:00']);
|
||||
$this->assertSame('dailyAt', $action->frequencyMethod());
|
||||
$this->assertSame(['09:00'], $action->frequencyArgs());
|
||||
}
|
||||
|
||||
public function test_frequency_method_parses_multiple_args(): void
|
||||
{
|
||||
$action = new ScheduledAction(['frequency' => 'weeklyOn:1,09:00']);
|
||||
$this->assertSame('weeklyOn', $action->frequencyMethod());
|
||||
$this->assertSame(['1', '09:00'], $action->frequencyArgs());
|
||||
}
|
||||
|
||||
public function test_mark_run_updates_last_run_at(): void
|
||||
{
|
||||
$action = ScheduledAction::create([
|
||||
'action_class' => 'App\\Actions\\Runnable',
|
||||
'frequency' => 'hourly',
|
||||
'is_enabled' => true,
|
||||
]);
|
||||
|
||||
$this->assertNull($action->last_run_at);
|
||||
|
||||
$action->markRun();
|
||||
$action->refresh();
|
||||
|
||||
$this->assertNotNull($action->last_run_at);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue