Add schedules API endpoints (GET, XList, Create)

This commit is contained in:
Prem Palanisamy
2026-02-13 12:01:21 +00:00
parent 77fd2c1f24
commit 64ed422277
14 changed files with 460 additions and 0 deletions
+6
View File
@@ -145,6 +145,12 @@ return [ // List of publicly visible scopes
'rules.write' => [
'description' => 'Access to create, update, and delete your project\'s proxy rules',
],
'schedules.read' => [
'description' => 'Access to read your project\'s schedules',
],
'schedules.write' => [
'description' => 'Access to create, update, and delete your project\'s schedules',
],
'migrations.read' => [
'description' => 'Access to read your project\'s migrations',
],
+3
View File
@@ -114,6 +114,7 @@ use Appwrite\Utopia\Response\Model\ResourceToken;
use Appwrite\Utopia\Response\Model\Row;
use Appwrite\Utopia\Response\Model\Rule;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\Schedule;
use Appwrite\Utopia\Response\Model\Session;
use Appwrite\Utopia\Response\Model\Site;
use Appwrite\Utopia\Response\Model\Specification;
@@ -198,6 +199,7 @@ Response::setModel(new BaseList('Metric List', Response::MODEL_METRIC_LIST, 'met
Response::setModel(new BaseList('Variables List', Response::MODEL_VARIABLE_LIST, 'variables', Response::MODEL_VARIABLE));
Response::setModel(new BaseList('Status List', Response::MODEL_HEALTH_STATUS_LIST, 'statuses', Response::MODEL_HEALTH_STATUS));
Response::setModel(new BaseList('Rule List', Response::MODEL_PROXY_RULE_LIST, 'rules', Response::MODEL_PROXY_RULE));
Response::setModel(new BaseList('Schedules List', Response::MODEL_SCHEDULE_LIST, 'schedules', Response::MODEL_SCHEDULE));
Response::setModel(new BaseList('Locale codes list', Response::MODEL_LOCALE_CODE_LIST, 'localeCodes', Response::MODEL_LOCALE_CODE));
Response::setModel(new BaseList('Provider list', Response::MODEL_PROVIDER_LIST, 'providers', Response::MODEL_PROVIDER));
Response::setModel(new BaseList('Message list', Response::MODEL_MESSAGE_LIST, 'messages', Response::MODEL_MESSAGE));
@@ -339,6 +341,7 @@ Response::setModel(new UsageProject());
Response::setModel(new Headers());
Response::setModel(new Specification());
Response::setModel(new Rule());
Response::setModel(new Schedule());
Response::setModel(new TemplateSMS());
Response::setModel(new TemplateEmail());
Response::setModel(new ConsoleVariables());
+1
View File
@@ -0,0 +1 @@
Create a new schedule for a resource.
+1
View File
@@ -0,0 +1 @@
Get a schedule by its unique ID.
+1
View File
@@ -0,0 +1 @@
Get a list of all the project's schedules. You can use the query params to filter your results.
+2
View File
@@ -11,6 +11,7 @@ use Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Health;
use Appwrite\Platform\Modules\Projects;
use Appwrite\Platform\Modules\Proxy;
use Appwrite\Platform\Modules\Schedules;
use Appwrite\Platform\Modules\Sites;
use Appwrite\Platform\Modules\Storage;
use Appwrite\Platform\Modules\Tokens;
@@ -28,6 +29,7 @@ class Appwrite extends Platform
$this->addModule(new Projects\Module());
$this->addModule(new Functions\Module());
$this->addModule(new Health\Module());
$this->addModule(new Schedules\Module());
$this->addModule(new Sites\Module());
$this->addModule(new Console\Module());
$this->addModule(new Proxy\Module());
@@ -0,0 +1,96 @@
<?php
namespace Appwrite\Platform\Modules\Schedules\Http\Schedules;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\WhiteList;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createSchedule';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/schedules')
->desc('Create schedule')
->groups(['api', 'schedules'])
->label('scope', 'schedules.write')
->label('audits.event', 'schedule.create')
->label('audits.resource', 'schedule/{response.$id}')
->label('sdk', new Method(
namespace: 'schedules',
group: 'schedules',
name: 'create',
description: '/docs/references/schedules/create.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_SCHEDULE,
)
],
))
->param('resourceType', '', new WhiteList([SCHEDULE_RESOURCE_TYPE_FUNCTION, SCHEDULE_RESOURCE_TYPE_EXECUTION, SCHEDULE_RESOURCE_TYPE_MESSAGE], true), 'The resource type for the schedule. Possible values: ' . implode(', ', [SCHEDULE_RESOURCE_TYPE_FUNCTION, SCHEDULE_RESOURCE_TYPE_EXECUTION, SCHEDULE_RESOURCE_TYPE_MESSAGE]) . '.')
->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)
->inject('response')
->inject('project')
->inject('dbForPlatform')
->inject('authorization')
->callback($this->action(...));
}
public function action(
string $resourceType,
string $resourceId,
string $schedule,
bool $active,
Response $response,
Document $project,
Database $dbForPlatform,
Authorization $authorization,
): void {
try {
$doc = $authorization->skip(
fn () => $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => $resourceType,
'resourceId' => $resourceId,
'resourceInternalId' => '',
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $schedule,
'active' => $active,
]))
);
} catch (DuplicateException) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to create schedule. Please try again.');
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($doc, Response::MODEL_SCHEDULE);
}
}
@@ -0,0 +1,76 @@
<?php
namespace Appwrite\Platform\Modules\Schedules\Http\Schedules;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Action
{
use HTTP;
public static function getName(): string
{
return 'getSchedule';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/schedules/:scheduleId')
->desc('Get schedule')
->groups(['api', 'schedules'])
->label('scope', 'schedules.read')
->label('sdk', new Method(
namespace: 'schedules',
group: 'schedules',
name: 'get',
description: '/docs/references/schedules/get.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_SCHEDULE,
)
]
))
->param('scheduleId', '', new UID(), 'Schedule ID.')
->inject('response')
->inject('project')
->inject('dbForPlatform')
->inject('authorization')
->callback($this->action(...));
}
public function action(
string $scheduleId,
Response $response,
Document $project,
Database $dbForPlatform,
Authorization $authorization,
): void {
$schedule = $authorization->skip(
fn () => $dbForPlatform->getDocument('schedules', $scheduleId)
);
if ($schedule->isEmpty()) {
throw new Exception(Exception::SCHEDULE_NOT_FOUND);
}
if ($schedule->getAttribute('projectId') !== $project->getId()) {
throw new Exception(Exception::SCHEDULE_NOT_FOUND);
}
$response->dynamic($schedule, Response::MODEL_SCHEDULE);
}
}
@@ -0,0 +1,117 @@
<?php
namespace Appwrite\Platform\Modules\Schedules\Http\Schedules;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Schedules;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
class XList extends Action
{
use HTTP;
public static function getName(): string
{
return 'listSchedules';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/schedules')
->desc('List schedules')
->groups(['api', 'schedules'])
->label('scope', 'schedules.read')
->label('sdk', new Method(
namespace: 'schedules',
group: 'schedules',
name: 'list',
description: '/docs/references/schedules/list.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_SCHEDULE_LIST,
)
]
))
->param('queries', [], new Schedules(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Schedules::ALLOWED_ATTRIBUTES), true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('project')
->inject('dbForPlatform')
->inject('authorization')
->callback($this->action(...));
}
public function action(
array $queries,
bool $includeTotal,
Response $response,
Document $project,
Database $dbForPlatform,
Authorization $authorization,
): void {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
// Always scope to the current project
$queries[] = Query::equal('projectId', [$project->getId()]);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$scheduleId = $cursor->getValue();
$cursorDocument = $authorization->skip(
fn () => $dbForPlatform->getDocument('schedules', $scheduleId)
);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Schedule '{$scheduleId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$schedules = $authorization->skip(
fn () => $dbForPlatform->find('schedules', $queries)
);
$total = $includeTotal ? $authorization->skip(
fn () => $dbForPlatform->count('schedules', $filterQueries, APP_LIMIT_COUNT)
) : 0;
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'schedules' => $schedules,
'total' => $total,
]), Response::MODEL_SCHEDULE_LIST);
}
}
@@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Schedules;
use Appwrite\Platform\Modules\Schedules\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -0,0 +1,20 @@
<?php
namespace Appwrite\Platform\Modules\Schedules\Services;
use Appwrite\Platform\Modules\Schedules\Http\Schedules\Create;
use Appwrite\Platform\Modules\Schedules\Http\Schedules\Get;
use Appwrite\Platform\Modules\Schedules\Http\Schedules\XList;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
$this->addAction(Get::getName(), new Get());
$this->addAction(XList::getName(), new XList());
$this->addAction(Create::getName(), new Create());
}
}
@@ -0,0 +1,24 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Schedules extends Base
{
public const ALLOWED_ATTRIBUTES = [
'resourceType',
'resourceId',
'projectId',
'schedule',
'active',
'region',
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('schedules', self::ALLOWED_ATTRIBUTES);
}
}
+4
View File
@@ -223,6 +223,10 @@ class Response extends SwooleResponse
public const MODEL_PROXY_RULE = 'proxyRule';
public const MODEL_PROXY_RULE_LIST = 'proxyRuleList';
// Schedules
public const MODEL_SCHEDULE = 'schedule';
public const MODEL_SCHEDULE_LIST = 'scheduleList';
// Migrations
public const MODEL_MIGRATION = 'migration';
public const MODEL_MIGRATION_LIST = 'migrationList';
@@ -0,0 +1,95 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Schedule extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Schedule ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Schedule creation date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Schedule update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('resourceType', [
'type' => self::TYPE_STRING,
'description' => 'The resource type associated with this schedule.',
'default' => '',
'example' => 'function',
])
->addRule('resourceId', [
'type' => self::TYPE_STRING,
'description' => 'The resource ID associated with this schedule.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('resourceUpdatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'The date the associated resource was last updated in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('projectId', [
'type' => self::TYPE_STRING,
'description' => 'The project ID associated with this schedule.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('schedule', [
'type' => self::TYPE_STRING,
'description' => 'The CRON schedule expression.',
'default' => '',
'example' => '5 4 * * *',
])
->addRule('active', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether the schedule is active.',
'default' => false,
'example' => true,
])
->addRule('region', [
'type' => self::TYPE_STRING,
'description' => 'The region where the schedule is deployed.',
'default' => '',
'example' => 'fra',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Schedule';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_SCHEDULE;
}
}