diff --git a/app/config/events.php b/app/config/events.php index c6006b569f..e029302214 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -405,6 +405,20 @@ return [ '$description' => 'This event triggers when a provider is deleted.' ], ], + 'schedules' => [ + '$model' => Response::MODEL_SCHEDULE, + '$resource' => true, + '$description' => 'This event triggers on any schedule event.', + 'create' => [ + '$description' => 'This event triggers when a schedule is created.', + ], + 'update' => [ + '$description' => 'This event triggers when a schedule is updated.', + ], + 'delete' => [ + '$description' => 'This event triggers when a schedule is deleted.', + ], + ], 'rules' => [ '$model' => Response::MODEL_PROXY_RULE, '$resource' => true, diff --git a/app/config/roles.php b/app/config/roles.php index 25a6bac4da..4473176c23 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -91,6 +91,8 @@ $admins = [ 'subscribers.read', 'tokens.read', 'tokens.write', + 'schedules.read', + 'schedules.write', ]; return [ diff --git a/src/Appwrite/Platform/Modules/Schedules/Http/Schedules/Create.php b/src/Appwrite/Platform/Modules/Schedules/Http/Schedules/Create.php index d3d4372172..5505a8ac8f 100644 --- a/src/Appwrite/Platform/Modules/Schedules/Http/Schedules/Create.php +++ b/src/Appwrite/Platform/Modules/Schedules/Http/Schedules/Create.php @@ -57,6 +57,7 @@ class Create extends Action ->param('active', false, new Boolean(), 'Whether the schedule is active.', true) ->inject('response') ->inject('project') + ->inject('dbForProject') ->inject('dbForPlatform') ->inject('authorization') ->callback($this->action(...)); @@ -69,16 +70,33 @@ class Create extends Action bool $active, Response $response, Document $project, + Database $dbForProject, Database $dbForPlatform, Authorization $authorization, ): void { + $collection = match ($resourceType) { + SCHEDULE_RESOURCE_TYPE_FUNCTION => 'functions', + SCHEDULE_RESOURCE_TYPE_EXECUTION => 'executions', + SCHEDULE_RESOURCE_TYPE_MESSAGE => 'messages', + }; + + $resource = $dbForProject->getDocument($collection, $resourceId); + + if ($resource->isEmpty()) { + throw new Exception(match ($resourceType) { + SCHEDULE_RESOURCE_TYPE_FUNCTION => Exception::FUNCTION_NOT_FOUND, + SCHEDULE_RESOURCE_TYPE_EXECUTION => Exception::EXECUTION_NOT_FOUND, + SCHEDULE_RESOURCE_TYPE_MESSAGE => Exception::MESSAGE_NOT_FOUND, + }); + } + try { $doc = $authorization->skip( fn () => $dbForPlatform->createDocument('schedules', new Document([ 'region' => $project->getAttribute('region'), 'resourceType' => $resourceType, 'resourceId' => $resourceId, - 'resourceInternalId' => '', + 'resourceInternalId' => $resource->getSequence(), 'resourceUpdatedAt' => DateTime::now(), 'projectId' => $project->getId(), 'schedule' => $schedule, diff --git a/tests/e2e/Services/Schedules/SchedulesBase.php b/tests/e2e/Services/Schedules/SchedulesBase.php index 01a7860de4..93d24a572d 100644 --- a/tests/e2e/Services/Schedules/SchedulesBase.php +++ b/tests/e2e/Services/Schedules/SchedulesBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Schedules; use Tests\E2E\Client; +use Utopia\Database\Helpers\ID; trait SchedulesBase { @@ -29,4 +30,18 @@ trait SchedulesBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), $params); } + + protected function createFunction(array $params = []): array + { + return $this->client->call(Client::METHOD_POST, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), array_merge([ + 'functionId' => ID::unique(), + 'name' => 'Test Schedule Function', + 'runtime' => 'node-22', + 'entrypoint' => 'index.js', + 'execute' => ['any'], + ], $params)); + } } diff --git a/tests/e2e/Services/Schedules/SchedulesCustomServerTest.php b/tests/e2e/Services/Schedules/SchedulesCustomServerTest.php index e4f9fd855b..aff48202fa 100644 --- a/tests/e2e/Services/Schedules/SchedulesCustomServerTest.php +++ b/tests/e2e/Services/Schedules/SchedulesCustomServerTest.php @@ -19,9 +19,13 @@ class SchedulesCustomServerTest extends Scope /** * Test for SUCCESS */ + $function = $this->createFunction(); + $this->assertEquals(201, $function['headers']['status-code']); + $functionId = $function['body']['$id']; + $response = $this->createSchedule([ 'resourceType' => 'function', - 'resourceId' => ID::unique(), + 'resourceId' => $functionId, 'schedule' => '0 0 * * *', 'active' => true, ]); @@ -31,39 +35,44 @@ class SchedulesCustomServerTest extends Scope $this->assertNotEmpty($response['body']['$createdAt']); $this->assertNotEmpty($response['body']['$updatedAt']); $this->assertEquals('function', $response['body']['resourceType']); - $this->assertNotEmpty($response['body']['resourceId']); + $this->assertEquals($functionId, $response['body']['resourceId']); $this->assertNotEmpty($response['body']['resourceUpdatedAt']); $this->assertNotEmpty($response['body']['projectId']); $this->assertEquals('0 0 * * *', $response['body']['schedule']); $this->assertTrue($response['body']['active']); $this->assertNotEmpty($response['body']['region']); - return ['scheduleId' => $response['body']['$id']]; + return ['scheduleId' => $response['body']['$id'], 'functionId' => $functionId]; } - public function testCreateScheduleExecutionType(): void + public function testCreateScheduleResourceNotFound(): void { + // Function not found + $response = $this->createSchedule([ + 'resourceType' => 'function', + 'resourceId' => ID::unique(), + 'schedule' => '0 0 * * *', + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + // Execution not found $response = $this->createSchedule([ 'resourceType' => 'execution', 'resourceId' => ID::unique(), 'schedule' => '*/10 * * * *', ]); - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals('execution', $response['body']['resourceType']); - $this->assertFalse($response['body']['active']); - } + $this->assertEquals(404, $response['headers']['status-code']); - public function testCreateScheduleMessageType(): void - { + // Message not found $response = $this->createSchedule([ 'resourceType' => 'message', 'resourceId' => ID::unique(), 'schedule' => '0 9 * * 1', ]); - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertEquals('message', $response['body']['resourceType']); + $this->assertEquals(404, $response['headers']['status-code']); } public function testCreateScheduleInvalidResourceType(): void @@ -220,10 +229,13 @@ class SchedulesCustomServerTest extends Scope public function testScheduleProjectIsolation(): void { - // Create a schedule in the current project + // Create a function and schedule in the current project + $function = $this->createFunction(); + $this->assertEquals(201, $function['headers']['status-code']); + $response = $this->createSchedule([ 'resourceType' => 'function', - 'resourceId' => ID::unique(), + 'resourceId' => $function['body']['$id'], 'schedule' => '0 12 * * *', ]);