Merge branch '1.8.x' into ser-331

This commit is contained in:
Hemachandar
2025-12-23 16:37:22 +05:30
committed by GitHub
47 changed files with 2184 additions and 1301 deletions
+3 -1
View File
@@ -130,7 +130,9 @@ class Mock extends OAuth2
*/
public function isEmailVerified(string $accessToken): bool
{
return true;
$user = $this->getUser($accessToken);
return $user['verified'] ?? true;
}
/**
@@ -0,0 +1,30 @@
<?php
namespace Appwrite\Auth\OAuth2;
class MockUnverified extends Mock
{
/**
* @return string
*/
public function getName(): string
{
return 'mock-unverified';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request('GET', 'http://localhost/' . $this->version . '/mock/tests/general/oauth2/user-unverified?token=' . \urlencode($accessToken));
$this->user = \json_decode($user, true);
}
return $this->user;
}
}
+9 -2
View File
@@ -107,9 +107,11 @@ class Action extends UtopiaAction
}
}
public function disableSubqueries()
public function disableSubqueries(array $filters = []): void
{
$filters = $this->filters;
if (empty($filters)) {
$filters = $this->filters;
}
foreach ($filters as $filter) {
Database::addFilter(
@@ -189,6 +191,11 @@ class Action extends UtopiaAction
}
}
// found a wildcard, return!
if (\in_array('*', $attributes)) {
return;
}
$responseModel = $response->getModel($model);
foreach ($responseModel->getRules() as $ruleName => $rule) {
if (\str_starts_with($ruleName, '$')) {
@@ -80,7 +80,7 @@ class Create extends Action
->param('permissions', null, new Nullable(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)
->param('attributes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.', true)
->param('attributes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.', true)
->param('indexes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional).', true)
->inject('response')
->inject('dbForProject')
@@ -34,7 +34,7 @@ class Create extends Action
->setHttpPath('/v1/projects/:projectId/dev-keys')
->desc('Create dev key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('scope', 'devKeys.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'devKeys',
@@ -28,7 +28,7 @@ class Delete extends Action
->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId')
->desc('Delete dev key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('scope', 'devKeys.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'devKeys',
@@ -28,7 +28,7 @@ class Get extends Action
->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId')
->desc('Get dev key')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('scope', 'devKeys.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'devKeys',
@@ -29,7 +29,7 @@ class Update extends Action
->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId')
->desc('Update dev key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('scope', 'devKeys.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'devKeys',
@@ -32,7 +32,7 @@ class XList extends Action
->setHttpPath('/v1/projects/:projectId/dev-keys')
->desc('List dev keys')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('scope', 'devKeys.read')
->label('sdk', new Method(
namespace: 'projects',
group: 'devKeys',
@@ -3,19 +3,21 @@
namespace Appwrite\Platform\Modules\Projects\Http\Projects;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator;
use Utopia\Validator\Boolean;
@@ -24,6 +26,10 @@ use Utopia\Validator\Text;
class XList extends Action
{
use HTTP;
// cached mapping of columns to their subQuery filters
private static ?array $attributeToSubQueryFilters = null;
public static function getName()
{
return 'listProjects';
@@ -61,12 +67,13 @@ class XList extends Action
->param('queries', [], $this->getQueriesValidator(), '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(', ', Projects::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('request')
->inject('response')
->inject('dbForPlatform')
->callback($this->action(...));
}
public function action(array $queries, string $search, bool $includeTotal, Response $response, Database $dbForPlatform)
public function action(array $queries, string $search, bool $includeTotal, Request $request, Response $response, Database $dbForPlatform)
{
try {
$queries = Query::parseQueries($queries);
@@ -103,16 +110,89 @@ class XList extends Action
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$projects = $dbForPlatform->find('projects', $queries);
$selectQueries = Query::groupByType($queries)['selections'] ?? [];
$filterQueries = Query::groupByType($queries)['filters'];
$projects = $this->find($dbForPlatform, $queries, $selectQueries);
$total = $includeTotal ? $dbForPlatform->count('projects', $filterQueries, APP_LIMIT_COUNT) : 0;
} catch (Order $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$this->applySelectQueries($request, $response, Response::MODEL_PROJECT);
$response->dynamic(new Document([
'projects' => $projects,
'total' => $total,
]), Response::MODEL_PROJECT_LIST);
}
// Build mapping of columns to their subQuery filters
private static function getAttributeToSubQueryFilters(): array
{
if (self::$attributeToSubQueryFilters !== null) {
return self::$attributeToSubQueryFilters;
}
self::$attributeToSubQueryFilters = [];
$collections = Config::getParam('collections', []);
$projectAttributes = $collections['platform']['projects']['attributes'] ?? [];
foreach ($projectAttributes as $attribute) {
$attributeId = $attribute['$id'] ?? null;
$filters = $attribute['filters'] ?? [];
if ($attributeId === null || empty($filters)) {
continue;
}
// extract only subQuery filters
$subQueryFilters = \array_filter($filters, function ($filter) {
return \str_starts_with($filter, 'subQuery');
});
if (!empty($subQueryFilters)) {
self::$attributeToSubQueryFilters[$attributeId] = \array_values($subQueryFilters);
}
}
return self::$attributeToSubQueryFilters;
}
private function find(Database $dbForPlatform, array $queries, array $selectQueries): array
{
if (empty($selectQueries)) {
return $dbForPlatform->find('projects', $queries);
}
$selectedAttributes = [];
foreach ($selectQueries as $query) {
foreach ($query->getValues() as $value) {
$selectedAttributes[] = $value;
}
}
if (\in_array('*', $selectedAttributes)) {
return $dbForPlatform->find('projects', $queries);
}
$filtersToSkipMap = [];
$selectedAttributesMap = \array_flip($selectedAttributes);
$attributeToSubQueryFilters = self::getAttributeToSubQueryFilters();
foreach ($attributeToSubQueryFilters as $attributeName => $subQueryFilters) {
if (!isset($selectedAttributesMap[$attributeName])) {
foreach ($subQueryFilters as $filter) {
$filtersToSkipMap[$filter] = true;
}
}
}
$filtersToSkip = \array_keys($filtersToSkipMap);
return empty($filtersToSkip)
? $dbForPlatform->find('projects', $queries)
: $dbForPlatform->skipFilters(fn () => $dbForPlatform->find('projects', $queries), $filtersToSkip);
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ class SDKs extends Action
public function action(?string $selectedPlatform, ?string $selectedSDK, ?string $version, ?string $git, ?string $production, ?string $message, ?string $release, ?string $commit, ?string $sdks): void
{
if (!$sdks) {
$selectedPlatform ??= Console::confirm('Choose Platform ("' . APP_SDK_PLATFORM_CLIENT . '", "' . APP_SDK_PLATFORM_SERVER . '", "' . APP_SDK_PLATFORM_CONSOLE . '" or "*" for all):');
$selectedPlatform ??= Console::confirm('Choose Platform ("' . implode('", "', Specs::getPlatforms()) . '" or "*" for all):');
$selectedSDK ??= \strtolower(Console::confirm('Choose SDK ("*" for all):'));
} else {
$sdks = explode(',', $sdks);
+2 -2
View File
@@ -66,7 +66,7 @@ class Specs extends Action
*
* @return array<string>
*/
protected function getPlatforms(): array
public static function getPlatforms(): array
{
return [
APP_SDK_PLATFORM_CLIENT,
@@ -239,7 +239,7 @@ class Specs extends Action
App::setResource('dbForPlatform', fn () => new Database(new MySQL(''), new Cache(new None())));
App::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
$platforms = $this->getPlatforms();
$platforms = self::getPlatforms();
$authCounts = $this->getAuthCounts();
$keys = $this->getKeys();
@@ -17,4 +17,9 @@ class Projects extends Base
{
parent::__construct('projects', self::ALLOWED_ATTRIBUTES);
}
public function isSelectQueryAllowed(): bool
{
return true;
}
}
+36 -6
View File
@@ -341,6 +341,20 @@ class Project extends Model
*/
public function filter(Document $document): Document
{
$this->expandSmtpFields($document);
$this->expandServiceFields($document);
$this->expandAuthFields($document);
$this->expandOAuthProviders($document);
return $document;
}
private function expandSmtpFields(Document $document): void
{
if (!$document->isSet('smtp')) {
return;
}
// SMTP
$smtp = $document->getAttribute('smtp', []);
$document->setAttribute('smtpEnabled', $smtp['enabled'] ?? false);
@@ -352,8 +366,14 @@ class Project extends Model
$document->setAttribute('smtpUsername', $smtp['username'] ?? '');
$document->setAttribute('smtpPassword', $smtp['password'] ?? '');
$document->setAttribute('smtpSecure', $smtp['secure'] ?? '');
}
private function expandServiceFields(Document $document): void
{
if (!$document->isSet('services')) {
return;
}
// Services
$values = $document->getAttribute('services', []);
$services = Config::getParam('services', []);
@@ -365,8 +385,14 @@ class Project extends Model
$value = $values[$key] ?? true;
$document->setAttribute('serviceStatusFor' . ucfirst($key), $value);
}
}
private function expandAuthFields(Document $document): void
{
if (!$document->isSet('auths')) {
return;
}
// Auth
$authValues = $document->getAttribute('auths', []);
$auth = Config::getParam('auth', []);
@@ -383,13 +409,19 @@ class Project extends Model
$document->setAttribute('authMembershipsMfa', $authValues['membershipsMfa'] ?? true);
$document->setAttribute('authInvalidateSessions', $authValues['invalidateSessions'] ?? false);
foreach ($auth as $index => $method) {
foreach ($auth as $method) {
$key = $method['key'];
$value = $authValues[$key] ?? true;
$document->setAttribute('auth' . ucfirst($key), $value);
}
}
private function expandOAuthProviders(Document $document): void
{
if (!$document->isSet('oAuthProviders')) {
return;
}
// OAuth Providers
$providers = Config::getParam('oAuthProviders', []);
$providerValues = $document->getAttribute('oAuthProviders', []);
$projectProviders = [];
@@ -410,7 +442,5 @@ class Project extends Model
}
$document->setAttribute('oAuthProviders', $projectProviders);
return $document;
}
}