mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'origin/1.8.x' into feat-apps-module-dl
This commit is contained in:
@@ -10,8 +10,15 @@ class Database extends Event
|
||||
{
|
||||
protected string $type = '';
|
||||
protected ?Document $database = null;
|
||||
protected ?Document $collection = null;
|
||||
|
||||
// tables api
|
||||
protected ?Document $row = null;
|
||||
protected ?Document $table = null;
|
||||
|
||||
// collections api
|
||||
protected ?Document $document = null;
|
||||
protected ?Document $collection = null;
|
||||
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
@@ -54,6 +61,61 @@ class Database extends Event
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set database for this event.
|
||||
*
|
||||
* @return null|Document
|
||||
*/
|
||||
public function getDatabase(): ?Document
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table for this database event.
|
||||
*
|
||||
* @param Document $table
|
||||
* @return self
|
||||
*/
|
||||
public function setTable(Document $table): self
|
||||
{
|
||||
$this->table = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set table for this event.
|
||||
*
|
||||
* @return null|Document
|
||||
*/
|
||||
public function getTable(): ?Document
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the row for this database event.
|
||||
*
|
||||
* @param Document $row
|
||||
* @return self
|
||||
*/
|
||||
public function setRow(Document $row): self
|
||||
{
|
||||
$this->row = $row;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set row for this database event.
|
||||
* @return null|Document
|
||||
*/
|
||||
public function getRow(): ?Document
|
||||
{
|
||||
return $this->row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the collection for this database event.
|
||||
*
|
||||
@@ -123,6 +185,8 @@ class Database extends Event
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'type' => $this->type,
|
||||
'table' => $this->table,
|
||||
'row' => $this->row,
|
||||
'collection' => $this->collection,
|
||||
'document' => $this->document,
|
||||
'database' => $this->database,
|
||||
|
||||
@@ -383,6 +383,8 @@ class Event
|
||||
{
|
||||
$this->params = [];
|
||||
$this->sensitive = [];
|
||||
$this->event = '';
|
||||
$this->payload = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -569,7 +571,12 @@ class Event
|
||||
/**
|
||||
* Force a non-assoc array.
|
||||
*/
|
||||
return \array_values($events);
|
||||
$eventValues = \array_values($events);
|
||||
|
||||
/**
|
||||
* Return a combined list of table, collection events.
|
||||
*/
|
||||
return Event::mirrorCollectionEvents($pattern, $eventValues[0], $eventValues);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -590,4 +597,57 @@ class Event
|
||||
$this->context = $event->context;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `table` events for `collection` events.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* `databases.*.collections.*.documents.*.update` →\
|
||||
* `[databases.*.collections.*.documents.*.update, databases.*.tables.*.rows.*.update]`
|
||||
*/
|
||||
private static function mirrorCollectionEvents(string $pattern, string $firstEvent, array $events): array
|
||||
{
|
||||
$tableEventMap = [
|
||||
'documents' => 'rows',
|
||||
'collections' => 'tables',
|
||||
'attributes' => 'columns',
|
||||
];
|
||||
|
||||
if (
|
||||
str_contains($pattern, 'databases.') &&
|
||||
str_contains($firstEvent, 'collections')
|
||||
) {
|
||||
$pairedEvents = [];
|
||||
|
||||
foreach ($events as $event) {
|
||||
$pairedEvents[] = $event;
|
||||
|
||||
if (str_contains($event, 'collections')) {
|
||||
$tableSideEvent = str_replace(
|
||||
array_keys($tableEventMap),
|
||||
array_values($tableEventMap),
|
||||
$event
|
||||
);
|
||||
$pairedEvents[] = $tableSideEvent;
|
||||
}
|
||||
}
|
||||
|
||||
$events = $pairedEvents;
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the queue.
|
||||
*
|
||||
* @param bool $failed Whether to include failed events in the count.
|
||||
* @return int The size of the queue.
|
||||
*/
|
||||
public function getSize(bool $failed = false): int
|
||||
{
|
||||
$queue = new Queue($this->getQueue());
|
||||
return $this->publisher->getQueueSize($queue, $failed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ class Mail extends Event
|
||||
protected string $name = '';
|
||||
protected string $subject = '';
|
||||
protected string $body = '';
|
||||
protected string $preview = '';
|
||||
protected array $smtp = [];
|
||||
protected array $variables = [];
|
||||
protected string $bodyTemplate = '';
|
||||
@@ -93,6 +94,28 @@ class Mail extends Event
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets preview for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function setPreview(string $preview): self
|
||||
{
|
||||
$this->preview = $preview;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns preview for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPreview(string $preview): string
|
||||
{
|
||||
return $this->preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets name for the mail event.
|
||||
*
|
||||
@@ -122,7 +145,7 @@ class Mail extends Event
|
||||
* @param string $bodyTemplate
|
||||
* @return self
|
||||
*/
|
||||
public function setbodyTemplate(string $bodyTemplate): self
|
||||
public function setBodyTemplate(string $bodyTemplate): self
|
||||
{
|
||||
$this->bodyTemplate = $bodyTemplate;
|
||||
|
||||
@@ -134,7 +157,7 @@ class Mail extends Event
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getbodyTemplate(): string
|
||||
public function getBodyTemplate(): string
|
||||
{
|
||||
return $this->bodyTemplate;
|
||||
}
|
||||
@@ -409,6 +432,7 @@ class Mail extends Event
|
||||
'subject' => $this->subject,
|
||||
'bodyTemplate' => $this->bodyTemplate,
|
||||
'body' => $this->body,
|
||||
'preview' => $this->preview,
|
||||
'smtp' => $this->smtp,
|
||||
'variables' => $this->variables,
|
||||
'attachment' => $this->attachment,
|
||||
|
||||
@@ -73,19 +73,21 @@ class Realtime extends Event
|
||||
}
|
||||
|
||||
$allEvents = Event::generateEvents($this->getEvent(), $this->getParams());
|
||||
|
||||
$payload = new Document($this->getPayload());
|
||||
|
||||
$db = $this->getContext('database');
|
||||
$collection = $this->getContext('collection');
|
||||
$bucket = $this->getContext('bucket');
|
||||
|
||||
// Can be Tables API or Collections API; generated channels include both!
|
||||
$tableOrCollection = $this->getContext('table') ?? $this->getContext('collection');
|
||||
|
||||
$target = RealtimeAdapter::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $this->getProject(),
|
||||
database: $db,
|
||||
collection: $collection,
|
||||
collection: $tableOrCollection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@ class Exception extends \Exception
|
||||
public const GENERAL_SMTP_DISABLED = 'general_smtp_disabled';
|
||||
public const GENERAL_PHONE_DISABLED = 'general_phone_disabled';
|
||||
public const GENERAL_ARGUMENT_INVALID = 'general_argument_invalid';
|
||||
public const GENERAL_QUERY_LIMIT_EXCEEDED = 'general_query_limit_exceeded';
|
||||
public const GENERAL_COLUMN_QUERY_LIMIT_EXCEEDED = 'general_column_query_limit_exceeded';
|
||||
public const GENERAL_ATTRIBUTE_QUERY_LIMIT_EXCEEDED = 'general_attribute_query_limit_exceeded';
|
||||
public const GENERAL_QUERY_INVALID = 'general_query_invalid';
|
||||
public const GENERAL_ROUTE_NOT_FOUND = 'general_route_not_found';
|
||||
public const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
|
||||
@@ -133,6 +134,7 @@ class Exception extends \Exception
|
||||
public const AVATAR_IMAGE_NOT_FOUND = 'avatar_image_not_found';
|
||||
public const AVATAR_REMOTE_URL_FAILED = 'avatar_remote_url_failed';
|
||||
public const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found';
|
||||
public const AVATAR_SVG_SANITIZATION_FAILED = 'avatar_svg_sanitization_failed';
|
||||
|
||||
/** Storage */
|
||||
public const STORAGE_FILE_ALREADY_EXISTS = 'storage_file_already_exists';
|
||||
@@ -198,6 +200,11 @@ class Exception extends \Exception
|
||||
public const COLLECTION_ALREADY_EXISTS = 'collection_already_exists';
|
||||
public const COLLECTION_LIMIT_EXCEEDED = 'collection_limit_exceeded';
|
||||
|
||||
/** Tables */
|
||||
public const TABLE_NOT_FOUND = 'table_not_found';
|
||||
public const TABLE_ALREADY_EXISTS = 'table_already_exists';
|
||||
public const TABLE_LIMIT_EXCEEDED = 'table_limit_exceeded';
|
||||
|
||||
/** Documents */
|
||||
public const DOCUMENT_NOT_FOUND = 'document_not_found';
|
||||
public const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure';
|
||||
@@ -207,7 +214,16 @@ class Exception extends \Exception
|
||||
public const DOCUMENT_UPDATE_CONFLICT = 'document_update_conflict';
|
||||
public const DOCUMENT_DELETE_RESTRICTED = 'document_delete_restricted';
|
||||
|
||||
/** Attribute */
|
||||
/** Rows */
|
||||
public const ROW_NOT_FOUND = 'row_not_found';
|
||||
public const ROW_INVALID_STRUCTURE = 'row_invalid_structure';
|
||||
public const ROW_MISSING_DATA = 'row_missing_data';
|
||||
public const ROW_MISSING_PAYLOAD = 'row_missing_payload';
|
||||
public const ROW_ALREADY_EXISTS = 'row_already_exists';
|
||||
public const ROW_UPDATE_CONFLICT = 'row_update_conflict';
|
||||
public const ROW_DELETE_RESTRICTED = 'row_delete_restricted';
|
||||
|
||||
/** Attributes */
|
||||
public const ATTRIBUTE_NOT_FOUND = 'attribute_not_found';
|
||||
public const ATTRIBUTE_UNKNOWN = 'attribute_unknown';
|
||||
public const ATTRIBUTE_NOT_AVAILABLE = 'attribute_not_available';
|
||||
@@ -219,6 +235,22 @@ class Exception extends \Exception
|
||||
public const ATTRIBUTE_TYPE_INVALID = 'attribute_type_invalid';
|
||||
public const ATTRIBUTE_INVALID_RESIZE = 'attribute_invalid_resize';
|
||||
|
||||
public const ATTRIBUTE_TYPE_NOT_SUPPORTED = 'ATTRIBUTE_TYPE_NOT_SUPPORTED';
|
||||
|
||||
/** Columns */
|
||||
public const COLUMN_NOT_FOUND = 'column_not_found';
|
||||
public const COLUMN_UNKNOWN = 'column_unknown';
|
||||
public const COLUMN_NOT_AVAILABLE = 'column_not_available';
|
||||
public const COLUMN_FORMAT_UNSUPPORTED = 'column_format_unsupported';
|
||||
public const COLUMN_DEFAULT_UNSUPPORTED = 'column_default_unsupported';
|
||||
public const COLUMN_ALREADY_EXISTS = 'column_already_exists';
|
||||
public const COLUMN_LIMIT_EXCEEDED = 'column_limit_exceeded';
|
||||
public const COLUMN_VALUE_INVALID = 'column_value_invalid';
|
||||
public const COLUMN_TYPE_INVALID = 'column_type_invalid';
|
||||
public const COLUMN_INVALID_RESIZE = 'column_invalid_resize';
|
||||
|
||||
public const COLUMN_TYPE_NOT_SUPPORTED = 'COLUMN_TYPE_NOT_SUPPORTED';
|
||||
|
||||
/** Relationship */
|
||||
public const RELATIONSHIP_VALUE_INVALID = 'relationship_value_invalid';
|
||||
|
||||
@@ -229,6 +261,13 @@ class Exception extends \Exception
|
||||
public const INDEX_INVALID = 'index_invalid';
|
||||
public const INDEX_DEPENDENCY = 'index_dependency';
|
||||
|
||||
/** Column Indexes */
|
||||
public const COLUMN_INDEX_NOT_FOUND = 'column_index_not_found';
|
||||
public const COLUMN_INDEX_LIMIT_EXCEEDED = 'column_index_limit_exceeded';
|
||||
public const COLUMN_INDEX_ALREADY_EXISTS = 'column_index_already_exists';
|
||||
public const COLUMN_INDEX_INVALID = 'column_index_invalid';
|
||||
public const COLUMN_INDEX_DEPENDENCY = 'column_index_dependency';
|
||||
|
||||
/** Projects */
|
||||
public const PROJECT_NOT_FOUND = 'project_not_found';
|
||||
public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
|
||||
@@ -316,7 +355,7 @@ class Exception extends \Exception
|
||||
public const MESSAGE_MISSING_SCHEDULE = 'message_missing_schedule';
|
||||
|
||||
/** Targets */
|
||||
public const TARGET_PROVIDER_INVALID_TYPE = 'target_provider_invalid_type';
|
||||
public const TARGET_PROVIDER_INVALID_TYPE = 'target_provider_invalid_type';
|
||||
|
||||
/** Schedules */
|
||||
public const SCHEDULE_NOT_FOUND = 'schedule_not_found';
|
||||
|
||||
@@ -58,6 +58,7 @@ class Mapper
|
||||
'json' => Types::json(),
|
||||
'none' => Types::json(),
|
||||
'any' => Types::json(),
|
||||
'array' => Types::json()
|
||||
];
|
||||
|
||||
foreach ($defaults as $type => $default) {
|
||||
@@ -226,7 +227,7 @@ class Mapper
|
||||
];
|
||||
|
||||
if (!$rule['required']) {
|
||||
$fields[$escapedKey]['defaultValue'] = $rule['default'];
|
||||
$fields[$escapedKey]['defaultValue'] = $rule['default'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,48 +268,53 @@ class Mapper
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? $validator::class : '') {
|
||||
case 'Appwrite\Network\Validator\CNAME':
|
||||
case 'Appwrite\Task\Validator\Cron':
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case 'Utopia\Validator\Domain':
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
case 'Appwrite\Event\Validator\Event':
|
||||
case 'Appwrite\Event\Validator\FunctionEvent':
|
||||
case 'Appwrite\Network\Validator\CNAME':
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case 'Appwrite\Network\Validator\Redirect':
|
||||
case 'Appwrite\Network\Validator\DNS':
|
||||
case 'Appwrite\Network\Validator\Origin':
|
||||
case 'Appwrite\Task\Validator\Cron':
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case 'Utopia\Database\Validator\Key':
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case 'Utopia\Validator\Domain':
|
||||
case 'Utopia\Validator\HexColor':
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\IP':
|
||||
case 'Utopia\Database\Validator\Key':
|
||||
case 'Utopia\Validator\Origin':
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
case 'Utopia\Validator\Text':
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case 'Utopia\Validator\URL':
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
default:
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Authorization':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Tables':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Columns':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
|
||||
case 'Utopia\Database\Validator\Queries\Documents':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Functions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
|
||||
case 'Utopia\Database\Validator\Permissions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
|
||||
case 'Utopia\Database\Validator\Queries':
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Teams':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Users':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
|
||||
case 'Utopia\Database\Validator\Authorization':
|
||||
case 'Utopia\Database\Validator\Permissions':
|
||||
case 'Utopia\Database\Validator\Queries':
|
||||
case 'Utopia\Database\Validator\Queries\Documents':
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
$type = Type::listOf(Type::string());
|
||||
break;
|
||||
case 'Utopia\Validator\Boolean':
|
||||
@@ -425,7 +431,9 @@ class Mapper
|
||||
|
||||
switch ($name) {
|
||||
case 'Attributes':
|
||||
return static::getAttributeImplementation($object);
|
||||
return static::getColumnImplementation($object);
|
||||
case 'Columns':
|
||||
return static::getColumnImplementation($object, true);
|
||||
case 'HashOptions':
|
||||
return static::getHashOptionsImplementation($object);
|
||||
}
|
||||
@@ -433,29 +441,27 @@ class Mapper
|
||||
throw new Exception('Unknown union type: ' . $name);
|
||||
}
|
||||
|
||||
private static function getAttributeImplementation(array $object): Type
|
||||
private static function getColumnImplementation(array $object, bool $isColumns = false): Type
|
||||
{
|
||||
switch ($object['type']) {
|
||||
case 'string':
|
||||
return match ($object['format'] ?? '') {
|
||||
'email' => static::model('AttributeEmail'),
|
||||
'url' => static::model('AttributeUrl'),
|
||||
'ip' => static::model('AttributeIp'),
|
||||
default => static::model('AttributeString'),
|
||||
};
|
||||
case 'integer':
|
||||
return static::model('AttributeInteger');
|
||||
case 'double':
|
||||
return static::model('AttributeFloat');
|
||||
case 'boolean':
|
||||
return static::model('AttributeBoolean');
|
||||
case 'datetime':
|
||||
return static::model('AttributeDatetime');
|
||||
case 'relationship':
|
||||
return static::model('AttributeRelationship');
|
||||
}
|
||||
$prefix = $isColumns ? 'Column' : 'Attribute';
|
||||
|
||||
throw new Exception('Unknown attribute implementation');
|
||||
return match ($object['type']) {
|
||||
'string' => match ($object['format'] ?? '') {
|
||||
'email' => static::model("{$prefix}Email"),
|
||||
'url' => static::model("{$prefix}Url"),
|
||||
'ip' => static::model("{$prefix}Ip"),
|
||||
default => static::model("{$prefix}String"),
|
||||
},
|
||||
'integer' => static::model("{$prefix}Integer"),
|
||||
'double' => static::model("{$prefix}Float"),
|
||||
'boolean' => static::model("{$prefix}Boolean"),
|
||||
'datetime' => static::model("{$prefix}Datetime"),
|
||||
'relationship' => static::model("{$prefix}Relationship"),
|
||||
'point' => static::model("{$prefix}Point"),
|
||||
'linestring' => static::model("{$prefix}Line"),
|
||||
'polygon' => static::model("{$prefix}Polygon"),
|
||||
default => throw new Exception('Unknown ' . strtolower($prefix) . ' implementation'),
|
||||
};
|
||||
}
|
||||
|
||||
private static function getHashOptionsImplementation(array $object): Type
|
||||
|
||||
@@ -230,7 +230,7 @@ class Realtime extends MessagingAdapter
|
||||
|
||||
foreach ($channels as $key => $value) {
|
||||
switch (true) {
|
||||
case \str_starts_with($key, 'account.'):
|
||||
case str_starts_with($key, 'account.'):
|
||||
unset($channels[$key]);
|
||||
break;
|
||||
|
||||
@@ -273,6 +273,7 @@ class Realtime extends MessagingAdapter
|
||||
$roles = [Role::user(ID::custom($parts[1]))->toString()];
|
||||
break;
|
||||
case 'rules':
|
||||
case 'migrations':
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $project->getId();
|
||||
$projectId = 'console';
|
||||
@@ -297,19 +298,24 @@ class Realtime extends MessagingAdapter
|
||||
$roles = [Role::team(ID::custom($parts[1]))->toString()];
|
||||
break;
|
||||
case 'databases':
|
||||
if (in_array($parts[4] ?? [], ['attributes', 'indexes'])) {
|
||||
$resource = $parts[4] ?? '';
|
||||
if (in_array($resource, ['columns', 'attributes', 'indexes'])) {
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $project->getId();
|
||||
$projectId = 'console';
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
} elseif (($parts[4] ?? '') === 'documents') {
|
||||
} elseif (in_array($resource, ['rows', 'documents'])) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new \Exception('Database needs to be passed to Realtime for Document events in the Database.');
|
||||
throw new \Exception('Database needs to be passed to Realtime for Document/Row events in the Database.');
|
||||
}
|
||||
if ($collection->isEmpty()) {
|
||||
throw new \Exception('Collection needs to be passed to Realtime for Document events in the Database.');
|
||||
throw new \Exception('Collection or the Table needs to be passed to Realtime for Document/Row events in the Database.');
|
||||
}
|
||||
|
||||
$channels[] = 'rows';
|
||||
$channels[] = 'databases.' . $database->getId() . '.tables.' . $payload->getAttribute('$tableId') . '.rows';
|
||||
$channels[] = 'databases.' . $database->getId() . '.tables.' . $payload->getAttribute('$tableId') . '.rows.' . $payload->getId();
|
||||
|
||||
$channels[] = 'documents';
|
||||
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getAttribute('$collectionId') . '.documents';
|
||||
$channels[] = 'databases.' . $database->getId() . '.collections.' . $payload->getAttribute('$collectionId') . '.documents.' . $payload->getId();
|
||||
@@ -334,7 +340,6 @@ class Realtime extends MessagingAdapter
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'functions':
|
||||
if ($parts[2] === 'executions') {
|
||||
if (!empty($payload->getRead())) {
|
||||
@@ -361,12 +366,6 @@ class Realtime extends MessagingAdapter
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
}
|
||||
break;
|
||||
case 'migrations':
|
||||
$channels[] = 'console';
|
||||
$channels[] = 'projects.' . $project->getId();
|
||||
$projectId = 'console';
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
@@ -89,6 +89,7 @@ abstract class Migration
|
||||
'1.7.2' => 'V22',
|
||||
'1.7.3' => 'V22',
|
||||
'1.7.4' => 'V22',
|
||||
'1.8.0' => 'V23'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -120,6 +121,7 @@ abstract class Migration
|
||||
* @param Document $project
|
||||
* @param Database $dbForProject
|
||||
* @param Database $dbForPlatform
|
||||
* @param callable|null $getProjectDB
|
||||
* @return self
|
||||
*/
|
||||
public function setProject(
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class V23 extends Migration
|
||||
{
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
/**
|
||||
* Disable SubQueries for Performance.
|
||||
*/
|
||||
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryDevKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
fn () => []
|
||||
);
|
||||
}
|
||||
|
||||
Console::info('Migrating databases');
|
||||
$this->migrateDatabases();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Databases.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|Throwable
|
||||
*/
|
||||
private function migrateDatabases(): void
|
||||
{
|
||||
if ($this->project->getId() === 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
// since required + default can't be used together
|
||||
// so first creating the attribute then bulk updating the attribute
|
||||
$this->createAttributeFromCollection($this->dbForProject, 'databases', 'type');
|
||||
$this->dbForProject->updateDocuments('databases', new Document(['type' => 'legacy']));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Network;
|
||||
|
||||
class Platform
|
||||
{
|
||||
public const TYPE_UNKNOWN = 'unknown';
|
||||
public const TYPE_WEB = 'web';
|
||||
public const TYPE_FLUTTER_IOS = 'flutter-ios';
|
||||
public const TYPE_FLUTTER_ANDROID = 'flutter-android';
|
||||
public const TYPE_FLUTTER_MACOS = 'flutter-macos';
|
||||
public const TYPE_FLUTTER_WINDOWS = 'flutter-windows';
|
||||
public const TYPE_FLUTTER_LINUX = 'flutter-linux';
|
||||
public const TYPE_FLUTTER_WEB = 'flutter-web';
|
||||
public const TYPE_APPLE_IOS = 'apple-ios';
|
||||
public const TYPE_APPLE_MACOS = 'apple-macos';
|
||||
public const TYPE_APPLE_WATCHOS = 'apple-watchos';
|
||||
public const TYPE_APPLE_TVOS = 'apple-tvos';
|
||||
public const TYPE_ANDROID = 'android';
|
||||
public const TYPE_UNITY = 'unity';
|
||||
public const TYPE_REACT_NATIVE_IOS = 'react-native-ios';
|
||||
public const TYPE_REACT_NATIVE_ANDROID = 'react-native-android';
|
||||
public const TYPE_SCHEME = 'scheme';
|
||||
|
||||
public const SCHEME_HTTP = 'http';
|
||||
public const SCHEME_HTTPS = 'https';
|
||||
public const SCHEME_CHROME_EXTENSION = 'chrome-extension';
|
||||
public const SCHEME_FIREFOX_EXTENSION = 'moz-extension';
|
||||
public const SCHEME_SAFARI_EXTENSION = 'safari-web-extension';
|
||||
public const SCHEME_EDGE_EXTENSION = 'ms-browser-extension';
|
||||
public const SCHEME_IOS = 'appwrite-ios';
|
||||
public const SCHEME_MACOS = 'appwrite-macos';
|
||||
public const SCHEME_WATCHOS = 'appwrite-watchos';
|
||||
public const SCHEME_TVOS = 'appwrite-tvos';
|
||||
public const SCHEME_ANDROID = 'appwrite-android';
|
||||
public const SCHEME_WINDOWS = 'appwrite-windows';
|
||||
public const SCHEME_LINUX = 'appwrite-linux';
|
||||
|
||||
/**
|
||||
* @var array<string, string> Map scheme types to user-friendly platform names.
|
||||
*/
|
||||
private static array $names = [
|
||||
self::SCHEME_HTTP => 'Web',
|
||||
self::SCHEME_HTTPS => 'Web',
|
||||
self::SCHEME_IOS => 'iOS',
|
||||
self::SCHEME_MACOS => 'macOS',
|
||||
self::SCHEME_WATCHOS => 'watchOS',
|
||||
self::SCHEME_TVOS => 'tvOS',
|
||||
self::SCHEME_ANDROID => 'Android',
|
||||
self::SCHEME_WINDOWS => 'Windows',
|
||||
self::SCHEME_LINUX => 'Linux',
|
||||
self::SCHEME_CHROME_EXTENSION => 'Web (Chrome Extension)',
|
||||
self::SCHEME_FIREFOX_EXTENSION => 'Web (Firefox Extension)',
|
||||
self::SCHEME_SAFARI_EXTENSION => 'Web (Safari Extension)',
|
||||
self::SCHEME_EDGE_EXTENSION => 'Web (Edge Extension)',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get user-friendly platform name from a scheme.
|
||||
*
|
||||
* @param string|null $scheme
|
||||
* @return string Empty string if scheme is not found.
|
||||
*/
|
||||
public static function getNameByScheme(?string $scheme): string
|
||||
{
|
||||
return self::$names[$scheme] ?? '';
|
||||
}
|
||||
|
||||
public static function getHostnames(array $platforms): array
|
||||
{
|
||||
$hostnames = [];
|
||||
foreach ($platforms as $platform) {
|
||||
$type = strtolower($platform['type'] ?? self::TYPE_UNKNOWN);
|
||||
$hostname = strtolower($platform['hostname'] ?? '');
|
||||
$key = strtolower($platform['key'] ?? '');
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_WEB:
|
||||
case self::TYPE_FLUTTER_WEB:
|
||||
if (!empty($hostname)) {
|
||||
$hostnames[] = $hostname;
|
||||
}
|
||||
break;
|
||||
case self::TYPE_FLUTTER_IOS:
|
||||
case self::TYPE_FLUTTER_ANDROID:
|
||||
case self::TYPE_FLUTTER_MACOS:
|
||||
case self::TYPE_FLUTTER_WINDOWS:
|
||||
case self::TYPE_FLUTTER_LINUX:
|
||||
case self::TYPE_ANDROID:
|
||||
case self::TYPE_APPLE_IOS:
|
||||
case self::TYPE_APPLE_MACOS:
|
||||
case self::TYPE_APPLE_WATCHOS:
|
||||
case self::TYPE_APPLE_TVOS:
|
||||
case self::TYPE_REACT_NATIVE_IOS:
|
||||
case self::TYPE_REACT_NATIVE_ANDROID:
|
||||
case self::TYPE_UNITY:
|
||||
if (!empty($key)) {
|
||||
$hostnames[] = $key;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array_unique($hostnames);
|
||||
}
|
||||
|
||||
public static function getSchemes(array $platforms): array
|
||||
{
|
||||
$schemes = [];
|
||||
foreach ($platforms as $platform) {
|
||||
$type = strtolower($platform['type'] ?? self::TYPE_UNKNOWN);
|
||||
$scheme = strtolower($platform['key'] ?? '');
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_SCHEME:
|
||||
if (!empty($scheme) && preg_match('/^[a-z][a-z0-9+.-]*$/', $scheme)) {
|
||||
$schemes[] = $scheme;
|
||||
}
|
||||
break;
|
||||
case self::TYPE_WEB:
|
||||
case self::TYPE_FLUTTER_WEB:
|
||||
$schemes[] = self::SCHEME_HTTP;
|
||||
$schemes[] = self::SCHEME_HTTPS;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_IOS:
|
||||
case self::TYPE_APPLE_IOS:
|
||||
case self::TYPE_REACT_NATIVE_IOS:
|
||||
$schemes[] = self::SCHEME_IOS;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_ANDROID:
|
||||
case self::TYPE_ANDROID:
|
||||
case self::TYPE_REACT_NATIVE_ANDROID:
|
||||
$schemes[] = self::SCHEME_ANDROID;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_MACOS:
|
||||
case self::TYPE_APPLE_MACOS:
|
||||
$schemes[] = self::SCHEME_MACOS;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_WINDOWS:
|
||||
case self::TYPE_UNITY:
|
||||
$schemes[] = self::SCHEME_WINDOWS;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_LINUX:
|
||||
$schemes[] = self::SCHEME_LINUX;
|
||||
break;
|
||||
case self::TYPE_APPLE_WATCHOS:
|
||||
$schemes[] = self::SCHEME_WATCHOS;
|
||||
break;
|
||||
case self::TYPE_APPLE_TVOS:
|
||||
$schemes[] = self::SCHEME_TVOS;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array_unique($schemes);
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,38 @@
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Utopia\DNS\Client;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
|
||||
class DNS extends Validator
|
||||
{
|
||||
public const RECORD_A = 'a';
|
||||
public const RECORD_AAAA = 'aaaa';
|
||||
public const RECORD_CNAME = 'cname';
|
||||
public const RECORD_A = 'A';
|
||||
public const RECORD_AAAA = 'AAAA';
|
||||
public const RECORD_CNAME = 'CNAME';
|
||||
public const RECORD_CAA = 'CAA'; // You can provide domain only (as $target) for CAA validation
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected mixed $logs;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $dnsServer;
|
||||
|
||||
/**
|
||||
* @param string $target
|
||||
*/
|
||||
public function __construct(protected string $target, protected string $type = self::RECORD_CNAME)
|
||||
public function __construct(protected string $target, protected string $type = self::RECORD_CNAME, string $dnsServer = '')
|
||||
{
|
||||
if (empty($dnsServer)) {
|
||||
$dnsServer = System::getEnv('_APP_DNS', '8.8.8.8');
|
||||
}
|
||||
|
||||
$this->dnsServer = $dnsServer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,42 +56,65 @@ class DNS extends Validator
|
||||
* Check if DNS record value matches specific value
|
||||
*
|
||||
* @param mixed $domain
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
$typeNative = match ($this->type) {
|
||||
self::RECORD_A => DNS_A,
|
||||
self::RECORD_AAAA => DNS_AAAA,
|
||||
self::RECORD_CNAME => DNS_CNAME,
|
||||
default => throw new \Exception('Record type not supported.')
|
||||
};
|
||||
|
||||
$dnsKey = match ($this->type) {
|
||||
self::RECORD_A => 'ip',
|
||||
self::RECORD_AAAA => 'ipv6',
|
||||
self::RECORD_CNAME => 'target',
|
||||
default => throw new \Exception('Record type not supported.')
|
||||
};
|
||||
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dns = new Client($this->dnsServer);
|
||||
|
||||
try {
|
||||
$records = \dns_get_record($value, $typeNative);
|
||||
$this->logs = $records;
|
||||
} catch (\Throwable $th) {
|
||||
$rawQuery = $dns->query($value, $this->type);
|
||||
|
||||
// Some DNS servers return all records, not only type that's asked for
|
||||
// Likely occurs when no records of specific type are found
|
||||
$query = array_filter($rawQuery, function ($record) {
|
||||
return $record->getTypeName() === $this->type;
|
||||
});
|
||||
|
||||
$this->logs = $query;
|
||||
} catch (\Exception $e) {
|
||||
$this->logs = ['error' => $e->getMessage()];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$records) {
|
||||
if (empty($query)) {
|
||||
// CAA records inherit from parent (custom CAA behaviour)
|
||||
if ($this->type === self::RECORD_CAA) {
|
||||
$domain = new Domain($value);
|
||||
if ($domain->get() === $domain->getApex()) {
|
||||
return true; // No CAA on apex domain means anyone can issue certificate
|
||||
}
|
||||
|
||||
// Recursive validation by parent domain
|
||||
$parts = \explode('.', $value);
|
||||
\array_shift($parts);
|
||||
$parentDomain = \implode('.', $parts);
|
||||
$validator = new DNS($this->target, DNS::RECORD_CAA, $this->dnsServer);
|
||||
return $validator->isValid($parentDomain);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
if (isset($record[$dnsKey]) && $record[$dnsKey] === $this->target) {
|
||||
foreach ($query as $record) {
|
||||
// CAA validation only needs to ensure domain
|
||||
if ($this->type === self::RECORD_CAA) {
|
||||
// Extract domain; comments showcase extraction steps in most complex scenario
|
||||
$rdata = $record->getRdata(); // 255 issuewild "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
|
||||
$rdata = \explode(' ', $rdata, 3)[2] ?? ''; // "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
|
||||
$rdata = \trim($rdata, '"'); // certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600
|
||||
$rdata = \explode(';', $rdata, 2)[0] ?? ''; // certainly.com
|
||||
|
||||
if ($rdata === $this->target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($record->getRdata() === $this->target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,150 +2,91 @@
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Hostname;
|
||||
|
||||
class Origin extends Validator
|
||||
{
|
||||
public const CLIENT_TYPE_UNKNOWN = 'unknown';
|
||||
public const CLIENT_TYPE_WEB = 'web';
|
||||
public const CLIENT_TYPE_FLUTTER_IOS = 'flutter-ios';
|
||||
public const CLIENT_TYPE_FLUTTER_ANDROID = 'flutter-android';
|
||||
public const CLIENT_TYPE_FLUTTER_MACOS = 'flutter-macos';
|
||||
public const CLIENT_TYPE_FLUTTER_WINDOWS = 'flutter-windows';
|
||||
public const CLIENT_TYPE_FLUTTER_LINUX = 'flutter-linux';
|
||||
public const CLIENT_TYPE_FLUTTER_WEB = 'flutter-web';
|
||||
public const CLIENT_TYPE_APPLE_IOS = 'apple-ios';
|
||||
public const CLIENT_TYPE_APPLE_MACOS = 'apple-macos';
|
||||
public const CLIENT_TYPE_APPLE_WATCHOS = 'apple-watchos';
|
||||
public const CLIENT_TYPE_APPLE_TVOS = 'apple-tvos';
|
||||
public const CLIENT_TYPE_ANDROID = 'android';
|
||||
public const CLIENT_TYPE_UNITY = 'unity';
|
||||
public const CLIENT_TYPE_REACT_NATIVE_IOS = 'react-native-ios';
|
||||
public const CLIENT_TYPE_REACT_NATIVE_ANDROID = 'react-native-android';
|
||||
|
||||
|
||||
public const SCHEME_TYPE_HTTP = 'http';
|
||||
public const SCHEME_TYPE_HTTPS = 'https';
|
||||
public const SCHEME_TYPE_IOS = 'appwrite-ios';
|
||||
public const SCHEME_TYPE_MACOS = 'appwrite-macos';
|
||||
public const SCHEME_TYPE_WATCHOS = 'appwrite-watchos';
|
||||
public const SCHEME_TYPE_TVOS = 'appwrite-tvos';
|
||||
public const SCHEME_TYPE_ANDROID = 'appwrite-android';
|
||||
public const SCHEME_TYPE_WINDOWS = 'appwrite-windows';
|
||||
public const SCHEME_TYPE_LINUX = 'appwrite-linux';
|
||||
protected array $hostnames = [];
|
||||
protected array $schemes = [];
|
||||
protected ?string $scheme = null;
|
||||
protected ?string $host = null;
|
||||
protected string $origin = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* Constructor
|
||||
*
|
||||
* @param array<\Utopia\Database\Document> $platforms
|
||||
*/
|
||||
protected $platforms = [
|
||||
self::SCHEME_TYPE_HTTP => 'Web',
|
||||
self::SCHEME_TYPE_HTTPS => 'Web',
|
||||
self::SCHEME_TYPE_IOS => 'iOS',
|
||||
self::SCHEME_TYPE_MACOS => 'macOS',
|
||||
self::SCHEME_TYPE_WATCHOS => 'watchOS',
|
||||
self::SCHEME_TYPE_TVOS => 'tvOS',
|
||||
self::SCHEME_TYPE_ANDROID => 'Android',
|
||||
self::SCHEME_TYPE_WINDOWS => 'Windows',
|
||||
self::SCHEME_TYPE_LINUX => 'Linux',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $clients = [
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $client = self::CLIENT_TYPE_UNKNOWN;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $host = '';
|
||||
|
||||
/**
|
||||
* @param string $target
|
||||
*/
|
||||
public function __construct($platforms)
|
||||
public function __construct(array $platforms)
|
||||
{
|
||||
foreach ($platforms as $platform) {
|
||||
$type = (isset($platform['type'])) ? $platform['type'] : '';
|
||||
|
||||
switch ($type) {
|
||||
case self::CLIENT_TYPE_WEB:
|
||||
case self::CLIENT_TYPE_FLUTTER_WEB:
|
||||
$this->clients[] = (isset($platform['hostname'])) ? $platform['hostname'] : '';
|
||||
break;
|
||||
|
||||
case self::CLIENT_TYPE_FLUTTER_IOS:
|
||||
case self::CLIENT_TYPE_FLUTTER_ANDROID:
|
||||
case self::CLIENT_TYPE_FLUTTER_MACOS:
|
||||
case self::CLIENT_TYPE_FLUTTER_WINDOWS:
|
||||
case self::CLIENT_TYPE_FLUTTER_LINUX:
|
||||
case self::CLIENT_TYPE_ANDROID:
|
||||
case self::CLIENT_TYPE_APPLE_IOS:
|
||||
case self::CLIENT_TYPE_APPLE_MACOS:
|
||||
case self::CLIENT_TYPE_APPLE_WATCHOS:
|
||||
case self::CLIENT_TYPE_APPLE_TVOS:
|
||||
case self::CLIENT_TYPE_REACT_NATIVE_IOS:
|
||||
case self::CLIENT_TYPE_REACT_NATIVE_ANDROID:
|
||||
$this->clients[] = (isset($platform['key'])) ? $platform['key'] : '';
|
||||
break;
|
||||
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->hostnames = Platform::getHostnames($platforms);
|
||||
$this->schemes = Platform::getSchemes($platforms);
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
if (!\array_key_exists($this->client, $this->platforms)) {
|
||||
return 'Unsupported platform';
|
||||
}
|
||||
|
||||
return 'Invalid Origin. Register your new client (' . $this->host . ') as a new '
|
||||
. $this->platforms[$this->client] . ' platform on your project console dashboard';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Origin has been allowed
|
||||
* for access to the API
|
||||
*
|
||||
* @param mixed $origin
|
||||
*
|
||||
* Check if Origin is valid.
|
||||
* @param mixed $origin The Origin URI.
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($origin): bool
|
||||
{
|
||||
if (!is_string($origin)) {
|
||||
$this->origin = $origin;
|
||||
$this->scheme = null;
|
||||
$this->host = null;
|
||||
|
||||
if (!is_string($origin) || empty($origin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$scheme = \parse_url($origin, PHP_URL_SCHEME);
|
||||
$host = \parse_url($origin, PHP_URL_HOST);
|
||||
$this->scheme = $this->parseScheme($origin);
|
||||
$this->host = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
|
||||
|
||||
$this->host = $host;
|
||||
$this->client = $scheme;
|
||||
$webPlatforms = [
|
||||
Platform::SCHEME_HTTP,
|
||||
Platform::SCHEME_HTTPS,
|
||||
Platform::SCHEME_CHROME_EXTENSION,
|
||||
Platform::SCHEME_FIREFOX_EXTENSION,
|
||||
Platform::SCHEME_SAFARI_EXTENSION,
|
||||
Platform::SCHEME_EDGE_EXTENSION,
|
||||
];
|
||||
if (in_array($this->scheme, $webPlatforms, true)) {
|
||||
$validator = new Hostname($this->hostnames);
|
||||
return $validator->isValid($this->host);
|
||||
}
|
||||
|
||||
if (empty($host)) {
|
||||
if (!empty($this->scheme) && in_array($this->scheme, $this->schemes, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$validator = new Hostname($this->clients);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $validator->isValid($host);
|
||||
/**
|
||||
* Get Description
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
$platform = $this->scheme ? Platform::getNameByScheme($this->scheme) : '';
|
||||
$host = $this->host ? '(' . $this->host . ')' : '';
|
||||
|
||||
if (empty($this->host) && empty($this->scheme)) {
|
||||
return 'Invalid Origin.';
|
||||
}
|
||||
|
||||
if (empty($platform)) {
|
||||
return 'Invalid Scheme. The scheme used (' . $this->scheme . ') in the Origin (' . $this->origin . ') is not supported. If you are using a custom scheme, please change it to `appwrite-callback-<PROJECT_ID>`';
|
||||
}
|
||||
|
||||
return 'Invalid Origin. Register your new client ' . $host . ' as a new '
|
||||
. $platform . ' platform on your project console dashboard';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
@@ -155,13 +96,35 @@ class Origin extends Validator
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the scheme from a URI string.
|
||||
*
|
||||
* @param string $uri The URI string to parse.
|
||||
* @return string|null The extracted scheme string (e.g., "http", "exp", "mailto")
|
||||
*/
|
||||
public function parseScheme(string $uri): ?string
|
||||
{
|
||||
$uri = trim($uri);
|
||||
if ($uri === '') {
|
||||
return null; // No scheme in empty string
|
||||
}
|
||||
|
||||
$scheme = parse_url($uri, PHP_URL_SCHEME);
|
||||
if ($scheme === false) {
|
||||
if (preg_match('/^([a-z][a-z0-9+.-]*):/i', $uri, $matches)) {
|
||||
return $matches[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return $scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
|
||||
class Redirect extends Origin
|
||||
{
|
||||
/**
|
||||
* Get Description
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
$platform = Platform::getNameByScheme($this->scheme);
|
||||
$host = $this->host ? '(' . $this->host . ')' : '';
|
||||
|
||||
if (empty($this->host) && empty($this->scheme)) {
|
||||
return 'Invalid URI.';
|
||||
}
|
||||
|
||||
return 'Invalid URI. Register your new client ' . $host . ' as a new '
|
||||
. $platform . ' platform on your project console dashboard';
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,13 @@
|
||||
|
||||
namespace Appwrite\Platform;
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Coroutine as Co;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
|
||||
@@ -16,6 +21,20 @@ class Action extends UtopiaAction
|
||||
*/
|
||||
protected mixed $logError;
|
||||
|
||||
protected array $filters = [
|
||||
'subQueryKeys', 'subQueryWebhooks', 'subQueryPlatforms', 'subQueryProjectVariables', 'subQueryBlocks', 'subQueryDevKeys', // Project
|
||||
'subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships', 'subQueryTargets', 'subQueryTopicTargets',// Users
|
||||
'subQueryVariables', // Sites
|
||||
];
|
||||
|
||||
/**
|
||||
* Attributes to remove from relationship path documents per API
|
||||
* Default is empty - APIs should set their specific attributes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $removableAttributes = [];
|
||||
|
||||
/**
|
||||
* Foreach Document
|
||||
* Call provided callback for each document in the collection
|
||||
@@ -87,4 +106,98 @@ class Action extends UtopiaAction
|
||||
$latestDocument = $results[array_key_last($results)];
|
||||
}
|
||||
}
|
||||
|
||||
public function disableSubqueries()
|
||||
{
|
||||
$filters = $this->filters;
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
Database::addFilter(
|
||||
$filter,
|
||||
function (mixed $value) {
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return [];
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump Log Message
|
||||
*
|
||||
* Logs messages to console with timestamp, method context, and project details.
|
||||
* Supports multiple log types: success, error, log, warning, and info (default).
|
||||
*
|
||||
* @param string $method The calling method name
|
||||
* @param string $log The log message
|
||||
* @param string $type The log type (success, error, log, warning, info)
|
||||
* @param Document|null $project The project document for context
|
||||
* @param string $collectionId The collection identifier
|
||||
* @return void
|
||||
*/
|
||||
public function dump(string $method, string $log, string $type = 'info', ?Document $project = null, string $collectionId = ''): void
|
||||
{
|
||||
if (empty($project)) {
|
||||
$project = new Document([]);
|
||||
}
|
||||
switch ($type) {
|
||||
case 'success':
|
||||
Console::success("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
|
||||
break;
|
||||
case 'error':
|
||||
Console::error("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
|
||||
break;
|
||||
case 'log':
|
||||
Console::log("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
|
||||
break;
|
||||
case 'warning':
|
||||
Console::warning("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
|
||||
break;
|
||||
default:
|
||||
Console::info("[" . DateTime::now() . "] " . $method . ' ' . $type . ' ' . $project->getSequence() . ' ' . $project->getId() . ' ' . $collectionId . ' ' . $log);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to apply (request) select queries to response model.
|
||||
*
|
||||
* This prevents default values of rules to be presnet for not-selected attributes
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Document $document
|
||||
* @return void
|
||||
*/
|
||||
public function applySelectQueries(Request $request, Response $response, string $model): void
|
||||
{
|
||||
$queries = $request->getParam('queries', []);
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$selectQueries = Query::groupByType($queries)['selections'] ?? [];
|
||||
|
||||
// No select queries means no filtering out
|
||||
if (empty($selectQueries)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
foreach ($selectQueries as $query) {
|
||||
foreach ($query->getValues() as $attribute) {
|
||||
$attributes[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
$responseModel = $response->getModel($model);
|
||||
foreach ($responseModel->getRules() as $ruleName => $rule) {
|
||||
if (\str_starts_with($ruleName, '$')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\in_array($ruleName, $attributes)) {
|
||||
$responseModel->removeRule($ruleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Platform;
|
||||
|
||||
use Appwrite\Platform\Modules\Console;
|
||||
use Appwrite\Platform\Modules\Core;
|
||||
use Appwrite\Platform\Modules\Databases;
|
||||
use Appwrite\Platform\Modules\Functions;
|
||||
use Appwrite\Platform\Modules\Projects;
|
||||
use Appwrite\Platform\Modules\Proxy;
|
||||
@@ -16,6 +17,7 @@ class Appwrite extends Platform
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(new Core());
|
||||
$this->addModule(new Databases\Module());
|
||||
$this->addModule(new Projects\Module());
|
||||
$this->addModule(new Functions\Module());
|
||||
$this->addModule(new Sites\Module());
|
||||
|
||||
@@ -4,13 +4,14 @@ namespace Appwrite\Platform\Modules\Compute;
|
||||
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Request;
|
||||
use Utopia\System\System;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
@@ -226,6 +227,73 @@ class Base extends Action
|
||||
]))
|
||||
);
|
||||
|
||||
if (!empty($commitDetails['commitHash'])) {
|
||||
$domain = "commit-" . substr($commitDetails['commitHash'], 0, 16) . ".{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'domain' => $domain,
|
||||
'type' => 'deployment',
|
||||
'trigger' => 'deployment',
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'deploymentInternalId' => $deployment->getSequence(),
|
||||
'deploymentResourceType' => 'site',
|
||||
'deploymentResourceId' => $site->getId(),
|
||||
'deploymentResourceInternalId' => $site->getSequence(),
|
||||
'deploymentVcsProviderBranch' => $providerBranch,
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
'owner' => 'Appwrite',
|
||||
'region' => $project->getAttribute('region')
|
||||
]))
|
||||
);
|
||||
} catch (Duplicate $err) {
|
||||
// Ignore, rule already exists; will be updated by builds worker
|
||||
}
|
||||
}
|
||||
|
||||
// VCS branch preview
|
||||
if (!empty($providerBranch)) {
|
||||
$branchPrefix = substr($providerBranch, 0, 16);
|
||||
if (strlen($providerBranch) > 16) {
|
||||
$remainingChars = substr($providerBranch, 16);
|
||||
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7);
|
||||
}
|
||||
$resourceProjectHash = substr(hash('sha256', $site->getId() . $project->getId()), 0, 7);
|
||||
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'domain' => $domain,
|
||||
'type' => 'deployment',
|
||||
'trigger' => 'deployment',
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'deploymentInternalId' => $deployment->getSequence(),
|
||||
'deploymentResourceType' => 'site',
|
||||
'deploymentResourceId' => $site->getId(),
|
||||
'deploymentResourceInternalId' => $site->getSequence(),
|
||||
'deploymentVcsProviderBranch' => $providerBranch,
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
'owner' => 'Appwrite',
|
||||
'region' => $project->getAttribute('region')
|
||||
]))
|
||||
);
|
||||
} catch (Duplicate $err) {
|
||||
// Ignore, rule already exists; will be updated by builds worker
|
||||
}
|
||||
}
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
->setResource($site)
|
||||
|
||||
@@ -11,9 +11,11 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Domains\Domain as Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Domain;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain as DomainValidator;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -57,7 +59,7 @@ class Get extends Action
|
||||
->param('type', '', new WhiteList(['rules']), 'Resource type.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->callback([$this, 'action']);
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
@@ -67,10 +69,59 @@ class Get extends Action
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
if ($type === 'rules') {
|
||||
$validator = new Domain($value);
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$restrictions = [];
|
||||
if (!empty($sitesDomain)) {
|
||||
// Ensure site domains are exactly 1 subdomain, and dont start with reserved prefix
|
||||
$domainLevel = \count(\explode('.', $sitesDomain));
|
||||
$restrictions[] = DomainValidator::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']);
|
||||
}
|
||||
if (!empty($functionsDomain)) {
|
||||
// Ensure function domains are exactly 1 subdomain
|
||||
$domainLevel = \count(\explode('.', $functionsDomain));
|
||||
$restrictions[] = DomainValidator::createRestriction($functionsDomain, $domainLevel + 1);
|
||||
}
|
||||
$validator = new DomainValidator($restrictions);
|
||||
|
||||
if (!$validator->isValid($value)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $validator->getDescription());
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
$deniedDomains = [
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
}
|
||||
|
||||
if (!empty($functionsDomain)) {
|
||||
$deniedDomains[] = $functionsDomain;
|
||||
}
|
||||
|
||||
$denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', '');
|
||||
$denyListDomains = \array_map('trim', explode(',', $denyListDomains));
|
||||
foreach ($denyListDomains as $denyListDomain) {
|
||||
if (empty($denyListDomain)) {
|
||||
continue;
|
||||
}
|
||||
$deniedDomains[] = $denyListDomain;
|
||||
}
|
||||
|
||||
if (\in_array($value, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
try {
|
||||
$domain = new Domain($value);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
$document = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Constants for identifying database resource types.
|
||||
*
|
||||
* - Tables vs. Collections,
|
||||
* - Rows vs. Documents, and
|
||||
* - Columns vs. Attributes
|
||||
*
|
||||
* are functionally equivalent and share the same underlying API structure.
|
||||
*
|
||||
* These constants help distinguish the context of an action,
|
||||
* enabling accurate error messages, realtime event triggers, and other context-aware behaviors.
|
||||
*/
|
||||
|
||||
const ROWS = 'row';
|
||||
const TABLES = 'table';
|
||||
const COLUMNS = 'column';
|
||||
const COLUMN_INDEX = 'columnIndex';
|
||||
|
||||
const INDEX = 'index';
|
||||
const DOCUMENTS = 'document';
|
||||
const ATTRIBUTES = 'attribute';
|
||||
const COLLECTIONS = 'collection';
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases;
|
||||
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
|
||||
class Action extends UtopiaAction
|
||||
{
|
||||
private string $context = 'legacy';
|
||||
|
||||
public function getDatabaseType(): string
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function setHttpPath(string $path): UtopiaAction
|
||||
{
|
||||
if (\str_contains($path, '/tablesdb')) {
|
||||
$this->context = 'tablesdb';
|
||||
}
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
abstract class Action extends UtopiaAction
|
||||
{
|
||||
/**
|
||||
* The current API context (either 'table' or 'collection').
|
||||
*/
|
||||
private ?string $context = COLLECTIONS;
|
||||
|
||||
/**
|
||||
* Get the response model used in the SDK and HTTP responses.
|
||||
*/
|
||||
abstract protected function getResponseModel(): string;
|
||||
|
||||
public function setHttpPath(string $path): UtopiaAction
|
||||
{
|
||||
if (\str_contains($path, '/tablesdb')) {
|
||||
$this->context = TABLES;
|
||||
}
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current API context.
|
||||
*/
|
||||
protected function getContext(): string
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key used in event parameters (e.g., 'collectionId' or 'tableId').
|
||||
*/
|
||||
protected function getEventsParamKey(): string
|
||||
{
|
||||
return $this->getContext() . 'Id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current action is for the Collections API.
|
||||
*/
|
||||
protected function isCollectionsAPI(): bool
|
||||
{
|
||||
return $this->getContext() === COLLECTIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK group name for the current action.
|
||||
*/
|
||||
protected function getSdkGroup(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'collections' : 'tables';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK namespace for the current action.
|
||||
*/
|
||||
protected function getSdkNamespace(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'databases' : 'tablesDB';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the resource already exists.
|
||||
*/
|
||||
protected function getDuplicateException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::COLLECTION_ALREADY_EXISTS
|
||||
: Exception::TABLE_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate index invalid exception.
|
||||
*/
|
||||
protected function getInvalidIndexException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::INDEX_INVALID
|
||||
: Exception::COLUMN_INDEX_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the resource is not found.
|
||||
*/
|
||||
protected function getNotFoundException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::COLLECTION_NOT_FOUND
|
||||
: Exception::TABLE_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the resource limit is exceeded.
|
||||
*/
|
||||
protected function getLimitException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::COLLECTION_LIMIT_EXCEEDED
|
||||
: Exception::TABLE_LIMIT_EXCEEDED;
|
||||
}
|
||||
}
|
||||
+669
@@ -0,0 +1,669 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\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\Relationship as RelationshipException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
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 = ATTRIBUTES;
|
||||
|
||||
/**
|
||||
* Get the correct response model.
|
||||
*/
|
||||
abstract protected function getResponseModel(): string|array;
|
||||
|
||||
public function setHttpPath(string $path): UtopiaAction
|
||||
{
|
||||
if (\str_contains($path, '/tablesdb')) {
|
||||
$this->context = COLUMNS;
|
||||
}
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current context.
|
||||
*/
|
||||
protected function getContext(): string
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if current context is Collections API.
|
||||
*/
|
||||
protected function isCollectionsAPI(): bool
|
||||
{
|
||||
// columns in tables context
|
||||
// attributes in collections context
|
||||
return $this->getContext() === ATTRIBUTES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK group name for the current action.
|
||||
*
|
||||
* Can be used for XList operations as well!
|
||||
*/
|
||||
protected function getSdkGroup(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'attributes' : 'columns';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK namespace for the current action.
|
||||
*/
|
||||
protected function getSdkNamespace(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'databases' : 'tablesDB';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate parent level not found exception.
|
||||
*/
|
||||
protected function getParentNotFoundException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::COLLECTION_NOT_FOUND
|
||||
: Exception::TABLE_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate not found exception.
|
||||
*/
|
||||
protected function getNotFoundException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_NOT_FOUND
|
||||
: Exception::COLUMN_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate not found exception.
|
||||
*/
|
||||
protected function getIndexDependencyException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::INDEX_DEPENDENCY
|
||||
: Exception::COLUMN_INDEX_DEPENDENCY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate already exists exception.
|
||||
*/
|
||||
protected function getDuplicateException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_ALREADY_EXISTS
|
||||
: Exception::COLUMN_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct invalid structure message.
|
||||
*/
|
||||
protected function getInvalidStructureException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_INVALID_STRUCTURE
|
||||
: Exception::ROW_INVALID_STRUCTURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate limit exceeded exception.
|
||||
*/
|
||||
protected function getLimitException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_LIMIT_EXCEEDED
|
||||
: Exception::COLUMN_LIMIT_EXCEEDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate index invalid exception.
|
||||
*/
|
||||
protected function getInvalidIndexException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::INDEX_INVALID
|
||||
: Exception::COLUMN_INDEX_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct default unsupported message.
|
||||
*/
|
||||
protected function getDefaultUnsupportedException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED
|
||||
: Exception::COLUMN_DEFAULT_UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct format unsupported message.
|
||||
*/
|
||||
protected function getFormatUnsupportedException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_FORMAT_UNSUPPORTED
|
||||
: Exception::COLUMN_FORMAT_UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception for invalid type or format mismatch.
|
||||
*/
|
||||
protected function getTypeInvalidException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_TYPE_INVALID
|
||||
: Exception::COLUMN_TYPE_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception for resizing invalid attributes/columns.
|
||||
*/
|
||||
protected function getInvalidResizeException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_INVALID_RESIZE
|
||||
: Exception::COLUMN_INVALID_RESIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception for invalid attributes/columns value.
|
||||
*/
|
||||
protected function getInvalidValueException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_VALUE_INVALID
|
||||
: Exception::COLUMN_VALUE_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception for non-available column/attribute.
|
||||
*/
|
||||
protected function getNotAvailableException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_NOT_AVAILABLE
|
||||
: Exception::COLUMN_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception for spatial type attribute not supported by the database adapter
|
||||
*/
|
||||
protected function getSpatialTypeNotSupportedException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? Exception::ATTRIBUTE_TYPE_NOT_SUPPORTED : Exception::COLUMN_TYPE_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct collections context for Events queue.
|
||||
*/
|
||||
protected function getCollectionsEventsContext(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'collection' : 'table';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the proper column/attribute type based on set context.
|
||||
*/
|
||||
protected function getModel(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_POINT => $isCollections
|
||||
? UtopiaResponse::MODEL_ATTRIBUTE_POINT
|
||||
: UtopiaResponse::MODEL_COLUMN_POINT,
|
||||
|
||||
Database::VAR_LINESTRING => $isCollections
|
||||
? UtopiaResponse::MODEL_ATTRIBUTE_LINE
|
||||
: UtopiaResponse::MODEL_COLUMN_LINE,
|
||||
|
||||
Database::VAR_POLYGON => $isCollections
|
||||
? UtopiaResponse::MODEL_ATTRIBUTE_POLYGON
|
||||
: UtopiaResponse::MODEL_COLUMN_POLYGON,
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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', []);
|
||||
|
||||
if (in_array($type, Database::SPATIAL_TYPES) && !$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
|
||||
throw new Exception($this->getSpatialTypeNotSupportedException());
|
||||
}
|
||||
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $db->getSequence(), $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;
|
||||
$relatedCollection = $dbForProject->getDocument('database_' . $db->getSequence(), $options['relatedCollection'] ?? '');
|
||||
if ($relatedCollection->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->getSequence() . '_' . $collection->getSequence() . '_' . $key),
|
||||
'key' => $key,
|
||||
'databaseInternalId' => $db->getSequence(),
|
||||
'databaseId' => $db->getId(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'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->getSequence(), $collectionId);
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $collection->getSequence());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $collection->getSequence());
|
||||
|
||||
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->getSequence() . '_' . $relatedCollection->getSequence() . '_' . $twoWayKey),
|
||||
'key' => $twoWayKey,
|
||||
'databaseInternalId' => $db->getSequence(),
|
||||
'databaseId' => $db->getId(),
|
||||
'collectionInternalId' => $relatedCollection->getSequence(),
|
||||
'collectionId' => $relatedCollection->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($relatedCollection, $twoWayAttribute);
|
||||
$dbForProject->createDocument('attributes', $twoWayAttribute);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (LimitException) {
|
||||
throw new Exception($this->getLimitException());
|
||||
} catch (StructureException) {
|
||||
throw new Exception($this->getInvalidStructureException());
|
||||
} catch (Throwable $e) {
|
||||
$dbForProject->deleteDocument('attributes', $attribute->getId());
|
||||
throw $e;
|
||||
} finally {
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $collection->getSequence());
|
||||
}
|
||||
|
||||
// If operation succeeded, purge the cache for the related collection too
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $relatedCollection->getId());
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $relatedCollection->getSequence());
|
||||
}
|
||||
|
||||
$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('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('attributeId', $attribute->getId())
|
||||
->setParam('columnId', $attribute->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
$response->setStatusCode(SwooleResponse::STATUS_CODE_CREATED);
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
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|array $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->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$attribute = $dbForProject->getDocument('attributes', $db->getSequence() . '_' . $collection->getSequence() . '_' . $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->getSequence() . '_collection_' . $collection->getSequence();
|
||||
|
||||
$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->getInvalidValueException(), 'Minimum value must be lesser than maximum value');
|
||||
}
|
||||
|
||||
if ($attribute->getAttribute('format') === APP_DATABASE_ATTRIBUTE_INT_RANGE) {
|
||||
$validator = new Range($min, $max, Database::VAR_INTEGER);
|
||||
} else {
|
||||
$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->getInvalidValueException(), $validator->getDescription());
|
||||
}
|
||||
|
||||
$options = [
|
||||
'min' => $min,
|
||||
'max' => $max
|
||||
];
|
||||
$attribute->setAttribute('formatOptions', $options);
|
||||
|
||||
break;
|
||||
case APP_DATABASE_ATTRIBUTE_ENUM:
|
||||
if (empty($elements)) {
|
||||
throw new Exception($this->getInvalidValueException(), 'Enum elements must not be empty');
|
||||
}
|
||||
|
||||
foreach ($elements as $element) {
|
||||
if (\strlen($element) === 0) {
|
||||
throw new Exception($this->getInvalidValueException(), 'Each enum element must not be empty');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($default) && !in_array($default, $elements)) {
|
||||
throw new Exception($this->getInvalidValueException(), '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 (IndexException) {
|
||||
throw new Exception(Exception::INDEX_INVALID);
|
||||
} catch (LimitException) {
|
||||
throw new Exception($this->getLimitException());
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception($this->getInvalidStructureException(), $e->getMessage());
|
||||
}
|
||||
|
||||
if ($primaryDocumentOptions['twoWay']) {
|
||||
$relatedCollection = $dbForProject->getDocument('database_' . $db->getSequence(), $primaryDocumentOptions['relatedCollection']);
|
||||
|
||||
$relatedAttribute = $dbForProject->getDocument('attributes', $db->getSequence() . '_' . $relatedCollection->getSequence() . '_' . $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->getSequence() . '_' . $relatedCollection->getSequence() . '_' . $primaryDocumentOptions['twoWayKey'], $relatedAttribute);
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $relatedCollection->getId());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$dbForProject->updateAttribute(
|
||||
collection: $collectionId,
|
||||
id: $key,
|
||||
size: $size,
|
||||
required: $required,
|
||||
default: $default,
|
||||
formatOptions: $options,
|
||||
newKey: $newKey ?? null
|
||||
);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (IndexException $e) {
|
||||
throw new Exception($this->getInvalidIndexException(), $e->getMessage());
|
||||
} catch (LimitException) {
|
||||
throw new Exception($this->getLimitException());
|
||||
} catch (TruncateException) {
|
||||
throw new Exception($this->getInvalidResizeException());
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($newKey) && $key !== $newKey) {
|
||||
$originalUid = $attribute->getId();
|
||||
|
||||
$attribute
|
||||
->setAttribute('$id', ID::custom($db->getSequence() . '_' . $collection->getSequence() . '_' . $newKey))
|
||||
->setAttribute('key', $newKey);
|
||||
|
||||
try {
|
||||
$dbForProject->updateDocument('attributes', $originalUid, $attribute);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Document $index
|
||||
*/
|
||||
foreach ($collection->getAttribute('indexes') as $index) {
|
||||
/**
|
||||
* @var array<string> $attributes
|
||||
*/
|
||||
$attributes = $index->getAttribute('attributes', []);
|
||||
$found = \array_search($key, $attributes);
|
||||
|
||||
if ($found !== false) {
|
||||
$attributes[$found] = $newKey;
|
||||
$index->setAttribute('attributes', $attributes);
|
||||
$dbForProject->updateDocument('indexes', $index->getId(), $index);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$attribute = $dbForProject->updateDocument('attributes', $db->getSequence() . '_' . $collection->getSequence() . '_' . $key, $attribute);
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collection->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $db)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('attributeId', $attribute->getId())
|
||||
->setParam('columnId', $attribute->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Boolean;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createBooleanAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_BOOLEAN;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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].attributes.[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: $this->getSdkNamespace(),
|
||||
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(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createBooleanColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Boolean;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateBooleanAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_BOOLEAN;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateBooleanColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Datetime;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createDatetimeAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_DATETIME;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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()
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createDatetimeColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Datetime;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateDatetimeAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_DATETIME;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateDatetimeColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\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\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\IndexDependency as IndexDependencyValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Delete extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'deleteAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_NONE;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.deleteColumn',
|
||||
),
|
||||
))
|
||||
->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->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$attribute = $dbForProject->getDocument('attributes', $db->getSequence() . '_' . $collection->getSequence() . '_' . $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($this->getIndexDependencyException());
|
||||
}
|
||||
|
||||
if ($attribute->getAttribute('status') === 'available') {
|
||||
$attribute = $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting'));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $collection->getSequence());
|
||||
|
||||
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
|
||||
$options = $attribute->getAttribute('options');
|
||||
if ($options['twoWay']) {
|
||||
$relatedCollection = $dbForProject->getDocument('database_' . $db->getSequence(), $options['relatedCollection']);
|
||||
if ($relatedCollection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$relatedAttribute = $dbForProject->getDocument('attributes', $db->getSequence() . '_' . $relatedCollection->getSequence() . '_' . $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->getSequence(), $options['relatedCollection']);
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $relatedCollection->getSequence());
|
||||
}
|
||||
}
|
||||
|
||||
$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->getModel($type, $format);
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $db)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('attributeId', $attribute->getId())
|
||||
->setParam('columnId', $attribute->getId())
|
||||
->setPayload($response->output($attribute, $model))
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Email;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createEmailAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_EMAIL;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createEmailColumn',
|
||||
),
|
||||
))
|
||||
->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 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 $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' => 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());
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Email;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateEmailAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_EMAIL;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateEmailColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Enum;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createEnumAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_ENUM;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/create-enum-attribute.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_ACCEPTED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createEnumColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Enum;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
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
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateEnumAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_ENUM;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateEnumColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Float;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\FloatValidator;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createFloatAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_FLOAT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createFloatColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Float;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\FloatValidator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateFloatAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_FLOAT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateFloatColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return [
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
||||
->desc('Get attribute')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/get-attribute.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel()
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.getColumn',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, 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->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$attribute = $dbForProject->getDocument('attributes', $database->getSequence() . '_' . $collection->getSequence() . '_' . $key);
|
||||
if ($attribute->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
$type = $attribute->getAttribute('type');
|
||||
$format = $attribute->getAttribute('format');
|
||||
$options = $attribute->getAttribute('options', []);
|
||||
$filters = $attribute->getAttribute('filters', []);
|
||||
foreach ($options as $key => $option) {
|
||||
$attribute->setAttribute($key, $option);
|
||||
}
|
||||
|
||||
$model = $this->getModel($type, $format);
|
||||
|
||||
$attribute->setAttribute('encrypt', in_array('encrypt', $filters));
|
||||
|
||||
$response->dynamic($attribute, $model);
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\IP;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\IP;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createIpAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_IP;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createIpColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\IP;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateIpAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_IP;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateIpColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Integer;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createIntegerAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_INTEGER;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createIntegerColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Integer;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateIntegerAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_INTEGER;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateIntegerColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createLineAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_LINE;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/line')
|
||||
->desc('Create line attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[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: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/create-line-attribute.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_ACCEPTED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createLineColumn',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for attribute when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when attribute is required.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
{
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
'type' => Database::VAR_LINESTRING,
|
||||
'required' => $required,
|
||||
'default' => $default
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($attribute, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateLineAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_LINE;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/line/:key')
|
||||
->desc('Update line attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/update-line-attribute.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateLineColumn',
|
||||
),
|
||||
))
|
||||
->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 Spatial(Database::VAR_LINESTRING)), 'Default value for attribute when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when attribute is required.', 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, ?array $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_LINESTRING,
|
||||
default: $default,
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
|
||||
->dynamic($attribute, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createPointAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_POINT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/point')
|
||||
->desc('Create point attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[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: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/create-point-attribute.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_ACCEPTED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createPointColumn',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for attribute when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when attribute is required.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
{
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
'type' => Database::VAR_POINT,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($attribute, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updatePointAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_POINT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/point/:key')
|
||||
->desc('Update point attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/update-point-attribute.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updatePointColumn',
|
||||
),
|
||||
))
|
||||
->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 Spatial(Database::VAR_POINT)), 'Default value for attribute when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when attribute is required.', 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, ?array $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_POINT,
|
||||
default: $default,
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
|
||||
->dynamic($attribute, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createPolygonAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_POLYGON;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/polygon')
|
||||
->desc('Create polygon attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[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: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/create-polygon-attribute.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_ACCEPTED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createPolygonColumn',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for attribute when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when attribute is required.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
{
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
'type' => Database::VAR_POLYGON,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($attribute, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updatePolygonAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_POLYGON;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/polygon/:key')
|
||||
->desc('Update polygon attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/update-polygon-attribute.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updatePolygonColumn',
|
||||
),
|
||||
))
|
||||
->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 Spatial(Database::VAR_POLYGON)), 'Default value for attribute when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when attribute is required.', 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, ?array $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_POLYGON,
|
||||
default: $default,
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
|
||||
->dynamic($attribute, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Relationship;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createRelationshipAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_RELATIONSHIP;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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()
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createRelationshipColumn',
|
||||
),
|
||||
))
|
||||
->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->getSequence(), $collectionId);
|
||||
$collection = $dbForProject->getCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence());
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$relatedCollectionDocument = $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId);
|
||||
$relatedCollection = $dbForProject->getCollection('database_' . $database->getSequence() . '_collection_' . $relatedCollectionDocument->getSequence());
|
||||
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());
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Relationship;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateRelationshipAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_RELATIONSHIP;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateRelationshipColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\String;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
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
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createStringAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_STRING;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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()
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createStringColumn',
|
||||
),
|
||||
))
|
||||
->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')
|
||||
->inject('plan')
|
||||
->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,
|
||||
array $plan
|
||||
): void {
|
||||
if (!App::isDevelopment() && $encrypt && !empty($plan) && !($plan['databasesAllowEncrypt'] ?? false)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Encrypted string ' . $this->getSdkGroup() . ' are not available on your plan. Please upgrade to create encrypted string ' . $this->getSdkGroup() . '.');
|
||||
}
|
||||
|
||||
if ($encrypt && $size < APP_DATABASE_ENCRYPT_SIZE_MIN) {
|
||||
throw new Exception(
|
||||
Exception::GENERAL_BAD_REQUEST,
|
||||
"Size too small. Encrypted strings require a minimum size of " . APP_DATABASE_ENCRYPT_SIZE_MIN . " characters."
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
);
|
||||
|
||||
$attribute->setAttribute('encrypt', $encrypt);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($attribute, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\String;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
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
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateStringAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_STRING;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateStringColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\URL;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createUrlAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_URL;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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(),
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createUrlColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\URL;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateUrlAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_URL;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateUrlColumn',
|
||||
),
|
||||
))
|
||||
->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());
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Attributes;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listAttributes';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_ATTRIBUTE_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$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: $this->getSdkNamespace(),
|
||||
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()
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.listColumns',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('queries', [], new Attributes(), '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 $collectionId, 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);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
\array_push(
|
||||
$queries,
|
||||
Query::equal('databaseInternalId', [$database->getSequence()]),
|
||||
Query::equal('collectionInternalId', [$collection->getSequence()])
|
||||
);
|
||||
|
||||
$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();
|
||||
try {
|
||||
$cursorDocument = $dbForProject->findOne('attributes', [
|
||||
Query::equal('databaseInternalId', [$database->getSequence()]),
|
||||
Query::equal('collectionInternalId', [$collection->getSequence()]),
|
||||
Query::equal('key', [$attributeId]),
|
||||
]);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
$type = ucfirst($this->getContext());
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "$type '$attributeId' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
try {
|
||||
$attributes = $dbForProject->find('attributes', $queries);
|
||||
$total = $dbForProject->count('attributes', $queries, 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);
|
||||
} catch (QueryException) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID);
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if ($attribute->getAttribute('type') === Database::VAR_STRING) {
|
||||
$filters = $attribute->getAttribute('filters', []);
|
||||
$attribute->setAttribute('encrypt', in_array('encrypt', $filters));
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $total,
|
||||
$this->getSdkGroup() => $attributes,
|
||||
]), $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
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\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createCollection';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLLECTION;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections')
|
||||
->desc('Create collections')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'collection.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'databases',
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/create-collection.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_CREATED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createTable',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collectionId = $collectionId === 'unique()' ? ID::unique() : $collectionId;
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions) ?? [];
|
||||
|
||||
try {
|
||||
$collection = $dbForProject->createDocument('database_' . $database->getSequence(), new Document([
|
||||
'$id' => $collectionId,
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'databaseId' => $databaseId,
|
||||
'$permissions' => $permissions,
|
||||
'documentSecurity' => $documentSecurity,
|
||||
'enabled' => $enabled,
|
||||
'name' => $name,
|
||||
'search' => \implode(' ', [$collectionId, $name]),
|
||||
]));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (LimitException) {
|
||||
throw new Exception($this->getLimitException());
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->createCollection(
|
||||
id: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
permissions: $permissions,
|
||||
documentSecurity: $documentSecurity
|
||||
);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (IndexException) {
|
||||
throw new Exception($this->getInvalidIndexException());
|
||||
} catch (LimitException) {
|
||||
throw new Exception($this->getLimitException());
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $database)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam($this->getEventsParamKey(), $collection->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_CREATED)
|
||||
->dynamic($collection, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections;
|
||||
|
||||
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\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Delete extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'deleteCollection';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLLECTION;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->desc('Delete collection')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].delete')
|
||||
->label('audits.event', 'collection.delete')
|
||||
->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/delete-collection.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_NOCONTENT,
|
||||
model: UtopiaResponse::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.deleteTable',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('database_' . $database->getSequence(), $collectionId)) {
|
||||
$type = $this->getContext();
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Failed to remove $type from DB");
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence());
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_DELETE_COLLECTION)
|
||||
->setDatabase($database);
|
||||
|
||||
if ($this->isCollectionsAPI()) {
|
||||
$queueForDatabase->setCollection($collection);
|
||||
} else {
|
||||
$queueForDatabase->setTable($collection);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setContext('database', $database)
|
||||
->setParam($this->getEventsParamKey(), $collection->getId())
|
||||
->setPayload($response->output($collection, $this->getResponseModel()));
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action as AppwriteAction;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
abstract class Action extends AppwriteAction
|
||||
{
|
||||
/**
|
||||
* @var string|null The current context (either 'row' or 'document')
|
||||
*/
|
||||
private ?string $context = DOCUMENTS;
|
||||
|
||||
/**
|
||||
* Get the response model used in the SDK and HTTP responses.
|
||||
*/
|
||||
abstract protected function getResponseModel(): string;
|
||||
|
||||
public function setHttpPath(string $path): AppwriteAction
|
||||
{
|
||||
if (str_contains($path, '/tablesdb/')) {
|
||||
$this->context = ROWS;
|
||||
}
|
||||
|
||||
$contextId = '$' . $this->getCollectionsEventsContext() . 'Id';
|
||||
$this->removableAttributes = [
|
||||
'*' => [
|
||||
'$sequence',
|
||||
'$databaseId',
|
||||
$contextId,
|
||||
],
|
||||
'privileged' => [
|
||||
'$createdAt',
|
||||
'$updatedAt',
|
||||
],
|
||||
];
|
||||
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plural of the given name.
|
||||
*
|
||||
* Used for endpoints with multiple sdk methods.
|
||||
*/
|
||||
protected function getBulkActionName(string $name): string
|
||||
{
|
||||
return "{$name}s";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current context.
|
||||
*/
|
||||
protected function getContext(): string
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if current context is Collections API.
|
||||
*/
|
||||
protected function isCollectionsAPI(): bool
|
||||
{
|
||||
// rows in tables api context
|
||||
// documents in collections api context
|
||||
return $this->getContext() === DOCUMENTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK group name for the current action.
|
||||
*
|
||||
* Can be used for XList operations as well!
|
||||
*/
|
||||
protected function getSdkGroup(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'documents' : 'rows';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK namespace for the current action.
|
||||
*/
|
||||
protected function getSdkNamespace(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'databases' : 'tablesDB';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct attribute/column structure context for errors.
|
||||
*/
|
||||
protected function getStructureContext(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'attributes' : 'columns';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate parent level not found exception.
|
||||
*/
|
||||
protected function getParentNotFoundException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::COLLECTION_NOT_FOUND
|
||||
: Exception::TABLE_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate attribute/column not found exception.
|
||||
*/
|
||||
protected function getStructureNotFoundException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_NOT_FOUND
|
||||
: Exception::COLUMN_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate not found exception.
|
||||
*/
|
||||
protected function getNotFoundException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_NOT_FOUND
|
||||
: Exception::ROW_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate already exists exception.
|
||||
*/
|
||||
protected function getDuplicateException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_ALREADY_EXISTS
|
||||
: Exception::ROW_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate conflict exception.
|
||||
*/
|
||||
protected function getConflictException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_UPDATE_CONFLICT
|
||||
: Exception::ROW_UPDATE_CONFLICT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate delete restricted exception.
|
||||
*/
|
||||
protected function getRestrictedException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_DELETE_RESTRICTED
|
||||
: Exception::ROW_DELETE_RESTRICTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct invalid structure message.
|
||||
*/
|
||||
protected function getInvalidStructureException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_INVALID_STRUCTURE
|
||||
: Exception::ROW_INVALID_STRUCTURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate missing data exception.
|
||||
*/
|
||||
protected function getMissingDataException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_MISSING_DATA
|
||||
: Exception::ROW_MISSING_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the resource limit is exceeded.
|
||||
*/
|
||||
protected function getLimitException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_LIMIT_EXCEEDED
|
||||
: Exception::COLUMN_LIMIT_EXCEEDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate missing payload exception.
|
||||
*/
|
||||
protected function getMissingPayloadException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_MISSING_PAYLOAD
|
||||
: Exception::ROW_MISSING_PAYLOAD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct collections context for Events queue.
|
||||
*/
|
||||
protected function getCollectionsEventsContext(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'collection' : 'table';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove configured removable attributes from a document.
|
||||
* Used for relationship path handling to remove API-specific attributes.
|
||||
*/
|
||||
protected function removeReadonlyAttributes(
|
||||
Document|array $document,
|
||||
bool $privileged = false,
|
||||
): Document|array {
|
||||
foreach ($this->removableAttributes['*'] as $attribute) {
|
||||
unset($document[$attribute]);
|
||||
}
|
||||
if (!$privileged) {
|
||||
foreach ($this->removableAttributes['privileged'] ?? [] as $attribute) {
|
||||
unset($document[$attribute]);
|
||||
}
|
||||
}
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves relationships in a document and attaches metadata.
|
||||
*/
|
||||
protected function processDocument(
|
||||
/* database */
|
||||
Document $database,
|
||||
Document $collection,
|
||||
Document $document,
|
||||
Database $dbForProject,
|
||||
|
||||
/* options */
|
||||
array &$collectionsCache,
|
||||
?int &$operations = null,
|
||||
): bool {
|
||||
|
||||
if ($operations !== null && $document->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($operations !== null) {
|
||||
$operations++;
|
||||
}
|
||||
|
||||
$collectionId = $collection->getId();
|
||||
$document->removeAttribute('$collection');
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
$document->setAttribute('$' . $this->getCollectionsEventsContext() . 'Id', $collectionId);
|
||||
|
||||
$relationships = $collectionsCache[$collectionId] ??= \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attr) => $attr->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
$key = $relationship->getAttribute('key');
|
||||
$related = $document->getAttribute($key);
|
||||
|
||||
if (empty($related)) {
|
||||
if (\in_array(\gettype($related), ['array', 'object']) && $operations !== null) {
|
||||
$operations++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$relations = \is_array($related) ? $related : [$related];
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
|
||||
if (!isset($collectionsCache[$relatedCollectionId])) {
|
||||
$relatedCollectionDoc = Authorization::skip(
|
||||
fn () => $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence(),
|
||||
$relatedCollectionId
|
||||
)
|
||||
);
|
||||
|
||||
$collectionsCache[$relatedCollectionId] = \array_filter(
|
||||
$relatedCollectionDoc->getAttribute('attributes', []),
|
||||
fn ($attr) => $attr->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
if ($relation instanceof Document) {
|
||||
$relatedCollection = new Document([
|
||||
'$id' => $relatedCollectionId,
|
||||
'attributes' => $collectionsCache[$relatedCollectionId],
|
||||
]);
|
||||
|
||||
$this->processDocument(
|
||||
database: $database,
|
||||
collection: $relatedCollection,
|
||||
document: $relation,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
operations: $operations
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_array($related)) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
|
||||
} elseif (empty($relations)) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), null);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For triggering different queues for each document for a bulk documents
|
||||
* @param string $event
|
||||
* @param Document $database
|
||||
* @param Document $collection
|
||||
* @param Document[] $documents
|
||||
* @param Event $queueForEvents
|
||||
* @param Event $queueForRealtime
|
||||
* @param Event $queueForFunctions
|
||||
* @param Event $queueForWebhooks
|
||||
* @return void
|
||||
*/
|
||||
protected function triggerBulk(
|
||||
string $event,
|
||||
Document $database,
|
||||
Document $collection,
|
||||
array $documents,
|
||||
Event $queueForEvents,
|
||||
Event $queueForRealtime,
|
||||
Event $queueForFunctions,
|
||||
Event $queueForWebhooks
|
||||
): void {
|
||||
$queueForEvents
|
||||
->setEvent($event)
|
||||
->setParam('databaseId', $database->getId())
|
||||
->setContext('database', $database)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
foreach ($documents as $document) {
|
||||
$queueForEvents
|
||||
->setParam('documentId', $document->getId())
|
||||
->setParam('rowId', $document->getId())
|
||||
->setPayload($document->getArrayCopy());
|
||||
|
||||
$queueForRealtime
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForEvents->reset();
|
||||
$queueForRealtime->reset();
|
||||
$queueForFunctions->reset();
|
||||
$queueForWebhooks->reset();
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Attribute;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use InvalidArgumentException;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Exception\NotFound as NotFoundException;
|
||||
use Utopia\Database\Exception\Type as TypeException;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Numeric;
|
||||
|
||||
class Decrement extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'decrementDocumentAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/:attribute/decrement')
|
||||
->desc('Decrement document attribute')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update')
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'documents.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/decrement-document-attribute.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT, AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.decrementRowColumn',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('attribute', '', new Key(), 'Attribute key.')
|
||||
->param('value', 1, new Numeric(), 'Value to increment the attribute by. The value must be a number.', true)
|
||||
->param('min', null, new Numeric(), 'Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$document = $dbForProject->decreaseDocumentAttribute(
|
||||
collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
id: $documentId,
|
||||
attribute: $attribute,
|
||||
value: $value,
|
||||
min: $min
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception($this->getStructureNotFoundException());
|
||||
} catch (LimitException) {
|
||||
throw new Exception($this->getLimitException(), $this->getSdkNamespace() . ' "' . $attribute . '" has reached the minimum value of ' . $min);
|
||||
} catch (TypeException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, $this->getSdkNamespace() . ' "' . $attribute . '" is not a number');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
\array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
)
|
||||
);
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collectionId)
|
||||
->setParam('tableId', $collectionId)
|
||||
->setParam('documentId', $documentId)
|
||||
->setParam('rowId', $documentId)
|
||||
->setContext('database', $database)
|
||||
->setContext($this->getCollectionsEventsContext(), $collection)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
|
||||
$response->dynamic($document, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Attribute;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use InvalidArgumentException;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Exception\NotFound as NotFoundException;
|
||||
use Utopia\Database\Exception\Type as TypeException;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Numeric;
|
||||
|
||||
class Increment extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'incrementDocumentAttribute';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/:attribute/increment')
|
||||
->desc('Increment document attribute')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update')
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'documents.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/increment-document-attribute.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT, AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.incrementRowColumn',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('attribute', '', new Key(), 'Attribute key.')
|
||||
->param('value', 1, new Numeric(), 'Value to increment the attribute by. The value must be a number.', true)
|
||||
->param('max', null, new Numeric(), 'Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$document = $dbForProject->increaseDocumentAttribute(
|
||||
collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
id: $documentId,
|
||||
attribute: $attribute,
|
||||
value: $value,
|
||||
max: $max
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception($this->getStructureNotFoundException());
|
||||
} catch (LimitException) {
|
||||
throw new Exception($this->getLimitException(), $this->getSdkNamespace() . ' "' . $attribute . '" has reached the maximum value of ' . $max);
|
||||
} catch (TypeException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, $this->getSdkNamespace() . ' "' . $attribute . '" is not a number');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
\array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
)
|
||||
);
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collectionId)
|
||||
->setParam('tableId', $collectionId)
|
||||
->setParam('documentId', $documentId)
|
||||
->setParam('rowId', $documentId)
|
||||
->setContext('database', $database)
|
||||
->setContext($this->getCollectionsEventsContext(), $collection)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
|
||||
$response->dynamic($document, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Bulk;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Exception\Restricted as RestrictedException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Delete extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'deleteDocuments';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->desc('Delete documents')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'documents.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/delete-documents.md',
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.deleteRows',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$hasRelationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
if ($hasRelationships) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk delete is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes');
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$documents = [];
|
||||
|
||||
try {
|
||||
$modified = $dbForProject->deleteDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$queries,
|
||||
onNext: function (Document $document) use ($plan, &$documents) {
|
||||
if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$documents[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (RestrictedException) {
|
||||
throw new Exception($this->getRestrictedException());
|
||||
}
|
||||
|
||||
foreach ($documents as $document) {
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
$document->setAttribute('$'.$this->getCollectionsEventsContext().'Id', $collection->getId());
|
||||
}
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $modified,
|
||||
$this->getSdkGroup() => $documents,
|
||||
]), $this->getResponseModel());
|
||||
|
||||
$this->triggerBulk(
|
||||
'databases.[databaseId].collections.[collectionId].documents.[documentId].delete',
|
||||
$database,
|
||||
$collection,
|
||||
$documents,
|
||||
$queueForEvents,
|
||||
$queueForRealtime,
|
||||
$queueForFunctions,
|
||||
$queueForWebhooks
|
||||
);
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Bulk;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Exception\Relationship as RelationshipException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateDocuments';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->desc('Update documents')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'documents.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/update-documents.md',
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateRows',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
|
||||
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string|array $data, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
|
||||
{
|
||||
$data = \is_string($data)
|
||||
? \json_decode($data, true)
|
||||
: $data;
|
||||
|
||||
if (empty($data)) {
|
||||
throw new Exception($this->getMissingPayloadException());
|
||||
}
|
||||
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$hasRelationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
if ($hasRelationships) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk update is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes');
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if ($data['$permissions']) {
|
||||
$validator = new Permissions();
|
||||
if (!$validator->isValid($data['$permissions'])) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->removeReadonlyAttributes($data, privileged: true);
|
||||
|
||||
$documents = [];
|
||||
|
||||
try {
|
||||
$modified = $dbForProject->withPreserveDates(function () use ($plan, &$documents, $dbForProject, $database, $collection, $data, $queries) {
|
||||
return $dbForProject->updateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
new Document($data),
|
||||
$queries,
|
||||
onNext: function (Document $document) use ($plan, &$documents) {
|
||||
if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$documents[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception($this->getInvalidStructureException(), $e->getMessage());
|
||||
}
|
||||
|
||||
foreach ($documents as $document) {
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
$document->setAttribute('$'.$this->getCollectionsEventsContext().'Id', $collection->getId());
|
||||
}
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $modified,
|
||||
$this->getSdkGroup() => $documents
|
||||
]), $this->getResponseModel());
|
||||
|
||||
$this->triggerBulk(
|
||||
'databases.[databaseId].collections.[collectionId].documents.[documentId].update',
|
||||
$database,
|
||||
$collection,
|
||||
$documents,
|
||||
$queueForEvents,
|
||||
$queueForRealtime,
|
||||
$queueForFunctions,
|
||||
$queueForWebhooks
|
||||
);
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Bulk;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Relationship as RelationshipException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\JSON;
|
||||
|
||||
class Upsert extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'upsertDocuments';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->desc('Upsert documents')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'document.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/upsert-documents.md',
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_CREATED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.upsertRows',
|
||||
),
|
||||
)
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', false, ['plan'])
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $documents, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$hasRelationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
if ($hasRelationships) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk upsert is not supported for ' . $this->getSdkNamespace() . ' with relationship attributes');
|
||||
}
|
||||
|
||||
foreach ($documents as $key => $document) {
|
||||
$document = $this->removeReadonlyAttributes($document, privileged: true);
|
||||
$documents[$key] = new Document($document);
|
||||
}
|
||||
|
||||
$upserted = [];
|
||||
|
||||
try {
|
||||
$modified = $dbForProject->withPreserveDates(function () use ($dbForProject, $database, $collection, $documents, $plan, &$upserted) {
|
||||
return $dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents,
|
||||
onNext: function (Document $document) use ($plan, &$upserted) {
|
||||
if (\count($upserted) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$upserted[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception($this->getInvalidStructureException(), $e->getMessage());
|
||||
}
|
||||
|
||||
foreach ($upserted as $document) {
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
$document->setAttribute('$'.$this->getCollectionsEventsContext().'Id', $collection->getId());
|
||||
}
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $modified,
|
||||
$this->getSdkGroup() => $upserted
|
||||
]), $this->getResponseModel());
|
||||
|
||||
$this->triggerBulk(
|
||||
'databases.[databaseId].collections.[collectionId].documents.[documentId].upsert',
|
||||
$database,
|
||||
$collection,
|
||||
$upserted,
|
||||
$queueForEvents,
|
||||
$queueForRealtime,
|
||||
$queueForFunctions,
|
||||
$queueForWebhooks
|
||||
);
|
||||
}
|
||||
}
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Parameter;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\NotFound as NotFoundException;
|
||||
use Utopia\Database\Exception\Relationship as RelationshipException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\JSON;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createDocument';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT;
|
||||
}
|
||||
|
||||
protected function getBulkResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->desc('Create document')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'document.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
desc: 'Create document',
|
||||
description: '/docs/references/databases/create-document.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_CREATED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
parameters: [
|
||||
new Parameter('databaseId', optional: false),
|
||||
new Parameter('collectionId', optional: false),
|
||||
new Parameter('documentId', optional: false),
|
||||
new Parameter('data', optional: false),
|
||||
new Parameter('permissions', optional: true),
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createRow',
|
||||
),
|
||||
),
|
||||
new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: $this->getBulkActionName(self::getName()),
|
||||
desc: 'Create documents',
|
||||
description: '/docs/references/databases/create-documents.md',
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_CREATED,
|
||||
model: $this->getBulkResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
parameters: [
|
||||
new Parameter('databaseId', optional: false),
|
||||
new Parameter('collectionId', optional: false),
|
||||
new Parameter('documents', optional: false),
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createRows',
|
||||
),
|
||||
)
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true)
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object.', true, example: '{"username":"walter.obrien","email":"walter.obrien@example.com","fullName":"Walter O\'Brien","age":30,"isAdmin":false}')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan'])
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void
|
||||
{
|
||||
$data = \is_string($data)
|
||||
? \json_decode($data, true)
|
||||
: $data;
|
||||
|
||||
/**
|
||||
* Determine which internal path to call, single or bulk
|
||||
*/
|
||||
if (empty($data) && empty($documents)) {
|
||||
// No single or bulk documents provided
|
||||
throw new Exception($this->getMissingDataException());
|
||||
}
|
||||
if (!empty($data) && !empty($documents)) {
|
||||
// Both single and bulk documents provided
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'You can only send one of the following parameters: data, ' . $this->getSdkGroup());
|
||||
}
|
||||
if (!empty($data) && empty($documentId)) {
|
||||
// Single document provided without document ID
|
||||
$document = $this->isCollectionsAPI() ? 'Document' : 'Row';
|
||||
$message = "$document ID is required when creating a single " . strtolower($document) . '.';
|
||||
throw new Exception($this->getMissingDataException(), $message);
|
||||
}
|
||||
if (!empty($documents) && !empty($documentId)) {
|
||||
// Bulk documents provided with document ID
|
||||
$documentId = $this->isCollectionsAPI() ? 'documentId' : 'rowId';
|
||||
throw new Exception(
|
||||
Exception::GENERAL_BAD_REQUEST,
|
||||
"Param \"$documentId\" is not allowed when creating multiple " . $this->getSdkGroup() . ', set "$id" on each instead.'
|
||||
);
|
||||
}
|
||||
if (!empty($documents) && !empty($permissions)) {
|
||||
// Bulk documents provided with permissions
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "permissions" is disallowed when creating multiple ' . $this->getSdkGroup() . ', set "$permissions" on each instead');
|
||||
}
|
||||
|
||||
$isBulk = true;
|
||||
if (!empty($data)) {
|
||||
// Single document provided, convert to single item array
|
||||
// But remember that it was single to respond with a single document
|
||||
$isBulk = false;
|
||||
$documents = [$data];
|
||||
}
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($isBulk && !$isAPIKey && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE);
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$hasRelationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
if ($isBulk && $hasRelationships) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk create is not supported for ' . $this->getSdkNamespace() .' with relationship ' . $this->getStructureContext());
|
||||
}
|
||||
|
||||
$setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk) {
|
||||
$allowedPermissions = [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
];
|
||||
|
||||
// If bulk, we need to validate permissions explicitly per document
|
||||
if ($isBulk) {
|
||||
$permissions = $document['$permissions'] ?? null;
|
||||
if (!empty($permissions)) {
|
||||
$validator = new Permissions();
|
||||
if (!$validator->isValid($permissions)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$permissions = Permission::aggregate($permissions, $allowedPermissions);
|
||||
|
||||
// Add permissions for current the user if none were provided.
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!empty($user->getId())) {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = Permission::parse($permission);
|
||||
if ($permission->getPermission() != $type) {
|
||||
continue;
|
||||
}
|
||||
$role = (new Role(
|
||||
$permission->getRole(),
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$document->setAttribute('$permissions', $permissions);
|
||||
};
|
||||
|
||||
$operations = 0;
|
||||
|
||||
$checkPermissions = function (Document $collection, Document $document, string $permission) use ($isAPIKey, $isPrivilegedUser, &$checkPermissions, $dbForProject, $database, &$operations) {
|
||||
$operations++;
|
||||
|
||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||
$validator = new Authorization($permission);
|
||||
|
||||
$valid = $validator->isValid($collection->getPermissionsByType($permission));
|
||||
if (($permission === Database::PERMISSION_UPDATE && !$documentSecurity) || !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if ($permission === Database::PERMISSION_UPDATE) {
|
||||
$valid = $valid || $validator->isValid($document->getUpdate());
|
||||
if ($documentSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
$related = $document->getAttribute($relationship->getAttribute('key'));
|
||||
|
||||
if (empty($related)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isList = \is_array($related) && \array_values($related) === $related;
|
||||
|
||||
if ($isList) {
|
||||
$relations = $related;
|
||||
} else {
|
||||
$relations = [$related];
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
$relatedCollection = Authorization::skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
|
||||
);
|
||||
|
||||
foreach ($relations as &$relation) {
|
||||
if (
|
||||
\is_array($relation)
|
||||
&& \array_values($relation) !== $relation
|
||||
&& !isset($relation['$id'])
|
||||
) {
|
||||
$relation['$id'] = ID::unique();
|
||||
$relation = new Document($relation);
|
||||
}
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$current = Authorization::skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId())
|
||||
);
|
||||
|
||||
if ($current->isEmpty()) {
|
||||
$type = Database::PERMISSION_CREATE;
|
||||
|
||||
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
|
||||
$relation['$id'] = ID::unique();
|
||||
}
|
||||
} else {
|
||||
$relation->setAttribute('$collection', $relatedCollection->getId());
|
||||
$type = Database::PERMISSION_UPDATE;
|
||||
}
|
||||
|
||||
$checkPermissions($relatedCollection, $relation, $type);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isList) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
|
||||
} else {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$documents = \array_map(function ($document) use ($collection, $permissions, $checkPermissions, $isBulk, $documentId, $setPermissions, $isAPIKey, $isPrivilegedUser) {
|
||||
$document['$collection'] = $collection->getId();
|
||||
|
||||
// Determine the source ID depending on whether it's a bulk operation.
|
||||
$sourceId = $isBulk
|
||||
? ($document['$id'] ?? ID::unique())
|
||||
: $documentId;
|
||||
|
||||
// If bulk, we need to validate ID explicitly
|
||||
if ($isBulk) {
|
||||
$validator = new CustomId();
|
||||
if (!$validator->isValid($sourceId)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
// Assign a unique ID if needed, otherwise use the provided ID.
|
||||
$document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId;
|
||||
$document = $this->removeReadonlyAttributes($document, $isAPIKey || $isPrivilegedUser);
|
||||
$document = new Document($document);
|
||||
$setPermissions($document, $permissions);
|
||||
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
|
||||
return $document;
|
||||
}, $documents);
|
||||
|
||||
try {
|
||||
$dbForProject->withPreserveDates(
|
||||
fn () => $dbForProject->createDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents,
|
||||
)
|
||||
);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception($this->getInvalidStructureException(), $e->getMessage());
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setContext('database', $database)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
$collectionsCache = [];
|
||||
foreach ($documents as $document) {
|
||||
$this->processDocument(
|
||||
database: $database,
|
||||
collection: $collection,
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
);
|
||||
}
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); // per collection
|
||||
|
||||
$response->setStatusCode(SwooleResponse::STATUS_CODE_CREATED);
|
||||
|
||||
if ($isBulk) {
|
||||
$response->dynamic(new Document([
|
||||
'total' => count($documents),
|
||||
$this->getSdkGroup() => $documents
|
||||
]), $this->getBulkResponseModel());
|
||||
|
||||
$this->triggerBulk(
|
||||
'databases.[databaseId].collections.[collectionId].documents.[documentId].create',
|
||||
$database,
|
||||
$collection,
|
||||
$documents,
|
||||
$queueForEvents,
|
||||
$queueForRealtime,
|
||||
$queueForFunctions,
|
||||
$queueForWebhooks
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('documentId', $documents[0]->getId())
|
||||
->setParam('rowId', $documents[0]->getId())
|
||||
->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].create');
|
||||
|
||||
$response->dynamic(
|
||||
$documents[0],
|
||||
$this->getResponseModel()
|
||||
);
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Restricted as RestrictedException;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Delete extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'deleteDocument';
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. `SDKResponse` uses `UtopiaResponse::MODEL_NONE`.
|
||||
* 2. But we later need the actual return type for events queue below!
|
||||
*/
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
|
||||
->desc('Delete document')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].delete')
|
||||
->label('audits.event', 'document.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{request.documentId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/delete-document.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_NOCONTENT,
|
||||
model: UtopiaResponse::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.deleteRow',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
// Read permission should not be required for delete
|
||||
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
|
||||
$dbForProject->deleteDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documentId
|
||||
);
|
||||
});
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (RestrictedException) {
|
||||
throw new Exception($this->getRestrictedException());
|
||||
}
|
||||
|
||||
$collectionsCache = [];
|
||||
|
||||
$this->processDocument(
|
||||
database: $database,
|
||||
collection: $collection,
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
);
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); // per collection
|
||||
|
||||
$response->addHeader('X-Debug-Operations', 1);
|
||||
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
\array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
)
|
||||
);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setContext('database', $database)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setParam('rowId', $document->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection)
|
||||
->setPayload($response->output($document, $this->getResponseModel()), sensitive: $relationships);
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getDocument';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
|
||||
->desc('Get document')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/get-document.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.getRow',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage): void
|
||||
{
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$selects = Query::groupByType($queries)['selections'] ?? [];
|
||||
|
||||
if (! empty($selects)) {
|
||||
// has selects, allow relationship on documents!
|
||||
$document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId, $queries);
|
||||
} else {
|
||||
// has no selects, disable relationship looping on documents!
|
||||
$document = $dbForProject->skipRelationships(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId, $queries));
|
||||
}
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
$operations = 0;
|
||||
$collectionsCache = [];
|
||||
$this->processDocument(
|
||||
database: $database,
|
||||
collection: $collection,
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
operations: $operations
|
||||
);
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations);
|
||||
|
||||
$response->addHeader('X-Debug-Operations', $operations);
|
||||
|
||||
$response->dynamic($document, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Logs;
|
||||
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listDocumentLogs';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_LOG_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/logs')
|
||||
->desc('List document logs')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: 'logs',
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/get-document-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.listRowLogs',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId);
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$type = $this->getCollectionsEventsContext();
|
||||
$context = $this->getContext();
|
||||
$resource = match ($context) {
|
||||
ROWS => "database/$databaseId/grid/$type/$collectionId/$context/{$document->getId()}",
|
||||
default => "database/$databaseId/$type/$collectionId/$context/{$document->getId()}",
|
||||
};
|
||||
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($logs as $i => &$log) {
|
||||
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
|
||||
|
||||
$detector = new Detector($log['userAgent']);
|
||||
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
|
||||
|
||||
$os = $detector->getOS();
|
||||
$client = $detector->getClient();
|
||||
$device = $detector->getDevice();
|
||||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
'userId' => $log['data']['userId'],
|
||||
'userEmail' => $log['data']['userEmail'] ?? null,
|
||||
'userName' => $log['data']['userName'] ?? null,
|
||||
'mode' => $log['data']['mode'] ?? null,
|
||||
'ip' => $log['ip'],
|
||||
'time' => $log['time'],
|
||||
'osCode' => $os['osCode'],
|
||||
'osName' => $os['osName'],
|
||||
'osVersion' => $os['osVersion'],
|
||||
'clientType' => $client['clientType'],
|
||||
'clientCode' => $client['clientCode'],
|
||||
'clientName' => $client['clientName'],
|
||||
'clientVersion' => $client['clientVersion'],
|
||||
'clientEngine' => $client['clientEngine'],
|
||||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel']
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
|
||||
if ($record) {
|
||||
$output[$i]['countryCode'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
|
||||
$output[$i]['countryName'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
} else {
|
||||
$output[$i]['countryCode'] = '--';
|
||||
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'logs' => $output,
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
]), $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Relationship as RelationshipException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\JSON;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateDocument';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
|
||||
->desc('Update document')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update')
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'document.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/update-document.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateRow',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
|
||||
{
|
||||
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
if (empty($data) && \is_null($permissions)) {
|
||||
throw new Exception($this->getMissingPayloadException());
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
// Read permission should not be required for update
|
||||
/** @var Document $document */
|
||||
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions, [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
]);
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = Permission::parse($permission);
|
||||
if ($permission->getPermission() != $type) {
|
||||
continue;
|
||||
}
|
||||
$role = (new Role(
|
||||
$permission->getRole(),
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = $document->getPermissions() ?? [];
|
||||
}
|
||||
|
||||
$data['$id'] = $documentId;
|
||||
$data['$permissions'] = $permissions;
|
||||
$data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser);
|
||||
$newDocument = new Document($data);
|
||||
|
||||
$operations = 0;
|
||||
|
||||
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) {
|
||||
$operations++;
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
$related = $document->getAttribute($relationship->getAttribute('key'));
|
||||
|
||||
if (empty($related)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isList = \is_array($related) && \array_values($related) === $related;
|
||||
|
||||
if ($isList) {
|
||||
$relations = $related;
|
||||
} else {
|
||||
$relations = [$related];
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
$relatedCollection = Authorization::skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
|
||||
);
|
||||
|
||||
foreach ($relations as &$relation) {
|
||||
// If the relation is an array it can be either update or create a child document.
|
||||
if (
|
||||
\is_array($relation)
|
||||
&& \array_values($relation) !== $relation
|
||||
&& !isset($relation['$id'])
|
||||
) {
|
||||
$relation['$id'] = ID::unique();
|
||||
$relation = new Document($relation);
|
||||
}
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
||||
$relation->getId()
|
||||
));
|
||||
|
||||
// Attribute $collection is required for Utopia.
|
||||
$relation->setAttribute(
|
||||
'$collection',
|
||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence()
|
||||
);
|
||||
|
||||
if ($oldDocument->isEmpty()) {
|
||||
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
|
||||
$relation['$id'] = ID::unique();
|
||||
}
|
||||
}
|
||||
$setCollection($relatedCollection, $relation);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isList) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
|
||||
} else {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$setCollection($collection, $newDocument);
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations);
|
||||
|
||||
try {
|
||||
$document = $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
fn () => $dbForProject->withPreserveDates(fn () => $dbForProject->updateDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$document->getId(),
|
||||
$newDocument
|
||||
))
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception($this->getInvalidStructureException(), $e->getMessage());
|
||||
}
|
||||
|
||||
$collectionsCache = [];
|
||||
$this->processDocument(
|
||||
database: $database,
|
||||
collection: $collection,
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
);
|
||||
|
||||
$response->dynamic($document, $this->getResponseModel());
|
||||
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
\array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
)
|
||||
);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setParam('rowId', $document->getId())
|
||||
->setContext('database', $database)
|
||||
->setContext($this->getCollectionsEventsContext(), $collection)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
}
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Relationship as RelationshipException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\JSON;
|
||||
|
||||
class Upsert extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'upsertDocument';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
|
||||
->desc('Upsert a document')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].upsert')
|
||||
->label('scope', 'documents.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'document.upsert')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/upsert-document.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_CREATED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.upsertRow',
|
||||
),
|
||||
),
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documentId', '', new CustomId(), 'Document ID.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object. Include all required attributes of the document to be created or updated.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
|
||||
{
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
if (empty($data) && \is_null($permissions)) {
|
||||
throw new Exception($this->getMissingPayloadException());
|
||||
}
|
||||
|
||||
if (\array_is_list($data) && \count($data) > 1) { // Allow 1 associated array
|
||||
throw new Exception($this->getMissingPayloadException());
|
||||
}
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
$allowedPermissions = [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
Database::PERMISSION_DELETE,
|
||||
];
|
||||
|
||||
$permissions = Permission::aggregate($permissions, $allowedPermissions);
|
||||
|
||||
// If no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario)
|
||||
if (\is_null($permissions)) {
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
if ($oldDocument->isEmpty()) {
|
||||
if (!empty($user->getId())) {
|
||||
$defaultPermissions = [];
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
$defaultPermissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
$permissions = $defaultPermissions;
|
||||
}
|
||||
} else {
|
||||
$permissions = $oldDocument->getPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = Permission::parse($permission);
|
||||
if ($permission->getPermission() != $type) {
|
||||
continue;
|
||||
}
|
||||
$role = (new Role(
|
||||
$permission->getRole(),
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['$id'] = $documentId;
|
||||
$data['$permissions'] = $permissions ?? [];
|
||||
$data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser);
|
||||
$newDocument = new Document($data);
|
||||
$operations = 0;
|
||||
|
||||
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) {
|
||||
$operations++;
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
$related = $document->getAttribute($relationship->getAttribute('key'));
|
||||
|
||||
if (empty($related)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isList = \is_array($related) && \array_values($related) === $related;
|
||||
|
||||
if ($isList) {
|
||||
$relations = $related;
|
||||
} else {
|
||||
$relations = [$related];
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
$relatedCollection = Authorization::skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
|
||||
);
|
||||
|
||||
foreach ($relations as &$relation) {
|
||||
// If the relation is an array it can be either update or create a child document.
|
||||
if (
|
||||
\is_array($relation)
|
||||
&& \array_values($relation) !== $relation
|
||||
&& !isset($relation['$id'])
|
||||
) {
|
||||
$relation['$id'] = ID::unique();
|
||||
$relation = new Document($relation);
|
||||
}
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
||||
$relation->getId()
|
||||
));
|
||||
|
||||
// Attribute $collection is required for Utopia.
|
||||
$relation->setAttribute(
|
||||
'$collection',
|
||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence()
|
||||
);
|
||||
|
||||
if ($oldDocument->isEmpty()) {
|
||||
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
|
||||
$relation['$id'] = ID::unique();
|
||||
}
|
||||
}
|
||||
$setCollection($relatedCollection, $relation);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isList) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
|
||||
} else {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$setCollection($collection, $newDocument);
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations));
|
||||
|
||||
$upserted = [];
|
||||
try {
|
||||
$dbForProject->withPreserveDates(function () use (&$upserted, $dbForProject, $database, $collection, $newDocument) {
|
||||
return $dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
[$newDocument],
|
||||
onNext: function (Document $document) use (&$upserted) {
|
||||
$upserted[] = $document;
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (RelationshipException $e) {
|
||||
throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception($this->getInvalidStructureException(), $e->getMessage());
|
||||
}
|
||||
|
||||
$collectionsCache = [];
|
||||
|
||||
if (empty($upserted[0])) {
|
||||
$upserted[0] = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId);
|
||||
}
|
||||
|
||||
$document = $upserted[0];
|
||||
|
||||
$this->processDocument(
|
||||
database: $database,
|
||||
collection: $collection,
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
);
|
||||
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
\array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
)
|
||||
);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setContext('database', $database)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setParam('rowId', $document->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
|
||||
$response->dynamic(
|
||||
$document,
|
||||
$this->getResponseModel()
|
||||
);
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listDocuments';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_DOCUMENT_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->desc('List documents')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/list-documents.md',
|
||||
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.listRows',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage): void
|
||||
{
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \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());
|
||||
}
|
||||
|
||||
$documentId = $cursor->getValue();
|
||||
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
$type = ucfirst($this->getContext());
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "$type '{$documentId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
try {
|
||||
$selectQueries = Query::groupByType($queries)['selections'] ?? [];
|
||||
|
||||
if (! empty($selectQueries)) {
|
||||
// has selects, allow relationship on documents
|
||||
$documents = $dbForProject->find('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries);
|
||||
} else {
|
||||
// has no selects, disable relationship loading on documents
|
||||
/* @type Document[] $documents */
|
||||
$documents = $dbForProject->skipRelationships(fn () => $dbForProject->find('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries));
|
||||
}
|
||||
|
||||
$total = $dbForProject->count('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries, 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);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$operations = 0;
|
||||
$collectionsCache = [];
|
||||
foreach ($documents as $document) {
|
||||
$this->processDocument(
|
||||
database: $database,
|
||||
collection: $collection,
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
operations: $operations,
|
||||
);
|
||||
}
|
||||
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1))
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations);
|
||||
|
||||
// Check if the SELECT query includes the removable attributes
|
||||
$hasWildcard = false;
|
||||
$hasSelectQueries = !empty($selectQueries);
|
||||
$requestedAttributes = [];
|
||||
|
||||
if ($hasSelectQueries) {
|
||||
foreach ($selectQueries as $query) {
|
||||
if ($query->getMethod() !== Query::TYPE_SELECT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values = $query->getValues();
|
||||
if (\in_array('*', $values, true)) {
|
||||
$hasWildcard = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check which removable attributes are explicitly requested
|
||||
foreach ($this->removableAttributes['*'] as $attribute) {
|
||||
if (\in_array($attribute, $values, true)) {
|
||||
$requestedAttributes[$attribute] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasWildcard) {
|
||||
foreach ($documents as $document) {
|
||||
// Remove attributes that are not explicitly requested
|
||||
foreach ($this->removableAttributes['*'] as $attribute) {
|
||||
if (!isset($requestedAttributes[$attribute])) {
|
||||
$document->removeAttribute($attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $total,
|
||||
// rows or documents
|
||||
$this->getSdkGroup() => $documents,
|
||||
]), $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCollection';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLLECTION;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->desc('Get collection')
|
||||
->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/get-collection.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.getTable',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, 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->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
$response->dynamic($collection, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
|
||||
abstract class Action extends UtopiaAction
|
||||
{
|
||||
/**
|
||||
* The current API context (either 'columnIndex' or 'index').
|
||||
*/
|
||||
private ?string $context = INDEX;
|
||||
|
||||
/**
|
||||
* Get the response model used in the SDK and HTTP responses.
|
||||
*/
|
||||
abstract protected function getResponseModel(): string;
|
||||
|
||||
public function setHttpPath(string $path): UtopiaAction
|
||||
{
|
||||
if (\str_contains($path, '/tablesdb')) {
|
||||
$this->context = COLUMN_INDEX;
|
||||
}
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current API's parent context.
|
||||
*/
|
||||
final protected function getParentContext(): string
|
||||
{
|
||||
return $this->getContext() === INDEX ? ATTRIBUTES : COLUMNS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current API context.
|
||||
*/
|
||||
final protected function getContext(): string
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current action is for the Collections API.
|
||||
*/
|
||||
final protected function isCollectionsAPI(): bool
|
||||
{
|
||||
return $this->getParentContext() === ATTRIBUTES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK group name for the current action.
|
||||
*/
|
||||
final protected function getSdkGroup(): string
|
||||
{
|
||||
return 'indexes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK namespace for the current action.
|
||||
*/
|
||||
final protected function getSdkNamespace(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'databases' : 'tablesDB';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the parent is unknown.
|
||||
*/
|
||||
final protected function getParentUnknownException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_UNKNOWN
|
||||
: Exception::COLUMN_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate grandparent level not found exception.
|
||||
*/
|
||||
final protected function getGrandParentNotFoundException(): 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::INDEX_NOT_FOUND
|
||||
: Exception::COLUMN_INDEX_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the parent type is invalid.
|
||||
*/
|
||||
final protected function getParentInvalidTypeException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_TYPE_INVALID
|
||||
: Exception::COLUMN_TYPE_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the index type is invalid.
|
||||
*/
|
||||
final protected function getInvalidTypeException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::INDEX_INVALID
|
||||
: Exception::COLUMN_INDEX_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the resource already exists.
|
||||
*/
|
||||
final protected function getDuplicateException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::INDEX_ALREADY_EXISTS
|
||||
: Exception::COLUMN_INDEX_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the resource limit is exceeded.
|
||||
*/
|
||||
final protected function getLimitException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::INDEX_LIMIT_EXCEEDED
|
||||
: Exception::COLUMN_INDEX_LIMIT_EXCEEDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception to throw when the parent attribute/column is not in `available` state.
|
||||
*/
|
||||
final protected function getParentNotAvailableException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_NOT_AVAILABLE
|
||||
: Exception::COLUMN_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct collections context for Events queue.
|
||||
*/
|
||||
final protected function getCollectionsEventsContext(): string
|
||||
{
|
||||
return $this->isCollectionsAPI() ? 'collection' : 'table';
|
||||
}
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes;
|
||||
|
||||
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\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Index as IndexValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createIndex';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_INDEX;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
->desc('Create index')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'index.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/create-index.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_ACCEPTED,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.createIndex',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('key', null, new Key(), 'Index Key.')
|
||||
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.')
|
||||
->param('attributes', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.')
|
||||
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
|
||||
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, array $lengths, 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->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
// table or collection.
|
||||
throw new Exception($this->getGrandParentNotFoundException());
|
||||
}
|
||||
|
||||
$count = $dbForProject->count('indexes', [
|
||||
Query::equal('collectionInternalId', [$collection->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$db->getSequence()])
|
||||
], 61);
|
||||
|
||||
$limit = $dbForProject->getLimitForIndexes();
|
||||
|
||||
if ($count >= $limit) {
|
||||
throw new Exception($this->getLimitException(), 'Index limit exceeded');
|
||||
}
|
||||
|
||||
// Convert Document array to array of attribute metadata
|
||||
$oldAttributes = \array_map(fn ($a) => $a->getArrayCopy(), $collection->getAttribute('attributes'));
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$id',
|
||||
'type' => Database::VAR_STRING,
|
||||
'status' => 'available',
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => Database::LENGTH_KEY
|
||||
];
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$createdAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'status' => 'available',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => 0
|
||||
];
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$updatedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'status' => 'available',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => 0
|
||||
];
|
||||
|
||||
$contextType = $this->getParentContext();
|
||||
foreach ($attributes as $i => $attribute) {
|
||||
// find attribute metadata in collection document
|
||||
$attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key'));
|
||||
|
||||
if ($attributeIndex === false) {
|
||||
throw new Exception($this->getParentUnknownException(), "Unknown $contextType: " . $attribute . ". Verify the $contextType name or create the $contextType.");
|
||||
}
|
||||
|
||||
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
|
||||
$attributeType = $oldAttributes[$attributeIndex]['type'];
|
||||
$attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false;
|
||||
|
||||
if ($attributeType === Database::VAR_RELATIONSHIP) {
|
||||
throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']);
|
||||
}
|
||||
|
||||
// ensure attribute is available
|
||||
if ($attributeStatus !== 'available') {
|
||||
$contextType = ucfirst($contextType);
|
||||
throw new Exception($this->getParentNotAvailableException(), "$contextType not available: " . $oldAttributes[$attributeIndex]['key']);
|
||||
}
|
||||
|
||||
if (empty($lengths[$i])) {
|
||||
$lengths[$i] = null;
|
||||
}
|
||||
|
||||
if ($attributeArray === true) {
|
||||
$lengths[$i] = Database::ARRAY_INDEX_LENGTH;
|
||||
$orders[$i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$index = new Document([
|
||||
'$id' => ID::custom($db->getSequence() . '_' . $collection->getSequence() . '_' . $key),
|
||||
'key' => $key,
|
||||
'status' => 'processing', // processing, available, failed, deleting, stuck
|
||||
'databaseInternalId' => $db->getSequence(),
|
||||
'databaseId' => $databaseId,
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'collectionId' => $collectionId,
|
||||
'type' => $type,
|
||||
'attributes' => $attributes,
|
||||
'lengths' => $lengths,
|
||||
'orders' => $orders,
|
||||
]);
|
||||
|
||||
$maxIndexLength = $dbForProject->getAdapter()->getMaxIndexLength();
|
||||
$internalIndexesKeys = $dbForProject->getAdapter()->getInternalIndexesKeys();
|
||||
$supportForIndexArray = $dbForProject->getAdapter()->getSupportForIndexArray();
|
||||
$supportForSpatialAttributes = $dbForProject->getAdapter()->getSupportForSpatialAttributes();
|
||||
$supportForSpatialIndexNull = $dbForProject->getAdapter()->getSupportForSpatialIndexNull();
|
||||
$supportForSpatialIndexOrder = $dbForProject->getAdapter()->getSupportForSpatialIndexOrder();
|
||||
|
||||
$validator = new IndexValidator(
|
||||
$collection->getAttribute('attributes'),
|
||||
$maxIndexLength,
|
||||
$internalIndexesKeys,
|
||||
$supportForIndexArray,
|
||||
$supportForSpatialAttributes,
|
||||
$supportForSpatialIndexNull,
|
||||
$supportForSpatialIndexOrder
|
||||
);
|
||||
|
||||
if (!$validator->isValid($index)) {
|
||||
throw new Exception($this->getInvalidTypeException(), $validator->getDescription());
|
||||
}
|
||||
|
||||
try {
|
||||
$index = $dbForProject->createDocument('indexes', $index);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_CREATE_INDEX)
|
||||
->setDatabase($db);
|
||||
|
||||
if ($this->isCollectionsAPI()) {
|
||||
$queueForDatabase
|
||||
->setCollection($collection)
|
||||
->setDocument($index);
|
||||
} else {
|
||||
$queueForDatabase
|
||||
->setTable($collection)
|
||||
->setRow($index);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $db)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('indexId', $index->getId())
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($index, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes;
|
||||
|
||||
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\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Delete extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'deleteIndex';
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. `SDKResponse` uses `UtopiaResponse::MODEL_NONE`.
|
||||
* 2. But we later need the actual return type for events queue below!
|
||||
*/
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_INDEX;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
||||
->desc('Delete index')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update')
|
||||
->label('audits.event', 'index.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/delete-index.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_NOCONTENT,
|
||||
model: UtopiaResponse::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.deleteIndex',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Index 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->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
// table or collection.
|
||||
throw new Exception($this->getGrandParentNotFoundException());
|
||||
}
|
||||
|
||||
$index = $dbForProject->getDocument('indexes', $db->getSequence() . '_' . $collection->getSequence() . '_' . $key);
|
||||
|
||||
if (empty($index->getId())) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
// Only update status if removing available index
|
||||
if ($index->getAttribute('status') === 'available') {
|
||||
$index = $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'deleting'));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_DELETE_INDEX)
|
||||
->setDatabase($db);
|
||||
|
||||
if ($this->isCollectionsAPI()) {
|
||||
$queueForDatabase
|
||||
->setCollection($collection)
|
||||
->setDocument($index);
|
||||
} else {
|
||||
$queueForDatabase
|
||||
->setTable($collection)
|
||||
->setRow($index);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $db)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('indexId', $index->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection)
|
||||
->setPayload($response->output($index, $this->getResponseModel()));
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getIndex';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_INDEX;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
||||
->desc('Get index')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/get-index.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.getIndex',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('key', null, new Key(), 'Index Key.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, 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->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
// table or collection.
|
||||
throw new Exception($this->getGrandParentNotFoundException());
|
||||
}
|
||||
|
||||
$index = $collection->find('key', $key, 'indexes');
|
||||
if (empty($index)) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
$response->dynamic($index, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Indexes;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Indexes;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listIndexes';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_INDEX_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
->desc('List indexes')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/list-indexes.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.listIndexes',
|
||||
),
|
||||
))
|
||||
->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#databasesCreateCollection).')
|
||||
->param('queries', [], new Indexes(), '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(', ', Indexes::ALLOWED_ATTRIBUTES), true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject): void
|
||||
{
|
||||
/** @var Document $database */
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
// table or collection.
|
||||
throw new Exception($this->getGrandParentNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
\array_push(
|
||||
$queries,
|
||||
Query::equal('databaseId', [$databaseId]),
|
||||
Query::equal('collectionId', [$collectionId]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \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());
|
||||
}
|
||||
|
||||
$indexId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->find('indexes', [
|
||||
Query::equal('collectionInternalId', [$collection->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$database->getSequence()]),
|
||||
Query::equal('key', [$indexId]),
|
||||
Query::limit(1)
|
||||
]));
|
||||
|
||||
if (empty($cursorDocument) || $cursorDocument[0]->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Index '{$indexId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument[0]);
|
||||
}
|
||||
|
||||
try {
|
||||
$total = $dbForProject->count('indexes', $queries, APP_LIMIT_COUNT);
|
||||
$indexes = $dbForProject->find('indexes', $queries);
|
||||
} 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);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $total,
|
||||
'indexes' => $indexes,
|
||||
]), $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Logs;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use DeviceDetector\DeviceDetector as Detector;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listCollectionLogs';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_LOG_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/logs')
|
||||
->desc('List collection logs')
|
||||
->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/get-collection-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.listTableLogs',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collectionDocument = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
$collection = $dbForProject->getCollection('database_' . $database->getSequence() . '_collection_' . $collectionDocument->getSequence());
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$context = $this->getContext();
|
||||
$resource = match ($context) {
|
||||
TABLES => "database/$databaseId/grid/$context/$collectionId",
|
||||
default => "database/$databaseId/$context/$collectionId",
|
||||
};
|
||||
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($logs as $i => &$log) {
|
||||
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
|
||||
|
||||
$detector = new Detector($log['userAgent']);
|
||||
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
|
||||
|
||||
$os = $detector->getOS();
|
||||
$client = $detector->getClient();
|
||||
$device = $detector->getDevice();
|
||||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
'userId' => $log['data']['userId'],
|
||||
'userEmail' => $log['data']['userEmail'] ?? null,
|
||||
'userName' => $log['data']['userName'] ?? null,
|
||||
'mode' => $log['data']['mode'] ?? null,
|
||||
'ip' => $log['ip'],
|
||||
'time' => $log['time'],
|
||||
'osCode' => $os['osCode'],
|
||||
'osName' => $os['osName'],
|
||||
'osVersion' => $os['osVersion'],
|
||||
'clientType' => $client['clientType'],
|
||||
'clientCode' => $client['clientCode'],
|
||||
'clientName' => $client['clientName'],
|
||||
'clientVersion' => $client['clientVersion'],
|
||||
'clientEngine' => $client['clientEngine'],
|
||||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel']
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
|
||||
if ($record) {
|
||||
$output[$i]['countryCode'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
|
||||
$output[$i]['countryName'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
} else {
|
||||
$output[$i]['countryCode'] = '--';
|
||||
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'logs' => $output,
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
]), $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateCollection';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLLECTION;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->desc('Update collection')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].update')
|
||||
->label('audits.event', 'collection.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collections/{request.collectionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'databases',
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/update-collection.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.updateTable',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
$permissions ??= $collection->getPermissions();
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
|
||||
$enabled ??= $collection->getAttribute('enabled', true);
|
||||
|
||||
$collection = $dbForProject->updateDocument(
|
||||
'database_' . $database->getSequence(),
|
||||
$collectionId,
|
||||
$collection
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('$permissions', $permissions)
|
||||
->setAttribute('documentSecurity', $documentSecurity)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', \implode(' ', [$collectionId, $name]))
|
||||
);
|
||||
|
||||
$dbForProject->updateCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $permissions, $documentSecurity);
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $database)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam($this->getEventsParamKey(), $collection->getId());
|
||||
|
||||
$response->dynamic($collection, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Usage;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCollectionUsage';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_USAGE_COLLECTION;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/usage')
|
||||
->desc('Get collection usage stats')
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'databases',
|
||||
group: null,
|
||||
name: self::getName(),
|
||||
description: '/docs/references/databases/get-collection-usage.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel()
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.getTableUsage',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $range, string $collectionId, UtopiaResponse $response, Database $dbForProject): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
$collectionDocument = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
$collection = $dbForProject->getCollection('database_' . $database->getSequence() . '_collection_' . $collectionDocument->getSequence());
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getNotFoundException());
|
||||
}
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
$days = $periods[$range];
|
||||
$metrics = [
|
||||
str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getSequence(), $collectionDocument->getSequence()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS),
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$stats[$metric]['total'] = $result['value'] ?? 0;
|
||||
$limit = $days['limit'];
|
||||
$period = $days['period'];
|
||||
$results = $dbForProject->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
$stats[$metric]['data'] = [];
|
||||
foreach ($results as $result) {
|
||||
$stats[$metric]['data'][$result->getAttribute('time')] = [
|
||||
'value' => $result->getAttribute('value'),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$format = match ($days['period']) {
|
||||
'1h' => 'Y-m-d\TH:00:00.000P',
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$usage[$metric]['total'] = $stats[$metric]['total'];
|
||||
$usage[$metric]['data'] = [];
|
||||
$leap = time() - ($days['limit'] * $days['factor']);
|
||||
while ($leap < time()) {
|
||||
$leap += $days['factor'];
|
||||
$formatDate = date($format, $leap);
|
||||
$usage[$metric]['data'][] = [
|
||||
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
|
||||
'date' => $formatDate,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$prefix = $this->isCollectionsAPI() ? 'documents' : 'rows';
|
||||
|
||||
// prefix, prefixTotal
|
||||
$usageDocument = new Document([
|
||||
'range' => $range,
|
||||
$prefix => $usage[$metrics[0]]['data'],
|
||||
$prefix . 'Total' => $usage[$metrics[0]]['total'],
|
||||
]);
|
||||
|
||||
$response->dynamic($usageDocument, $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Collections;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listCollections';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLLECTION_LIST;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/collections')
|
||||
->desc('List collections')
|
||||
->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-collections.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.listTables',
|
||||
),
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('queries', [], new Collections(), '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(', ', Collections::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, array $queries, string $search, UtopiaResponse $response, Database $dbForProject): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \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());
|
||||
}
|
||||
|
||||
$collectionIdId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionIdId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, ucfirst($this->getContext()) . " '$collectionIdId' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
try {
|
||||
$collections = $dbForProject->find('database_' . $database->getSequence(), $queries);
|
||||
$total = $dbForProject->count('database_' . $database->getSequence(), $queries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL);
|
||||
} catch (QueryException) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $total,
|
||||
$this->getSdkGroup() => $collections,
|
||||
]), $this->getResponseModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Config\Config;
|
||||
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\Structure as StructureException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createDatabase';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/databases')
|
||||
->desc('Create database')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].create')
|
||||
->label('scope', 'databases.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'database.create')
|
||||
->label('audits.resource', 'database/{response.$id}')
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'databases',
|
||||
group: 'databases',
|
||||
name: 'create',
|
||||
description: '/docs/references/databases/create.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_CREATED,
|
||||
model: UtopiaResponse::MODEL_DATABASE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.create',
|
||||
)
|
||||
)
|
||||
])
|
||||
->param('databaseId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Database name. Max length: 128 chars.')
|
||||
->param('enabled', true, new Boolean(), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $name, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
{
|
||||
$databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId;
|
||||
|
||||
try {
|
||||
$dbForProject->createDocument('databases', new Document([
|
||||
'$id' => $databaseId,
|
||||
'name' => $name,
|
||||
'enabled' => $enabled,
|
||||
'search' => implode(' ', [$databaseId, $name]),
|
||||
'type' => $this->getDatabaseType(),
|
||||
]));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DATABASE_ALREADY_EXISTS);
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
}
|
||||
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
$collections = (Config::getParam('collections', [])['databases'] ?? [])['collections'] ?? [];
|
||||
if (empty($collections)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.');
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
foreach ($collections['attributes'] as $attribute) {
|
||||
$attributes[] = new Document($attribute);
|
||||
}
|
||||
|
||||
$indexes = [];
|
||||
foreach ($collections['indexes'] as $index) {
|
||||
$indexes[] = new Document($index);
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->createCollection('database_' . $database->getSequence(), $attributes, $indexes);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DATABASE_ALREADY_EXISTS);
|
||||
} catch (IndexException) {
|
||||
throw new Exception(Exception::INDEX_INVALID);
|
||||
} catch (LimitException) {
|
||||
// TODO: @Jake, how do we handle this collection/table?
|
||||
// there's no context awareness at this level on what the api is.
|
||||
throw new Exception(Exception::COLLECTION_LIMIT_EXCEEDED);
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('databaseId', $database->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_CREATED)
|
||||
->dynamic($database, UtopiaResponse::MODEL_DATABASE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases;
|
||||
|
||||
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\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Delete extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'deleteDatabase';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/databases/:databaseId')
|
||||
->desc('Delete database')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'databases.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].delete')
|
||||
->label('audits.event', 'database.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}')
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'databases',
|
||||
group: 'databases',
|
||||
name: 'delete',
|
||||
description: '/docs/references/databases/delete.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_NOCONTENT,
|
||||
model: UtopiaResponse::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.delete',
|
||||
)
|
||||
),
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('databases', $databaseId)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB');
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('databases', $database->getId());
|
||||
$dbForProject->purgeCachedCollection('databases_' . $database->getSequence());
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_DELETE_DATABASE)
|
||||
->setDatabase($database);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $database->getId())
|
||||
->setPayload($response->output($database, UtopiaResponse::MODEL_DATABASE));
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getDatabase';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId')
|
||||
->desc('Get database')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'databases.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'databases',
|
||||
group: 'databases',
|
||||
name: 'get',
|
||||
description: '/docs/references/databases/get.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: UtopiaResponse::MODEL_DATABASE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.get',
|
||||
)
|
||||
),
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, UtopiaResponse $response, Database $dbForProject): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($database, UtopiaResponse::MODEL_DATABASE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Logs;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use DeviceDetector\DeviceDetector as Detector;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listDatabaseLogs';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/logs')
|
||||
->desc('List database logs')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'databases.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'databases',
|
||||
group: 'logs',
|
||||
name: 'listLogs',
|
||||
description: '/docs/references/databases/get-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: UtopiaResponse::MODEL_LOG_LIST,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.listDatabaseLogs',
|
||||
)
|
||||
),
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'database/' . $databaseId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($logs as $i => &$log) {
|
||||
$log['userAgent'] = $log['userAgent'] ?: 'UNKNOWN';
|
||||
$detector = new Detector($log['userAgent']);
|
||||
$detector->skipBotDetection();
|
||||
|
||||
$os = $detector->getOS();
|
||||
$client = $detector->getClient();
|
||||
$device = $detector->getDevice();
|
||||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
'userId' => ID::custom($log['data']['userId']),
|
||||
'userEmail' => $log['data']['userEmail'] ?? null,
|
||||
'userName' => $log['data']['userName'] ?? null,
|
||||
'mode' => $log['data']['mode'] ?? null,
|
||||
'ip' => $log['ip'],
|
||||
'time' => $log['time'],
|
||||
'osCode' => $os['osCode'],
|
||||
'osName' => $os['osName'],
|
||||
'osVersion' => $os['osVersion'],
|
||||
'clientType' => $client['clientType'],
|
||||
'clientCode' => $client['clientCode'],
|
||||
'clientName' => $client['clientName'],
|
||||
'clientVersion' => $client['clientVersion'],
|
||||
'clientEngine' => $client['clientEngine'],
|
||||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel'],
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
if ($record) {
|
||||
$countryCode = strtolower($record['country']['iso_code']);
|
||||
$output[$i]['countryCode'] = $locale->getText("countries.{$countryCode}", false) ? $countryCode : '--';
|
||||
$output[$i]['countryName'] = $locale->getText("countries.{$countryCode}", $locale->getText('locale.country.unknown'));
|
||||
} else {
|
||||
$output[$i]['countryCode'] = '--';
|
||||
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
'logs' => $output,
|
||||
]), UtopiaResponse::MODEL_LOG_LIST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateDatabase';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/databases/:databaseId')
|
||||
->desc('Update database')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'databases.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].update')
|
||||
->label('audits.event', 'database.update')
|
||||
->label('audits.resource', 'database/{response.$id}')
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'databases',
|
||||
group: 'databases',
|
||||
name: 'update',
|
||||
description: '/docs/references/databases/update.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: UtopiaResponse::MODEL_DATABASE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.update',
|
||||
)
|
||||
),
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('name', null, new Text(128), 'Database name. Max length: 128 chars.')
|
||||
->param('enabled', true, new Boolean(), 'Is database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $name, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$database = $dbForProject->updateDocument('databases', $databaseId, $database
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', implode(' ', [$databaseId, $name])));
|
||||
|
||||
$queueForEvents->setParam('databaseId', $database->getId());
|
||||
|
||||
$response->dynamic($database, UtopiaResponse::MODEL_DATABASE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Usage;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getDatabaseUsage';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/:databaseId/usage')
|
||||
->desc('Get database usage stats')
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'databases',
|
||||
group: null,
|
||||
name: 'getUsage',
|
||||
description: '/docs/references/databases/get-database-usage.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: UtopiaResponse::MODEL_USAGE_DATABASE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.getUsage'
|
||||
)
|
||||
)
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $range, UtopiaResponse $response, Database $dbForProject): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
$days = $periods[$range];
|
||||
$metrics = [
|
||||
str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_COLLECTIONS),
|
||||
str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_DOCUMENTS),
|
||||
str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_STORAGE),
|
||||
str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASES_OPERATIONS_READS),
|
||||
str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASES_OPERATIONS_WRITES)
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$stats[$metric]['total'] = $result['value'] ?? 0;
|
||||
$limit = $days['limit'];
|
||||
$period = $days['period'];
|
||||
$results = $dbForProject->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
$stats[$metric]['data'] = [];
|
||||
foreach ($results as $result) {
|
||||
$stats[$metric]['data'][$result->getAttribute('time')] = [
|
||||
'value' => $result->getAttribute('value'),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$format = match ($days['period']) {
|
||||
'1h' => 'Y-m-d\TH:00:00.000P',
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$usage[$metric]['total'] = $stats[$metric]['total'];
|
||||
$usage[$metric]['data'] = [];
|
||||
$leap = time() - ($days['limit'] * $days['factor']);
|
||||
while ($leap < time()) {
|
||||
$leap += $days['factor'];
|
||||
$formatDate = date($format, $leap);
|
||||
$usage[$metric]['data'][] = [
|
||||
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
|
||||
'date' => $formatDate,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'range' => $range,
|
||||
'collectionsTotal' => $usage[$metrics[0]]['total'],
|
||||
'tablesTotal' => $usage[$metrics[0]]['total'],
|
||||
'documentsTotal' => $usage[$metrics[1]]['total'],
|
||||
'rowsTotal' => $usage[$metrics[1]]['total'],
|
||||
'storageTotal' => $usage[$metrics[2]]['total'],
|
||||
'databaseReadsTotal' => $usage[$metrics[3]]['total'],
|
||||
'databaseWritesTotal' => $usage[$metrics[4]]['total'],
|
||||
'collections' => $usage[$metrics[0]]['data'],
|
||||
'tables' => $usage[$metrics[0]]['data'],
|
||||
'documents' => $usage[$metrics[1]]['data'],
|
||||
'rows' => $usage[$metrics[1]]['data'],
|
||||
'storage' => $usage[$metrics[2]]['data'],
|
||||
'databaseReads' => $usage[$metrics[3]]['data'],
|
||||
'databaseWrites' => $usage[$metrics[4]]['data'],
|
||||
]), UtopiaResponse::MODEL_USAGE_DATABASE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Usage;
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listDatabaseUsage';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases/usage')
|
||||
->desc('Get databases usage stats')
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'databases',
|
||||
group: null,
|
||||
name: 'listUsage',
|
||||
description: '/docs/references/databases/list-usage.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: UtopiaResponse::MODEL_USAGE_DATABASES,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.listUsage'
|
||||
)
|
||||
),
|
||||
])
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $range, UtopiaResponse $response, Database $dbForProject): void
|
||||
{
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
$days = $periods[$range];
|
||||
$metrics = [
|
||||
METRIC_DATABASES,
|
||||
METRIC_COLLECTIONS,
|
||||
METRIC_DOCUMENTS,
|
||||
METRIC_DATABASES_STORAGE,
|
||||
METRIC_DATABASES_OPERATIONS_READS,
|
||||
METRIC_DATABASES_OPERATIONS_WRITES,
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$stats[$metric]['total'] = $result['value'] ?? 0;
|
||||
$limit = $days['limit'];
|
||||
$period = $days['period'];
|
||||
$results = $dbForProject->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
$stats[$metric]['data'] = [];
|
||||
foreach ($results as $result) {
|
||||
$stats[$metric]['data'][$result->getAttribute('time')] = [
|
||||
'value' => $result->getAttribute('value'),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$format = match ($days['period']) {
|
||||
'1h' => 'Y-m-d\TH:00:00.000P',
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
$usage[$metric]['total'] = $stats[$metric]['total'];
|
||||
$usage[$metric]['data'] = [];
|
||||
$leap = time() - ($days['limit'] * $days['factor']);
|
||||
while ($leap < time()) {
|
||||
$leap += $days['factor'];
|
||||
$formatDate = date($format, $leap);
|
||||
$usage[$metric]['data'][] = [
|
||||
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
|
||||
'date' => $formatDate,
|
||||
];
|
||||
}
|
||||
}
|
||||
$response->dynamic(new Document([
|
||||
'range' => $range,
|
||||
'databasesTotal' => $usage[$metrics[0]]['total'],
|
||||
'collectionsTotal' => $usage[$metrics[1]]['total'],
|
||||
'tablesTotal' => $usage[$metrics[1]]['total'],
|
||||
'documentsTotal' => $usage[$metrics[2]]['total'],
|
||||
'rowsTotal' => $usage[$metrics[2]]['total'],
|
||||
'storageTotal' => $usage[$metrics[3]]['total'],
|
||||
'databasesReadsTotal' => $usage[$metrics[4]]['total'],
|
||||
'databasesWritesTotal' => $usage[$metrics[5]]['total'],
|
||||
'databases' => $usage[$metrics[0]]['data'],
|
||||
'collections' => $usage[$metrics[1]]['data'],
|
||||
'tables' => $usage[$metrics[1]]['data'],
|
||||
'documents' => $usage[$metrics[2]]['data'],
|
||||
'rows' => $usage[$metrics[2]]['data'],
|
||||
'storage' => $usage[$metrics[3]]['data'],
|
||||
'databasesReads' => $usage[$metrics[4]]['data'],
|
||||
'databasesWrites' => $usage[$metrics[5]]['data'],
|
||||
]), UtopiaResponse::MODEL_USAGE_DATABASES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Databases;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listDatabases';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/databases')
|
||||
->desc('List databases')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'databases.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'databases',
|
||||
group: 'databases',
|
||||
name: 'list',
|
||||
description: '/docs/references/databases/list.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: UtopiaResponse::MODEL_DATABASE_LIST,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'tablesDB.list',
|
||||
)
|
||||
),
|
||||
])
|
||||
->param('queries', [], new Databases(), '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(', ', Databases::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(array $queries, string $search, UtopiaResponse $response, Database $dbForProject): void
|
||||
{
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$databaseId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Database '{$databaseId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
try {
|
||||
$databases = $dbForProject->find('databases', $queries);
|
||||
$total = $dbForProject->count('databases', $queries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order column '{$e->getAttribute()}' had a null value. Cursor pagination requires all rows order column values are non-null.");
|
||||
} catch (QueryException) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'databases' => $databases,
|
||||
'total' => $total,
|
||||
]), UtopiaResponse::MODEL_DATABASE_LIST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Init;
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Platform\Action;
|
||||
|
||||
/**
|
||||
* Project database timeout,
|
||||
*/
|
||||
class Timeout extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'projectDatabaseTimeout';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setType(Action::TYPE_INIT)
|
||||
->groups(['api', 'database'])
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->callback(function (Request $request, Database $dbForProject) {
|
||||
$timeout = \intval($request->getHeader('x-appwrite-timeout'));
|
||||
|
||||
if (!empty($timeout) && App::isDevelopment()) {
|
||||
$dbForProject->setTimeout($timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Create as DatabaseCreate;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends DatabaseCreate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createTablesDatabase';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/tablesdb')
|
||||
->desc('Create database')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].create')
|
||||
->label('scope', 'databases.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'database.create')
|
||||
->label('audits.resource', 'database/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tablesDB',
|
||||
group: 'tablesdb',
|
||||
name: 'create',
|
||||
description: '/docs/references/tablesdb/create.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_CREATED,
|
||||
model: UtopiaResponse::MODEL_DATABASE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('databaseId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Database name. Max length: 128 chars.')
|
||||
->param('enabled', true, new Boolean(), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Delete as DatabaseDelete;
|
||||
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\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Delete extends DatabaseDelete
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'deleteTablesDatabase';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId')
|
||||
->desc('Delete database')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'databases.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].delete')
|
||||
->label('audits.event', 'database.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tablesDB',
|
||||
group: 'tablesdb',
|
||||
name: 'delete',
|
||||
description: '/docs/references/tablesdb/delete.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.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Get as DatabaseGet;
|
||||
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\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Get extends DatabaseGet
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getTablesDatabase';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId')
|
||||
->desc('Get database')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'databases.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tablesDB',
|
||||
group: 'tablesdb',
|
||||
name: 'get',
|
||||
description: '/docs/references/tablesdb/get.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: UtopiaResponse::MODEL_DATABASE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Logs;
|
||||
|
||||
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 DeviceDetector\DeviceDetector as Detector;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listLogs';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/logs')
|
||||
->desc('List database logs')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'databases.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'tablesDB',
|
||||
group: 'logs',
|
||||
name: 'listDatabaseLogs',
|
||||
description: '/docs/references/tablesdb/get-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: UtopiaResponse::MODEL_LOG_LIST,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
),
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Temp fix for logs
|
||||
$queries[] = Query::or([
|
||||
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
|
||||
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
|
||||
]);
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'database/' . $databaseId;
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($logs as $i => &$log) {
|
||||
$log['userAgent'] = $log['userAgent'] ?: 'UNKNOWN';
|
||||
$detector = new Detector($log['userAgent']);
|
||||
$detector->skipBotDetection();
|
||||
|
||||
$os = $detector->getOS();
|
||||
$client = $detector->getClient();
|
||||
$device = $detector->getDevice();
|
||||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
'userId' => ID::custom($log['data']['userId']),
|
||||
'userEmail' => $log['data']['userEmail'] ?? null,
|
||||
'userName' => $log['data']['userName'] ?? null,
|
||||
'mode' => $log['data']['mode'] ?? null,
|
||||
'ip' => $log['ip'],
|
||||
'time' => $log['time'],
|
||||
'osCode' => $os['osCode'],
|
||||
'osName' => $os['osName'],
|
||||
'osVersion' => $os['osVersion'],
|
||||
'clientType' => $client['clientType'],
|
||||
'clientCode' => $client['clientCode'],
|
||||
'clientName' => $client['clientName'],
|
||||
'clientVersion' => $client['clientVersion'],
|
||||
'clientEngine' => $client['clientEngine'],
|
||||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel'],
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
if ($record) {
|
||||
$countryCode = strtolower($record['country']['iso_code']);
|
||||
$output[$i]['countryCode'] = $locale->getText("countries.{$countryCode}", false) ? $countryCode : '--';
|
||||
$output[$i]['countryName'] = $locale->getText("countries.{$countryCode}", $locale->getText('locale.country.unknown'));
|
||||
} else {
|
||||
$output[$i]['countryCode'] = '--';
|
||||
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource, $queries),
|
||||
'logs' => $output,
|
||||
]), UtopiaResponse::MODEL_LOG_LIST);
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Boolean;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Boolean\Create as BooleanCreate;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class Create extends BooleanCreate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createBooleanColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_BOOLEAN;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/boolean')
|
||||
->desc('Create boolean column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/create-boolean-column.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(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
|
||||
->param('key', '', new Key(), 'Column Key.')
|
||||
->param('required', null, new Boolean(), 'Is column required?')
|
||||
->param('default', null, new Boolean(), 'Default value for column when not provided. Cannot be set when column is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is column an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Boolean;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Boolean\Update as BooleanUpdate;
|
||||
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\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends BooleanUpdate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateBooleanColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_BOOLEAN;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/boolean/:key')
|
||||
->desc('Update boolean column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/update-boolean-column.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('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
|
||||
->param('key', '', new Key(), 'Column Key.')
|
||||
->param('required', null, new Boolean(), 'Is column required?')
|
||||
->param('default', null, new Nullable(new Boolean()), 'Default value for column when not provided. Cannot be set when column is required.')
|
||||
->param('newKey', null, new Key(), 'New Column Key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Datetime;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Datetime\Create as DatetimeCreate;
|
||||
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\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class Create extends DatetimeCreate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createDatetimeColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_DATETIME;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/datetime')
|
||||
->desc('Create datetime column')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/create-datetime-column.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(), '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 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')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Datetime;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Datetime\Update as DatetimeUpdate;
|
||||
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\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends DatetimeUpdate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateDatetimeColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_DATETIME;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/datetime/:key')
|
||||
->desc('Update dateTime column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/update-datetime-column.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('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 Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime())), 'Default value for column when not provided. Cannot be set when column is required.', injections: ['dbForProject'])
|
||||
->param('newKey', null, new Key(), 'New Column Key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Delete as AttributesDelete;
|
||||
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\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Delete extends AttributesDelete
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'deleteColumn';
|
||||
}
|
||||
|
||||
// parent handles multiple model types internally
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_NONE;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/:key')
|
||||
->desc('Delete column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/delete-column.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_NOCONTENT,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('tableId', '', new UID(), 'Table ID.')
|
||||
->param('key', '', new Key(), 'Column Key.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Email;
|
||||
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Email\Create as EmailCreate;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class Create extends EmailCreate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createEmailColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_EMAIL;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/email')
|
||||
->desc('Create email column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/create-email-column.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(), 'Table ID.')
|
||||
->param('key', '', new Key(), 'Column Key.')
|
||||
->param('required', null, new Boolean(), 'Is column required?')
|
||||
->param('default', null, new Email(), 'Default value for column when not provided. Cannot be set when column is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is column an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Email;
|
||||
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Email\Update as EmailUpdate;
|
||||
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\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends EmailUpdate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateEmailColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_EMAIL;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/email/:key')
|
||||
->desc('Update email column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/update-email-column.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('tableId', '', new UID(), 'Table ID.')
|
||||
->param('key', '', new Key(), 'Column Key.')
|
||||
->param('required', null, new Boolean(), 'Is column required?')
|
||||
->param('default', null, new Nullable(new Email()), 'Default value for column when not provided. Cannot be set when column is required.')
|
||||
->param('newKey', null, new Key(), 'New Column Key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Enum;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Enum\Create as EnumCreate;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends EnumCreate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createEnumColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_ENUM;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/enum')
|
||||
->desc('Create enum column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/create-enum-column.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(), 'Table ID.')
|
||||
->param('key', '', new Key(), 'Column 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 column required?')
|
||||
->param('default', null, new Text(0), 'Default value for column when not provided. Cannot be set when column is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is column an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Enum;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Enum\Update as EnumUpdate;
|
||||
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\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends EnumUpdate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateEnumColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_ENUM;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/enum/:key')
|
||||
->desc('Update enum column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/update-enum-column.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('tableId', '', new UID(), 'Table ID.')
|
||||
->param('key', '', new Key(), 'Column 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 column required?')
|
||||
->param('default', null, new Nullable(new Text(0)), 'Default value for column when not provided. Cannot be set when column is required.')
|
||||
->param('newKey', null, new Key(), 'New Column Key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Float;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Float\Create as FloatCreate;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\FloatValidator;
|
||||
|
||||
class Create extends FloatCreate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'createFloatColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_FLOAT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/float')
|
||||
->desc('Create float column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/create-float-column.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(), 'Table ID.')
|
||||
->param('key', '', new Key(), 'Column Key.')
|
||||
->param('required', null, new Boolean(), 'Is column 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 column an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Float;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Float\Update as FloatUpdate;
|
||||
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\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\FloatValidator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Update extends FloatUpdate
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'updateFloatColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return UtopiaResponse::MODEL_COLUMN_FLOAT;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/float/:key')
|
||||
->desc('Update float column')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', ['tables.write', 'collections.write'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/update-float-column.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('tableId', '', new UID(), 'Table ID.')
|
||||
->param('key', '', new Key(), 'Column Key.')
|
||||
->param('required', null, new Boolean(), 'Is column 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 Column Key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Get as AttributesGet;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
|
||||
class Get extends AttributesGet
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getColumn';
|
||||
}
|
||||
|
||||
protected function getResponseModel(): string|array
|
||||
{
|
||||
return [
|
||||
UtopiaResponse::MODEL_COLUMN_BOOLEAN,
|
||||
UtopiaResponse::MODEL_COLUMN_INTEGER,
|
||||
UtopiaResponse::MODEL_COLUMN_FLOAT,
|
||||
UtopiaResponse::MODEL_COLUMN_EMAIL,
|
||||
UtopiaResponse::MODEL_COLUMN_ENUM,
|
||||
UtopiaResponse::MODEL_COLUMN_URL,
|
||||
UtopiaResponse::MODEL_COLUMN_IP,
|
||||
UtopiaResponse::MODEL_COLUMN_DATETIME,
|
||||
UtopiaResponse::MODEL_COLUMN_RELATIONSHIP,
|
||||
UtopiaResponse::MODEL_COLUMN_STRING,
|
||||
];
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/:key')
|
||||
->desc('Get column')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', ['tables.read', 'collections.read'])
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
name: self::getName(),
|
||||
description: '/docs/references/tablesdb/get-column.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(...));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user