diff --git a/app/init.php b/app/init.php index c32f1eb9a8..91a64a72ce 100644 --- a/app/init.php +++ b/app/init.php @@ -26,6 +26,7 @@ require_once __DIR__ . '/init/database/filters.php'; require_once __DIR__ . '/init/database/formats.php'; require_once __DIR__ . '/init/locales.php'; require_once __DIR__ . '/init/registers.php'; +require_once __DIR__ . '/init/models.php'; require_once __DIR__ . '/init/resources.php'; \stream_context_set_default([ // Set global user agent and http settings diff --git a/app/init/models.php b/app/init/models.php new file mode 100644 index 0000000000..fdfa0271b4 --- /dev/null +++ b/app/init/models.php @@ -0,0 +1,344 @@ +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; - } - } - - // found a wildcard, return! - if (\in_array('*', $attributes)) { - return; - } - - $responseModel = $response->getModel($model); - foreach ($responseModel->getRules() as $ruleName => $rule) { - if (\str_starts_with($ruleName, '$')) { - continue; - } - - if (!\in_array($ruleName, $attributes)) { - $responseModel->removeRule($ruleName); - } - } - } } diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/XList.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/XList.php index 2717e99ee0..55711495e9 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/XList.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/XList.php @@ -10,6 +10,7 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Database\Validator\Queries\Deployments; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Filters\ListSelection; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Order as OrderException; @@ -119,7 +120,9 @@ class XList extends Base $cursor->setValue($cursorDocument); } - $filterQueries = Query::groupByType($queries)['filters']; + $grouped = Query::groupByType($queries); + $filterQueries = $grouped['filters']; + $selectQueries = $grouped['selections'] ?? []; try { $results = $dbForProject->find('deployments', $queries); @@ -128,7 +131,8 @@ class XList extends Base throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); } - $this->applySelectQueries($request, $response, Response::MODEL_DEPLOYMENT); + $response->addFilter(new ListSelection($selectQueries, 'deployments')); + $response->dynamic(new Document([ 'deployments' => $results, 'total' => $total, diff --git a/src/Appwrite/Platform/Modules/Projects/Http/Projects/XList.php b/src/Appwrite/Platform/Modules/Projects/Http/Projects/XList.php index 32318dd189..7269582a15 100644 --- a/src/Appwrite/Platform/Modules/Projects/Http/Projects/XList.php +++ b/src/Appwrite/Platform/Modules/Projects/Http/Projects/XList.php @@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Database\Validator\Queries\Projects; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Filters\ListSelection; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -120,7 +121,8 @@ class XList extends Action throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); } - $this->applySelectQueries($request, $response, Response::MODEL_PROJECT); + $response->addFilter(new ListSelection($selectQueries, 'projects')); + $response->dynamic(new Document([ 'projects' => $projects, 'total' => $total, diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/XList.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/XList.php index b7f9386f06..73e5ea4d77 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/XList.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/XList.php @@ -10,6 +10,7 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Database\Validator\Queries\Deployments; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Filters\ListSelection; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Order as OrderException; @@ -119,7 +120,9 @@ class XList extends Base $cursor->setValue($cursorDocument); } - $filterQueries = Query::groupByType($queries)['filters']; + $grouped = Query::groupByType($queries); + $filterQueries = $grouped['filters']; + $selectQueries = $grouped['selections'] ?? []; try { $results = $dbForProject->find('deployments', $queries); @@ -128,7 +131,8 @@ class XList extends Base throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null."); } - $this->applySelectQueries($request, $response, Response::MODEL_DEPLOYMENT); + $response->addFilter(new ListSelection($selectQueries, 'deployments')); + $response->dynamic(new Document([ 'deployments' => $results, 'total' => $total, diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 33351bea14..1dfaa1a41f 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -6,147 +6,8 @@ use Appwrite\Utopia\Database\Documents\User as DBUser; use Appwrite\Utopia\Fetch\BodyMultipart; use Appwrite\Utopia\Response\Filter; use Appwrite\Utopia\Response\Model; -use Appwrite\Utopia\Response\Model\Account; -use Appwrite\Utopia\Response\Model\AlgoArgon2; -use Appwrite\Utopia\Response\Model\AlgoBcrypt; -use Appwrite\Utopia\Response\Model\AlgoMd5; -use Appwrite\Utopia\Response\Model\AlgoPhpass; -use Appwrite\Utopia\Response\Model\AlgoScrypt; -use Appwrite\Utopia\Response\Model\AlgoScryptModified; -use Appwrite\Utopia\Response\Model\AlgoSha; -use Appwrite\Utopia\Response\Model\Any; -use Appwrite\Utopia\Response\Model\Attribute; -use Appwrite\Utopia\Response\Model\AttributeBoolean; -use Appwrite\Utopia\Response\Model\AttributeDatetime; -use Appwrite\Utopia\Response\Model\AttributeEmail; -use Appwrite\Utopia\Response\Model\AttributeEnum; -use Appwrite\Utopia\Response\Model\AttributeFloat; -use Appwrite\Utopia\Response\Model\AttributeInteger; -use Appwrite\Utopia\Response\Model\AttributeIP; -use Appwrite\Utopia\Response\Model\AttributeLine; -use Appwrite\Utopia\Response\Model\AttributeList; -use Appwrite\Utopia\Response\Model\AttributePoint; -use Appwrite\Utopia\Response\Model\AttributePolygon; -use Appwrite\Utopia\Response\Model\AttributeRelationship; -use Appwrite\Utopia\Response\Model\AttributeString; -use Appwrite\Utopia\Response\Model\AttributeURL; -use Appwrite\Utopia\Response\Model\AuthProvider; -use Appwrite\Utopia\Response\Model\BaseList; -use Appwrite\Utopia\Response\Model\Branch; -use Appwrite\Utopia\Response\Model\Bucket; -use Appwrite\Utopia\Response\Model\Collection; -use Appwrite\Utopia\Response\Model\Column; -use Appwrite\Utopia\Response\Model\ColumnBoolean; -use Appwrite\Utopia\Response\Model\ColumnDatetime; -use Appwrite\Utopia\Response\Model\ColumnEmail; -use Appwrite\Utopia\Response\Model\ColumnEnum; -use Appwrite\Utopia\Response\Model\ColumnFloat; -use Appwrite\Utopia\Response\Model\ColumnIndex; -use Appwrite\Utopia\Response\Model\ColumnInteger; -use Appwrite\Utopia\Response\Model\ColumnIP; -use Appwrite\Utopia\Response\Model\ColumnLine; -use Appwrite\Utopia\Response\Model\ColumnList; -use Appwrite\Utopia\Response\Model\ColumnPoint; -use Appwrite\Utopia\Response\Model\ColumnPolygon; -use Appwrite\Utopia\Response\Model\ColumnRelationship; -use Appwrite\Utopia\Response\Model\ColumnString; -use Appwrite\Utopia\Response\Model\ColumnURL; -use Appwrite\Utopia\Response\Model\ConsoleVariables; -use Appwrite\Utopia\Response\Model\Continent; -use Appwrite\Utopia\Response\Model\Country; -use Appwrite\Utopia\Response\Model\Currency; -use Appwrite\Utopia\Response\Model\Database; -use Appwrite\Utopia\Response\Model\Deployment; -use Appwrite\Utopia\Response\Model\DetectionFramework; -use Appwrite\Utopia\Response\Model\DetectionRuntime; -use Appwrite\Utopia\Response\Model\DetectionVariable; -use Appwrite\Utopia\Response\Model\DevKey; -use Appwrite\Utopia\Response\Model\Document as ModelDocument; -use Appwrite\Utopia\Response\Model\Error; -use Appwrite\Utopia\Response\Model\ErrorDev; -use Appwrite\Utopia\Response\Model\Execution; -use Appwrite\Utopia\Response\Model\File; -use Appwrite\Utopia\Response\Model\Framework; -use Appwrite\Utopia\Response\Model\FrameworkAdapter; -use Appwrite\Utopia\Response\Model\Func; -use Appwrite\Utopia\Response\Model\Headers; -use Appwrite\Utopia\Response\Model\HealthAntivirus; -use Appwrite\Utopia\Response\Model\HealthCertificate; -use Appwrite\Utopia\Response\Model\HealthQueue; -use Appwrite\Utopia\Response\Model\HealthStatus; -use Appwrite\Utopia\Response\Model\HealthTime; -use Appwrite\Utopia\Response\Model\HealthVersion; -use Appwrite\Utopia\Response\Model\Identity; -use Appwrite\Utopia\Response\Model\Index; -use Appwrite\Utopia\Response\Model\Installation; -use Appwrite\Utopia\Response\Model\JWT; -use Appwrite\Utopia\Response\Model\Key; -use Appwrite\Utopia\Response\Model\Language; -use Appwrite\Utopia\Response\Model\Locale; -use Appwrite\Utopia\Response\Model\LocaleCode; -use Appwrite\Utopia\Response\Model\Log; -use Appwrite\Utopia\Response\Model\Membership; -use Appwrite\Utopia\Response\Model\Message; -use Appwrite\Utopia\Response\Model\Metric; -use Appwrite\Utopia\Response\Model\MetricBreakdown; -use Appwrite\Utopia\Response\Model\MFAChallenge; -use Appwrite\Utopia\Response\Model\MFAFactors; -use Appwrite\Utopia\Response\Model\MFARecoveryCodes; -use Appwrite\Utopia\Response\Model\MFAType; -use Appwrite\Utopia\Response\Model\Migration; -use Appwrite\Utopia\Response\Model\MigrationFirebaseProject; -use Appwrite\Utopia\Response\Model\MigrationReport; -use Appwrite\Utopia\Response\Model\Mock; -use Appwrite\Utopia\Response\Model\MockNumber; -use Appwrite\Utopia\Response\Model\None; -use Appwrite\Utopia\Response\Model\Phone; -use Appwrite\Utopia\Response\Model\Platform; -use Appwrite\Utopia\Response\Model\Preferences; -use Appwrite\Utopia\Response\Model\Project; -use Appwrite\Utopia\Response\Model\Provider; -use Appwrite\Utopia\Response\Model\ProviderRepository; -use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework; -use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime; -use Appwrite\Utopia\Response\Model\ResourceToken; -use Appwrite\Utopia\Response\Model\Row; -use Appwrite\Utopia\Response\Model\Rule; -use Appwrite\Utopia\Response\Model\Runtime; -use Appwrite\Utopia\Response\Model\Session; -use Appwrite\Utopia\Response\Model\Site; -use Appwrite\Utopia\Response\Model\Specification; -use Appwrite\Utopia\Response\Model\Subscriber; -use Appwrite\Utopia\Response\Model\Table; -use Appwrite\Utopia\Response\Model\Target; -use Appwrite\Utopia\Response\Model\Team; -use Appwrite\Utopia\Response\Model\TemplateEmail; -use Appwrite\Utopia\Response\Model\TemplateFramework; -use Appwrite\Utopia\Response\Model\TemplateFunction; -use Appwrite\Utopia\Response\Model\TemplateRuntime; -use Appwrite\Utopia\Response\Model\TemplateSite; -use Appwrite\Utopia\Response\Model\TemplateSMS; -use Appwrite\Utopia\Response\Model\TemplateVariable; -use Appwrite\Utopia\Response\Model\Token; -use Appwrite\Utopia\Response\Model\Topic; -use Appwrite\Utopia\Response\Model\Transaction; -use Appwrite\Utopia\Response\Model\UsageBuckets; -use Appwrite\Utopia\Response\Model\UsageCollection; -use Appwrite\Utopia\Response\Model\UsageDatabase; -use Appwrite\Utopia\Response\Model\UsageDatabases; -use Appwrite\Utopia\Response\Model\UsageFunction; -use Appwrite\Utopia\Response\Model\UsageFunctions; -use Appwrite\Utopia\Response\Model\UsageProject; -use Appwrite\Utopia\Response\Model\UsageSite; -use Appwrite\Utopia\Response\Model\UsageSites; -use Appwrite\Utopia\Response\Model\UsageStorage; -use Appwrite\Utopia\Response\Model\UsageTable; -use Appwrite\Utopia\Response\Model\UsageUsers; -use Appwrite\Utopia\Response\Model\User; -use Appwrite\Utopia\Response\Model\Variable; -use Appwrite\Utopia\Response\Model\VcsContent; -use Appwrite\Utopia\Response\Model\Webhook; use Exception; use JsonException; -// Keep last use Swoole\Http\Response as SwooleHTTPResponse; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; @@ -418,6 +279,11 @@ class Response extends SwooleResponse */ protected static bool $showSensitive = false; + /** + * @var array + */ + protected static array $models = []; + protected SwooleHTTPResponse $swoole; /** @@ -428,206 +294,6 @@ class Response extends SwooleResponse public function __construct(SwooleHTTPResponse $response) { $this->swoole = $response; - - $this - // General - ->setModel(new None()) - ->setModel(new Any()) - ->setModel(new Error()) - ->setModel(new ErrorDev()) - // Lists - ->setModel(new BaseList('Rows List', self::MODEL_ROW_LIST, 'rows', self::MODEL_ROW)) - ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) - ->setModel(new BaseList('Tables List', self::MODEL_TABLE_LIST, 'tables', self::MODEL_TABLE)) - ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION)) - ->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE)) - ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX)) - ->setModel(new BaseList('Column Indexes List', self::MODEL_COLUMN_INDEX_LIST, 'indexes', self::MODEL_COLUMN_INDEX)) - ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) - ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) - ->setModel(new BaseList('Identities List', self::MODEL_IDENTITY_LIST, 'identities', self::MODEL_IDENTITY)) - ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG)) - ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE)) - ->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET)) - ->setModel(new BaseList('Resource Tokens List', self::MODEL_RESOURCE_TOKEN_LIST, 'tokens', self::MODEL_RESOURCE_TOKEN)) - ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) - ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) - ->setModel(new BaseList('Sites List', self::MODEL_SITE_LIST, 'sites', self::MODEL_SITE)) - ->setModel(new BaseList('Site Templates List', self::MODEL_TEMPLATE_SITE_LIST, 'templates', self::MODEL_TEMPLATE_SITE)) - ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION)) - ->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION)) - ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION)) - ->setModel(new BaseList('Framework Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK)) - ->setModel(new BaseList('Runtime Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_RUNTIME)) - ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH)) - ->setModel(new BaseList('Frameworks List', self::MODEL_FRAMEWORK_LIST, 'frameworks', self::MODEL_FRAMEWORK)) - ->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME)) - ->setModel(new BaseList('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT)) - ->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION)) - ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) - ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) - ->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false)) - ->setModel(new BaseList('Dev Keys List', self::MODEL_DEV_KEY_LIST, 'devKeys', self::MODEL_DEV_KEY, true, false)) - ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false)) - ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false)) - ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) - ->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT)) - ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE)) - ->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY)) - ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE)) - ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false)) - ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE)) - ->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS)) - ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE)) - ->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE)) - ->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER)) - ->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE)) - ->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC)) - ->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER)) - ->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET)) - ->setModel(new BaseList('Transaction List', self::MODEL_TRANSACTION_LIST, 'transactions', self::MODEL_TRANSACTION)) - ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION)) - ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT)) - ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION)) - ->setModel(new BaseList('VCS Content List', self::MODEL_VCS_CONTENT_LIST, 'contents', self::MODEL_VCS_CONTENT)) - // Entities - ->setModel(new Database()) - // Collection API Models - ->setModel(new Collection()) - ->setModel(new Attribute()) - ->setModel(new AttributeList()) - ->setModel(new AttributeString()) - ->setModel(new AttributeInteger()) - ->setModel(new AttributeFloat()) - ->setModel(new AttributeBoolean()) - ->setModel(new AttributeEmail()) - ->setModel(new AttributeEnum()) - ->setModel(new AttributeIP()) - ->setModel(new AttributeURL()) - ->setModel(new AttributeDatetime()) - ->setModel(new AttributeRelationship()) - ->setModel(new AttributePoint()) - ->setModel(new AttributeLine()) - ->setModel(new AttributePolygon()) - // Table API Models - ->setModel(new Table()) - ->setModel(new Column()) - ->setModel(new ColumnList()) - ->setModel(new ColumnString()) - ->setModel(new ColumnInteger()) - ->setModel(new ColumnFloat()) - ->setModel(new ColumnBoolean()) - ->setModel(new ColumnEmail()) - ->setModel(new ColumnEnum()) - ->setModel(new ColumnIP()) - ->setModel(new ColumnURL()) - ->setModel(new ColumnDatetime()) - ->setModel(new ColumnRelationship()) - ->setModel(new ColumnPoint()) - ->setModel(new ColumnLine()) - ->setModel(new ColumnPolygon()) - ->setModel(new Index()) - ->setModel(new ColumnIndex()) - ->setModel(new Row()) - ->setModel(new ModelDocument()) - ->setModel(new Log()) - ->setModel(new User()) - ->setModel(new AlgoMd5()) - ->setModel(new AlgoSha()) - ->setModel(new AlgoPhpass()) - ->setModel(new AlgoBcrypt()) - ->setModel(new AlgoScrypt()) - ->setModel(new AlgoScryptModified()) - ->setModel(new AlgoArgon2()) - ->setModel(new Account()) - ->setModel(new Preferences()) - ->setModel(new Session()) - ->setModel(new Identity()) - ->setModel(new Token()) - ->setModel(new JWT()) - ->setModel(new Locale()) - ->setModel(new LocaleCode()) - ->setModel(new File()) - ->setModel(new Bucket()) - ->setModel(new ResourceToken()) - ->setModel(new Team()) - ->setModel(new Membership()) - ->setModel(new Site()) - ->setModel(new TemplateSite()) - ->setModel(new TemplateFramework()) - ->setModel(new Func()) - ->setModel(new TemplateFunction()) - ->setModel(new TemplateRuntime()) - ->setModel(new TemplateVariable()) - ->setModel(new Installation()) - ->setModel(new ProviderRepository()) - ->setModel(new ProviderRepositoryFramework()) - ->setModel(new ProviderRepositoryRuntime()) - ->setModel(new DetectionFramework()) - ->setModel(new DetectionRuntime()) - ->setModel(new DetectionVariable()) - ->setModel(new VcsContent()) - ->setModel(new Branch()) - ->setModel(new Runtime()) - ->setModel(new Framework()) - ->setModel(new FrameworkAdapter()) - ->setModel(new Deployment()) - ->setModel(new Execution()) - ->setModel(new Project()) - ->setModel(new Webhook()) - ->setModel(new Key()) - ->setModel(new DevKey()) - ->setModel(new MockNumber()) - ->setModel(new AuthProvider()) - ->setModel(new Platform()) - ->setModel(new Variable()) - ->setModel(new Country()) - ->setModel(new Continent()) - ->setModel(new Language()) - ->setModel(new Currency()) - ->setModel(new Phone()) - ->setModel(new HealthAntivirus()) - ->setModel(new HealthQueue()) - ->setModel(new HealthStatus()) - ->setModel(new HealthCertificate()) - ->setModel(new HealthTime()) - ->setModel(new HealthVersion()) - ->setModel(new Metric()) - ->setModel(new MetricBreakdown()) - ->setModel(new UsageDatabases()) - ->setModel(new UsageDatabase()) - ->setModel(new UsageTable()) - ->setModel(new UsageCollection()) - ->setModel(new UsageUsers()) - ->setModel(new UsageStorage()) - ->setModel(new UsageBuckets()) - ->setModel(new UsageFunctions()) - ->setModel(new UsageFunction()) - ->setModel(new UsageSites()) - ->setModel(new UsageSite()) - ->setModel(new UsageProject()) - ->setModel(new Headers()) - ->setModel(new Specification()) - ->setModel(new Rule()) - ->setModel(new TemplateSMS()) - ->setModel(new TemplateEmail()) - ->setModel(new ConsoleVariables()) - ->setModel(new MFAChallenge()) - ->setModel(new MFARecoveryCodes()) - ->setModel(new MFAType()) - ->setModel(new MFAFactors()) - ->setModel(new Provider()) - ->setModel(new Message()) - ->setModel(new Topic()) - ->setModel(new Transaction()) - ->setModel(new Subscriber()) - ->setModel(new Target()) - ->setModel(new Migration()) - ->setModel(new MigrationReport()) - ->setModel(new MigrationFirebaseProject()) - // Tests (keep last) - ->setModel(new Mock()); - parent::__construct($response); } @@ -639,20 +305,14 @@ class Response extends SwooleResponse public const CONTENT_TYPE_MULTIPART = 'multipart/form-data'; /** - * List of defined output objects - */ - protected $models = []; - - /** - * Set Model Object + * Register a model * - * @return self + * @param Model $model + * @return void */ - public function setModel(Model $instance): Response + public static function setModel(Model $model): void { - $this->models[$instance->getType()] = $instance; - - return $this; + self::$models[$model->getType()] = $model; } /** @@ -664,11 +324,11 @@ class Response extends SwooleResponse */ public function getModel(string $key): Model { - if (!isset($this->models[$key])) { + if (!isset(self::$models[$key])) { throw new Exception('Undefined model: ' . $key); } - return $this->models[$key]; + return self::$models[$key]; } /** @@ -678,7 +338,18 @@ class Response extends SwooleResponse */ public function getModels(): array { - return $this->models; + return self::$models; + } + + /** + * Check if a model exists + * + * @param string $key + * @return bool + */ + public static function hasModel(string $key): bool + { + return isset(self::$models[$key]); } public function applyFilters(array $data, string $model): array @@ -774,7 +445,7 @@ class Response extends SwooleResponse } if ($rule['array']) { - if (!is_array($data[$key])) { + if (!\is_array($data[$key])) { throw new Exception($key . ' must be an array of type ' . $rule['type']); } @@ -798,7 +469,7 @@ class Response extends SwooleResponse $ruleType = $rule['type']; } - if (!array_key_exists($ruleType, $this->models)) { + if (!self::hasModel($ruleType)) { throw new Exception('Missing model for rule: ' . $ruleType); } diff --git a/src/Appwrite/Utopia/Response/Filters/ListSelection.php b/src/Appwrite/Utopia/Response/Filters/ListSelection.php new file mode 100644 index 0000000000..53c5ae75cf --- /dev/null +++ b/src/Appwrite/Utopia/Response/Filters/ListSelection.php @@ -0,0 +1,41 @@ +selectQueries)) { + return $content; + } + + $selections = []; + foreach ($this->selectQueries as $query) { + foreach ($query->getValues() as $value) { + if ($value === '*') { + return $content; + } + $selections[$value] = true; + } + } + + return $this->handleList($content, $this->itemsKey, function (array $item) use ($selections) { + $filtered = []; + foreach ($item as $key => $value) { + if (isset($selections[$key]) || \str_starts_with($key, '$')) { + $filtered[$key] = $value; + } + } + return $filtered; + }); + } +} diff --git a/src/Appwrite/Utopia/Response/Model.php b/src/Appwrite/Utopia/Response/Model.php index 59c786ee1f..687b8b3eba 100644 --- a/src/Appwrite/Utopia/Response/Model.php +++ b/src/Appwrite/Utopia/Response/Model.php @@ -101,22 +101,6 @@ abstract class Model return $this; } - /** - * Delete an existing Rule - * If rule exists, it will be removed - * - * @param string $key - * @return Model - */ - public function removeRule(string $key): self - { - if (isset($this->rules[$key])) { - unset($this->rules[$key]); - } - - return $this; - } - /** * @return array */ diff --git a/src/Appwrite/Utopia/Response/Model/Account.php b/src/Appwrite/Utopia/Response/Model/Account.php index 07fd4e92ab..2ccbd2e480 100644 --- a/src/Appwrite/Utopia/Response/Model/Account.php +++ b/src/Appwrite/Utopia/Response/Model/Account.php @@ -3,18 +3,131 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Model; +use Utopia\Database\Document; -class Account extends User +class Account extends Model { public function __construct() { - parent::__construct(); - $this - ->removeRule('password') - ->removeRule('hash') - ->removeRule('mfaRecoveryCodes') - ->removeRule('hashOptions'); + ->addRule('$id', [ + 'type' => self::TYPE_STRING, + 'description' => 'User ID.', + 'default' => '', + 'example' => '5e5ea5c16897e', + ]) + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'User creation date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'User update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'User name.', + 'default' => '', + 'example' => 'John Doe', + ]) + ->addRule('registration', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'User registration date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('status', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'User status. Pass `true` for enabled and `false` for disabled.', + 'default' => true, + 'example' => true, + ]) + ->addRule('labels', [ + 'type' => self::TYPE_STRING, + 'description' => 'Labels for the user.', + 'default' => [], + 'example' => ['vip'], + 'array' => true, + ]) + ->addRule('passwordUpdate', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Password update time in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('email', [ + 'type' => self::TYPE_STRING, + 'description' => 'User email address.', + 'default' => '', + 'example' => 'john@appwrite.io', + ]) + ->addRule('phone', [ + 'type' => self::TYPE_STRING, + 'description' => 'User phone number in E.164 format.', + 'default' => '', + 'example' => '+4930901820', + ]) + ->addRule('emailVerification', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Email verification status.', + 'default' => false, + 'example' => true, + ]) + ->addRule('phoneVerification', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Phone verification status.', + 'default' => false, + 'example' => true, + ]) + ->addRule('mfa', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Multi factor authentication status.', + 'default' => false, + 'example' => true, + ]) + ->addRule('prefs', [ + 'type' => Response::MODEL_PREFERENCES, + 'description' => 'User preferences as a key-value object', + 'default' => new \stdClass(), + 'example' => ['theme' => 'pink', 'timezone' => 'UTC'], + ]) + ->addRule('targets', [ + 'type' => Response::MODEL_TARGET, + 'description' => 'A user-owned message receiver. A single user may have multiple e.g. emails, phones, and a browser. Each target is registered with a single provider.', + 'default' => [], + 'array' => true, + 'example' => [], + ]) + ->addRule('accessedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_USER_ACCESS / 60 / 60 . ' hours.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ; + } + + /** + * Get Collection + * + * @return Document + */ + public function filter(Document $document): Document + { + $prefs = $document->getAttribute('prefs'); + if ($prefs instanceof Document) { + $prefs = $prefs->getArrayCopy(); + } + + if (is_array($prefs) && empty($prefs)) { + $document->setAttribute('prefs', new \stdClass()); + } + return $document; } /** diff --git a/src/Appwrite/Utopia/Response/Model/UsageSites.php b/src/Appwrite/Utopia/Response/Model/UsageSites.php index 74435b332c..fea87c7718 100644 --- a/src/Appwrite/Utopia/Response/Model/UsageSites.php +++ b/src/Appwrite/Utopia/Response/Model/UsageSites.php @@ -3,15 +3,19 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Model; -class UsageSites extends UsageFunctions +class UsageSites extends Model { public function __construct() { - parent::__construct(); $this - ->removeRule('functionsTotal') - ->removeRule('functions') + ->addRule('range', [ + 'type' => self::TYPE_STRING, + 'description' => 'Time range of the usage stats.', + 'default' => '', + 'example' => '30d', + ]) ->addRule('sitesTotal', [ 'type' => self::TYPE_INTEGER, 'description' => 'Total aggregated number of sites.', @@ -25,6 +29,60 @@ class UsageSites extends UsageFunctions 'example' => [], 'array' => true ]) + ->addRule('deploymentsTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated number of sites deployments.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('deploymentsStorageTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated sum of sites deployment storage.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('buildsTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated number of sites build.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('buildsStorageTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'total aggregated sum of sites build storage.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('buildsTimeTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated sum of sites build compute time.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('buildsMbSecondsTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated sum of sites build mbSeconds.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('executionsTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated number of sites execution.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('executionsTimeTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated sum of sites execution compute time.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('executionsMbSecondsTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated sum of sites execution mbSeconds.', + 'default' => 0, + 'example' => 0, + ]) ->addRule('requestsTotal', [ 'type' => self::TYPE_INTEGER, 'description' => 'Total aggregated number of requests.', @@ -64,6 +122,95 @@ class UsageSites extends UsageFunctions 'example' => [], 'array' => true ]) + ->addRule('deployments', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated number of sites deployment per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('deploymentsStorage', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated number of sites deployment storage per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('buildsSuccessTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated number of successful site builds.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('buildsFailedTotal', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total aggregated number of failed site builds.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('builds', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated number of sites build per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('buildsStorage', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated sum of sites build storage per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('buildsTime', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated sum of sites build compute time per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('buildsMbSeconds', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated sum of sites build mbSeconds per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('executions', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated number of sites execution per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('executionsTime', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated number of sites execution compute time per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('executionsMbSeconds', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated number of sites mbSeconds per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('buildsSuccess', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated number of successful site builds per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('buildsFailed', [ + 'type' => Response::MODEL_METRIC, + 'description' => 'Aggregated number of failed site builds per period.', + 'default' => [], + 'example' => [], + 'array' => true + ]) ; }