diff --git a/src/Appwrite/Platform/Modules/Schedules/Http/Schedules/Create.php b/src/Appwrite/Platform/Modules/Schedules/Http/Schedules/Create.php index eabc382131..4214778566 100644 --- a/src/Appwrite/Platform/Modules/Schedules/Http/Schedules/Create.php +++ b/src/Appwrite/Platform/Modules/Schedules/Http/Schedules/Create.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Modules\Schedules\Http\Schedules; +use Appwrite\Event\Event; use Appwrite\Extend\Exception; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; @@ -17,6 +18,7 @@ use Utopia\Database\Validator\UID; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; +use Utopia\Validator\JSON; use Utopia\Validator\WhiteList; class Create extends Action @@ -65,6 +67,7 @@ class Create extends Action ->desc('Create schedule') ->groups(['api', 'projects']) ->label('scope', 'schedules.write') + ->label('event', 'schedules.[scheduleId].create') ->label('audits.event', 'schedule.create') ->label('audits.resource', 'schedule/{response.$id}') ->label('sdk', new Method( @@ -77,18 +80,20 @@ class Create extends Action new SDKResponse( code: Response::STATUS_CODE_CREATED, model: Response::MODEL_SCHEDULE, - ) + ), ], )) - ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('resourceType', '', new WhiteList($resourceTypes, true), 'The resource type for the schedule. Possible values: ' . implode(', ', $resourceTypes) . '.') - ->param('resourceId', '', new UID(), 'The resource ID to associate with this schedule.') - ->param('schedule', '', new Cron(), 'Schedule CRON expression.') - ->param('active', false, new Boolean(), 'Whether the schedule is active.', true) + ->param('projectId', '', new UID, 'Project unique ID.') + ->param('resourceType', '', new WhiteList($resourceTypes, true), 'The resource type for the schedule. Possible values: '.implode(', ', $resourceTypes).'.') + ->param('resourceId', '', new UID, 'The resource ID to associate with this schedule.') + ->param('schedule', '', new Cron, 'Schedule CRON expression.') + ->param('active', false, new Boolean, 'Whether the schedule is active.', true) + ->param('data', null, new JSON, 'Schedule data as a JSON string. Used to store resource-specific context needed for execution.', true) ->inject('response') ->inject('dbForPlatform') ->inject('getProjectDB') ->inject('authorization') + ->inject('queueForEvents') ->callback($this->action(...)); } @@ -98,10 +103,12 @@ class Create extends Action string $resourceId, string $schedule, bool $active, + ?string $data, Response $response, Database $dbForPlatform, callable $getProjectDB, Authorization $authorization, + Event $queueForEvents, ): void { $project = $dbForPlatform->getDocument('projects', $projectId); @@ -112,7 +119,7 @@ class Create extends Action $dbForProject = $getProjectDB($project); $collectionMap = $this->getCollectionMap(); - $collection = $collectionMap[$resourceType] ?? throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid resource type: ' . $resourceType); + $collection = $collectionMap[$resourceType] ?? throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid resource type: '.$resourceType); $resource = $dbForProject->getDocument($collection, $resourceId); @@ -121,23 +128,31 @@ class Create extends Action throw new Exception($notFoundMap[$resourceType] ?? Exception::GENERAL_ARGUMENT_INVALID, 'Resource not found'); } + $attributes = [ + 'region' => $project->getAttribute('region'), + 'resourceType' => $resourceType, + 'resourceId' => $resourceId, + 'resourceInternalId' => $resource->getSequence(), + 'resourceUpdatedAt' => DateTime::now(), + 'projectId' => $project->getId(), + 'schedule' => $schedule, + 'active' => $active, + ]; + + if ($data !== null) { + $attributes['data'] = \json_decode($data, true); + } + try { $doc = $authorization->skip( - fn () => $dbForPlatform->createDocument('schedules', new Document([ - 'region' => $project->getAttribute('region'), - 'resourceType' => $resourceType, - 'resourceId' => $resourceId, - 'resourceInternalId' => $resource->getSequence(), - 'resourceUpdatedAt' => DateTime::now(), - 'projectId' => $project->getId(), - 'schedule' => $schedule, - 'active' => $active, - ])) + fn () => $dbForPlatform->createDocument('schedules', new Document($attributes)) ); } catch (DuplicateException) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to create schedule. Please try again.'); + throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); } + $queueForEvents->setParam('scheduleId', $doc->getId()); + $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($doc, Response::MODEL_SCHEDULE); diff --git a/tests/e2e/Services/Schedules/SchedulesBase.php b/tests/e2e/Services/Schedules/SchedulesBase.php index cd8f424f8d..98f822fd23 100644 --- a/tests/e2e/Services/Schedules/SchedulesBase.php +++ b/tests/e2e/Services/Schedules/SchedulesBase.php @@ -34,7 +34,7 @@ trait SchedulesBase $projectId = $project['body']['$id']; - $key = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', array_merge([ + $key = $this->client->call(Client::METHOD_POST, '/projects/'.$projectId.'/keys', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ diff --git a/tests/e2e/Services/Schedules/SchedulesConsoleClientTest.php b/tests/e2e/Services/Schedules/SchedulesConsoleClientTest.php index 8d9d556f12..99a7571340 100644 --- a/tests/e2e/Services/Schedules/SchedulesConsoleClientTest.php +++ b/tests/e2e/Services/Schedules/SchedulesConsoleClientTest.php @@ -12,8 +12,8 @@ use Utopia\System\System; class SchedulesConsoleClientTest extends Scope { - use SchedulesBase; use ProjectConsole; + use SchedulesBase; use SideClient; /** @@ -42,7 +42,7 @@ class SchedulesConsoleClientTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); $functionId = $function['body']['$id']; - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -74,7 +74,7 @@ class SchedulesConsoleClientTest extends Scope */ // Resource not found - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -86,7 +86,7 @@ class SchedulesConsoleClientTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); // Invalid resource type - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -98,7 +98,7 @@ class SchedulesConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); // Invalid cron - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -110,7 +110,7 @@ class SchedulesConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); // Missing resourceType - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -121,7 +121,7 @@ class SchedulesConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); // Missing resourceId - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -132,7 +132,7 @@ class SchedulesConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); // Missing schedule - $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -156,7 +156,7 @@ class SchedulesConsoleClientTest extends Scope /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules/' . $scheduleId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules/'.$scheduleId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -171,7 +171,7 @@ class SchedulesConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules/error', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules/'.ID::unique(), array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -191,7 +191,7 @@ class SchedulesConsoleClientTest extends Scope /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -214,7 +214,7 @@ class SchedulesConsoleClientTest extends Scope $this->assertArrayHasKey('region', $schedule); /** Filter by resourceType */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -229,7 +229,7 @@ class SchedulesConsoleClientTest extends Scope } /** Filter by active status */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -243,7 +243,7 @@ class SchedulesConsoleClientTest extends Scope } /** List with total disabled */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -257,7 +257,7 @@ class SchedulesConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -301,7 +301,7 @@ class SchedulesConsoleClientTest extends Scope $otherProjectId = $otherProject['body']['$id']; // Try to get the schedule from the other project - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $otherProjectId . '/schedules/' . $scheduleId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$otherProjectId.'/schedules/'.$scheduleId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -309,7 +309,7 @@ class SchedulesConsoleClientTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); // List should not include schedules from other projects - $response = $this->client->call(Client::METHOD_GET, '/projects/' . $otherProjectId . '/schedules', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/'.$otherProjectId.'/schedules', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []);