mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d46ae9b61 | ||
|
|
e377b2de76 | ||
|
|
b2421216e4 | ||
|
|
22cbe11ff1 | ||
|
|
64ed422277 |
@@ -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,
|
||||
|
||||
@@ -91,6 +91,8 @@ $admins = [
|
||||
'subscribers.read',
|
||||
'tokens.read',
|
||||
'tokens.write',
|
||||
'schedules.read',
|
||||
'schedules.write',
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Create a new schedule for a resource.
|
||||
@@ -0,0 +1 @@
|
||||
Get a schedule by its unique ID.
|
||||
@@ -0,0 +1 @@
|
||||
Get a list of all the project's schedules. You can use the query params to filter your results.
|
||||
@@ -35,6 +35,7 @@
|
||||
<directory>./tests/e2e/Services/Webhooks</directory>
|
||||
<directory>./tests/e2e/Services/Messaging</directory>
|
||||
<directory>./tests/e2e/Services/Migrations</directory>
|
||||
<directory>./tests/e2e/Services/Schedules</directory>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsCustomServerTest.php</file>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsCustomClientTest.php</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,114 @@
|
||||
<?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('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $resourceType,
|
||||
string $resourceId,
|
||||
string $schedule,
|
||||
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' => $resource->getSequence(),
|
||||
'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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,8 @@ trait ProjectCustom
|
||||
'subscribers.read',
|
||||
'migrations.write',
|
||||
'migrations.read',
|
||||
'schedules.read',
|
||||
'schedules.write',
|
||||
'tokens.read',
|
||||
'tokens.write',
|
||||
],
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Schedules;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
trait SchedulesBase
|
||||
{
|
||||
protected function createSchedule(array $params = []): array
|
||||
{
|
||||
return $this->client->call(Client::METHOD_POST, '/schedules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), $params);
|
||||
}
|
||||
|
||||
protected function getSchedule(string $scheduleId): array
|
||||
{
|
||||
return $this->client->call(Client::METHOD_GET, '/schedules/' . $scheduleId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
}
|
||||
|
||||
protected function listSchedules(array $params = []): array
|
||||
{
|
||||
return $this->client->call(Client::METHOD_GET, '/schedules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Schedules;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class SchedulesCustomServerTest extends Scope
|
||||
{
|
||||
use SchedulesBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
public function testCreateSchedule(): array
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$function = $this->createFunction();
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$functionId = $function['body']['$id'];
|
||||
|
||||
$response = $this->createSchedule([
|
||||
'resourceType' => 'function',
|
||||
'resourceId' => $functionId,
|
||||
'schedule' => '0 0 * * *',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertNotEmpty($response['body']['$createdAt']);
|
||||
$this->assertNotEmpty($response['body']['$updatedAt']);
|
||||
$this->assertEquals('function', $response['body']['resourceType']);
|
||||
$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'], 'functionId' => $functionId];
|
||||
}
|
||||
|
||||
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(404, $response['headers']['status-code']);
|
||||
|
||||
// Message not found
|
||||
$response = $this->createSchedule([
|
||||
'resourceType' => 'message',
|
||||
'resourceId' => ID::unique(),
|
||||
'schedule' => '0 9 * * 1',
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateScheduleInvalidResourceType(): void
|
||||
{
|
||||
$response = $this->createSchedule([
|
||||
'resourceType' => 'invalid',
|
||||
'resourceId' => ID::unique(),
|
||||
'schedule' => '0 0 * * *',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateScheduleInvalidCron(): void
|
||||
{
|
||||
$response = $this->createSchedule([
|
||||
'resourceType' => 'function',
|
||||
'resourceId' => ID::unique(),
|
||||
'schedule' => 'not-a-cron',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateScheduleMissingRequired(): void
|
||||
{
|
||||
// Missing resourceType
|
||||
$response = $this->createSchedule([
|
||||
'resourceId' => ID::unique(),
|
||||
'schedule' => '0 0 * * *',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// Missing resourceId
|
||||
$response = $this->createSchedule([
|
||||
'resourceType' => 'function',
|
||||
'schedule' => '0 0 * * *',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// Missing schedule
|
||||
$response = $this->createSchedule([
|
||||
'resourceType' => 'function',
|
||||
'resourceId' => ID::unique(),
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateSchedule
|
||||
*/
|
||||
public function testGetSchedule(array $data): void
|
||||
{
|
||||
$scheduleId = $data['scheduleId'];
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->getSchedule($scheduleId);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($scheduleId, $response['body']['$id']);
|
||||
$this->assertEquals('function', $response['body']['resourceType']);
|
||||
$this->assertEquals('0 0 * * *', $response['body']['schedule']);
|
||||
$this->assertTrue($response['body']['active']);
|
||||
}
|
||||
|
||||
public function testGetScheduleNotFound(): void
|
||||
{
|
||||
$response = $this->getSchedule('nonexistent');
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateSchedule
|
||||
*/
|
||||
public function testListSchedules(array $data): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->listSchedules();
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsArray($response['body']['schedules']);
|
||||
$this->assertGreaterThanOrEqual(1, $response['body']['total']);
|
||||
$this->assertGreaterThanOrEqual(1, \count($response['body']['schedules']));
|
||||
|
||||
// Verify schedule structure
|
||||
$schedule = $response['body']['schedules'][0];
|
||||
$this->assertArrayHasKey('$id', $schedule);
|
||||
$this->assertArrayHasKey('$createdAt', $schedule);
|
||||
$this->assertArrayHasKey('$updatedAt', $schedule);
|
||||
$this->assertArrayHasKey('resourceType', $schedule);
|
||||
$this->assertArrayHasKey('resourceId', $schedule);
|
||||
$this->assertArrayHasKey('projectId', $schedule);
|
||||
$this->assertArrayHasKey('schedule', $schedule);
|
||||
$this->assertArrayHasKey('active', $schedule);
|
||||
$this->assertArrayHasKey('region', $schedule);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateSchedule
|
||||
*/
|
||||
public function testListSchedulesWithQuery(array $data): void
|
||||
{
|
||||
// Filter by resourceType
|
||||
$response = $this->listSchedules([
|
||||
'queries' => [Query::equal('resourceType', ['function'])->toString()],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertGreaterThanOrEqual(1, $response['body']['total']);
|
||||
|
||||
foreach ($response['body']['schedules'] as $schedule) {
|
||||
$this->assertEquals('function', $schedule['resourceType']);
|
||||
}
|
||||
|
||||
// Filter by active status
|
||||
$response = $this->listSchedules([
|
||||
'queries' => [Query::equal('active', [true])->toString()],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
foreach ($response['body']['schedules'] as $schedule) {
|
||||
$this->assertTrue($schedule['active']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testListSchedulesWithTotalDisabled(): void
|
||||
{
|
||||
$response = $this->listSchedules([
|
||||
'total' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(0, $response['body']['total']);
|
||||
$this->assertIsArray($response['body']['schedules']);
|
||||
}
|
||||
|
||||
public function testListSchedulesInvalidQuery(): void
|
||||
{
|
||||
$response = $this->listSchedules([
|
||||
'queries' => [Query::equal('nonexistent', ['value'])->toString()],
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testScheduleProjectIsolation(): void
|
||||
{
|
||||
// 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' => $function['body']['$id'],
|
||||
'schedule' => '0 12 * * *',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$scheduleId = $response['body']['$id'];
|
||||
|
||||
// Create a fresh project with its own key
|
||||
$freshProject = $this->getProject(true);
|
||||
|
||||
// Try to get the schedule from the other project
|
||||
$response = $this->client->call(\Tests\E2E\Client::METHOD_GET, '/schedules/' . $scheduleId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $freshProject['$id'],
|
||||
'x-appwrite-key' => $freshProject['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
// List should not include schedules from other projects
|
||||
$response = $this->client->call(\Tests\E2E\Client::METHOD_GET, '/schedules', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $freshProject['$id'],
|
||||
'x-appwrite-key' => $freshProject['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$scheduleIds = array_column($response['body']['schedules'], '$id');
|
||||
$this->assertNotContains($scheduleId, $scheduleIds);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user