Merge branch '1.9.x' into add-codex-plugin

This commit is contained in:
Aditya Oberai
2026-05-08 09:34:43 +00:00
221 changed files with 8698 additions and 2464 deletions
+7
View File
@@ -37,6 +37,13 @@ class Authentik extends OAuth2
return 'authentik';
}
public function verifyCredentials(): void
{
if (empty($this->getAuthentikDomain())) {
throw new \Exception('Authentik endpoint is required.');
}
}
/**
* @return string
*/
+7
View File
@@ -37,6 +37,13 @@ class FusionAuth extends OAuth2
return 'fusionauth';
}
public function verifyCredentials(): void
{
if (empty($this->getFusionAuthDomain())) {
throw new \Exception('FusionAuth endpoint is required.');
}
}
/**
* @return string
*/
+11
View File
@@ -37,6 +37,17 @@ class Keycloak extends OAuth2
return 'keycloak';
}
public function verifyCredentials(): void
{
if (empty($this->getKeycloakDomain())) {
throw new \Exception('Keycloak endpoint is required.');
}
if (empty($this->getKeycloakRealm())) {
throw new \Exception('Keycloak realm name is required.');
}
}
/**
* @return string
*/
+9 -2
View File
@@ -36,6 +36,13 @@ class Microsoft extends OAuth2
return 'microsoft';
}
public function verifyCredentials(): void
{
if (empty($this->getTenantID())) {
throw new \Exception('Microsoft tenant is required.');
}
}
/**
* @return string
*/
@@ -201,7 +208,7 @@ class Microsoft extends OAuth2
}
/**
* Extracts the Tenant Id from the JSON stored in appSecret. Defaults to 'common' as a fallback
* Extracts the Tenant Id from the JSON stored in appSecret.
*
* @return string
*/
@@ -209,6 +216,6 @@ class Microsoft extends OAuth2
{
$secret = $this->getAppSecret();
return $secret['tenantID'] ?? 'common';
return $secret['tenantID'] ?? '';
}
}
-146
View File
@@ -1,146 +0,0 @@
<?php
namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Build extends Event
{
protected string $type = '';
protected ?Document $resource = null;
protected ?Document $deployment = null;
protected ?Document $template = null;
public function __construct(protected Publisher $publisher)
{
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
->setClass(System::getEnv('_APP_BUILDS_CLASS_NAME', Event::BUILDS_CLASS_NAME));
}
/**
* Sets template for the build event.
*
* @param Document $template
* @return self
*/
public function setTemplate(Document $template): self
{
$this->template = $template;
return $this;
}
/**
* Sets resource document for the build event.
*
* @param Document $resource
* @return self
*/
public function setResource(Document $resource): self
{
$this->resource = $resource;
return $this;
}
/**
* Returns set resource document for the build event.
*
* @return null|Document
*/
public function getResource(): ?Document
{
return $this->resource;
}
/**
* Sets deployment for the build event.
*
* @param Document $deployment
* @return self
*/
public function setDeployment(Document $deployment): self
{
$this->deployment = $deployment;
return $this;
}
/**
* Returns set deployment for the build event.
*
* @return null|Document
*/
public function getDeployment(): ?Document
{
return $this->deployment;
}
/**
* Sets type for the build event.
*
* @param string $type Can be `BUILD_TYPE_DEPLOYMENT` or `BUILD_TYPE_RETRY`.
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Returns set type for the function event.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Prepare payload for queue.
*
* @return array
*/
protected function preparePayload(): array
{
$platform = $this->platform;
if (empty($platform)) {
$platform = Config::getParam('platform', []);
}
return [
'project' => $this->project,
'resource' => $this->resource,
'deployment' => $this->deployment,
'type' => $this->type,
'template' => $this->template,
'platform' => $platform,
];
}
/**
* Resets event.
*
* @return self
*/
public function reset(): self
{
$this->type = '';
$this->resource = null;
$this->deployment = null;
$this->template = null;
$this->platform = [];
parent::reset();
return $this;
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace Appwrite\Event\Message;
use Utopia\Config\Config;
use Utopia\Database\Document;
final class Build extends Base
{
public function __construct(
public readonly Document $project,
public readonly Document $resource,
public readonly Document $deployment,
public readonly string $type,
public readonly ?Document $template = null,
public readonly array $platform = [],
) {
}
public function toArray(): array
{
$platform = !empty($this->platform) ? $this->platform : Config::getParam('platform', []);
return [
'project' => $this->project->getArrayCopy(),
'resource' => $this->resource->getArrayCopy(),
'deployment' => $this->deployment->getArrayCopy(),
'type' => $this->type,
'template' => $this->template?->getArrayCopy(),
'platform' => $platform,
];
}
public static function fromArray(array $data): static
{
return new self(
project: new Document($data['project'] ?? []),
resource: new Document($data['resource'] ?? []),
deployment: new Document($data['deployment'] ?? []),
type: $data['type'] ?? '',
template: !empty($data['template']) ? new Document($data['template']) : null,
platform: $data['platform'] ?? [],
);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Appwrite\Event\Publisher;
use Appwrite\Event\Message\Build as BuildMessage;
use Utopia\Queue\Publisher;
use Utopia\Queue\Queue;
readonly class Build extends Base
{
public function __construct(
Publisher $publisher,
protected Queue $queue
) {
parent::__construct($publisher);
}
public function enqueue(BuildMessage $message, ?Queue $queue = null): string|bool
{
return $this->publish($queue ?? $this->queue, $message);
}
public function getSize(bool $failed = false, ?Queue $queue = null): int
{
return $this->getQueueSize($queue ?? $this->queue, $failed);
}
}
+6
View File
@@ -178,6 +178,7 @@ class Exception extends \Exception
public const string FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
public const string FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing';
public const string FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout';
public const string FUNCTION_ASYNCHRONOUS_TIMEOUT = 'function_asynchronous_timeout';
public const string FUNCTION_TEMPLATE_NOT_FOUND = 'function_template_not_found';
public const string FUNCTION_RUNTIME_NOT_DETECTED = 'function_runtime_not_detected';
public const string FUNCTION_EXECUTE_PERMISSION_MISSING = 'function_execute_permission_missing';
@@ -192,6 +193,7 @@ class Exception extends \Exception
public const string BUILD_ALREADY_COMPLETED = 'build_already_completed';
public const string BUILD_CANCELED = 'build_canceled';
public const string BUILD_FAILED = 'build_failed';
public const string BUILD_TIMEOUT = 'build_timeout';
/** Execution */
public const string EXECUTION_NOT_FOUND = 'execution_not_found';
@@ -346,6 +348,10 @@ class Exception extends \Exception
public const string MIGRATION_IN_PROGRESS = 'migration_in_progress';
public const string MIGRATION_PROVIDER_ERROR = 'migration_provider_error';
public const string MIGRATION_DATABASE_TYPE_UNSUPPORTED = 'migration_database_type_unsupported';
public const string MIGRATION_SOURCE_PROJECT_ID_REQUIRED = 'migration_source_project_id_required';
public const string MIGRATION_SOURCE_PROJECT_NOT_FOUND = 'migration_source_project_not_found';
public const string MIGRATION_SOURCE_TYPE_INVALID = 'migration_source_type_invalid';
public const string MIGRATION_DESTINATION_TYPE_INVALID = 'migration_destination_type_invalid';
/** Realtime */
public const string REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid';
+1
View File
@@ -96,6 +96,7 @@ abstract class Migration
'1.9.1' => 'V24',
'1.9.2' => 'V24',
'1.9.3' => 'V24',
'1.9.4' => 'V24',
];
/**
+20 -13
View File
@@ -2,7 +2,8 @@
namespace Appwrite\Platform\Modules\Compute;
use Appwrite\Event\Build;
use Appwrite\Event\Message\Build as BuildMessage;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use Appwrite\Platform\Action;
@@ -57,7 +58,7 @@ class Base extends Action
return $allowedSpecifications[0] ?? APP_COMPUTE_SPECIFICATION_DEFAULT;
}
public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document
public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, BuildPublisher $publisherForBuilds, Document $template, GitHub $github, bool $activate, array $platform = [], string $referenceType = 'branch', string $reference = ''): Document
{
$deploymentId = ID::unique();
$entrypoint = $function->getAttribute('entrypoint', '');
@@ -150,16 +151,19 @@ class Base extends Action
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
$publisherForBuilds->enqueue(new BuildMessage(
project: $project,
resource: $function,
deployment: $deployment,
type: BUILD_TYPE_DEPLOYMENT,
template: $template,
platform: $platform,
));
return $deployment;
}
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, Authorization $authorization, array $platform, string $referenceType = 'branch', string $reference = ''): Document
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, BuildPublisher $publisherForBuilds, Document $template, GitHub $github, bool $activate, Authorization $authorization, array $platform, string $referenceType = 'branch', string $reference = ''): Document
{
$deploymentId = ID::unique();
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
@@ -358,11 +362,14 @@ class Base extends Action
$this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform, $authorization);
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment)
->setTemplate($template);
$publisherForBuilds->enqueue(new BuildMessage(
project: $project,
resource: $site,
deployment: $deployment,
type: BUILD_TYPE_DEPLOYMENT,
template: $template,
platform: $platform,
));
return $deployment;
}
@@ -54,9 +54,9 @@ class XList extends Action
$actions = OAuth2Base::getProviderActions();
$providers = [];
foreach ($actions as $providerId => $updateClass) {
$config = $providersConfig[$providerId] ?? null;
if ($config === null) {
foreach ($providersConfig as $providerId => $config) {
$updateClass = $actions[$providerId] ?? null;
if ($updateClass === null) {
continue;
}
if (!($config['enabled'] ?? false)) {
@@ -18,21 +18,21 @@ class XList extends Action
public static function getName(): string
{
return 'listKeyScopes';
return 'listConsoleProjectScopes';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/console/scopes/key')
->desc('List key scopes')
->setHttpPath('/v1/console/scopes/project')
->desc('List project scopes')
->groups(['api'])
->label('scope', 'public')
->label('sdk', new Method(
namespace: 'console',
group: 'console',
name: 'listKeyScopes',
name: 'listProjectScopes',
description: 'List all scopes available for project API keys, along with a description for each scope.',
auth: [AuthType::ADMIN],
responses: [
@@ -56,6 +56,8 @@ class XList extends Action
$scopes[] = new Document([
'$id' => $scopeId,
'description' => $scope['description'] ?? '',
'category' => $scope['category'] ?? '',
'deprecated' => $scope['deprecated'] ?? false,
]);
}
@@ -241,6 +241,10 @@ abstract class Action extends UtopiaAction
? UtopiaResponse::MODEL_ATTRIBUTE_INTEGER
: UtopiaResponse::MODEL_COLUMN_INTEGER,
Database::VAR_BIGINT => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_BIGINT
: UtopiaResponse::MODEL_COLUMN_BIGINT,
Database::VAR_FLOAT => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_FLOAT
: UtopiaResponse::MODEL_COLUMN_FLOAT,
@@ -540,6 +544,7 @@ abstract class Action extends UtopiaAction
switch ($attribute->getAttribute('format')) {
case APP_DATABASE_ATTRIBUTE_INT_RANGE:
case APP_DATABASE_ATTRIBUTE_BIGINT_RANGE:
case APP_DATABASE_ATTRIBUTE_FLOAT_RANGE:
$min ??= $attribute->getAttribute('formatOptions')['min'];
$max ??= $attribute->getAttribute('formatOptions')['max'];
@@ -548,14 +553,15 @@ abstract class Action extends UtopiaAction
throw new Exception($this->getInvalidValueException(), 'Minimum value must be lesser than maximum value');
}
if ($attribute->getAttribute('format') === APP_DATABASE_ATTRIBUTE_INT_RANGE) {
$validator = new Range($min, $max, Database::VAR_INTEGER);
} else {
if ($attribute->getAttribute('format') === APP_DATABASE_ATTRIBUTE_FLOAT_RANGE) {
$validator = new Range($min, $max, Database::VAR_FLOAT);
if (!is_null($default)) {
$default = \floatval($default);
}
} else {
// intRange and bigintRange share the same integer range semantics
$validator = new Range($min, $max, Range::TYPE_INTEGER);
}
if (!is_null($default) && !$validator->isValid($default)) {
@@ -0,0 +1,117 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\BigInt;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
use Utopia\Validator\Range;
class Create extends Action
{
public static function getName(): string
{
return 'createBigIntAttribute';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_ATTRIBUTE_BIGINT;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/bigint')
->desc('Create bigint attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('audits.event', 'attribute.create')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: $this->getSDKNamespace(),
group: $this->getSDKGroup(),
name: self::getName(),
description: '/docs/references/databases/create-bigint-attribute.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
],
deprecated: new Deprecated(
since: '1.8.0',
replaceWith: 'tablesDB.createBigIntColumn',
),
))
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new Nullable(new Integer(false, 64)), 'Minimum value', true)
->param('max', null, new Nullable(new Integer(false, 64)), 'Maximum value', true)
->param('default', null, new Nullable(new Integer(false, 64)), 'Default value. Cannot be set when attribute is required.', true)
->param('array', false, new Boolean(), 'Is attribute an array?', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->inject('authorization')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
{
$min ??= \PHP_INT_MIN;
$max ??= \PHP_INT_MAX;
if ($min > $max) {
throw new Exception($this->getInvalidValueException(), 'Minimum value must be lesser than maximum value');
}
$validator = new Range($min, $max, Range::TYPE_INTEGER);
if (!\is_null($default) && !$validator->isValid($default)) {
throw new Exception($this->getInvalidValueException(), $validator->getDescription());
}
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_BIGINT,
'size' => 8,
'required' => $required,
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_BIGINT_RANGE,
'formatOptions' => ['min' => $min, 'max' => $max],
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
$formatOptions = $attribute->getAttribute('formatOptions', []);
if (!empty($formatOptions)) {
$attribute->setAttribute('min', \intval($formatOptions['min']));
$attribute->setAttribute('max', \intval($formatOptions['max']));
}
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}
@@ -0,0 +1,106 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\BigInt;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
class Update extends Action
{
public static function getName(): string
{
return 'updateBigIntAttribute';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_ATTRIBUTE_BIGINT;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/bigint/:key')
->desc('Update bigint attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
->label('audits.event', 'attribute.update')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: $this->getSDKNamespace(),
group: $this->getSDKGroup(),
name: self::getName(),
description: '/docs/references/databases/update-bigint-attribute.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON,
deprecated: new Deprecated(
since: '1.8.0',
replaceWith: 'tablesDB.updateBigIntColumn',
),
))
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new Nullable(new Integer(false, 64)), 'Minimum value', true)
->param('max', null, new Nullable(new Integer(false, 64)), 'Maximum value', true)
->param('default', null, new Nullable(new Integer(false, 64)), 'Default value. Cannot be set when attribute is required.')
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New Attribute Key.', true, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('authorization')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
{
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_BIGINT,
default: $default,
required: $required,
min: $min,
max: $max,
newKey: $newKey
);
$formatOptions = $attribute->getAttribute('formatOptions', []);
if (!empty($formatOptions)) {
$attribute->setAttribute('min', \intval($formatOptions['min']));
$attribute->setAttribute('max', \intval($formatOptions['max']));
}
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}
@@ -290,13 +290,15 @@ class Create extends Action
}
if (isset($attribute['min']) || isset($attribute['max'])) {
$format = $type === Database::VAR_INTEGER
? APP_DATABASE_ATTRIBUTE_INT_RANGE
: APP_DATABASE_ATTRIBUTE_FLOAT_RANGE;
$format = match($type) {
Database::VAR_INTEGER => APP_DATABASE_ATTRIBUTE_INT_RANGE,
Database::VAR_BIGINT => APP_DATABASE_ATTRIBUTE_BIGINT_RANGE,
default => APP_DATABASE_ATTRIBUTE_FLOAT_RANGE,
};
$formatOptions = [
'min' => $attribute['min'] ?? ($type === Database::VAR_INTEGER ? \PHP_INT_MIN : -\PHP_FLOAT_MAX),
'max' => $attribute['max'] ?? ($type === Database::VAR_INTEGER ? \PHP_INT_MAX : \PHP_FLOAT_MAX),
'min' => $attribute['min'] ?? ($type === Database::VAR_INTEGER || $type === Database::VAR_BIGINT ? \PHP_INT_MIN : -\PHP_FLOAT_MAX),
'max' => $attribute['max'] ?? ($type === Database::VAR_INTEGER || $type === Database::VAR_BIGINT ? \PHP_INT_MAX : \PHP_FLOAT_MAX),
];
}
@@ -0,0 +1,70 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\BigInt;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\BigInt\Create as BigIntCreate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
class Create extends BigIntCreate
{
public static function getName(): string
{
return 'createBigIntColumn';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_COLUMN_BIGINT;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/bigint')
->desc('Create bigint column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSDKNamespace(),
group: $this->getSDKGroup(),
name: self::getName(),
description: '/docs/references/tablesdb/create-bigint-column.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('tableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Table ID.', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Column Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is column required?')
->param('min', null, new Nullable(new Integer(false, 64)), 'Minimum value', true)
->param('max', null, new Nullable(new Integer(false, 64)), 'Maximum value', true)
->param('default', null, new Nullable(new Integer(false, 64)), 'Default value. Cannot be set when column is required.', true)
->param('array', false, new Boolean(), 'Is column an array?', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->inject('authorization')
->callback($this->action(...));
}
}
@@ -0,0 +1,71 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\BigInt;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\BigInt\Update as BigIntUpdate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
class Update extends BigIntUpdate
{
public static function getName(): string
{
return 'updateBigIntColumn';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_COLUMN_BIGINT;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/bigint/:key')
->desc('Update bigint column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSDKNamespace(),
group: $this->getSDKGroup(),
name: self::getName(),
description: '/docs/references/tablesdb/update-bigint-column.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('tableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Table ID.', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Column Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is column required?')
->param('min', null, new Nullable(new Integer(false, 64)), 'Minimum value', true)
->param('max', null, new Nullable(new Integer(false, 64)), 'Maximum value', true)
->param('default', null, new Nullable(new Integer(false, 64)), 'Default value. Cannot be set when column is required.')
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New Column Key.', true, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('authorization')
->callback($this->action(...));
}
}
@@ -34,7 +34,7 @@ class Create extends BooleanCreate
->desc('Create boolean column')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -34,7 +34,7 @@ class Update extends BooleanUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/boolean/:key')
->desc('Update boolean column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -34,7 +34,7 @@ class Create extends DatetimeCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/datetime')
->desc('Create datetime column')
->groups(['api', 'database'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -35,7 +35,7 @@ class Update extends DatetimeUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/datetime/:key')
->desc('Update dateTime column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -33,7 +33,7 @@ class Delete extends AttributesDelete
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/:key')
->desc('Delete column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.delete')
@@ -34,7 +34,7 @@ class Create extends EmailCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/email')
->desc('Create email column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -35,7 +35,7 @@ class Update extends EmailUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/email/:key')
->desc('Update email column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -35,7 +35,7 @@ class Create extends EnumCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/enum')
->desc('Create enum column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -36,7 +36,7 @@ class Update extends EnumUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/enum/:key')
->desc('Update enum column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -34,7 +34,7 @@ class Create extends FloatCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/float')
->desc('Create float column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -35,7 +35,7 @@ class Update extends FloatUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/float/:key')
->desc('Update float column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -42,7 +42,7 @@ class Get extends AttributesGet
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/:key')
->desc('Get column')
->groups(['api', 'database'])
->label('scope', ['tables.read', 'collections.read'])
->label('scope', ['tables.read', 'collections.read', 'columns.read', 'attributes.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSDKNamespace(),
@@ -34,7 +34,7 @@ class Create extends IPCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/ip')
->desc('Create IP address column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -35,7 +35,7 @@ class Update extends IPUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/ip/:key')
->desc('Update IP address column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -34,7 +34,7 @@ class Create extends IntegerCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/integer')
->desc('Create integer column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -35,7 +35,7 @@ class Update extends IntegerUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/integer/:key')
->desc('Update integer column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -35,7 +35,7 @@ class Create extends LineCreate
->desc('Create line column')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -35,7 +35,7 @@ class Update extends LineUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/line/:key')
->desc('Update line column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -33,7 +33,7 @@ class Create extends LongtextCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/longtext')
->desc('Create longtext column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends LongtextUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/longtext/:key')
->desc('Update longtext column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -33,7 +33,7 @@ class Create extends MediumtextCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/mediumtext')
->desc('Create mediumtext column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends MediumtextUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/mediumtext/:key')
->desc('Update mediumtext column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -35,7 +35,7 @@ class Create extends PointCreate
->desc('Create point column')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -35,7 +35,7 @@ class Update extends PointUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/point/:key')
->desc('Update point column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -35,7 +35,7 @@ class Create extends PolygonCreate
->desc('Create polygon column')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -35,7 +35,7 @@ class Update extends PolygonUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/polygon/:key')
->desc('Update polygon column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -34,7 +34,7 @@ class Create extends RelationshipCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/relationship')
->desc('Create relationship column')
->groups(['api', 'database'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends RelationshipUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/:key/relationship')
->desc('Update relationship column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -37,7 +37,7 @@ class Create extends StringCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/string')
->desc('Create string column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -37,7 +37,7 @@ class Update extends StringUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/string/:key')
->desc('Update string column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -33,7 +33,7 @@ class Create extends TextCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/text')
->desc('Create text column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -34,7 +34,7 @@ class Update extends TextUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/text/:key')
->desc('Update text column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -34,7 +34,7 @@ class Create extends URLCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/url')
->desc('Create URL column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -35,7 +35,7 @@ class Update extends URLUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/url/:key')
->desc('Update URL column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -35,7 +35,7 @@ class Create extends VarcharCreate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/varchar')
->desc('Create varchar column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
@@ -36,7 +36,7 @@ class Update extends VarcharUpdate
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/varchar/:key')
->desc('Update varchar column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'columns.write', 'attributes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
@@ -33,7 +33,7 @@ class XList extends AttributesXList
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns')
->desc('List columns')
->groups(['api', 'database'])
->label('scope', ['tables.read', 'collections.read'])
->label('scope', ['tables.read', 'collections.read', 'columns.read', 'attributes.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSDKNamespace(),
@@ -37,7 +37,7 @@ class Create extends IndexCreate
->desc('Create index')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].create')
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'indexes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'index.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
@@ -36,7 +36,7 @@ class Delete extends IndexDelete
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/indexes/:key')
->desc('Delete index')
->groups(['api', 'database'])
->label('scope', ['tables.write', 'collections.write'])
->label('scope', ['tables.write', 'collections.write', 'indexes.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].update')
->label('audits.event', 'index.delete')
@@ -32,7 +32,7 @@ class Get extends IndexGet
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/indexes/:key')
->desc('Get index')
->groups(['api', 'database'])
->label('scope', ['tables.read', 'collections.read'])
->label('scope', ['tables.read', 'collections.read', 'indexes.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSDKNamespace(),
@@ -33,7 +33,7 @@ class XList extends IndexXList
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/indexes')
->desc('List indexes')
->groups(['api', 'database'])
->label('scope', ['tables.read', 'collections.read'])
->label('scope', ['tables.read', 'collections.read', 'indexes.read'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: $this->getSDKNamespace(),
@@ -2,6 +2,8 @@
namespace Appwrite\Platform\Modules\Databases\Services\Registry;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\BigInt\Create as CreateBigIntAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\BigInt\Update as UpdateBigIntAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Boolean\Create as CreateBooleanAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Boolean\Update as UpdateBooleanAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Datetime\Create as CreateDatetimeAttribute;
@@ -171,6 +173,10 @@ class Legacy extends Base
$service->addAction(CreateIntegerAttribute::getName(), new CreateIntegerAttribute());
$service->addAction(UpdateIntegerAttribute::getName(), new UpdateIntegerAttribute());
// Attribute: BigInt
$service->addAction(CreateBigIntAttribute::getName(), new CreateBigIntAttribute());
$service->addAction(UpdateBigIntAttribute::getName(), new UpdateBigIntAttribute());
// Attribute: IP
$service->addAction(CreateIPAttribute::getName(), new CreateIPAttribute());
$service->addAction(UpdateIPAttribute::getName(), new UpdateIPAttribute());
@@ -5,6 +5,8 @@ namespace Appwrite\Platform\Modules\Databases\Services\Registry;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Create as CreateTablesDatabase;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Delete as DeleteTablesDatabase;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Get as GetTablesDatabase;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\BigInt\Create as CreateBigInt;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\BigInt\Update as UpdateBigInt;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Boolean\Create as CreateBoolean;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Boolean\Update as UpdateBoolean;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Datetime\Create as CreateDatetime;
@@ -151,6 +153,10 @@ class TablesDB extends Base
$service->addAction(CreateInteger::getName(), new CreateInteger());
$service->addAction(UpdateInteger::getName(), new UpdateInteger());
// Column: BigInt
$service->addAction(CreateBigInt::getName(), new CreateBigInt());
$service->addAction(UpdateBigInt::getName(), new UpdateBigInt());
// Column: IP
$service->addAction(CreateIP::getName(), new CreateIP());
$service->addAction(UpdateIP::getName(), new UpdateIP());
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Message\Build as BuildMessage;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -87,9 +88,10 @@ class Create extends Action
->inject('project')
->inject('deviceForFunctions')
->inject('deviceForLocal')
->inject('queueForBuilds')
->inject('publisherForBuilds')
->inject('plan')
->inject('authorization')
->inject('platform')
->callback($this->action(...));
}
@@ -106,9 +108,10 @@ class Create extends Action
Document $project,
Device $deviceForFunctions,
Device $deviceForLocal,
Build $queueForBuilds,
BuildPublisher $publisherForBuilds,
array $plan,
Authorization $authorization
Authorization $authorization,
array $platform
) {
$activate = \strval($activate) === 'true' || \strval($activate) === '1';
@@ -175,15 +178,8 @@ class Create extends Action
throw new Exception(Exception::STORAGE_INVALID_CONTENT_RANGE);
}
// TODO remove the condition that checks `$end === $fileSize` in next breaking version
if ($end === $fileSize - 1 || $end === $fileSize) {
//if it's a last chunks the chunk size might differ, so we set the $chunks and $chunk to notify it's last chunk
$chunks = $chunk = -1;
} else {
// Calculate total number of chunks based on the chunk size i.e ($rangeEnd - $rangeStart)
$chunks = (int) ceil($fileSize / ($end + 1 - $start));
$chunk = (int) ($start / ($end + 1 - $start)) + 1;
}
$chunks = (int) ceil($fileSize / APP_LIMIT_UPLOAD_CHUNK_SIZE);
$chunk = (int) ($start / APP_LIMIT_UPLOAD_CHUNK_SIZE) + 1;
}
if (!$fileSizeValidator->isValid($fileSize) && $functionSizeLimit !== 0) { // Check if file size is exceeding allowed limit
@@ -202,15 +198,14 @@ class Create extends Action
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
if (!$deployment->isEmpty()) {
$chunks = $deployment->getAttribute('sourceChunksTotal', 1);
$uploaded = $deployment->getAttribute('sourceChunksUploaded', 0);
$metadata = $deployment->getAttribute('sourceMetadata', []);
if ($chunk === -1) {
$chunk = $chunks;
}
} else {
// Guard against manually setting range header for single chunk upload
if ($chunks === -1) {
$chunks = 1;
$chunk = 1;
if ($uploaded === $chunks) {
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
return;
}
}
@@ -258,6 +253,8 @@ class Create extends Action
'sourcePath' => $path,
'sourceSize' => $fileSize,
'totalSize' => $fileSize,
'sourceChunksTotal' => $chunks,
'sourceChunksUploaded' => $chunksUploaded,
'activate' => $activate,
'sourceMetadata' => $metadata,
'type' => $type
@@ -272,15 +269,19 @@ class Create extends Action
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, new Document([
'sourceSize' => $fileSize,
'sourceChunksUploaded' => $chunksUploaded,
'sourceMetadata' => $metadata,
]));
}
// Start the build
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment);
$publisherForBuilds->enqueue(new BuildMessage(
project: $project,
resource: $function,
deployment: $deployment,
type: BUILD_TYPE_DEPLOYMENT,
platform: $platform,
));
} else {
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Duplicate;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Message\Build as BuildMessage;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -61,8 +62,10 @@ class Create extends Action
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('publisherForBuilds')
->inject('deviceForFunctions')
->inject('project')
->inject('platform')
->callback($this->action(...));
}
@@ -73,8 +76,10 @@ class Create extends Action
Response $response,
Database $dbForProject,
Event $queueForEvents,
Build $queueForBuilds,
Device $deviceForFunctions
BuildPublisher $publisherForBuilds,
Device $deviceForFunctions,
Document $project,
array $platform
) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -127,10 +132,13 @@ class Create extends Action
'latestDeploymentStatus' => $function->getAttribute('latestDeploymentStatus'),
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment);
$publisherForBuilds->enqueue(new BuildMessage(
project: $project,
resource: $function,
deployment: $deployment,
type: BUILD_TYPE_DEPLOYMENT,
platform: $platform,
));
$queueForEvents
->setParam('functionId', $function->getId())
@@ -2,8 +2,9 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Template;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Message\Build as BuildMessage;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
@@ -76,9 +77,10 @@ class Create extends Base
->inject('dbForPlatform')
->inject('queueForEvents')
->inject('project')
->inject('queueForBuilds')
->inject('publisherForBuilds')
->inject('gitHub')
->inject('authorization')
->inject('platform')
->callback($this->action(...));
}
@@ -96,9 +98,10 @@ class Create extends Base
Database $dbForPlatform,
Event $queueForEvents,
Document $project,
Build $queueForBuilds,
BuildPublisher $publisherForBuilds,
GitHub $github,
Authorization $authorization
Authorization $authorization,
array $platform
) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -127,10 +130,11 @@ class Create extends Base
project: $project,
installation: $installation,
dbForProject: $dbForProject,
queueForBuilds: $queueForBuilds,
publisherForBuilds: $publisherForBuilds,
template: $template,
github: $github,
activate: $activate,
platform: $platform,
referenceType: $type,
reference: $reference
);
@@ -184,11 +188,14 @@ class Create extends Base
$this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform, $authorization);
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
$publisherForBuilds->enqueue(new BuildMessage(
project: $project,
resource: $function,
deployment: $deployment,
type: BUILD_TYPE_DEPLOYMENT,
template: $template,
platform: $platform,
));
$queueForEvents
->setParam('functionId', $function->getId())
@@ -2,8 +2,8 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Vcs;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
@@ -70,8 +70,9 @@ class Create extends Base
->inject('dbForPlatform')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('publisherForBuilds')
->inject('gitHub')
->inject('platform')
->callback($this->action(...));
}
@@ -86,8 +87,9 @@ class Create extends Base
Database $dbForPlatform,
Document $project,
Event $queueForEvents,
Build $queueForBuilds,
BuildPublisher $publisherForBuilds,
GitHub $github,
array $platform,
) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -105,10 +107,11 @@ class Create extends Base
project: $project,
installation: $installation,
dbForProject: $dbForProject,
queueForBuilds: $queueForBuilds,
publisherForBuilds: $publisherForBuilds,
template: $template,
github: $github,
activate: $activate,
platform: $platform,
reference: $reference,
referenceType: $type
);
@@ -17,6 +17,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Usage\Context;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Response;
use Executor\Exception\Timeout as ExecutorTimeout;
use Executor\Executor;
use MaxMind\Db\Reader;
use Utopia\Auth\Proofs\Token;
@@ -60,7 +61,7 @@ class Create extends Base
->setHttpPath('/v1/functions/:functionId/executions')
->desc('Create execution')
->groups(['api', 'functions'])
->label('scope', 'execution.write')
->label('scope', ['executions.write', 'execution.write'])
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'functions.[functionId].executions.[executionId].create')
->label('sdk', new Method(
@@ -417,25 +418,29 @@ class Create extends Base
$source = $deployment->getAttribute('buildPath', '');
$extension = str_ends_with($source, '.tar') ? 'tar' : 'tar.gz';
$command = $version === 'v2' ? '' : "cp /tmp/code.$extension /mnt/code/code.$extension && nohup helpers/start.sh \"$command\"";
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
source: $source,
entrypoint: $deployment->getAttribute('entrypoint', ''),
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
try {
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
source: $source,
entrypoint: $deployment->getAttribute('entrypoint', ''),
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
} catch (ExecutorTimeout $th) {
throw new AppwriteException(AppwriteException::FUNCTION_SYNCHRONOUS_TIMEOUT, previous: $th);
}
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
@@ -35,7 +35,7 @@ class Delete extends Base
->setHttpPath('/v1/functions/:functionId/executions/:executionId')
->desc('Delete execution')
->groups(['api', 'functions'])
->label('scope', 'execution.write')
->label('scope', ['executions.write', 'execution.write'])
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'functions.[functionId].executions.[executionId].delete')
->label('audits.event', 'executions.delete')
@@ -31,7 +31,7 @@ class Get extends Base
->setHttpPath('/v1/functions/:functionId/executions/:executionId')
->desc('Get execution')
->groups(['api', 'functions'])
->label('scope', 'execution.read')
->label('scope', ['executions.read', 'execution.read'])
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('sdk', new Method(
namespace: 'functions',
@@ -39,7 +39,7 @@ class XList extends Base
->setHttpPath('/v1/functions/:functionId/executions')
->desc('List executions')
->groups(['api', 'functions'])
->label('scope', 'execution.read')
->label('scope', ['executions.read', 'execution.read'])
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('sdk', new Method(
namespace: 'functions',
@@ -2,9 +2,10 @@
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Message\Build as BuildMessage;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Event\Realtime;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Event\Webhook;
@@ -115,7 +116,7 @@ class Create extends Base
->inject('timelimit')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('publisherForBuilds')
->inject('queueForRealtime')
->inject('queueForWebhooks')
->inject('queueForFunctions')
@@ -157,7 +158,7 @@ class Create extends Base
callable $timelimit,
Document $project,
Event $queueForEvents,
Build $queueForBuilds,
BuildPublisher $publisherForBuilds,
Realtime $queueForRealtime,
Webhook $queueForWebhooks,
Func $queueForFunctions,
@@ -326,10 +327,11 @@ class Create extends Base
project: $project,
installation: $installation,
dbForProject: $dbForProject,
queueForBuilds: $queueForBuilds,
publisherForBuilds: $publisherForBuilds,
template: $template,
github: $github,
activate: true,
platform: $platform,
reference: $providerBranch,
referenceType: 'branch'
);
@@ -367,11 +369,14 @@ class Create extends Base
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
$publisherForBuilds->enqueue(new BuildMessage(
project: $project,
resource: $function,
deployment: $deployment,
type: BUILD_TYPE_DEPLOYMENT,
template: $template,
platform: $platform,
));
}
$functionsDomain = $platform['functionsDomain'];
@@ -2,8 +2,8 @@
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
@@ -105,11 +105,12 @@ class Update extends Base
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('publisherForBuilds')
->inject('dbForPlatform')
->inject('gitHub')
->inject('executor')
->inject('authorization')
->inject('platform')
->callback($this->action(...));
}
@@ -139,11 +140,12 @@ class Update extends Base
Database $dbForProject,
Document $project,
Event $queueForEvents,
Build $queueForBuilds,
BuildPublisher $publisherForBuilds,
Database $dbForPlatform,
GitHub $github,
Executor $executor,
Authorization $authorization
Authorization $authorization,
array $platform
) {
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
@@ -281,7 +283,7 @@ class Update extends Base
// Redeploy logic
if (!$isConnected && !empty($providerRepositoryId)) {
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true);
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $publisherForBuilds, new Document(), $github, true, $platform);
}
// Inform scheduler if function is still active
@@ -2,11 +2,13 @@
namespace Appwrite\Platform\Modules\Functions\Http\Variables;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -38,6 +40,7 @@ class Create extends Base
->groups(['api', 'functions'])
->label('scope', 'functions.write')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'variables.[variableId].create')
->label('audits.event', 'variable.create')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk', new Method(
@@ -56,10 +59,12 @@ class Create extends Base
]
))
->param('functionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Function unique ID.', false, ['dbForProject'])
->param('variableId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Variable ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true)
->inject('response')
->inject('queueForEvents')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
@@ -69,10 +74,12 @@ class Create extends Base
public function action(
string $functionId,
string $variableId,
string $key,
string $value,
bool $secret,
Response $response,
QueueEvent $queueForEvents,
Database $dbForProject,
Database $dbForPlatform,
Document $project,
@@ -84,7 +91,7 @@ class Create extends Base
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$variableId = ID::unique();
$variableId = ($variableId === 'unique()') ? ID::unique() : $variableId;
$teamId = $project->getAttribute('teamId', '');
$variable = new Document([
@@ -120,6 +127,8 @@ class Create extends Base
'active' => $schedule->getAttribute('active'),
])));
$queueForEvents->setParam('variableId', $variable->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($variable, Response::MODEL_VARIABLE);
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Variables;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
@@ -35,6 +36,7 @@ class Delete extends Base
->groups(['api', 'functions'])
->label('scope', 'functions.write')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'variables.[variableId].delete')
->label('audits.event', 'variable.delete')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk', new Method(
@@ -56,6 +58,7 @@ class Delete extends Base
->param('functionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Function unique ID.', false, ['dbForProject'])
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->inject('response')
->inject('queueForEvents')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
@@ -66,6 +69,7 @@ class Delete extends Base
string $functionId,
string $variableId,
Response $response,
QueueEvent $queueForEvents,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization
@@ -98,6 +102,8 @@ class Delete extends Base
'active' => $schedule->getAttribute('active'),
])));
$queueForEvents->setParam('variableId', $variable->getId());
$response->noContent();
}
}
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Variables;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
@@ -38,6 +39,7 @@ class Update extends Base
->groups(['api', 'functions'])
->label('scope', 'functions.write')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'variables.[variableId].update')
->label('audits.event', 'variable.update')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk', new Method(
@@ -57,10 +59,11 @@ class Update extends Base
))
->param('functionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Function unique ID.', false, ['dbForProject'])
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('key', null, new Nullable(new Text(255, 0)), 'Variable key. Max length: 255 chars.', true)
->param('value', null, new Nullable(new Text(8192, 0)), 'Variable value. Max length: 8192 chars.', true)
->param('secret', null, new Nullable(new Boolean()), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true)
->inject('response')
->inject('queueForEvents')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
@@ -70,10 +73,11 @@ class Update extends Base
public function action(
string $functionId,
string $variableId,
string $key,
?string $key,
?string $value,
?bool $secret,
Response $response,
QueueEvent $queueForEvents,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization
@@ -93,19 +97,27 @@ class Update extends Base
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
}
$variable
->setAttribute('key', $key)
->setAttribute('value', $value ?? $variable->getAttribute('value'))
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function']));
if (\is_null($key) && \is_null($value) && \is_null($secret)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID);
}
$updates = new Document();
if (!\is_null($key)) {
$updates->setAttribute('key', $key);
$updates->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function']));
}
if (!\is_null($value)) {
$updates->setAttribute('value', $value);
}
if (!\is_null($secret)) {
$updates->setAttribute('secret', $secret);
}
try {
$dbForProject->updateDocument('variables', $variable->getId(), new Document([
'key' => $key,
'value' => $value ?? $variable->getAttribute('value'),
'secret' => $secret ?? $variable->getAttribute('secret'),
'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']),
]));
$variable = $dbForProject->updateDocument('variables', $variable->getId(), $updates);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
}
@@ -125,6 +137,8 @@ class Update extends Base
'active' => $schedule->getAttribute('active'),
])));
$queueForEvents->setParam('variableId', $variable->getId());
$response->dynamic($variable, Response::MODEL_VARIABLE);
}
}
@@ -7,12 +7,18 @@ use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Variables;
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\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
class XList extends Base
{
@@ -51,22 +57,74 @@ class XList extends Base
)
)
->param('functionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Function unique ID.', false, ['dbForProject'])
->param('queries', [], new Variables(), '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(', ', Variables::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('dbForProject')
->callback($this->action(...));
}
public function action(string $functionId, Response $response, Database $dbForProject)
{
/**
* @param array<string> $queries
*/
public function action(
string $functionId,
array $queries,
bool $includeTotal,
Response $response,
Database $dbForProject
) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries[] = Query::equal('resourceType', ['function']);
$queries[] = Query::equal('resourceInternalId', [$function->getSequence()]);
$queries[] = Query::orderAsc();
$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());
}
$variableId = $cursor->getValue();
$cursorDocument = $dbForProject->findOne('variables', [
Query::equal('$id', [$variableId]),
Query::equal('resourceType', ['function']),
Query::equal('resourceInternalId', [$function->getSequence()]),
]);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Variable '{$variableId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$variables = $dbForProject->find('variables', $queries);
$total = $includeTotal ? $dbForProject->count('variables', $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([
'variables' => $function->getAttribute('vars', []),
'total' => \count($function->getAttribute('vars', [])),
'variables' => $variables,
'total' => $total,
]), Response::MODEL_VARIABLE_LIST);
}
}
@@ -10,11 +10,13 @@ use Appwrite\Event\Publisher\Screenshot;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use Appwrite\Usage\Context;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Vcs\Comment;
use Exception;
use Executor\Exception\Timeout as ExecutorTimeout;
use Executor\Executor;
use Swoole\Coroutine as Co;
use Utopia\Cache\Cache;
@@ -34,6 +36,7 @@ use Utopia\Detector\Detector\Rendering;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Span\Span;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\System\System;
@@ -183,6 +186,12 @@ class Builds extends Action
array $platform,
int $timeout
): void {
Span::add('projectId', $project->getId());
Span::add('resourceId', $resource->getId());
Span::add('resourceType', $resource->getCollection());
Span::add('deploymentId', $deployment->getId());
Span::add('timeout', $timeout);
Console::info('Deployment action started');
$startTime = DateTime::now();
@@ -223,8 +232,12 @@ class Builds extends Action
$version = $this->getVersion($resource);
$runtime = $this->getRuntime($resource, $version);
Span::add('runtime', $resource->getAttribute($resource->getCollection() === 'sites' ? 'buildRuntime' : 'runtime', ''));
Span::add('version', $version);
$spec = Config::getParam('specifications')[$resource->getAttribute('buildSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
Span::add('cpus', (float) ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT));
Span::add('memory', (int) ($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT));
// Realtime preparation
$event = "{$resource->getCollection()}.[{$resourceKey}].deployments.[deploymentId].update";
@@ -720,6 +733,9 @@ class Builds extends Action
);
Console::log('createRuntime finished');
} catch (ExecutorTimeout $error) {
Console::warning('createRuntime timed out');
$err = new AppwriteException(AppwriteException::BUILD_TIMEOUT, previous: $error);
} catch (\Throwable $error) {
Console::warning('createRuntime failed');
$err = $error;
@@ -1147,13 +1163,11 @@ class Builds extends Action
$message = \str_replace('{APPWRITE_DETECTION_SEPARATOR_START}', '', $message);
$message = \str_replace('{APPWRITE_DETECTION_SEPARATOR_END}', '', $message);
// Combine with previous logs if deployment got past build process
$previousLogs = '';
if (! is_null($deployment->getAttribute('buildSize', null))) {
$previousLogs = $deployment->getAttribute('buildLogs', '');
if (! empty($previousLogs)) {
$message = $previousLogs . "\n" . $message;
}
// Append error to whatever build logs were already streamed
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$previousLogs = $deployment->getAttribute('buildLogs', '');
if (! empty($previousLogs)) {
$message = $previousLogs . "\n" . $message;
}
$endTime = DateTime::now();
@@ -2,7 +2,7 @@
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Builds;
use Appwrite\Event\Build;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -42,16 +42,16 @@ class Get extends Base
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queueForBuilds')
->inject('publisherForBuilds')
->inject('response')
->callback($this->action(...));
}
public function action(int|string $threshold, Build $queueForBuilds, Response $response): void
public function action(int|string $threshold, BuildPublisher $publisherForBuilds, Response $response): void
{
$threshold = (int) $threshold;
$size = $queueForBuilds->getSize();
$size = $publisherForBuilds->getSize();
$this->assertQueueThreshold($size, $threshold);
@@ -2,7 +2,6 @@
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Failed;
use Appwrite\Event\Build;
use Appwrite\Event\Database;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
@@ -10,6 +9,7 @@ use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Publisher\Audit;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Event\Publisher\Certificate;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Event\Publisher\Screenshot;
@@ -83,7 +83,7 @@ class Get extends Base
->inject('publisherForUsage')
->inject('queueForWebhooks')
->inject('publisherForCertificates')
->inject('queueForBuilds')
->inject('publisherForBuilds')
->inject('queueForMessaging')
->inject('publisherForMigrations')
->inject('publisherForScreenshots')
@@ -103,7 +103,7 @@ class Get extends Base
UsagePublisher $publisherForUsage,
Webhook $queueForWebhooks,
Certificate $publisherForCertificates,
Build $queueForBuilds,
BuildPublisher $publisherForBuilds,
Messaging $queueForMessaging,
MigrationPublisher $publisherForMigrations,
Screenshot $publisherForScreenshots,
@@ -120,7 +120,7 @@ class Get extends Base
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME) => $publisherForUsage,
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME) => $queueForWebhooks,
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $publisherForCertificates,
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $queueForBuilds,
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $publisherForBuilds,
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME) => $publisherForScreenshots,
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $publisherForMigrations,
@@ -13,6 +13,7 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\UID;
use Utopia\Migration\Destinations\OnDuplicate;
use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
@@ -57,6 +58,7 @@ class Create extends Action
->param('endpoint', '', new URL(), 'Source Appwrite endpoint')
->param('projectId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Source Project ID', false, ['dbForProject'])
->param('apiKey', '', new Text(512), 'Source API Key')
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true)
->inject('response')
->inject('dbForProject')
->inject('project')
@@ -71,6 +73,7 @@ class Create extends Action
string $endpoint,
string $projectId,
string $apiKey,
string $onDuplicate,
Response $response,
Database $dbForProject,
Document $project,
@@ -93,6 +96,9 @@ class Create extends Action
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
'options' => [
'onDuplicate' => $onDuplicate,
],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
@@ -20,6 +20,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Migration\Destinations\OnDuplicate;
use Utopia\Migration\Resource;
use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Migration\Sources\CSV;
@@ -29,6 +30,7 @@ use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\WhiteList;
class Create extends Action
{
@@ -67,6 +69,7 @@ class Create extends Action
->param('fileId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'File ID.', false, ['dbForProject'])
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true)
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
@@ -85,6 +88,7 @@ class Create extends Action
string $fileId,
string $resourceId,
bool $internalFile,
string $onDuplicate,
Response $response,
Database $dbForProject,
Database $dbForPlatform,
@@ -183,6 +187,7 @@ class Create extends Action
'options' => [
'path' => $newPath,
'size' => $fileSize,
'onDuplicate' => $onDuplicate,
],
]));
@@ -20,6 +20,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Migration\Destinations\OnDuplicate;
use Utopia\Migration\Resource;
use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Migration\Sources\JSON as JSONSource;
@@ -29,6 +30,7 @@ use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\WhiteList;
class Create extends Action
{
@@ -66,6 +68,7 @@ class Create extends Action
->param('fileId', '', new UID(), 'File ID.')
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true)
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
@@ -84,6 +87,7 @@ class Create extends Action
string $fileId,
string $resourceId,
bool $internalFile,
string $onDuplicate,
Response $response,
Database $dbForProject,
Database $dbForPlatform,
@@ -183,6 +187,7 @@ class Create extends Action
'options' => [
'path' => $newPath,
'size' => $fileSize,
'onDuplicate' => $onDuplicate,
],
]));
@@ -1,6 +1,6 @@
<?php
namespace Appwrite\Platform\Modules\Project\Http\Project\Keys\Standard;
namespace Appwrite\Platform\Modules\Project\Http\Project\Keys;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
@@ -30,17 +30,16 @@ class Create extends Base
public static function getName()
{
return 'createStandardProjectKey';
return 'createProjectKey';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/project/keys/standard')
->httpAlias('/v1/project/keys')
->setHttpPath('/v1/project/keys')
->httpAlias('/v1/projects/:projectId/keys')
->desc('Create standard project key')
->desc('Create project key')
->groups(['api', 'project'])
->label('scope', 'keys.write')
->label('event', 'keys.[keyId].create')
@@ -49,9 +48,9 @@ class Create extends Base
->label('sdk', new Method(
namespace: 'project',
group: 'keys',
name: 'createStandardKey',
name: 'createKey',
description: <<<EOT
Create a new standard API key. It's recommended to have multiple API keys with strict scopes for separate functions within your project.
Create a new API key. It's recommended to have multiple API keys with strict scopes for separate functions within your project.
You can also create an ephemeral API key if you need a short-lived key instead.
EOT,
@@ -59,7 +59,7 @@ class Create extends Base
],
))
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false)
->param('duration', 900, new Range(1, 3600), 'Time in seconds before ephemeral key expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->param('duration', null, new Range(1, 3600), 'Time in seconds before ephemeral key expires. Maximum duration is 3600 seconds.', optional: false, example: 600)
->inject('response')
->inject('queueForEvents')
->inject('project')
@@ -146,13 +146,14 @@ class Update extends Base
{
$providerId = static::getProviderId();
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
$storedSecret = $this->decodeStoredSecret($project);
return new Document([
'$id' => $providerId,
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
'keyId' => '',
'teamId' => '',
'keyId' => $storedSecret['keyID'] ?? '',
'teamId' => $storedSecret['teamID'] ?? '',
'p8File' => '',
]);
}
@@ -105,7 +105,7 @@ class Update extends Base
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('endpoint', '', new Text(256, 1), 'Domain of Authentik instance. For example: example.authentik.com', optional: false)
->param('endpoint', null, new Nullable(new Text(256, 0)), 'Domain of Authentik instance. For example: example.authentik.com', optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
@@ -138,7 +138,7 @@ class Update extends Base
public function handle(
?string $clientId,
?string $clientSecret,
string $endpoint,
?string $endpoint,
?bool $enabled,
Response $response,
Database $dbForPlatform,
@@ -151,7 +151,7 @@ class Update extends Base
// The secret is stored as JSON `{"clientSecret": "...", "authentikDomain": "..."}`
// to match the shape Authentik's OAuth2 adapter expects (getAuthentikDomain()).
// The `endpoint` param is required on every call, so it's always written.
// The `endpoint` param is optional; if omitted, the existing stored endpoint is preserved.
// `clientSecret` is optional; if omitted, the existing stored secret is preserved.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
@@ -160,7 +160,7 @@ class Update extends Base
}
$encodedSecret = \json_encode([
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'authentikDomain' => $endpoint,
'authentikDomain' => $endpoint ?? ($existing['authentikDomain'] ?? ''),
]);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
@@ -105,7 +105,7 @@ class Update extends Base
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('endpoint', '', new Text(256, 1), 'Domain of FusionAuth instance. For example: example.fusionauth.io', optional: false)
->param('endpoint', null, new Nullable(new Text(256, 0)), 'Domain of FusionAuth instance. For example: example.fusionauth.io', optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
@@ -138,7 +138,7 @@ class Update extends Base
public function handle(
?string $clientId,
?string $clientSecret,
string $endpoint,
?string $endpoint,
?bool $enabled,
Response $response,
Database $dbForPlatform,
@@ -151,7 +151,7 @@ class Update extends Base
// The secret is stored as JSON `{"clientSecret": "...", "fusionAuthDomain": "..."}`
// to match the shape FusionAuth's OAuth2 adapter expects (getFusionAuthDomain()).
// The `endpoint` param is required on every call, so it's always written.
// The `endpoint` param is optional; if omitted, the existing stored endpoint is preserved.
// `clientSecret` is optional; if omitted, the existing stored secret is preserved.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
@@ -160,7 +160,7 @@ class Update extends Base
}
$encodedSecret = \json_encode([
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'fusionAuthDomain' => $endpoint,
'fusionAuthDomain' => $endpoint ?? ($existing['fusionAuthDomain'] ?? ''),
]);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
@@ -11,7 +11,7 @@ use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
class Get extends Action
{
@@ -86,28 +86,28 @@ class Get extends Action
)
]
))
->param('provider', '', new Text(128), 'OAuth2 provider key. For example: github, google, apple.')
->param('providerId', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders', [])), true), 'OAuth2 provider key. For example: github, google, apple.', aliases: ['provider'])
->inject('response')
->inject('project')
->callback($this->action(...));
}
public function action(
string $provider,
string $providerId,
Response $response,
Document $project,
): void {
$providers = Config::getParam('oAuthProviders', []);
if (!\array_key_exists($provider, $providers) || !($providers[$provider]['enabled'] ?? false)) {
if (!\array_key_exists($providerId, $providers) || !($providers[$providerId]['enabled'] ?? false)) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$actions = Base::getProviderActions();
if (!isset($actions[$provider])) {
if (!isset($actions[$providerId])) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$updateClass = $actions[$provider];
$updateClass = $actions[$providerId];
$action = new $updateClass();
$response->dynamic($action->buildReadResponse($project), $updateClass::getResponseModel());
@@ -35,7 +35,7 @@ class Update extends Base
public static function getClientIdName(): string
{
return 'OAuth 2 app Client ID, or App ID';
return 'OAuth2 app Client ID, or App ID';
}
public static function getClientIdExample(): string
@@ -111,8 +111,8 @@ class Update extends Base
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('endpoint', '', new Text(256, 1), 'Domain of Keycloak instance. For example: keycloak.example.com', optional: false)
->param('realmName', '', new Text(256, 1), 'Keycloak realm name. For example: appwrite-realm', optional: false)
->param('endpoint', null, new Nullable(new Text(256, 0)), 'Domain of Keycloak instance. For example: keycloak.example.com', optional: true)
->param('realmName', null, new Nullable(new Text(256, 0)), 'Keycloak realm name. For example: appwrite-realm', optional: true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
@@ -147,8 +147,8 @@ class Update extends Base
public function handle(
?string $clientId,
?string $clientSecret,
string $endpoint,
string $realmName,
?string $endpoint,
?string $realmName,
?bool $enabled,
Response $response,
Database $dbForPlatform,
@@ -161,7 +161,7 @@ class Update extends Base
// The secret is stored as JSON `{"clientSecret": "...", "keycloakDomain": "...", "keycloakRealm": "..."}`
// to match the shape Keycloak's OAuth2 adapter expects (getKeycloakDomain(), getKeycloakRealm()).
// The `endpoint` and `realmName` params are required on every call, so they're always written.
// The `endpoint` and `realmName` params are optional; if omitted, existing stored values are preserved.
// `clientSecret` is optional; if omitted, the existing stored secret is preserved.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
@@ -170,8 +170,8 @@ class Update extends Base
}
$encodedSecret = \json_encode([
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'keycloakDomain' => $endpoint,
'keycloakRealm' => $realmName,
'keycloakDomain' => $endpoint ?? ($existing['keycloakDomain'] ?? ''),
'keycloakRealm' => $realmName ?? ($existing['keycloakRealm'] ?? ''),
]);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
@@ -115,7 +115,7 @@ class Update extends Base
))
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('tenant', '', new Text(256, 1), 'Microsoft Entra ID tenant identifier. Use \'common\', \'organizations\', \'consumers\' or a specific tenant ID. For example: common', optional: false)
->param('tenant', null, new Nullable(new Text(256, 0)), 'Microsoft Entra ID tenant identifier. Use \'common\', \'organizations\', \'consumers\' or a specific tenant ID. For example: common', true)
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
@@ -148,7 +148,7 @@ class Update extends Base
public function handle(
?string $applicationId,
?string $applicationSecret,
string $tenant,
?string $tenant,
?bool $enabled,
Response $response,
Database $dbForPlatform,
@@ -161,7 +161,7 @@ class Update extends Base
// The secret is stored as JSON `{"clientSecret": "...", "tenantID": "..."}`
// to match the shape Microsoft's OAuth2 adapter expects (getTenantID()).
// The `tenant` param is required on every call, so it's always written.
// The `tenant` param is optional; if omitted, the existing stored tenant is preserved.
// `applicationSecret` is optional; if omitted, the existing stored secret is preserved.
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
$existing = [];
@@ -170,7 +170,7 @@ class Update extends Base
}
$encodedSecret = \json_encode([
'clientSecret' => $applicationSecret ?? ($existing['clientSecret'] ?? ''),
'tenantID' => $tenant,
'tenantID' => $tenant ?? ($existing['tenantID'] ?? ''),
]);
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $applicationId, $encodedSecret, $enabled);
@@ -82,13 +82,13 @@ class Update extends Base
'hint' => '',
],
[
'$id' => 'tokenUrl',
'$id' => 'tokenURL',
'name' => 'Token URL',
'example' => 'https://myoauth.com/oauth2/token',
'hint' => '',
],
[
'$id' => 'userInfoUrl',
'$id' => 'userInfoURL',
'name' => 'User Info URL',
'example' => 'https://myoauth.com/oauth2/userinfo',
'hint' => '',
@@ -127,8 +127,8 @@ class Update extends Base
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
->param('wellKnownURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect well-known configuration URL. When provided, authorization, token, and user info endpoints can be discovered automatically. For example: https://myoauth.com/.well-known/openid-configuration', optional: true)
->param('authorizationURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect authorization endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/authorize', optional: true)
->param('tokenUrl', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect token endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/token', optional: true)
->param('userInfoUrl', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect user info endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/userinfo', optional: true)
->param('tokenURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect token endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/token', optional: true, aliases: ['tokenUrl'])
->param('userInfoURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect user info endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/userinfo', optional: true, aliases: ['userInfoUrl'])
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
->inject('response')
->inject('dbForPlatform')
@@ -151,8 +151,8 @@ class Update extends Base
static::getClientSecretParamName() => '',
'wellKnownURL' => $decoded['wellKnownEndpoint'] ?? '',
'authorizationURL' => $decoded['authorizationEndpoint'] ?? '',
'tokenUrl' => $decoded['tokenEndpoint'] ?? '',
'userInfoUrl' => $decoded['userInfoEndpoint'] ?? '',
'tokenURL' => $decoded['tokenEndpoint'] ?? '',
'userInfoURL' => $decoded['userInfoEndpoint'] ?? '',
]);
}
@@ -174,8 +174,8 @@ class Update extends Base
?string $clientSecret,
?string $wellKnownURL,
?string $authorizationURL,
?string $tokenUrl,
?string $userInfoUrl,
?string $tokenURL,
?string $userInfoURL,
?bool $enabled,
Response $response,
Database $dbForPlatform,
@@ -201,8 +201,8 @@ class Update extends Base
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
'wellKnownEndpoint' => $wellKnownURL ?? ($existing['wellKnownEndpoint'] ?? ''),
'authorizationEndpoint' => $authorizationURL ?? ($existing['authorizationEndpoint'] ?? ''),
'tokenEndpoint' => $tokenUrl ?? ($existing['tokenEndpoint'] ?? ''),
'userInfoEndpoint' => $userInfoUrl ?? ($existing['userInfoEndpoint'] ?? ''),
'tokenEndpoint' => $tokenURL ?? ($existing['tokenEndpoint'] ?? ''),
'userInfoEndpoint' => $userInfoURL ?? ($existing['userInfoEndpoint'] ?? ''),
];
// When enabling, require either wellKnownEndpoint alone, or all three
@@ -215,7 +215,7 @@ class Update extends Base
&& !empty($merged['userInfoEndpoint']);
if (!$hasWellKnown && !$hasAllDiscovery) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Enabling OpenID Connect requires either wellKnownURL, or all of authorizationURL, tokenUrl, and userInfoUrl.');
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Enabling OpenID Connect requires either wellKnownURL, or all of authorizationURL, tokenURL, and userInfoURL.');
}
}
@@ -2,14 +2,21 @@
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
class XList extends Action
{
@@ -43,15 +50,28 @@ class XList extends Action
)
]
))
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', 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')
->callback($this->action(...));
}
/**
* @param array<string> $queries
*/
public function action(
array $queries,
bool $includeTotal,
Response $response,
Document $project,
): void {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$providers = Config::getParam('oAuthProviders', []);
$actions = Base::getProviderActions();
@@ -66,8 +86,16 @@ class XList extends Action
$documents[] = $action->buildReadResponse($project);
}
$total = $includeTotal ? \count($documents) : 0;
$grouped = Query::groupByType($queries);
$offset = $grouped['offset'] ?? 0;
$limit = $grouped['limit'] ?? null;
$documents = \array_slice($documents, $offset, $limit);
$response->dynamic(new Document([
'total' => \count($documents),
'total' => $total,
'providers' => $documents,
]), Response::MODEL_OAUTH2_PROVIDER_LIST);
}
@@ -27,7 +27,7 @@ class Get extends Action
->setHttpPath('/v1/project/policies/:policyId')
->desc('Get project policy')
->groups(['api', 'project'])
->label('scope', 'policies.read')
->label('scope', ['policies.read', 'project.policies.read'])
->label('sdk', new Method(
namespace: 'project',
group: 'policies',
@@ -31,7 +31,7 @@ class Update extends Action
->httpAlias('/v1/projects/:projectId/auth/memberships-privacy')
->desc('Update membership privacy policy')
->groups(['api', 'project'])
->label('scope', 'policies.write')
->label('scope', ['policies.write', 'project.policies.write'])
->label('event', 'projects.[projectId].policies.[policy].update')
->label('audits.event', 'projects.[projectId].policies.[policy].update')
->label('audits.resource', 'project/{response.$id}')
@@ -31,7 +31,7 @@ class Update extends Action
->httpAlias('/v1/projects/:projectId/auth/password-dictionary')
->desc('Update password dictionary policy')
->groups(['api', 'project'])
->label('scope', 'policies.write')
->label('scope', ['policies.write', 'project.policies.write'])
->label('event', 'projects.[projectId].policies.[policy].update')
->label('audits.event', 'projects.[projectId].policies.[policy].update')
->label('audits.resource', 'project/{response.$id}')
@@ -32,7 +32,7 @@ class Update extends Action
->httpAlias('/v1/projects/:projectId/auth/password-history')
->desc('Update password history policy')
->groups(['api', 'project'])
->label('scope', 'policies.write')
->label('scope', ['policies.write', 'project.policies.write'])
->label('event', 'projects.[projectId].policies.[policy].update')
->label('audits.event', 'projects.[projectId].policies.[policy].update')
->label('audits.resource', 'project/{response.$id}')
@@ -31,7 +31,7 @@ class Update extends Action
->httpAlias('/v1/projects/:projectId/auth/personal-data')
->desc('Update password personal data policy')
->groups(['api', 'project'])
->label('scope', 'policies.write')
->label('scope', ['policies.write', 'project.policies.write'])
->label('event', 'projects.[projectId].policies.[policy].update')
->label('audits.event', 'projects.[projectId].policies.[policy].update')
->label('audits.resource', 'project/{response.$id}')
@@ -31,7 +31,7 @@ class Update extends Action
->httpAlias('/v1/projects/:projectId/auth/session-alerts')
->desc('Update session alert policy')
->groups(['api', 'project'])
->label('scope', 'policies.write')
->label('scope', ['policies.write', 'project.policies.write'])
->label('event', 'projects.[projectId].policies.[policy].update')
->label('audits.event', 'projects.[projectId].policies.[policy].update')
->label('audits.resource', 'project/{response.$id}')
@@ -31,7 +31,7 @@ class Update extends Action
->httpAlias('/v1/projects/:projectId/auth/duration')
->desc('Update session duration policy')
->groups(['api', 'project'])
->label('scope', 'policies.write')
->label('scope', ['policies.write', 'project.policies.write'])
->label('event', 'projects.[projectId].policies.[policy].update')
->label('audits.event', 'projects.[projectId].policies.[policy].update')
->label('audits.resource', 'project/{response.$id}')
@@ -31,7 +31,7 @@ class Update extends Action
->httpAlias('/v1/projects/:projectId/auth/session-invalidation')
->desc('Update session invalidation policy')
->groups(['api', 'project'])
->label('scope', 'policies.write')
->label('scope', ['policies.write', 'project.policies.write'])
->label('event', 'projects.[projectId].policies.[policy].update')
->label('audits.event', 'projects.[projectId].policies.[policy].update')
->label('audits.resource', 'project/{response.$id}')

Some files were not shown because too many files have changed in this diff Show More