mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'master' into feat-implement-transfers
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
/**
|
||||
* Password.
|
||||
*
|
||||
* Validates user password string
|
||||
*/
|
||||
class PasswordDictionary extends Password
|
||||
{
|
||||
protected array $dictionary;
|
||||
protected bool $enabled;
|
||||
|
||||
public function __construct(array $dictionary, bool $enabled = false)
|
||||
{
|
||||
$this->dictionary = $dictionary;
|
||||
$this->enabled = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Password must be at least 8 characters and should not be one of the commonly used password.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!parent::isValid($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->enabled && array_key_exists($value, $this->dictionary)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
|
||||
/**
|
||||
* Password.
|
||||
*
|
||||
* Validates user password string
|
||||
*/
|
||||
class PasswordHistory extends Password
|
||||
{
|
||||
protected array $history;
|
||||
protected string $algo;
|
||||
protected array $algoOptions;
|
||||
|
||||
public function __construct(array $history, string $algo, array $algoOptions = [])
|
||||
{
|
||||
$this->history = $history;
|
||||
$this->algo = $algo;
|
||||
$this->algoOptions = $algoOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Password shouldn\'t be in the history.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
foreach ($this->history as $hash) {
|
||||
if (Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ class Exception extends \Exception
|
||||
public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited';
|
||||
public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
|
||||
public const USER_NOT_FOUND = 'user_not_found';
|
||||
public const USER_PASSWORD_RECENTLY_USED = 'password_recently_used';
|
||||
public const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
|
||||
public const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
|
||||
public const USER_SESSION_NOT_FOUND = 'user_session_not_found';
|
||||
@@ -135,6 +136,8 @@ class Exception extends \Exception
|
||||
public const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure';
|
||||
public const DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload';
|
||||
public const DOCUMENT_ALREADY_EXISTS = 'document_already_exists';
|
||||
public const DOCUMENT_UPDATE_CONFLICT = 'document_update_conflict';
|
||||
public const DOCUMENT_DELETE_RESTRICTED = 'document_delete_restricted';
|
||||
|
||||
/** Attribute */
|
||||
public const ATTRIBUTE_NOT_FOUND = 'attribute_not_found';
|
||||
@@ -145,6 +148,7 @@ class Exception extends \Exception
|
||||
public const ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists';
|
||||
public const ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded';
|
||||
public const ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid';
|
||||
public const ATTRIBUTE_TYPE_INVALID = 'attribute_type_invalid';
|
||||
|
||||
/** Indexes */
|
||||
public const INDEX_NOT_FOUND = 'index_not_found';
|
||||
@@ -178,6 +182,7 @@ class Exception extends \Exception
|
||||
public const DOMAIN_NOT_FOUND = 'domain_not_found';
|
||||
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
|
||||
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
|
||||
public const DOMAIN_TARGET_INVALID = 'domain_target_invalid';
|
||||
|
||||
/** GraphqQL */
|
||||
public const GRAPHQL_NO_QUERY = 'graphql_no_query';
|
||||
|
||||
@@ -11,6 +11,7 @@ use GraphQL\Type\Definition\UnionType;
|
||||
use Utopia\App;
|
||||
use Utopia\Route;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Mapper
|
||||
{
|
||||
@@ -109,9 +110,6 @@ class Mapper
|
||||
'type' => $parameterType,
|
||||
'description' => $parameter['description'],
|
||||
];
|
||||
if ($parameter['optional']) {
|
||||
$params[$name]['defaultValue'] = $parameter['default'];
|
||||
}
|
||||
}
|
||||
|
||||
$field = [
|
||||
@@ -224,6 +222,12 @@ class Mapper
|
||||
? \call_user_func_array($validator, $utopia->getResources($injections))
|
||||
: $validator;
|
||||
|
||||
$isNullable = $validator instanceof Nullable;
|
||||
|
||||
if ($isNullable) {
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? $validator::class : '') {
|
||||
case 'Appwrite\Network\Validator\CNAME':
|
||||
case 'Appwrite\Task\Validator\Cron':
|
||||
@@ -294,7 +298,7 @@ class Mapper
|
||||
break;
|
||||
}
|
||||
|
||||
if ($required) {
|
||||
if ($required && !$isNullable) {
|
||||
$type = Type::nonNull($type);
|
||||
}
|
||||
|
||||
@@ -404,6 +408,8 @@ class Mapper
|
||||
return static::model('AttributeBoolean');
|
||||
case 'datetime':
|
||||
return static::model('AttributeDatetime');
|
||||
case 'relationship':
|
||||
return static::model('AttributeRelationship');
|
||||
}
|
||||
|
||||
throw new Exception('Unknown attribute implementation');
|
||||
|
||||
@@ -37,6 +37,8 @@ abstract class Migration
|
||||
*/
|
||||
protected Database $consoleDB;
|
||||
|
||||
protected \PDO $pdo;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@@ -50,6 +52,8 @@ abstract class Migration
|
||||
'1.1.2' => 'V16',
|
||||
'1.2.0' => 'V17',
|
||||
'1.2.1' => 'V17',
|
||||
'1.3.0' => 'V18',
|
||||
'1.3.1' => 'V18',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -98,6 +102,13 @@ abstract class Migration
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPDO(\PDO $pdo): self
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through every document.
|
||||
*
|
||||
@@ -331,6 +342,25 @@ abstract class Migration
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a collection attribute's internal type
|
||||
*
|
||||
* @param string $collection
|
||||
* @param string $attribute
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
protected function changeAttributeInternalType(string $collection, string $attribute, string $type): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare("ALTER TABLE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_{$collection}` MODIFY `$attribute` $type;");
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
} catch (\Exception $e) {
|
||||
Console::warning($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes migration for set project.
|
||||
*/
|
||||
|
||||
@@ -17,11 +17,6 @@ use Utopia\Database\Helpers\Role;
|
||||
|
||||
class V15 extends Migration
|
||||
{
|
||||
/**
|
||||
* @var \PDO $pdo
|
||||
*/
|
||||
private $pdo;
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class V18 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
|
||||
/**
|
||||
* Disable SubQueries for Performance.
|
||||
*/
|
||||
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables'] as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
fn () => []
|
||||
);
|
||||
}
|
||||
|
||||
Console::log('Migrating Project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
|
||||
$this->projectDB->setNamespace("_{$this->project->getInternalId()}");
|
||||
|
||||
Console::info('Migrating Databases');
|
||||
$this->migrateDatabases();
|
||||
|
||||
Console::info('Migrating Collections');
|
||||
$this->migrateCollections();
|
||||
|
||||
Console::info('Migrating Documents');
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Databases.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function migrateDatabases(): void
|
||||
{
|
||||
foreach ($this->documentsIterator('databases') as $database) {
|
||||
$databaseTable = "database_{$database->getInternalId()}";
|
||||
|
||||
Console::info("Migrating Collections of {$database->getId()} ({$database->getAttribute('name')})");
|
||||
|
||||
foreach ($this->documentsIterator($databaseTable) as $collection) {
|
||||
$collectionTable = "{$databaseTable}_collection_{$collection->getInternalId()}";
|
||||
|
||||
foreach ($collection['attributes'] ?? [] as $attribute) {
|
||||
if ($attribute['type'] !== Database::VAR_FLOAT) {
|
||||
continue;
|
||||
}
|
||||
$this->changeAttributeInternalType($collectionTable, $attribute['key'], 'DOUBLE');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Collections.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function migrateCollections(): void
|
||||
{
|
||||
foreach ($this->collections as $collection) {
|
||||
$id = $collection['$id'];
|
||||
|
||||
Console::log("Migrating Collection \"{$id}\"");
|
||||
|
||||
foreach ($collection['attributes'] ?? [] as $attribute) {
|
||||
if ($attribute['type'] !== Database::VAR_FLOAT) {
|
||||
continue;
|
||||
}
|
||||
$this->changeAttributeInternalType($id, $attribute['$id'], 'DOUBLE');
|
||||
}
|
||||
|
||||
switch ($id) {
|
||||
case 'users':
|
||||
try {
|
||||
/**
|
||||
* Create 'passwordHistory' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'passwordHistory');
|
||||
$this->projectDB->deleteCachedCollection($id);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'passwordHistory' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
case 'teams':
|
||||
try {
|
||||
/**
|
||||
* Create 'prefs' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'prefs');
|
||||
$this->projectDB->deleteCachedCollection($id);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'prefs' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
case 'attributes':
|
||||
try {
|
||||
/**
|
||||
* Create 'options' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'options');
|
||||
$this->projectDB->deleteCachedCollection($id);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'options' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix run on each document
|
||||
*
|
||||
* @param Document $document
|
||||
* @return Document
|
||||
*/
|
||||
protected function fixDocument(Document $document): Document
|
||||
{
|
||||
switch ($document->getCollection()) {
|
||||
case 'projects':
|
||||
/**
|
||||
* Bump version number.
|
||||
*/
|
||||
$document->setAttribute('version', '1.3.0');
|
||||
|
||||
/**
|
||||
* Set default passwordHistory
|
||||
*/
|
||||
$document->setAttribute('auths', array_merge($document->getAttribute('auths', []), [
|
||||
'passwordHistory' => 0,
|
||||
'passwordDictionary' => false,
|
||||
]));
|
||||
break;
|
||||
case 'users':
|
||||
/**
|
||||
* Default Password history
|
||||
*/
|
||||
$document->setAttribute('passwordHistory', []);
|
||||
break;
|
||||
case 'teams':
|
||||
/**
|
||||
* Default prefs
|
||||
*/
|
||||
$document->setAttribute('prefs', new \stdClass());
|
||||
break;
|
||||
case 'attributes':
|
||||
/**
|
||||
* Default options
|
||||
*/
|
||||
$document->setAttribute('options', new \stdClass());
|
||||
break;
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class OpenAPI3 extends Format
|
||||
{
|
||||
@@ -170,6 +171,9 @@ class OpenAPI3 extends Format
|
||||
'scope' => $route->getLabel('scope', ''),
|
||||
'platforms' => $sdkPlatforms,
|
||||
'packaging' => $route->getLabel('sdk.packaging', false),
|
||||
'offline-model' => $route->getLabel('sdk.offline.model', ''),
|
||||
'offline-key' => $route->getLabel('sdk.offline.key', ''),
|
||||
'offline-response-key' => $route->getLabel('sdk.offline.response.key', '$id'),
|
||||
],
|
||||
];
|
||||
|
||||
@@ -281,6 +285,13 @@ class OpenAPI3 extends Format
|
||||
}
|
||||
}
|
||||
|
||||
$isNullable = $validator instanceof Nullable;
|
||||
|
||||
if ($isNullable) {
|
||||
/** @var Nullable $validator */
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? \get_class($validator) : '') {
|
||||
case 'Utopia\Validator\Text':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
@@ -446,6 +457,10 @@ class OpenAPI3 extends Format
|
||||
if ($node['x-global'] ?? false) {
|
||||
$body['content'][$consumes[0]]['schema']['properties'][$name]['x-global'] = true;
|
||||
}
|
||||
|
||||
if ($isNullable) {
|
||||
$body['content'][$consumes[0]]['schema']['properties'][$name]['x-nullable'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$url = \str_replace(':' . $name, '{' . $name . '}', $url);
|
||||
|
||||
@@ -8,6 +8,7 @@ use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Swagger2 extends Format
|
||||
{
|
||||
@@ -171,6 +172,9 @@ class Swagger2 extends Format
|
||||
'scope' => $route->getLabel('scope', ''),
|
||||
'platforms' => $sdkPlatforms,
|
||||
'packaging' => $route->getLabel('sdk.packaging', false),
|
||||
'offline-model' => $route->getLabel('sdk.offline.model', ''),
|
||||
'offline-key' => $route->getLabel('sdk.offline.key', ''),
|
||||
'offline-response-key' => $route->getLabel('sdk.offline.response.key', '$id'),
|
||||
],
|
||||
];
|
||||
|
||||
@@ -282,6 +286,13 @@ class Swagger2 extends Format
|
||||
}
|
||||
}
|
||||
|
||||
$isNullable = $validator instanceof Nullable;
|
||||
|
||||
if ($isNullable) {
|
||||
/** @var Nullable $validator */
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? \get_class($validator) : '') {
|
||||
case 'Utopia\Validator\Text':
|
||||
$node['type'] = $validator->getType();
|
||||
@@ -445,6 +456,10 @@ class Swagger2 extends Format
|
||||
$body['schema']['properties'][$name]['x-global'] = true;
|
||||
}
|
||||
|
||||
if ($isNullable) {
|
||||
$body['schema']['properties'][$name]['x-nullable'] = true;
|
||||
}
|
||||
|
||||
if (\array_key_exists('items', $node)) {
|
||||
$body['schema']['properties'][$name]['items'] = $node['items'];
|
||||
}
|
||||
|
||||
@@ -10,26 +10,26 @@ use Utopia\Database\Query;
|
||||
class IndexedQueries extends Queries
|
||||
{
|
||||
/**
|
||||
* @var Document[]
|
||||
* @var array<Document>
|
||||
*/
|
||||
protected $attributes = [];
|
||||
protected array $attributes = [];
|
||||
|
||||
/**
|
||||
* @var Document[]
|
||||
* @var array<Document>
|
||||
*/
|
||||
protected $indexes = [];
|
||||
protected array $indexes = [];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* This Queries Validator filters indexes for only available indexes
|
||||
*
|
||||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @param array<Document> $attributes
|
||||
* @param array<Document> $indexes
|
||||
* @param Base ...$validators
|
||||
* @param bool $strict
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($attributes = [], $indexes = [], Base ...$validators)
|
||||
public function __construct(array $attributes = [], array $indexes = [], Base ...$validators)
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
|
||||
@@ -55,33 +55,6 @@ class IndexedQueries extends Queries
|
||||
parent::__construct(...$validators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if indexed array $indexes matches $queries
|
||||
*
|
||||
* @param array $indexes
|
||||
* @param array $queries
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function arrayMatch(array $indexes, array $queries): bool
|
||||
{
|
||||
// Check the count of indexes first for performance
|
||||
if (count($queries) !== count($indexes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort them for comparison, the order is not important here anymore.
|
||||
sort($indexes, SORT_STRING);
|
||||
sort($queries, SORT_STRING);
|
||||
|
||||
// Only matching arrays will have equal diffs in both directions
|
||||
if (array_diff_assoc($indexes, $queries) !== array_diff_assoc($queries, $indexes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
@@ -111,41 +84,26 @@ class IndexedQueries extends Queries
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
/** @var Query[] */ $filters = $grouped['filters'];
|
||||
/** @var string[] */ $orderAttributes = $grouped['orderAttributes'];
|
||||
$filters = $grouped['filters'];
|
||||
|
||||
// Check filter queries for exact index match
|
||||
if (count($filters) > 0) {
|
||||
$filtersByAttribute = [];
|
||||
foreach ($filters as $filter) {
|
||||
$filtersByAttribute[$filter->getAttribute()] = $filter->getMethod();
|
||||
}
|
||||
foreach ($filters as $filter) {
|
||||
if ($filter->getMethod() === Query::TYPE_SEARCH) {
|
||||
$matched = false;
|
||||
|
||||
$found = null;
|
||||
foreach ($this->indexes as $index) {
|
||||
if (
|
||||
$index->getAttribute('type') === Database::INDEX_FULLTEXT
|
||||
&& $index->getAttribute('attributes') === [$filter->getAttribute()]
|
||||
) {
|
||||
$matched = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->indexes as $index) {
|
||||
if ($this->arrayMatch($index->getAttribute('attributes'), array_keys($filtersByAttribute))) {
|
||||
$found = $index;
|
||||
if (!$matched) {
|
||||
$this->message = "Searching by attribute \"{$filter->getAttribute()}\" requires a fulltext index.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$this->message = 'Index not found: ' . implode(",", array_keys($filtersByAttribute));
|
||||
return false;
|
||||
}
|
||||
|
||||
// search method requires fulltext index
|
||||
if (in_array(Query::TYPE_SEARCH, array_values($filtersByAttribute)) && $found['type'] !== Database::INDEX_FULLTEXT) {
|
||||
$this->message = 'Search method requires fulltext index: ' . implode(",", array_keys($filtersByAttribute));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check order attributes for exact index match
|
||||
$validator = new OrderAttributes($this->attributes, $this->indexes, true);
|
||||
if (count($orderAttributes) > 0 && !$validator->isValid($orderAttributes)) {
|
||||
$this->message = $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -11,12 +11,12 @@ class Queries extends Validator
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Invalid queries';
|
||||
protected string $message = 'Invalid queries';
|
||||
|
||||
/**
|
||||
* @var Base[]
|
||||
* @var array<Base>
|
||||
*/
|
||||
protected $validators;
|
||||
protected array $validators;
|
||||
|
||||
/**
|
||||
* Queries constructor
|
||||
@@ -57,41 +57,35 @@ class Queries extends Validator
|
||||
if (!$query instanceof Query) {
|
||||
try {
|
||||
$query = Query::parse($query);
|
||||
} catch (\Throwable $th) {
|
||||
$this->message = 'Invalid query: ${query}';
|
||||
} catch (\Throwable) {
|
||||
$this->message = "Invalid query: {$query}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$method = $query->getMethod();
|
||||
$methodType = '';
|
||||
switch ($method) {
|
||||
case Query::TYPE_LIMIT:
|
||||
$methodType = Base::METHOD_TYPE_LIMIT;
|
||||
break;
|
||||
case Query::TYPE_OFFSET:
|
||||
$methodType = Base::METHOD_TYPE_OFFSET;
|
||||
break;
|
||||
case Query::TYPE_CURSORAFTER:
|
||||
case Query::TYPE_CURSORBEFORE:
|
||||
$methodType = Base::METHOD_TYPE_CURSOR;
|
||||
break;
|
||||
case Query::TYPE_ORDERASC:
|
||||
case Query::TYPE_ORDERDESC:
|
||||
$methodType = Base::METHOD_TYPE_ORDER;
|
||||
break;
|
||||
case Query::TYPE_EQUAL:
|
||||
case Query::TYPE_NOTEQUAL:
|
||||
case Query::TYPE_LESSER:
|
||||
case Query::TYPE_LESSEREQUAL:
|
||||
case Query::TYPE_GREATER:
|
||||
case Query::TYPE_GREATEREQUAL:
|
||||
case Query::TYPE_SEARCH:
|
||||
$methodType = Base::METHOD_TYPE_FILTER;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$methodType = match ($method) {
|
||||
Query::TYPE_SELECT => Base::METHOD_TYPE_SELECT,
|
||||
Query::TYPE_LIMIT => Base::METHOD_TYPE_LIMIT,
|
||||
Query::TYPE_OFFSET => Base::METHOD_TYPE_OFFSET,
|
||||
Query::TYPE_CURSORAFTER,
|
||||
Query::TYPE_CURSORBEFORE => Base::METHOD_TYPE_CURSOR,
|
||||
Query::TYPE_ORDERASC,
|
||||
Query::TYPE_ORDERDESC => Base::METHOD_TYPE_ORDER,
|
||||
Query::TYPE_EQUAL,
|
||||
Query::TYPE_NOTEQUAL,
|
||||
Query::TYPE_LESSER,
|
||||
Query::TYPE_LESSEREQUAL,
|
||||
Query::TYPE_GREATER,
|
||||
Query::TYPE_GREATEREQUAL,
|
||||
Query::TYPE_SEARCH,
|
||||
Query::TYPE_IS_NULL,
|
||||
Query::TYPE_IS_NOT_NULL,
|
||||
Query::TYPE_BETWEEN,
|
||||
Query::TYPE_STARTS_WITH,
|
||||
Query::TYPE_ENDS_WITH => Base::METHOD_TYPE_FILTER,
|
||||
default => '',
|
||||
};
|
||||
|
||||
$methodIsValid = false;
|
||||
foreach ($this->validators as $validator) {
|
||||
|
||||
@@ -8,6 +8,7 @@ use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Select;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -19,6 +20,7 @@ class Base extends Queries
|
||||
*
|
||||
* @param string $collection
|
||||
* @param string[] $allowedAttributes
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(string $collection, array $allowedAttributes)
|
||||
{
|
||||
@@ -65,6 +67,7 @@ class Base extends Queries
|
||||
new Cursor(),
|
||||
new Filter($attributes),
|
||||
new Order($attributes),
|
||||
new Select($attributes),
|
||||
];
|
||||
|
||||
parent::__construct(...$validators);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Select;
|
||||
use Utopia\Database\Database;
|
||||
|
||||
class Document extends Queries
|
||||
{
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* @param array $attributes
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(array $attributes)
|
||||
{
|
||||
$attributes[] = new \Utopia\Database\Document([
|
||||
'key' => '$id',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
]);
|
||||
$attributes[] = new \Utopia\Database\Document([
|
||||
'key' => '$createdAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'array' => false,
|
||||
]);
|
||||
$attributes[] = new \Utopia\Database\Document([
|
||||
'key' => '$updatedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'array' => false,
|
||||
]);
|
||||
|
||||
$validators = [
|
||||
new Select($attributes),
|
||||
];
|
||||
|
||||
parent::__construct(...$validators);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\IndexedQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Select;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
@@ -17,7 +18,7 @@ class Documents extends IndexedQueries
|
||||
* Expression constructor
|
||||
*
|
||||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(array $attributes, array $indexes)
|
||||
{
|
||||
@@ -43,6 +44,7 @@ class Documents extends IndexedQueries
|
||||
new Cursor(),
|
||||
new Filter($attributes),
|
||||
new Order($attributes),
|
||||
new Select($attributes),
|
||||
];
|
||||
|
||||
parent::__construct($attributes, $indexes, ...$validators);
|
||||
|
||||
@@ -12,6 +12,7 @@ abstract class Base extends Validator
|
||||
public const METHOD_TYPE_CURSOR = 'cursor';
|
||||
public const METHOD_TYPE_ORDER = 'order';
|
||||
public const METHOD_TYPE_FILTER = 'filter';
|
||||
public const METHOD_TYPE_SELECT = 'select';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
||||
@@ -18,6 +18,8 @@ class Filter extends Base
|
||||
*/
|
||||
protected $schema = [];
|
||||
|
||||
private int $maxValuesCount;
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
@@ -34,6 +36,18 @@ class Filter extends Base
|
||||
|
||||
protected function isValidAttribute($attribute): bool
|
||||
{
|
||||
if (\str_contains($attribute, '.')) {
|
||||
// For relationships, just validate the top level.
|
||||
// Utopia will validate each nested level during the recursive calls.
|
||||
$attribute = \explode('.', $attribute)[0];
|
||||
|
||||
// TODO: Remove this when nested queries are supported
|
||||
if (isset($this->schema[$attribute])) {
|
||||
$this->message = 'Cannot query nested attribute on: ' . $attribute;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Search for attribute in schema
|
||||
if (!isset($this->schema[$attribute])) {
|
||||
$this->message = 'Attribute not found in schema: ' . $attribute;
|
||||
@@ -49,6 +63,12 @@ class Filter extends Base
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\str_contains($attribute, '.')) {
|
||||
// For relationships, just validate the top level.
|
||||
// Utopia will validate each nested level during the recursive calls.
|
||||
$attribute = \explode('.', $attribute)[0];
|
||||
}
|
||||
|
||||
$attributeSchema = $this->schema[$attribute];
|
||||
|
||||
if (count($values) > $this->maxValuesCount) {
|
||||
@@ -61,7 +81,9 @@ class Filter extends Base
|
||||
|
||||
foreach ($values as $value) {
|
||||
$condition = match ($attributeType) {
|
||||
Database::VAR_RELATIONSHIP => true,
|
||||
Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING,
|
||||
Database::VAR_FLOAT => (gettype($value) === Database::VAR_FLOAT || gettype($value) === Database::VAR_INTEGER),
|
||||
default => gettype($value) === $attributeType
|
||||
};
|
||||
|
||||
@@ -99,6 +121,11 @@ class Filter extends Base
|
||||
case Query::TYPE_GREATER:
|
||||
case Query::TYPE_GREATEREQUAL:
|
||||
case Query::TYPE_SEARCH:
|
||||
case Query::TYPE_STARTS_WITH:
|
||||
case Query::TYPE_ENDS_WITH:
|
||||
case Query::TYPE_BETWEEN:
|
||||
case Query::TYPE_IS_NULL:
|
||||
case Query::TYPE_IS_NOT_NULL:
|
||||
$values = $query->getValues();
|
||||
return $this->isValidAttributeAndValues($attribute, $values);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class Limit extends Base
|
||||
*
|
||||
* @param int $maxLimit
|
||||
*/
|
||||
public function __construct(int $maxLimit = 100)
|
||||
public function __construct(int $maxLimit = PHP_INT_MAX)
|
||||
{
|
||||
$this->maxLimit = $maxLimit;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
@@ -15,7 +14,7 @@ class Offset extends Base
|
||||
*
|
||||
* @param int $maxOffset
|
||||
*/
|
||||
public function __construct(int $maxOffset = 5000)
|
||||
public function __construct(int $maxOffset = PHP_INT_MAX)
|
||||
{
|
||||
$this->maxOffset = $maxOffset;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Select extends Base
|
||||
{
|
||||
protected array $schema = [];
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is TYPE_SELECT selections are valid
|
||||
*
|
||||
* Otherwise, returns false
|
||||
*
|
||||
* @param $query
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
/* @var $query Query */
|
||||
|
||||
if ($query->getMethod() !== Query::TYPE_SELECT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($query->getValues() as $attribute) {
|
||||
if (\str_contains($attribute, '.')) {
|
||||
// For relationships, just validate the top level.
|
||||
// Utopia will validate each nested level during the recursive calls.
|
||||
$attribute = \explode('.', $attribute)[0];
|
||||
}
|
||||
if (!isset($this->schema[$attribute]) && $attribute !== '*') {
|
||||
$this->message = 'Attribute not found in schema: ' . $attribute;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_SELECT;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ use Appwrite\Utopia\Response\Model\AttributeEnum;
|
||||
use Appwrite\Utopia\Response\Model\AttributeIP;
|
||||
use Appwrite\Utopia\Response\Model\AttributeURL;
|
||||
use Appwrite\Utopia\Response\Model\AttributeDatetime;
|
||||
use Appwrite\Utopia\Response\Model\AttributeRelationship;
|
||||
use Appwrite\Utopia\Response\Model\BaseList;
|
||||
use Appwrite\Utopia\Response\Model\Collection;
|
||||
use Appwrite\Utopia\Response\Model\Database;
|
||||
@@ -44,6 +45,7 @@ use Appwrite\Utopia\Response\Model\Execution;
|
||||
use Appwrite\Utopia\Response\Model\Build;
|
||||
use Appwrite\Utopia\Response\Model\File;
|
||||
use Appwrite\Utopia\Response\Model\Bucket;
|
||||
use Appwrite\Utopia\Response\Model\ConsoleVariables;
|
||||
use Appwrite\Utopia\Response\Model\Func;
|
||||
use Appwrite\Utopia\Response\Model\Index;
|
||||
use Appwrite\Utopia\Response\Model\JWT;
|
||||
@@ -138,6 +140,7 @@ class Response extends SwooleResponse
|
||||
public const MODEL_ATTRIBUTE_IP = 'attributeIp';
|
||||
public const MODEL_ATTRIBUTE_URL = 'attributeUrl';
|
||||
public const MODEL_ATTRIBUTE_DATETIME = 'attributeDatetime';
|
||||
public const MODEL_ATTRIBUTE_RELATIONSHIP = 'attributeRelationship';
|
||||
|
||||
// Users
|
||||
public const MODEL_ACCOUNT = 'account';
|
||||
@@ -219,6 +222,9 @@ class Response extends SwooleResponse
|
||||
public const MODEL_HEALTH_TIME = 'healthTime';
|
||||
public const MODEL_HEALTH_ANTIVIRUS = 'healthAntivirus';
|
||||
|
||||
// Console
|
||||
public const MODEL_CONSOLE_VARIABLES = 'consoleVariables';
|
||||
|
||||
// Transfers
|
||||
public const MODEL_TRANSFER = 'transfer';
|
||||
public const MODEL_TRANSFER_LIST = 'transferList';
|
||||
@@ -308,6 +314,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new AttributeIP())
|
||||
->setModel(new AttributeURL())
|
||||
->setModel(new AttributeDatetime())
|
||||
->setModel(new AttributeRelationship())
|
||||
->setModel(new Index())
|
||||
->setModel(new ModelDocument())
|
||||
->setModel(new Log())
|
||||
@@ -361,6 +368,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new UsageFunctions())
|
||||
->setModel(new UsageFunction())
|
||||
->setModel(new UsageProject())
|
||||
->setModel(new ConsoleVariables())
|
||||
->setModel(new Transfer())
|
||||
->setModel(new Destination())
|
||||
->setModel(new DestinationValidation())
|
||||
|
||||
@@ -13,6 +13,7 @@ abstract class Model
|
||||
public const TYPE_JSON = 'json';
|
||||
public const TYPE_DATETIME = 'datetime';
|
||||
public const TYPE_DATETIME_EXAMPLE = '2020-10-15T06:38:00.000+00:00';
|
||||
public const TYPE_RELATIONSHIP = 'relationship';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
@@ -80,6 +81,7 @@ abstract class Model
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $options
|
||||
* @return Model
|
||||
*/
|
||||
protected function addRule(string $key, array $options): self
|
||||
{
|
||||
@@ -98,7 +100,7 @@ abstract class Model
|
||||
* If rule exists, it will be removed
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $options
|
||||
* @return Model
|
||||
*/
|
||||
protected function removeRule(string $key): self
|
||||
{
|
||||
@@ -109,7 +111,10 @@ abstract class Model
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRequired()
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRequired(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
|
||||
class AttributeBoolean extends Attribute
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
|
||||
class AttributeEmail extends Attribute
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
|
||||
class AttributeEnum extends Attribute
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
|
||||
class AttributeFloat extends Attribute
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
|
||||
class AttributeIP extends Attribute
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
|
||||
class AttributeInteger extends Attribute
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class AttributeList extends Model
|
||||
{
|
||||
@@ -27,6 +26,7 @@ class AttributeList extends Model
|
||||
Response::MODEL_ATTRIBUTE_URL,
|
||||
Response::MODEL_ATTRIBUTE_IP,
|
||||
Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Response::MODEL_ATTRIBUTE_RELATIONSHIP,
|
||||
Response::MODEL_ATTRIBUTE_STRING // needs to be last, since its condition would dominate any other string attribute
|
||||
],
|
||||
'description' => 'List of attributes.',
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class AttributeRelationship extends Attribute
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this
|
||||
->addRule('relatedCollection', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The ID of the related collection.',
|
||||
'default' => null,
|
||||
'example' => 'collection',
|
||||
])
|
||||
->addRule('relationType', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The type of the relationship.',
|
||||
'default' => '',
|
||||
'example' => 'oneToOne|oneToMany|manyToOne|manyToMany',
|
||||
])
|
||||
->addRule('twoWay', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Is the relationship two-way?',
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('twoWayKey', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The key of the two-way relationship.',
|
||||
'default' => '',
|
||||
'example' => 'string',
|
||||
])
|
||||
->addRule('onDelete', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'How deleting the parent document will propagate to child documents.',
|
||||
'default' => 'restrict',
|
||||
'example' => 'restrict|cascade|setNull',
|
||||
])
|
||||
->addRule('side', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Whether this is the parent or child side of the relationship',
|
||||
'default' => '',
|
||||
'example' => 'parent|child',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public array $conditions = [
|
||||
'type' => self::TYPE_RELATIONSHIP,
|
||||
];
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'AttributeRelationship';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_ATTRIBUTE_RELATIONSHIP;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
|
||||
class AttributeString extends Attribute
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Attribute;
|
||||
|
||||
class AttributeURL extends Attribute
|
||||
{
|
||||
|
||||
@@ -69,6 +69,7 @@ class Collection extends Model
|
||||
Response::MODEL_ATTRIBUTE_URL,
|
||||
Response::MODEL_ATTRIBUTE_IP,
|
||||
Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Response::MODEL_ATTRIBUTE_RELATIONSHIP,
|
||||
Response::MODEL_ATTRIBUTE_STRING, // needs to be last, since its condition would dominate any other string attribute
|
||||
],
|
||||
'description' => 'Collection attributes.',
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class ConsoleVariables extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('_APP_DOMAIN_TARGET', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'CNAME target for your Appwrite custom domains.',
|
||||
'default' => '',
|
||||
'example' => 'appwrite.io',
|
||||
])
|
||||
->addRule('_APP_STORAGE_LIMIT', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Maximum file size allowed for file upload in bytes.',
|
||||
'default' => '',
|
||||
'example' => '30000000',
|
||||
])
|
||||
->addRule('_APP_FUNCTIONS_SIZE_LIMIT', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Maximum file size allowed for deployment in bytes.',
|
||||
'default' => '',
|
||||
'example' => '30000000',
|
||||
])
|
||||
->addRule('_APP_USAGE_STATS', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Defines if usage stats are enabled. This value is set to \'enabled\' by default, to disable the usage stats set the value to \'disabled\'.',
|
||||
'default' => '',
|
||||
'example' => 'enabled',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Console Variables';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_CONSOLE_VARIABLES;
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,18 @@ class Document extends Any
|
||||
$document->removeAttribute('$internalId');
|
||||
$document->removeAttribute('$collection'); // $collection is the internal collection ID
|
||||
|
||||
foreach ($document->getAttributes() as $attribute) {
|
||||
if (\is_array($attribute)) {
|
||||
foreach ($attribute as $subAttribute) {
|
||||
if ($subAttribute instanceof DatabaseDocument) {
|
||||
$this->filter($subAttribute);
|
||||
}
|
||||
}
|
||||
} elseif ($attribute instanceof DatabaseDocument) {
|
||||
$this->filter($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,18 @@ class Project extends Model
|
||||
'default' => 10,
|
||||
'example' => 10,
|
||||
])
|
||||
->addRule('authPasswordHistory', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Max allowed passwords in the history list per user. Max passwords limit allowed in history is 20. Use 0 for disabling password history.',
|
||||
'default' => 0,
|
||||
'example' => 5,
|
||||
])
|
||||
->addRule('authPasswordDictionary', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to check user\'s password against most commonly used passwords.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('providers', [
|
||||
'type' => Response::MODEL_PROVIDER,
|
||||
'description' => 'List of Providers.',
|
||||
@@ -240,6 +252,8 @@ class Project extends Model
|
||||
$document->setAttribute('authLimit', $authValues['limit'] ?? 0);
|
||||
$document->setAttribute('authDuration', $authValues['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
$document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT);
|
||||
$document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0);
|
||||
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
|
||||
|
||||
foreach ($auth as $index => $method) {
|
||||
$key = $method['key'];
|
||||
|
||||
@@ -40,6 +40,12 @@ class Team extends Model
|
||||
'default' => 0,
|
||||
'example' => 7,
|
||||
])
|
||||
->addRule('prefs', [
|
||||
'type' => Response::MODEL_PREFERENCES,
|
||||
'description' => 'Team preferences as a key-value object',
|
||||
'default' => new \stdClass(),
|
||||
'example' => ['theme' => 'pink', 'timezone' => 'UTC'],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,12 +38,14 @@ class User extends Model
|
||||
->addRule('password', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Hashed user password.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'example' => '$argon2id$v=19$m=2048,t=4,p=3$aUZjLnliVWRINmFNTWMudg$5S+x+7uA31xFnrHFT47yFwcJeaP0w92L/4LdgrVRXxE',
|
||||
])
|
||||
->addRule('hash', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Password hashing algorithm.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'example' => 'argon2',
|
||||
])
|
||||
@@ -58,6 +60,7 @@ class User extends Model
|
||||
Response::MODEL_ALGO_MD5, // keep least secure at the bottom. this order will be used in docs
|
||||
],
|
||||
'description' => 'Password hashing algorithm configuration.',
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
'array' => false,
|
||||
|
||||
Reference in New Issue
Block a user