update: abstraction over attribute and column apis [WIP].

This commit is contained in:
Darshan 2025-05-07 20:01:46 +05:30
parent 69cc4f0bb6
commit bc8a38ba55
34 changed files with 3182 additions and 10 deletions

View file

@ -0,0 +1,627 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response as UtopiaResponse;
use Throwable;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Index as IndexException;
use Utopia\Database\Exception\Limit as LimitException;
use Utopia\Database\Exception\NotFound as NotFoundException;
use Utopia\Database\Exception\Truncate as TruncateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Structure;
use Utopia\Platform\Action as UtopiaAction;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Range;
abstract class Action extends UtopiaAction
{
/**
* @var string|null The current context (either 'column' or 'attribute')
*/
private ?string $context = DATABASE_ATTRIBUTES_CONTEXT;
/**
* @var string|array|null The current response model for the attribute/column type.
*/
private string|array|null $responseModel = null;
/**
* Set the context to either `column` or `attribute`.
*
* @throws \InvalidArgumentException If the context is invalid.
*/
final protected function setContext(string $context): void
{
if (!\in_array($context, [DATABASE_COLUMNS_CONTEXT, DATABASE_ATTRIBUTES_CONTEXT], true)) {
throw new \InvalidArgumentException("Invalid context '{$context}'. Use `DATABASE_COLUMNS_CONTEXT` or `DATABASE_ATTRIBUTES_CONTEXT`");
}
$this->context = $context;
}
/**
* Get the current context.
*
* @throws \LogicException If context has not been set.
*/
final protected function getContext(): string
{
return $this->context;
}
/**
* Returns true if current context is Collections API.
*/
final protected function isCollectionsAPI(): bool
{
// columns in tables context
// attributes in collections context
return $this->getContext() === DATABASE_ATTRIBUTES_CONTEXT;
}
/**
* Get the SDK group name for the current action.
*
* Can be used for XList operations as well!
*/
final protected function getSdkGroup(): string
{
return $this->isCollectionsAPI() ? 'attributes' : 'columns';
}
/**
* Get the correct parent param key (e.g. `tableId` or `collectionId`)
*/
final protected function getParentEventsParamKey(): string
{
return $this->isCollectionsAPI() ? 'collectionId' : 'tableId';
}
/**
* Get the correct param key (e.g. `attributeId` or `columnId`)
*/
final protected function getEventsParamKey(): string
{
return $this->getContext() . 'Id';
}
/**
* Set the correct response model.
*/
final protected function setResponseModel(string|array $model): void
{
$this->responseModel = $model;
}
/**
* Get the correct response model.
*/
final protected function getResponseModel(): string|array
{
if ($this->responseModel === null) {
throw new \LogicException("Missing response model: you must call setResponseModel() before using it.");
}
return $this->responseModel;
}
/**
* Get the appropriate parent level not found exception.
*/
final protected function getParentNotFoundException(): string
{
return $this->isCollectionsAPI()
? Exception::COLLECTION_NOT_FOUND
: Exception::TABLE_NOT_FOUND;
}
/**
* Get the appropriate not found exception.
*/
final protected function getNotFoundException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_NOT_FOUND
: Exception::COLUMN_NOT_FOUND;
}
/**
* Get the appropriate already exists exception.
*/
final protected function getDuplicateException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_ALREADY_EXISTS
: Exception::COLUMN_ALREADY_EXISTS;
}
/**
* Get the appropriate limit exceeded exception.
*/
final protected function getLimitException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_LIMIT_EXCEEDED
: Exception::COLUMN_LIMIT_EXCEEDED;
}
/**
* Get the correct default unsupported message.
*/
final protected function getDefaultUnsupportedException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED
: Exception::COLUMN_DEFAULT_UNSUPPORTED;
}
/**
* Get the correct format unsupported message.
*/
final protected function getFormatUnsupportedException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_FORMAT_UNSUPPORTED
: Exception::COLUMN_FORMAT_UNSUPPORTED;
}
/**
* Get the exception for invalid type or format mismatch.
*/
final protected function getTypeInvalidException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_TYPE_INVALID
: Exception::COLUMN_TYPE_INVALID;
}
/**
* Get the exception for resizing invalid attributes/columns.
*/
final protected function getInvalidResizeException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_INVALID_RESIZE
: Exception::COLUMN_INVALID_RESIZE;
}
/**
* Get the exception for invalid attributes/columns value.
*/
final protected function getInvalidValueException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_VALUE_INVALID
: Exception::COLUMN_VALUE_INVALID;
}
/**
* Get the exception for non-available column/attribute.
*/
final protected function getNotAvailableException(): string
{
return $this->isCollectionsAPI()
? Exception::ATTRIBUTE_NOT_AVAILABLE
: Exception::COLUMN_NOT_AVAILABLE;
}
/**
* Get the proper column/attribute type based on set context.
*/
final protected function getCorrectModel(string $type, string $format): string
{
$isCollections = $this->isCollectionsAPI();
return match ($type) {
Database::VAR_BOOLEAN => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_BOOLEAN
: UtopiaResponse::MODEL_COLUMN_BOOLEAN,
Database::VAR_INTEGER => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_INTEGER
: UtopiaResponse::MODEL_COLUMN_INTEGER,
Database::VAR_FLOAT => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_FLOAT
: UtopiaResponse::MODEL_COLUMN_FLOAT,
Database::VAR_DATETIME => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_DATETIME
: UtopiaResponse::MODEL_COLUMN_DATETIME,
Database::VAR_RELATIONSHIP => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_RELATIONSHIP
: UtopiaResponse::MODEL_COLUMN_RELATIONSHIP,
Database::VAR_STRING => match ($format) {
APP_DATABASE_ATTRIBUTE_EMAIL => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_EMAIL
: UtopiaResponse::MODEL_COLUMN_EMAIL,
APP_DATABASE_ATTRIBUTE_ENUM => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_ENUM
: UtopiaResponse::MODEL_COLUMN_ENUM,
APP_DATABASE_ATTRIBUTE_IP => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_IP
: UtopiaResponse::MODEL_COLUMN_IP,
APP_DATABASE_ATTRIBUTE_URL => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_URL
: UtopiaResponse::MODEL_COLUMN_URL,
default => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_STRING
: UtopiaResponse::MODEL_COLUMN_STRING,
},
default => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE
: UtopiaResponse::MODEL_COLUMN,
};
}
final protected function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): Document
{
$key = $attribute->getAttribute('key');
$type = $attribute->getAttribute('type', '');
$size = $attribute->getAttribute('size', 0);
$required = $attribute->getAttribute('required', true);
$signed = $attribute->getAttribute('signed', true); // integers are signed by default
$array = $attribute->getAttribute('array', false);
$format = $attribute->getAttribute('format', '');
$formatOptions = $attribute->getAttribute('formatOptions', []);
$filters = $attribute->getAttribute('filters', []); // filters are hidden from the endpoint
$default = $attribute->getAttribute('default');
$options = $attribute->getAttribute('options', []);
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception($this->getParentNotFoundException());
}
if (!empty($format)) {
if (!Structure::hasFormat($format, $type)) {
throw new Exception($this->getFormatUnsupportedException(), "Format {$format} not available for {$type} columns.");
}
}
// Must throw here since dbForProject->createAttribute is performed by db worker
if ($required && isset($default)) {
throw new Exception($this->getDefaultUnsupportedException(), 'Cannot set default value for required ' . $this->getContext());
}
if ($array && isset($default)) {
throw new Exception($this->getDefaultUnsupportedException(), 'Cannot set default value for array ' . $this->getContext() . 's');
}
if ($type === Database::VAR_RELATIONSHIP) {
$options['side'] = Database::RELATION_SIDE_PARENT;
$relatedTable = $dbForProject->getDocument('database_' . $db->getInternalId(), $options['relatedCollection'] ?? '');
if ($relatedTable->isEmpty()) {
$parent = $this->isCollectionsAPI() ? 'collection' : 'table';
throw new Exception($this->getParentNotFoundException(), "The related $parent was not found.");
}
}
try {
$attribute = new Document([
'$id' => ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key),
'key' => $key,
'databaseInternalId' => $db->getInternalId(),
'databaseId' => $db->getId(),
'collectionInternalId' => $collection->getInternalId(),
'collectionId' => $collectionId,
'type' => $type,
'status' => 'processing', // processing, available, failed, deleting, stuck
'size' => $size,
'required' => $required,
'signed' => $signed,
'default' => $default,
'array' => $array,
'format' => $format,
'formatOptions' => $formatOptions,
'filters' => $filters,
'options' => $options,
]);
$dbForProject->checkAttribute($collection, $attribute);
$attribute = $dbForProject->createDocument('attributes', $attribute);
} catch (DuplicateException) {
throw new Exception($this->getDuplicateException());
} catch (LimitException) {
throw new Exception($this->getLimitException());
} catch (Throwable $e) {
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
throw $e;
}
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) {
$twoWayKey = $options['twoWayKey'];
$options['relatedCollection'] = $collection->getId();
$options['twoWayKey'] = $key;
$options['side'] = Database::RELATION_SIDE_CHILD;
try {
$twoWayAttribute = new Document([
'$id' => ID::custom($db->getInternalId() . '_' . $relatedTable->getInternalId() . '_' . $twoWayKey),
'key' => $twoWayKey,
'databaseInternalId' => $db->getInternalId(),
'databaseId' => $db->getId(),
'collectionInternalId' => $relatedTable->getInternalId(),
'collectionId' => $relatedTable->getId(),
'type' => $type,
'status' => 'processing', // processing, available, failed, deleting, stuck
'size' => $size,
'required' => $required,
'signed' => $signed,
'default' => $default,
'array' => $array,
'format' => $format,
'formatOptions' => $formatOptions,
'filters' => $filters,
'options' => $options,
]);
$dbForProject->checkAttribute($relatedTable, $twoWayAttribute);
$dbForProject->createDocument('attributes', $twoWayAttribute);
} catch (DuplicateException) {
$dbForProject->deleteDocument('attributes', $attribute->getId());
throw new Exception($this->getDuplicateException());
} catch (LimitException) {
$dbForProject->deleteDocument('attributes', $attribute->getId());
throw new Exception($this->getLimitException());
} catch (Throwable $e) {
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedTable->getId());
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedTable->getInternalId());
throw $e;
}
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedTable->getId());
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedTable->getInternalId());
}
$queueForDatabase
->setType(DATABASE_TYPE_CREATE_ATTRIBUTE)
->setDatabase($db);
if ($this->isCollectionsAPI()) {
$queueForDatabase
->setDocument($attribute)
->setCollection($collection);
} else {
$queueForDatabase
->setRow($attribute)
->setTable($collection);
}
$queueForEvents
->setContext('database', $db)
->setParam('databaseId', $databaseId)
->setParam($this->getEventsParamKey(), $attribute->getId())
// tableId or columnId
->setParam($this->getParentEventsParamKey(), $collection->getId())
->setContext($this->isCollectionsAPI() ? 'collection' : 'table', $collection);
$response->setStatusCode(SwooleResponse::STATUS_CODE_CREATED);
return $attribute;
}
final protected function updateAttribute(string $databaseId, string $collectionId, string $key, Database $dbForProject, Event $queueForEvents, string $type, int $size = null, string $filter = null, string|bool|int|float $default = null, bool $required = null, int|float|null $min = null, int|float|null $max = null, array $elements = null, array $options = [], string $newKey = null): Document
{
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception($this->getParentNotFoundException());
}
$attribute = $dbForProject->getDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if ($attribute->isEmpty()) {
throw new Exception($this->getNotFoundException());
}
if ($attribute->getAttribute('status') !== 'available') {
throw new Exception($this->getNotAvailableException());
}
if ($attribute->getAttribute(('type') !== $type)) {
throw new Exception($this->getTypeInvalidException());
}
if ($attribute->getAttribute('type') === Database::VAR_STRING && $attribute->getAttribute(('filter') !== $filter)) {
throw new Exception($this->getTypeInvalidException());
}
if ($required && isset($default)) {
throw new Exception($this->getDefaultUnsupportedException(), 'Cannot set default value for required ' . $this->getContext());
}
if ($attribute->getAttribute('array', false) && isset($default)) {
throw new Exception($this->getDefaultUnsupportedException(), 'Cannot set default value for array ' . $this->getContext() . 's');
}
$collectionId = 'database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId();
$attribute
->setAttribute('default', $default)
->setAttribute('required', $required);
if (!empty($size)) {
$attribute->setAttribute('size', $size);
}
switch ($attribute->getAttribute('format')) {
case APP_DATABASE_ATTRIBUTE_INT_RANGE:
case APP_DATABASE_ATTRIBUTE_FLOAT_RANGE:
$min ??= $attribute->getAttribute('formatOptions')['min'];
$max ??= $attribute->getAttribute('formatOptions')['max'];
if ($min > $max) {
throw new Exception($this->getTypeInvalidException(), '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 {
$validator = new Range($min, $max, Database::VAR_FLOAT);
if (!is_null($default)) {
$default = \floatval($default);
}
}
if (!is_null($default) && !$validator->isValid($default)) {
throw new Exception($this->getTypeInvalidException(), $validator->getDescription());
}
$options = [
'min' => $min,
'max' => $max
];
$attribute->setAttribute('formatOptions', $options);
break;
case APP_DATABASE_ATTRIBUTE_ENUM:
if (empty($elements)) {
throw new Exception($this->getTypeInvalidException(), 'Enum elements must not be empty');
}
foreach ($elements as $element) {
if (\strlen($element) === 0) {
throw new Exception($this->getTypeInvalidException(), 'Each enum element must not be empty');
}
}
if (!is_null($default) && !in_array($default, $elements)) {
throw new Exception($this->getTypeInvalidException(), 'Default value not found in elements');
}
$options = [
'elements' => $elements
];
$attribute->setAttribute('formatOptions', $options);
break;
}
if ($type === Database::VAR_RELATIONSHIP) {
$primaryDocumentOptions = \array_merge($attribute->getAttribute('options', []), $options);
$attribute->setAttribute('options', $primaryDocumentOptions);
try {
$dbForProject->updateRelationship(
collection: $collectionId,
id: $key,
newKey: $newKey,
onDelete: $primaryDocumentOptions['onDelete'],
);
} catch (NotFoundException) {
throw new Exception($this->getNotFoundException());
}
if ($primaryDocumentOptions['twoWay']) {
$relatedCollection = $dbForProject->getDocument('database_' . $db->getInternalId(), $primaryDocumentOptions['relatedCollection']);
$relatedAttribute = $dbForProject->getDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $primaryDocumentOptions['twoWayKey']);
if (!empty($newKey) && $newKey !== $key) {
$options['twoWayKey'] = $newKey;
}
$relatedOptions = \array_merge($relatedAttribute->getAttribute('options'), $options);
$relatedAttribute->setAttribute('options', $relatedOptions);
$dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $primaryDocumentOptions['twoWayKey'], $relatedAttribute);
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
}
} else {
try {
$dbForProject->updateAttribute(
collection: $collectionId,
id: $key,
size: $size,
required: $required,
default: $default,
formatOptions: $options,
newKey: $newKey ?? null
);
} catch (TruncateException) {
throw new Exception($this->getInvalidResizeException());
} catch (NotFoundException) {
throw new Exception($this->getNotFoundException());
} catch (LimitException) {
throw new Exception($this->getLimitException());
} catch (IndexException $e) {
throw new Exception(Exception::INDEX_INVALID, $e->getMessage());
}
}
if (!empty($newKey) && $key !== $newKey) {
$originalUid = $attribute->getId();
$attribute
->setAttribute('$id', ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $newKey))
->setAttribute('key', $newKey);
$dbForProject->updateDocument('attributes', $originalUid, $attribute);
/**
* @var Document $index
*/
foreach ($collection->getAttribute('indexes') as $index) {
/**
* @var string[] $attribute
*/
$attribute = $index->getAttribute('attributes', []);
$found = \array_search($key, $attribute);
if ($found !== false) {
$attribute[$found] = $newKey;
$index->setAttribute('attributes', $attribute);
$dbForProject->updateDocument('indexes', $index->getId(), $index);
}
}
} else {
$attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute);
}
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collection->getId());
$queueForEvents
->setContext('database', $db)
->setParam('databaseId', $databaseId)
->setParam($this->getEventsParamKey(), $attribute->getId())
// tableId or columnId
->setParam($this->getParentEventsParamKey(), $collection->getId())
->setContext($this->isCollectionsAPI() ? 'collection' : 'table', $collection);
return $attribute;
}
}

View file

@ -0,0 +1,94 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Boolean;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createBooleanColumn';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_BOOLEAN);
$this->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
->desc('Create boolean attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].collections.[attributeId].create')
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'attribute.create')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-boolean-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Boolean(), 'Default value for attribute when not provided. 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')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?bool $default,
bool $array,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_BOOLEAN,
'size' => 0,
'required' => $required,
'default' => $default,
'array' => $array,
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Boolean;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateBooleanColumn';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_BOOLEAN);
$this->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean/:key')
->desc('Update boolean 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-boolean-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#createCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Boolean()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?bool $default,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_BOOLEAN,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Datetime;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createDatetimeAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_DATETIME);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime')
->desc('Create datetime 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-datetime-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel()
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#createCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject'])
->param('array', false, new Boolean(), 'Is attribute an array?', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?string $default,
bool $array,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
$attribute = $this->createAttribute(
$databaseId,
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_DATETIME,
'size' => 0,
'required' => $required,
'default' => $default,
'array' => $array,
'filters' => ['datetime'],
]),
$response,
$dbForProject,
$queueForDatabase,
$queueForEvents
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,97 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Datetime;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateDatetimeAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_DATETIME);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime/:key')
->desc('Update datetime 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-datetime-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel()
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, fn (Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject'])
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?string $default,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_DATETIME,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,161 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
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\Authorization;
use Utopia\Database\Validator\IndexDependency as IndexDependencyValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
class Delete extends Action
{
use HTTP;
public static function getName(): string
{
return 'deleteAttribute';
}
public function __construct()
{
// we should correctly & carefully set the context later.
$this->setResponseModel(UtopiaResponse::MODEL_NONE);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
->desc('Delete 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.delete')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: 'databases',
group: 'attributes',
name: 'deleteAttribute',
description: '/docs/references/databases/delete-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_NOCONTENT,
model: UtopiaResponse::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception($this->getParentNotFoundException());
}
$attribute = $dbForProject->getDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if ($attribute->isEmpty()) {
throw new Exception($this->getNotFoundException());
}
$validator = new IndexDependencyValidator(
$collection->getAttribute('indexes'),
$dbForProject->getAdapter()->getSupportForCastIndexArray(),
);
if (!$validator->isValid($attribute)) {
throw new Exception(Exception::INDEX_DEPENDENCY);
}
if ($attribute->getAttribute('status') === 'available') {
$attribute = $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting'));
}
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
$options = $attribute->getAttribute('options');
if ($options['twoWay']) {
$relatedCollection = $dbForProject->getDocument('database_' . $db->getInternalId(), $options['relatedCollection']);
if ($relatedCollection->isEmpty()) {
throw new Exception($this->getParentNotFoundException());
}
$relatedAttribute = $dbForProject->getDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']);
if ($relatedAttribute->isEmpty()) {
throw new Exception($this->getNotFoundException());
}
if ($relatedAttribute->getAttribute('status') === 'available') {
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'deleting'));
}
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $options['relatedCollection']);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
}
}
$queueForDatabase
->setDatabase($db)
->setType(DATABASE_TYPE_DELETE_ATTRIBUTE);
if ($this->isCollectionsAPI()) {
$queueForDatabase
->setRow($attribute)
->setTable($collection);
} else {
$queueForDatabase
->setDocument($attribute)
->setCollection($collection);
}
$type = $attribute->getAttribute('type');
$format = $attribute->getAttribute('format');
$model = $this->getCorrectModel($type, $format);
$queueForEvents
->setContext('database', $db)
->setParam('databaseId', $databaseId)
->setPayload($response->output($attribute, $model))
->setParam($this->getEventsParamKey(), $attribute->getId())
// tableId or columnId
->setParam($this->getParentEventsParamKey(), $collection->getId())
// set proper context
->setContext($this->isCollectionsAPI() ? 'collection' : 'table', $collection);
$response->noContent();
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Email;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Network\Validator\Email;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createEmailAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_EMAIL);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
->desc('Create email 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-email-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Email(), 'Default value for attribute when not provided. 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')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $tableId,
string $key,
?bool $required,
?string $default,
bool $array,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
$attribute = $this->createAttribute(
$databaseId,
$tableId,
new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'size' => 254,
'required' => $required,
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_EMAIL,
]),
$response,
$dbForProject,
$queueForDatabase,
$queueForEvents
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Email;
use Appwrite\Event\Event;
use Appwrite\Network\Validator\Email;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateEmailAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_EMAIL);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/email/:key')
->desc('Update email 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-email-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Email()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New Attribute Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?string $default,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_STRING,
filter: APP_DATABASE_ATTRIBUTE_EMAIL,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,113 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Enum;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createEnumAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_ENUM);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
->desc('Create enum 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-attribute-enum.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('elements', [], new ArrayList(new Text(Database::LENGTH_KEY), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of enum values.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Text(0), 'Default value for attribute when not provided. 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')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
array $elements,
?bool $required,
?string $default,
bool $array,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
if (!is_null($default) && !\in_array($default, $elements, true)) {
throw new Exception($this->getInvalidValueException(), 'Default value not found in elements');
}
$attribute = $this->createAttribute(
$databaseId,
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'size' => Database::LENGTH_KEY,
'required' => $required,
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_ENUM,
'formatOptions' => ['elements' => $elements],
]),
$response,
$dbForProject,
$queueForDatabase,
$queueForEvents
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,102 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Enum;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateEnumAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_ENUM);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/:key')
->desc('Update enum 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-enum-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('elements', null, new ArrayList(new Text(Database::LENGTH_KEY), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Updated list of enum values.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Text(0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New Attribute Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?array $elements,
?bool $required,
?string $default,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_STRING,
filter: APP_DATABASE_ATTRIBUTE_ENUM,
default: $default,
required: $required,
elements: $elements,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Float;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\FloatValidator;
use Utopia\Validator\Range;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createFloatAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_FLOAT);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
->desc('Create float 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-float-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new FloatValidator(), 'Minimum value.', true)
->param('max', null, new FloatValidator(), 'Maximum value.', true)
->param('default', null, new FloatValidator(), 'Default value. Cannot be set when required.', true)
->param('array', false, new Boolean(), 'Is attribute an array?', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?float $min,
?float $max,
?float $default,
bool $array,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
$min ??= -PHP_FLOAT_MAX;
$max ??= PHP_FLOAT_MAX;
if ($min > $max) {
throw new Exception($this->getInvalidValueException(), 'Minimum value must be lesser than maximum value');
}
$validator = new Range($min, $max, Database::VAR_FLOAT);
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_FLOAT,
'size' => 0,
'required' => $required,
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_FLOAT_RANGE,
'formatOptions' => ['min' => $min, 'max' => $max],
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$formatOptions = $attribute->getAttribute('formatOptions', []);
if (!empty($formatOptions)) {
$attribute->setAttribute('min', \floatval($formatOptions['min']));
$attribute->setAttribute('max', \floatval($formatOptions['max']));
}
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Float;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\FloatValidator;
use Utopia\Validator\Nullable;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateFloatAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_FLOAT);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/float/:key')
->desc('Update float 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-float-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new FloatValidator(), 'Minimum value.', true)
->param('max', null, new FloatValidator(), 'Maximum value.', true)
->param('default', null, new Nullable(new FloatValidator()), 'Default value. Cannot be set when required.')
->param('newKey', null, new Key(), 'New Attribute Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?float $min,
?float $max,
?float $default,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_FLOAT,
default: $default,
required: $required,
min: $min,
max: $max,
newKey: $newKey
);
$formatOptions = $attribute->getAttribute('formatOptions', []);
if (!empty($formatOptions)) {
$attribute->setAttribute('min', \floatval($formatOptions['min']));
$attribute->setAttribute('max', \floatval($formatOptions['max']));
}
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes;
use Appwrite\Extend\Exception;
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\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
class Get extends Action
{
use HTTP;
public static function getName(): string
{
return 'getColumn';
}
public function __construct()
{
$this->setResponseModel([
UtopiaResponse::MODEL_ATTRIBUTE_BOOLEAN,
UtopiaResponse::MODEL_ATTRIBUTE_INTEGER,
UtopiaResponse::MODEL_ATTRIBUTE_FLOAT,
UtopiaResponse::MODEL_ATTRIBUTE_EMAIL,
UtopiaResponse::MODEL_ATTRIBUTE_ENUM,
UtopiaResponse::MODEL_ATTRIBUTE_URL,
UtopiaResponse::MODEL_ATTRIBUTE_IP,
UtopiaResponse::MODEL_ATTRIBUTE_DATETIME,
UtopiaResponse::MODEL_ATTRIBUTE_RELATIONSHIP,
UtopiaResponse::MODEL_ATTRIBUTE_STRING,
]);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
->desc('Get column')
->groups(['api', 'database'])
->label('scope', 'collections.read')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: 'databases',
group: 'columns',
name: 'getColumn',
description: '/docs/references/databases/get-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel()
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Table ID.')
->param('key', '', new Key(), 'Column Key.')
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $tableId,
string $key,
UtopiaResponse $response,
Database $dbForProject
): void {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $tableId);
if ($collection->isEmpty()) {
throw new Exception($this->getParentNotFoundException());
}
$attribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $collection->getInternalId() . '_' . $key);
if ($attribute->isEmpty()) {
throw new Exception($this->getNotFoundException());
}
foreach ($attribute->getAttribute('options', []) as $optKey => $optVal) {
$attribute->setAttribute($optKey, $optVal);
}
$type = $attribute->getAttribute('type');
$format = $attribute->getAttribute('format');
$model = $this->getCorrectModel($type, $format);
$response->dynamic($attribute, $model);
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\IP;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\IP;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createIpAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_IP);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
->desc('Create IP address 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-ip-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new IP(), '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')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?string $default,
bool $array,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
$attribute = $this->createAttribute(
$databaseId,
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'size' => 39,
'required' => $required,
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_IP,
]),
$response,
$dbForProject,
$queueForDatabase,
$queueForEvents
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\IP;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\IP;
use Utopia\Validator\Nullable;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateIpAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_IP);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:key')
->desc('Update IP address 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-ip-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new IP()), 'Default value. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New Attribute Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?string $default,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_STRING,
filter: APP_DATABASE_ATTRIBUTE_IP,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,123 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Integer;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Range;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createIntegerAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_INTEGER);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
->desc('Create integer 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-integer-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new Integer(), 'Minimum value', true)
->param('max', null, new Integer(), 'Maximum value', true)
->param('default', null, new Integer(), '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')
->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
): 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, Database::VAR_INTEGER);
if (!\is_null($default) && !$validator->isValid($default)) {
throw new Exception($this->getInvalidValueException(), $validator->getDescription());
}
$size = $max > 2147483647 ? 8 : 4;
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_INTEGER,
'size' => $size,
'required' => $required,
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_INT_RANGE,
'formatOptions' => ['min' => $min, 'max' => $max],
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$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());
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Integer;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateIntegerAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_INTEGER);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/integer/:key')
->desc('Update integer 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-integer-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new Integer(), 'Minimum value', true)
->param('max', null, new Integer(), 'Maximum value', true)
->param('default', null, new Nullable(new Integer()), 'Default value. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New Attribute Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->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
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_INTEGER,
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());
}
}

View file

@ -0,0 +1,169 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Relationship;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\WhiteList;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createRelationshipAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_RELATIONSHIP);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship')
->desc('Create relationship 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-relationship-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel()
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('relatedCollectionId', '', new UID(), 'Related Collection ID.')
->param('type', '', new WhiteList([
Database::RELATION_ONE_TO_ONE,
Database::RELATION_MANY_TO_ONE,
Database::RELATION_MANY_TO_MANY,
Database::RELATION_ONE_TO_MANY
], true), 'Relation type')
->param('twoWay', false, new Boolean(), 'Is Two Way?', true)
->param('key', null, new Key(), 'Attribute Key.', true)
->param('twoWayKey', null, new Key(), 'Two Way Attribute Key.', true)
->param('onDelete', Database::RELATION_MUTATE_RESTRICT, new WhiteList([
Database::RELATION_MUTATE_CASCADE,
Database::RELATION_MUTATE_RESTRICT,
Database::RELATION_MUTATE_SET_NULL
], true), 'Constraints option', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $relatedCollectionId,
string $type,
bool $twoWay,
?string $key,
?string $twoWayKey,
string $onDelete,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
$key ??= $relatedCollectionId;
$twoWayKey ??= $collectionId;
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
$collection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
if ($collection->isEmpty()) {
throw new Exception($this->getParentNotFoundException());
}
$relatedCollectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId);
$relatedCollection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollectionDocument->getInternalId());
if ($relatedCollection->isEmpty()) {
throw new Exception($this->getParentNotFoundException());
}
$attributes = $collection->getAttribute('attributes', []);
foreach ($attributes as $attribute) {
if ($attribute->getAttribute('type') !== Database::VAR_RELATIONSHIP) {
continue;
}
if (\strtolower($attribute->getId()) === \strtolower($key)) {
throw new Exception($this->getDuplicateException());
}
if (
\strtolower($attribute->getAttribute('options')['twoWayKey']) === \strtolower($twoWayKey) &&
$attribute->getAttribute('options')['relatedCollection'] === $relatedCollection->getId()
) {
throw new Exception($this->getDuplicateException(), 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.');
}
if (
$type === Database::RELATION_MANY_TO_MANY &&
$attribute->getAttribute('options')['relationType'] === Database::RELATION_MANY_TO_MANY &&
$attribute->getAttribute('options')['relatedCollection'] === $relatedCollection->getId()
) {
$parentType = $this->isCollectionsAPI() ? 'collection' : 'table';
throw new Exception($this->getDuplicateException(), "Creating more than one \"manyToMany\" relationship on the same $parentType is currently not permitted.");
}
}
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_RELATIONSHIP,
'size' => 0,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
'options' => [
'relatedCollection' => $relatedCollectionId,
'relationType' => $type,
'twoWay' => $twoWay,
'twoWayKey' => $twoWayKey,
'onDelete' => $onDelete,
]
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
foreach ($attribute->getAttribute('options', []) as $k => $option) {
$attribute->setAttribute($k, $option);
}
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\Relationship;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\WhiteList;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateRelationshipAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_RELATIONSHIP);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/relationship')
->desc('Update relationship 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-relationship-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel()
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('onDelete', null, new WhiteList([
Database::RELATION_MUTATE_CASCADE,
Database::RELATION_MUTATE_RESTRICT,
Database::RELATION_MUTATE_SET_NULL
], true), 'Constraints option', true)
->param('newKey', null, new Key(), 'New Attribute Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?string $onDelete,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_RELATIONSHIP,
required: false,
options: [
'onDelete' => $onDelete
],
newKey: $newKey
);
foreach ($attribute->getAttribute('options', []) as $k => $option) {
$attribute->setAttribute($k, $option);
}
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,122 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\String;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createStringAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_STRING);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/string')
->desc('Create string 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-string-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel()
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Validator::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Text(0, 0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
->param('array', false, new Boolean(), 'Is attribute an array?', true)
->param('encrypt', false, new Boolean(), 'Toggle encryption for the attribute. Encryption enhances security by not storing any plain text values in the database. However, encrypted attributes cannot be queried.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?int $size,
?bool $required,
?string $default,
bool $array,
bool $encrypt,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
// Ensure default fits in the given size
$validator = new Text($size, 0);
if (!is_null($default) && !$validator->isValid($default)) {
throw new Exception($this->getInvalidValueException(), $validator->getDescription());
}
$filters = [];
if ($encrypt) {
$filters[] = 'encrypt';
}
$attribute = $this->createAttribute(
$databaseId,
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'size' => $size,
'required' => $required,
'default' => $default,
'array' => $array,
'filters' => $filters,
]),
$response,
$dbForProject,
$queueForDatabase,
$queueForEvents
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,102 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\String;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateStringAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_STRING);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/string/:key')
->desc('Update string 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-string-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Validator::TYPE_INTEGER), 'Maximum size of the string attribute.', true)
->param('newKey', null, new Key(), 'New Attribute Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?string $default,
?int $size,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_STRING,
size: $size,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\URL;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\URL;
class Create extends Action
{
use HTTP;
public static function getName(): string
{
return 'createUrlAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_URL);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
->desc('Create URL 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-url-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new URL(), 'Default value for attribute when not provided. 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')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?string $default,
bool $array,
UtopiaResponse $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
Event $queueForEvents
): void {
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'size' => 2000,
'required' => $required,
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_URL,
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes\URL;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Attributes\Action;
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\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\URL;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateUrlAttribute';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_URL);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:key')
->desc('Update URL 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: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-url-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new URL()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New Attribute Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(
string $databaseId,
string $collectionId,
string $key,
?bool $required,
?string $default,
?string $newKey,
UtopiaResponse $response,
Database $dbForProject,
Event $queueForEvents
): void {
$attribute = $this->updateAttribute(
$databaseId,
$collectionId,
$key,
$dbForProject,
$queueForEvents,
type: Database::VAR_STRING,
filter: APP_DATABASE_ATTRIBUTE_URL,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Attributes;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Attributes;
use Appwrite\Utopia\Database\Validator\Queries\Columns;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Response as SwooleResponse;
class XList extends Action
{
use HTTP;
public static function getName(): string
{
return 'listAttributes';
}
public function __construct()
{
$this->setResponseModel(UtopiaResponse::MODEL_ATTRIBUTE_LIST);
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes')
->desc('List attributes')
->groups(['api', 'database'])
->label('scope', 'collections.read')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('sdk', new Method(
namespace: 'databases',
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/list-attributes.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel()
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('queries', [], new Columns(), '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(', ', Attributes::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $databaseId, string $tableId, array $queries, UtopiaResponse $response, Database $dbForProject): void
{
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$table = $dbForProject->getDocument('database_' . $database->getInternalId(), $tableId);
if ($table->isEmpty()) {
throw new Exception($this->getParentNotFoundException());
}
$queries = Query::parseQueries($queries);
\array_push(
$queries,
Query::equal('databaseInternalId', [$database->getInternalId()]),
Query::equal('collectionInternalId', [$table->getInternalId()])
);
$cursor = \array_filter(
$queries,
fn ($query) => \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE])
);
$cursor = \reset($cursor);
if ($cursor) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$attributeId = $cursor->getValue();
$cursorDocument = Authorization::skip(
fn () => $dbForProject->find('attributes', [
Query::equal('databaseInternalId', [$database->getInternalId()]),
Query::equal('collectionInternalId', [$table->getInternalId()]),
Query::equal('key', [$attributeId]),
Query::limit(1),
])
);
if (empty($cursorDocument) || $cursorDocument[0]->isEmpty()) {
$type = ucfirst($this->getContext());
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "$type '{$attributeId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument[0]);
}
$filters = Query::groupByType($queries)['filters'];
try {
$attributes = $dbForProject->find('attributes', $queries);
$total = $dbForProject->count('attributes', $filters, APP_LIMIT_COUNT);
} catch (OrderException $e) {
$documents = $this->isCollectionsAPI() ? 'documents' : 'rows';
$attribute = $this->isCollectionsAPI() ? 'attribute' : 'column';
$message = "The order $attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all $documents order $attribute values are non-null.";
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, $message);
}
$response->dynamic(new Document([
'total' => $total,
$this->getSdkGroup() => $attributes,
]), $this->getResponseModel());
}
}

View file

@ -13,7 +13,6 @@ use Utopia\Platform\Action as UtopiaAction;
*/
abstract class Action extends UtopiaAction
{
/**
* The current API context (either 'table' or 'collection').
*/

View file

@ -58,7 +58,7 @@ class Create extends ColumnAction
->param('tableId', '', new UID(), 'Table ID.')
->param('key', '', new Key(), 'Column Key.')
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when column is required.', true, ['dbForProject'])
->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime()), 'Default value for the column in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when column is required.', true, ['dbForProject'])
->param('array', false, new Boolean(), 'Is column an array?', true)
->inject('response')
->inject('dbForProject')

View file

@ -61,7 +61,7 @@ class Update extends ColumnAction
->param('key', '', new Key(), 'Column Key.')
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Text(0, 0)), 'Default value for column when not provided. Cannot be set when column is required.')
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Validator::TYPE_INTEGER), 'Maximum size of the string attribute.', true)
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Validator::TYPE_INTEGER), 'Maximum size of the string column.', true)
->param('newKey', null, new Key(), 'New Column Key.', true)
->inject('response')
->inject('dbForProject')

View file

@ -3,7 +3,6 @@
namespace Appwrite\Platform\Modules\Databases\Http\Tables;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Collections\Action;
use Appwrite\Platform\Modules\Databases\Http\Collections\Create as CollectionCreate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;

View file

@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Databases\Http\Tables;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Collections\Action;
use Appwrite\Platform\Modules\Databases\Http\Collections\Delete as CollectionDelete;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;

View file

@ -2,7 +2,6 @@
namespace Appwrite\Platform\Modules\Databases\Http\Tables;
use Appwrite\Platform\Modules\Databases\Http\Collections\Action;
use Appwrite\Platform\Modules\Databases\Http\Collections\Get as CollectionGet;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;

View file

@ -2,7 +2,6 @@
namespace Appwrite\Platform\Modules\Databases\Http\Tables\Logs;
use Appwrite\Platform\Modules\Databases\Http\Collections\Action;
use Appwrite\Platform\Modules\Databases\Http\Collections\Logs\XList as CollectionLogXList;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;

View file

@ -3,7 +3,6 @@
namespace Appwrite\Platform\Modules\Databases\Http\Tables;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Collections\Action;
use Appwrite\Platform\Modules\Databases\Http\Collections\Update as CollectionUpdate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;

View file

@ -2,7 +2,6 @@
namespace Appwrite\Platform\Modules\Databases\Http\Tables\Usage;
use Appwrite\Platform\Modules\Databases\Http\Collections\Action;
use Appwrite\Platform\Modules\Databases\Http\Collections\Usage\Get as CollectionUsageGet;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;

View file

@ -2,7 +2,6 @@
namespace Appwrite\Platform\Modules\Databases\Http\Tables;
use Appwrite\Platform\Modules\Databases\Http\Collections\Action;
use Appwrite\Platform\Modules\Databases\Http\Collections\XList as CollectionXList;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;