Merge branch '1.6.x' into databases-reads-writes

This commit is contained in:
Darshan
2025-01-23 18:54:28 +05:30
137 changed files with 6376 additions and 8028 deletions
+7
View File
@@ -43,6 +43,13 @@ class Auth
public const USER_ROLE_APPS = 'apps';
public const USER_ROLE_SYSTEM = 'system';
/**
* Activity associated with user or the app.
*/
public const ACTIVITY_TYPE_APP = 'app';
public const ACTIVITY_TYPE_USER = 'user';
public const ACTIVITY_TYPE_GUEST = 'guest';
/**
* Token Types.
*/
+2 -2
View File
@@ -43,7 +43,7 @@ class Amazon extends OAuth2
*/
public function parseState(string $state)
{
return \json_decode(\html_entity_decode($state), true);
return \json_decode(\urldecode(\html_entity_decode($state)), true);
}
@@ -56,7 +56,7 @@ class Amazon extends OAuth2
'response_type' => 'code',
'client_id' => $this->appID,
'scope' => \implode(' ', $this->getScopes()),
'state' => \json_encode($this->state),
'state' => \urlencode(\json_encode($this->state)),
'redirect_uri' => $this->callback
]);
}
+10
View File
@@ -2,6 +2,8 @@
namespace Appwrite\Auth\Validator;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use Utopia\Validator;
/**
@@ -12,10 +14,12 @@ use Utopia\Validator;
class Phone extends Validator
{
protected bool $allowEmpty;
protected PhoneNumberUtil $helper;
public function __construct(bool $allowEmpty = false)
{
$this->allowEmpty = $allowEmpty;
$this->helper = PhoneNumberUtil::getInstance();
}
/**
@@ -47,6 +51,12 @@ class Phone extends Validator
return true;
}
try {
$this->helper->parse($value);
} catch (NumberParseException $e) {
return false;
}
return !!\preg_match('/^\+[1-9]\d{6,14}$/', $value);
}
+26 -17
View File
@@ -98,27 +98,36 @@ class Schema
foreach ($routes as $route) {
/** @var Route $route */
$namespace = $route->getLabel('sdk.namespace', '');
$method = $route->getLabel('sdk.method', '');
$name = $namespace . \ucfirst($method);
/** @var \Appwrite\SDK\Method $sdk */
$sdk = $route->getLabel('sdk', false);
if (empty($name)) {
if (empty($sdk)) {
continue;
}
foreach (Mapper::route($utopia, $route, $complexity) as $field) {
switch ($route->getMethod()) {
case 'GET':
$queries[$name] = $field;
break;
case 'POST':
case 'PUT':
case 'PATCH':
case 'DELETE':
$mutations[$name] = $field;
break;
default:
throw new \Exception("Unsupported method: {$route->getMethod()}");
if (!\is_array($sdk)) {
$sdk = [$sdk];
}
foreach ($sdk as $method) {
$namespace = $method->getNamespace();
$methodName = $method->getMethodName();
$name = $namespace . \ucfirst($methodName);
foreach (Mapper::route($utopia, $route, $method, $complexity) as $field) {
switch ($route->getMethod()) {
case 'GET':
$queries[$name] = $field;
break;
case 'POST':
case 'PUT':
case 'PATCH':
case 'DELETE':
$mutations[$name] = $field;
break;
default:
throw new \Exception("Unsupported method: {$route->getMethod()}");
}
}
}
}
+36 -5
View File
@@ -4,6 +4,7 @@ namespace Appwrite\GraphQL\Types;
use Appwrite\GraphQL\Resolvers;
use Appwrite\GraphQL\Types;
use Appwrite\SDK\Method;
use Exception;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
@@ -78,6 +79,7 @@ class Mapper
public static function route(
App $utopia,
Route $route,
Method $method,
callable $complexity
): iterable {
foreach (self::$blacklist as $blacklist) {
@@ -86,10 +88,27 @@ class Mapper
}
}
$names = $route->getLabel('sdk.response.model', 'none');
$models = \is_array($names)
? \array_map(static fn ($m) => static::$models[$m], $names)
: [static::$models[$names]];
$responses = $method->getResponses() ?? [];
// If responses is an array, map each response to its model
if (\is_array($responses)) {
$models = [];
foreach ($responses as $response) {
$modelName = $response->getModel();
if (\is_array($modelName)) {
foreach ($modelName as $name) {
$models[] = static::$models[$name];
}
} else {
$models[] = static::$models[$modelName];
}
}
} else {
// If single response, get its model and wrap in array
$modelName = $responses->getModel();
$models = [static::$models[$modelName]];
}
foreach ($models as $model) {
$type = Mapper::model(\ucfirst($model->getType()));
@@ -98,13 +117,25 @@ class Mapper
$list = false;
foreach ($route->getParams() as $name => $parameter) {
$methodParameters = $method->getParameters();
if (!empty($methodParameters)) {
if (!array_key_exists($name, $methodParameters)) {
continue;
}
$optional = $methodParameters[$name]['optional'];
} else {
$optional = $parameter['optional'];
}
if ($name === 'queries') {
$list = true;
}
$parameterType = Mapper::param(
$utopia,
$parameter['validator'],
!$parameter['optional'],
!$optional,
$parameter['injections']
);
$params[$name] = [
+52 -38
View File
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\SDK\AuthType;
use Appwrite\Specification\Format\OpenAPI3;
use Appwrite\Specification\Format\Swagger2;
use Appwrite\Specification\Specification;
@@ -182,56 +183,69 @@ class Specs extends Action
foreach ($appRoutes as $key => $method) {
foreach ($method as $route) {
$hide = $route->getLabel('sdk.hide', false);
if ($hide === true || (\is_array($hide) && \in_array($platform, $hide))) {
$sdks = $route->getLabel('sdk', false);
if (empty($sdks)) {
continue;
}
/** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatforms = [];
if (!\is_array($sdks)) {
$sdks = [$sdks];
}
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_JWT:
case APP_AUTH_TYPE_KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
break;
foreach ($sdks as $sdk) {
/** @var \Appwrite\SDK\Method $sdks */
$hide = $sdk->isHidden();
if ($hide === true || (\is_array($hide) && \in_array($platform, $hide))) {
continue;
}
}
if (empty($routeSecurity)) {
$sdkPlatforms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
}
$routeSecurity = $sdk->getAuth();
$sdkPlatforms = [];
if (!$route->getLabel('docs', true)) {
continue;
}
foreach ($routeSecurity as $value) {
switch ($value) {
case AuthType::SESSION:
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case AuthType::JWT:
case AuthType::KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case AuthType::ADMIN:
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if ($route->getLabel('sdk.mock', false) && !$mocks) {
continue;
}
if (empty($routeSecurity)) {
$sdkPlatforms[] = APP_PLATFORM_SERVER;
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('sdk.mock', false) && $mocks) {
continue;
}
if (!$route->getLabel('docs', true)) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($route->getLabel('mock', false) && !$mocks) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatforms)) {
continue;
}
if (!$route->getLabel('mock', false) && $mocks) {
continue;
}
$routes[] = $route;
if (empty($sdk->getNamespace())) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatforms)) {
continue;
}
$routes[] = $route;
}
}
}
+3
View File
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Workers;
use Appwrite\Auth\Auth;
use Exception;
use Throwable;
use Utopia\Audit\Audit;
@@ -60,6 +61,7 @@ class Audits extends Action
$userName = $user->getAttribute('name', '');
$userEmail = $user->getAttribute('email', '');
$userType = $user->getAttribute('type', Auth::ACTIVITY_TYPE_USER);
$audit = new Audit($dbForProject);
$audit->log(
@@ -74,6 +76,7 @@ class Audits extends Action
'userId' => $user->getId(),
'userName' => $userName,
'userEmail' => $userEmail,
'userType' => $userType,
'mode' => $mode,
'data' => $auditPayload,
]
@@ -147,7 +147,8 @@ class Certificates extends Action
// If certificate exists already, double-check expiry date. Skip if job is forced
if (!$certificates->isRenewRequired($domain->get(), $log)) {
throw new Exception('Renew isn\'t required.');
Console::info("Skipping, renew isn't required");
return;
}
}
+2 -2
View File
@@ -535,6 +535,8 @@ class Databases extends Action
$databaseId = $database->getId();
$databaseInternalId = $database->getInternalId();
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
/**
* Related collections relating to current collection
*/
@@ -553,8 +555,6 @@ class Databases extends Action
}
);
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
$this->deleteByGroup('attributes', [
Query::equal('databaseInternalId', [$databaseInternalId]),
Query::equal('collectionInternalId', [$collectionInternalId])
+6 -10
View File
@@ -30,16 +30,13 @@ class Usage extends Action
*/
public function __construct()
{
$this
->desc('Usage worker')
->inject('message')
->inject('project')
->inject('getProjectDB')
->inject('queueForUsageDump')
->callback(function (Message $message, Document $project, callable $getProjectDB, UsageDump $queueForUsageDump) {
$this->action($message, $project, $getProjectDB, $queueForUsageDump);
});
->desc('Usage worker')
->inject('message')
->inject('project')
->inject('getProjectDB')
->inject('queueForUsageDump')
->callback([$this, 'action']);
$this->aggregationInterval = (int) System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '20');
$this->lastTriggeredTime = time();
@@ -61,7 +58,6 @@ class Usage extends Action
throw new Exception('Missing payload');
}
if (empty($project->getAttribute('database'))) {
var_dump($payload);
return;
+195 -180
View File
@@ -7,7 +7,7 @@ use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\NotFound;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\System\System;
@@ -38,9 +38,7 @@ class UsageDump extends Action
$this
->inject('message')
->inject('getProjectDB')
->callback(function (Message $message, callable $getProjectDB) {
$this->action($message, $getProjectDB);
});
->callback([$this, 'action']);
}
/**
@@ -57,230 +55,247 @@ class UsageDump extends Action
throw new Exception('Missing payload');
}
try {
foreach ($payload['stats'] ?? [] as $stats) {
$project = new Document($stats['project'] ?? []);
$numberOfKeys = !empty($stats['keys']) ? \count($stats['keys']) : 0;
$receivedAt = $stats['receivedAt'] ?? 'NONE';
if ($numberOfKeys === 0) {
continue;
}
foreach ($payload['stats'] ?? [] as $stats) {
$project = new Document($stats['project'] ?? []);
/**
* End temp bug fallback
*/
$numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0;
$receivedAt = $stats['receivedAt'] ?? 'NONE';
if ($numberOfKeys === 0) {
continue;
}
console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
try {
$dbForProject = $getProjectDB($project);
$projectDocuments = [];
$databaseCache = [];
$collectionSizeCache = [];
Console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys . ' Started');
$start = \microtime(true);
foreach ($stats['keys'] ?? [] as $key => $value) {
if ($value == 0) {
continue;
}
if (str_contains($key, METRIC_DATABASES_STORAGE)) {
try {
$this->handleDatabaseStorage($key, $dbForProject);
} catch (\Exception $e) {
console::error('[' . DateTime::now() . '] failed to calculate database storage for key [' . $key . '] ' . $e->getMessage());
}
continue;
}
foreach ($this->periods as $period => $format) {
$time = 'inf' === $period ? null : date($format, time());
$time = 'inf' === $period ? null : \date($format, \time());
$id = \md5("{$time}_{$period}_{$key}");
try {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $key,
'value' => $value,
'region' => System::getEnv('_APP_REGION', 'default'),
]));
} catch (Duplicate $th) {
if ($value < 0) {
$dbForProject->decreaseDocumentAttribute(
'stats',
$id,
'value',
abs($value)
);
} else {
$dbForProject->increaseDocumentAttribute(
'stats',
$id,
'value',
$value
);
}
if (\str_contains($key, METRIC_DATABASES_STORAGE)) {
$this->handleDatabaseStorage(
$id,
$key,
$time,
$period,
$dbForProject,
$projectDocuments,
$databaseCache,
$collectionSizeCache
);
continue;
}
$projectDocuments[] = new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $key,
'value' => $value,
'region' => System::getEnv('_APP_REGION', 'default'),
]);
}
}
} catch (\Exception $e) {
console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
$dbForProject->createOrUpdateDocumentsWithIncrease(
collection: 'stats',
attribute: 'value',
documents: $projectDocuments
);
$end = \microtime(true);
Console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys. ' Time: '.($end - $start).'s');
}
} catch (\Exception $e) {
Console::error('[' . DateTime::now() . '] Error processing stats: ' . $e->getMessage());
}
}
private function handleDatabaseStorage(string $key, Database $dbForProject): void
{
$data = explode('.', $key);
$start = microtime(true);
private function handleDatabaseStorage(
string $id,
string $key,
?string $time,
string $period,
Database $dbForProject,
array &$projectDocuments,
array &$databaseCache,
array &$collectionSizeCache,
): void {
$data = \explode('.', $key);
$value = 0;
$previousValue = 0;
$updateMetric = function (Database $dbForProject, int $value, string $key, string $period, string|null $time) {
$id = \md5("{$time}_{$period}_{$key}");
try {
$previousValue = $dbForProject
->getDocument('stats', $id)
->getAttribute('value', 0);
} catch (\Exception) {
// No previous value
}
try {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $key,
'value' => $value,
'region' => System::getEnv('_APP_REGION', 'default'),
]));
} catch (Duplicate $th) {
if ($value < 0) {
$dbForProject->decreaseDocumentAttribute(
'stats',
$id,
'value',
abs($value)
);
} else {
$dbForProject->increaseDocumentAttribute(
'stats',
$id,
'value',
$value
);
switch (\count($data)) {
case METRIC_COLLECTION_LEVEL_STORAGE:
$databaseInternalId = $data[0];
$collectionInternalId = $data[1];
$collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}";
if (!isset($collectionSizeCache[$collectionId])) {
try {
$collectionSizeCache[$collectionId] = $dbForProject->getSizeOfCollection($collectionId);
} catch (\Exception $e) {
if (!$e instanceof NotFound) {
throw $e;
}
$collectionSizeCache[$collectionId] = 0;
}
}
}
};
foreach ($this->periods as $period => $format) {
$time = 'inf' === $period ? null : date($format, time());
$id = \md5("{$time}_{$period}_{$key}");
$value = $collectionSizeCache[$collectionId];
$value = 0;
$previousValue = 0;
try {
$previousValue = ($dbForProject->getDocument('stats', $id))->getAttribute('value', 0);
} catch (\Exception $e) {
// No previous value
}
switch (count($data)) {
// Collection Level
case METRIC_COLLECTION_LEVEL_STORAGE:
Console::log('[' . DateTime::now() . '] Collection Level Storage Calculation [' . $key . ']');
$databaseInternalId = $data[0];
$collectionInternalId = $data[1];
try {
$value = $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collectionInternalId);
} catch (\Exception $e) {
// Collection not found
if ($e->getMessage() !== 'Collection not found') {
throw $e;
}
}
// Compare with previous value
$diff = $value - $previousValue;
if ($diff === 0) {
break;
}
// Update Collection
$updateMetric($dbForProject, $diff, $key, $period, $time);
// Update Database
$databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
$updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
// Update Project
$projectKey = METRIC_DATABASES_STORAGE;
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
$diff = $value - $previousValue;
if ($diff === 0) {
break;
// Database Level
case METRIC_DATABASE_LEVEL_STORAGE:
Console::log('[' . DateTime::now() . '] Database Level Storage Calculation [' . $key . ']');
$databaseInternalId = $data[0];
}
$collections = [];
$keys = [
$key,
\str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE),
METRIC_DATABASES_STORAGE
];
foreach ($keys as $metric) {
$projectDocuments[] = $this->createStatsDocument($id, $period, $time, $metric, $diff);
}
break;
case METRIC_DATABASE_LEVEL_STORAGE:
$databaseInternalId = $data[0];
$databaseId = "database_{$databaseInternalId}";
if (!isset($databaseCache[$databaseId])) {
try {
$collections = $dbForProject->find('database_' . $databaseInternalId);
$databaseCache[$databaseId] = $dbForProject->find($databaseId);
} catch (\Exception $e) {
// Database not found
if ($e->getMessage() !== 'Collection not found') {
if (!$e instanceof NotFound) {
throw $e;
}
$databaseCache[$databaseId] = [];
}
}
foreach ($collections as $collection) {
foreach ($databaseCache[$databaseId] as $collection) {
$collectionId = "{$databaseId}_collection_{$collection->getInternalId()}";
if (!isset($collectionSizeCache[$collectionId])) {
try {
$value += $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
$collectionSizeCache[$collectionId] = $dbForProject->getSizeOfCollection($collectionId);
} catch (\Exception $e) {
// Collection not found
if ($e->getMessage() !== 'Collection not found') {
if (!$e instanceof NotFound) {
throw $e;
}
$collectionSizeCache[$collectionId] = 0;
}
}
$value += $collectionSizeCache[$collectionId];
}
$diff = $value - $previousValue;
if ($diff === 0) {
break;
}
$keys = [
\str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE),
METRIC_DATABASES_STORAGE
];
foreach ($keys as $metric) {
$projectDocuments[] = $this->createStatsDocument($id, $period, $time, $metric, $diff);
}
break;
case METRIC_PROJECT_LEVEL_STORAGE:
if (!isset($databaseCache['*'])) {
try {
$databaseCache['*'] = $dbForProject->find('databases');
} catch (\Exception $e) {
if (!$e instanceof NotFound) {
throw $e;
}
$databaseCache['*'] = [];
}
}
foreach ($databaseCache['*'] as $database) {
$databaseId = "database_{$database->getInternalId()}";
if (!isset($databaseCache[$databaseId])) {
try {
$databaseCache[$databaseId] = $dbForProject->find($databaseId);
} catch (\Exception $e) {
if (!$e instanceof NotFound) {
throw $e;
}
$databaseCache[$databaseId] = [];
}
}
$diff = $value - $previousValue;
foreach ($databaseCache[$databaseId] as $collection) {
$collectionId = "{$databaseId}_collection_{$collection->getInternalId()}";
if ($diff === 0) {
break;
}
// Update Database
$databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
$updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
// Update Project
$projectKey = METRIC_DATABASES_STORAGE;
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
break;
// Project Level
case METRIC_PROJECT_LEVEL_STORAGE:
Console::log('[' . DateTime::now() . '] Project Level Storage Calculation [' . $key . ']');
// Get all project databases
$databases = $dbForProject->find('database');
// Recalculate all databases
foreach ($databases as $database) {
$collections = $dbForProject->find('database_' . $database->getInternalId());
foreach ($collections as $collection) {
if (!isset($collectionSizeCache[$collectionId])) {
try {
$value += $dbForProject->getSizeOfCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
$collectionSizeCache[$collectionId] = $dbForProject->getSizeOfCollection($collectionId);
} catch (\Exception $e) {
// Collection not found
if ($e->getMessage() !== 'Collection not found') {
if (!$e instanceof NotFound) {
throw $e;
}
$collectionSizeCache[$collectionId] = 0;
}
}
$value += $collectionSizeCache[$collectionId];
}
}
$diff = $value - $previousValue;
// Update Project
$projectKey = METRIC_DATABASES_STORAGE;
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
$diff = $value - $previousValue;
if ($diff === 0) {
break;
}
}
$keys = [
METRIC_DATABASES_STORAGE
];
foreach ($keys as $metric) {
$projectDocuments[] = $this->createStatsDocument($id, $period, $time, $metric, $diff);
}
break;
}
}
$end = microtime(true);
console::log('[' . DateTime::now() . '] DB Storage Calculation [' . $key . '] took ' . (($end - $start) * 1000) . ' milliseconds');
private function createStatsDocument(
string $id,
string $period,
?string $time,
string $key,
int $diff,
): Document {
return new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $key,
'value' => $diff,
'region' => System::getEnv('_APP_REGION', 'default'),
]);
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Appwrite\SDK;
enum AuthType: string
{
case JWT = APP_AUTH_TYPE_JWT;
case KEY = APP_AUTH_TYPE_KEY;
case SESSION = APP_AUTH_TYPE_SESSION;
case ADMIN = APP_AUTH_TYPE_ADMIN;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Appwrite\SDK;
enum ContentType: string
{
case NONE = '';
case JSON = 'application/json';
case IMAGE = 'image/*';
case IMAGE_PNG = 'image/png';
case MULTIPART = 'multipart/form-data';
case HTML = 'text/html';
case TEXT = 'text/plain';
case ANY = '*/*';
}
+286
View File
@@ -0,0 +1,286 @@
<?php
namespace Appwrite\SDK;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Swoole\Http\Response as HttpResponse;
class Method
{
public static array $processed = [];
public static array $errors = [];
/**
* Initialise a new SDK method
*
* @param string $namespace
* @param string $name
* @param string $description
* @param array<AuthType> $auth
* @param array<SDKResponse> $responses
* @param ContentType $responseType
* @param MethodType|null $methodType
* @param bool $deprecated
* @param array|bool $hide
* @param bool $packaging
* @param string $requestType
* @param array $parameters
* @param array $additionalParameters
*
* @throws \Exception
*/
public function __construct(
protected string $namespace,
protected string $name,
protected string $description,
protected array $auth,
protected array $responses,
protected ContentType $contentType = ContentType::JSON,
protected ?MethodType $type = null,
protected bool $deprecated = false,
protected array|bool $hide = false,
protected bool $packaging = false,
protected string $requestType = 'application/json',
protected array $parameters = [],
protected array $additionalParameters = []
) {
$this->validateMethod($name, $namespace);
$this->validateAuthTypes($auth);
$this->validateDesc($description);
foreach ($responses as $response) {
/** @var SDKResponse $response */
$this->validateResponseModel($response->getModel());
$this->validateNoContent($response);
}
}
protected function getRouteName(): string
{
return $this->namespace . '.' . $this->name;
}
protected function validateMethod(string $name, string $namespace): void
{
if (\in_array($this->getRouteName(), self::$processed)) {
self::$errors[] = "Error with {$this->getRouteName()} method: Method already exists in namespace {$namespace}";
}
self::$processed[] = $this->getRouteName();
}
protected function validateAuthTypes(array $authTypes): void
{
foreach ($authTypes as $authType) {
if (!($authType instanceof AuthType)) {
self::$errors[] = "Error with {$this->getRouteName()} method: Invalid auth type";
}
}
}
protected function validateDesc(string $desc): void
{
if (empty($desc)) {
self::$errors[] = "Error with {$this->getRouteName()} method: Description label is empty";
return;
}
$descPath = $this->getDescriptionFilePath();
if (empty($descPath)) {
self::$errors[] = "Error with {$this->getRouteName()} method: Description file not found at {$desc}";
return;
}
}
protected function validateResponseModel(string|array $responseModel): void
{
$response = new Response(new HttpResponse());
if (!\is_array($responseModel)) {
$responseModel = [$responseModel];
}
foreach ($responseModel as $model) {
try {
$response->getModel($model);
} catch (\Exception $e) {
self::$errors[] = "Error with {$this->getRouteName()} method: Invalid response model, make sure the model has been defined in Response.php";
}
}
}
protected function validateNoContent(SDKResponse $response): void
{
if ($response->getCode() === 204) {
if ($response->getModel() !== Response::MODEL_NONE) {
self::$errors[] = "Error with {$this->getRouteName()} method: Response code 204 must have response model 'none'";
}
}
}
public function getNamespace(): string
{
return $this->namespace;
}
public function getMethodName(): string
{
return $this->name;
}
public function getDescription(): string
{
return $this->description;
}
/**
* This method returns the absolute path to the description file returning null if the file does not exist.
*
* @return string|null
*/
public function getDescriptionFilePath(): ?string
{
return \realpath(__DIR__ . '/../../../' . $this->getDescription()) ?: null;
}
public function getAuth(): array
{
return $this->auth;
}
/**
* @return array<SDKResponse>
*/
public function getResponses(): array
{
return $this->responses;
}
public function getContentType(): ContentType
{
return $this->contentType;
}
public function getType(): ?MethodType
{
return $this->type;
}
public function isDeprecated(): bool
{
return $this->deprecated;
}
public function isHidden(): bool|array
{
return $this->hide ?? false;
}
public function isPackaging(): bool
{
return $this->packaging;
}
public function getRequestType(): string
{
return $this->requestType;
}
public function getParameters(): array
{
return $this->parameters;
}
public function getAdditionalParameters(): array
{
return $this->additionalParameters;
}
public function setNamespace(string $namespace): self
{
$this->namespace = $namespace;
return $this;
}
public function setMethodName(string $name): self
{
$this->name = $name;
return $this;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function setAuth(array $auth): self
{
$this->validateAuthTypes($auth);
$this->auth = $auth;
return $this;
}
/**
* @param array<SDKResponse> $responses
*/
public function setResponses(array $responses): self
{
foreach ($responses as $response) {
$this->validateResponseModel($response->getModel());
$this->validateNoContent($response);
}
$this->responses = $responses;
return $this;
}
public function setContentType(ContentType $contentType): self
{
$this->contentType = $contentType;
return $this;
}
public function setType(?MethodType $type): self
{
$this->type = $type;
return $this;
}
public function setDeprecated(bool $deprecated): self
{
$this->deprecated = $deprecated;
return $this;
}
public function setHide(bool|array $hide): self
{
$this->hide = $hide;
return $this;
}
public function setPackaging(bool $packaging): self
{
$this->packaging = $packaging;
return $this;
}
public function setRequestType(string $requestType): self
{
$this->requestType = $requestType;
return $this;
}
public function setParameters(array $parameters): self
{
$this->parameters = $parameters;
return $this;
}
public static function getErrors(): array
{
return self::$errors;
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Appwrite\SDK;
enum MethodType: string
{
case WEBAUTH = 'webAuth';
case LOCATION = 'location';
case GRAPHQL = 'graphql';
case UPLOAD = 'upload';
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Appwrite\SDK;
class Response
{
/**
* @param int $code
* @param string|array $model
* @param string $description
*/
public function __construct(
private int $code,
private string|array $model
) {
}
public function getCode(): int
{
return $this->code;
}
public function getModel(): string|array
{
return $this->model;
}
}
+137 -77
View File
@@ -2,6 +2,8 @@
namespace Appwrite\Specification\Format;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\MethodType;
use Appwrite\Specification\Format;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
@@ -120,28 +122,49 @@ class OpenAPI3 extends Format
foreach ($this->routes as $route) {
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
$sdk = $route->getLabel('sdk', false);
$method = $route->getLabel('sdk.method', \uniqid());
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
$routeSecurity = $route->getLabel('sdk.auth', []);
if (empty($sdk)) {
continue;
}
$additionalMethods = null;
if (is_array($sdk)) {
$mainSdk = array_shift($sdk);
$additionalMethods = $sdk;
$sdk = $mainSdk;
}
/**
* @var \Appwrite\SDK\Method $sdk
*/
$consumes = [$sdk->getRequestType()];
$method = $sdk->getMethodName() ?? \uniqid();
if (!empty($method) && is_array($method)) {
$method = array_keys($method)[0];
}
$desc = $sdk->getDescriptionFilePath();
$produces = ($sdk->getContentType())->value;
$routeSecurity = $sdk->getAuth() ?? [];
$sdkPlatforms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
case AuthType::SESSION:
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
case AuthType::KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
case AuthType::JWT:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
case AuthType::ADMIN:
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
break;
}
@@ -152,102 +175,140 @@ class OpenAPI3 extends Format
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
}
$namespace = $sdk->getNamespace() ?? 'default';
$temp = [
'summary' => $route->getDesc(),
'operationId' => $route->getLabel('sdk.namespace', 'default') . ucfirst($method),
'tags' => [$route->getLabel('sdk.namespace', 'default')],
'operationId' => $namespace . ucfirst($method),
'tags' => [$namespace],
'description' => ($desc) ? \file_get_contents($desc) : '',
'responses' => [],
'x-appwrite' => [ // Appwrite related metadata
'method' => $method,
'weight' => $route->getOrder(),
'cookies' => $route->getLabel('sdk.cookies', false),
'type' => $route->getLabel('sdk.methodType', ''),
'deprecated' => $route->getLabel('sdk.deprecated', false),
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')) . '/' . Template::fromCamelCaseToDash($method) . '.md',
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
'type' => $sdk->getType()->value ?? '',
'deprecated' => $sdk->isDeprecated(),
'demo' => Template::fromCamelCaseToDash($namespace) . '/' . Template::fromCamelCaseToDash($method) . '.md',
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $sdk->getDescription() ?? '',
'rate-limit' => $route->getLabel('abuse-limit', 0),
'rate-time' => $route->getLabel('abuse-time', 3600),
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
'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'),
'packaging' => $sdk->isPackaging()
],
];
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
} else {
if ($value->getType() === $model) {
$model = $value;
break;
if (!empty($additionalMethods)) {
$temp['x-appwrite']['additional-methods'] = [];
foreach ($additionalMethods as $method) {
/** @var \Appwrite\SDK\Method $method */
$additionalMethod = [
'name' => $method->getMethodName(),
'parameters' => [],
'required' => [],
'responses' => []
];
foreach ($method->getParameters() as $name => $param) {
$additionalMethod['parameters'][] = $name;
if (!$param['optional']) {
$additionalMethod['required'][] = $name;
}
}
foreach ($method->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/components/schemas/' . $response->getModel()
];
}
$temp['x-appwrite']['additional-methods'][] = $additionalMethod;
}
}
if (!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => in_array($produces, [
'image/*',
'image/jpeg',
'image/gif',
'image/png',
'image/webp',
'image/svg-x',
'image/x-icon',
'image/bmp',
]) ? 'Image' : 'File',
];
} else {
if (\is_array($model)) {
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
// Handle response models
foreach ($sdk->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$model = $response->getModel();
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
$usedModels[] = $m->getType();
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
} else {
if ($value->getType() === $model) {
$model = $value;
break;
}
}
}
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $modelDescription,
'content' => [
$produces => [
'schema' => [
'oneOf' => \array_map(fn ($m) => ['$ref' => '#/components/schemas/' . $m->getType()], $model)
],
],
],
if (!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$response->getCode() ?? '500'] = [
'description' => in_array($produces, [
'image/*',
'image/jpeg',
'image/gif',
'image/png',
'image/webp',
'image/svg-x',
'image/x-icon',
'image/bmp',
]) ? 'Image' : 'File',
];
} else {
// Response definition using one type
$usedModels[] = $model->getType();
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $model->getName(),
'content' => [
$produces => [
'schema' => [
'$ref' => '#/components/schemas/' . $model->getType(),
if (\is_array($model)) {
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
$usedModels[] = $m->getType();
}
$temp['responses'][(string)$response->getCode() ?? '500'] = [
'description' => $modelDescription,
'content' => [
$produces => [
'schema' => [
'oneOf' => \array_map(fn ($m) => ['$ref' => '#/components/schemas/' . $m->getType()], $model)
],
],
],
],
];
];
} else {
// Response definition using one type
$usedModels[] = $model->getType();
$temp['responses'][(string)$response->getCode() ?? '500'] = [
'description' => $model->getName(),
'content' => [
$produces => [
'schema' => [
'$ref' => '#/components/schemas/' . $model->getType(),
],
],
],
];
}
}
}
if ($route->getLabel('sdk.response.code', 500) === 204) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['description'] = 'No content';
unset($temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['schema']);
if (($response->getCode() ?? 500) === 204) {
$temp['responses'][(string)$response->getCode() ?? '500']['description'] = 'No content';
unset($temp['responses'][(string)$response->getCode() ?? '500']['schema']);
}
}
if ((!empty($scope))) { // && 'public' != $scope
$securities = ['Project' => []];
foreach ($route->getLabel('sdk.auth', []) as $security) {
if (array_key_exists($security, $this->keys)) {
$securities[$security] = [];
foreach ($sdk->getAuth() as $security) {
/** @var \Appwrite\SDK\AuthType $security */
if (array_key_exists($security->value, $this->keys)) {
$securities[$security->value] = [];
}
}
@@ -298,7 +359,7 @@ class OpenAPI3 extends Format
$node['schema']['x-example'] = false;
break;
case 'Appwrite\Utopia\Database\Validator\CustomId':
if ($route->getLabel('sdk.methodType', '') === 'upload') {
if ($sdk->getType() === MethodType::UPLOAD) {
$node['schema']['x-upload-id'] = true;
}
$node['schema']['type'] = $validator->getType();
@@ -323,7 +384,6 @@ class OpenAPI3 extends Format
case 'Utopia\Validator\JSON':
case 'Utopia\Validator\Mock':
case 'Utopia\Validator\Assoc':
case 'Appwrite\Functions\Validator\Payload':
$param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
$node['schema']['type'] = 'object';
$node['schema']['x-example'] = '{}';
@@ -423,7 +483,7 @@ class OpenAPI3 extends Format
$allowed = true;
foreach ($this->enumBlacklist as $blacklist) {
if (
$blacklist['namespace'] == $route->getLabel('sdk.namespace', '')
$blacklist['namespace'] == $sdk->getNamespace()
&& $blacklist['method'] == $method
&& $blacklist['parameter'] == $name
) {
@@ -434,8 +494,8 @@ class OpenAPI3 extends Format
if ($allowed) {
$node['schema']['enum'] = $validator->getList();
$node['schema']['x-enum-name'] = $this->getEnumName($route->getLabel('sdk.namespace', ''), $method, $name);
$node['schema']['x-enum-keys'] = $this->getEnumKeys($route->getLabel('sdk.namespace', ''), $method, $name);
$node['schema']['x-enum-name'] = $this->getEnumName($sdk->getNamespace() ?? '', $method, $name);
$node['schema']['x-enum-keys'] = $this->getEnumKeys($sdk->getNamespace() ?? '', $method, $name);
}
if ($validator->getType() === 'integer') {
$node['format'] = 'int32';
+137 -75
View File
@@ -2,6 +2,8 @@
namespace Appwrite\Specification\Format;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\MethodType;
use Appwrite\Specification\Format;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
@@ -34,6 +36,7 @@ class Swagger2 extends Format
foreach ($this->models as $m) {
if ($m->getType() === $ruleType) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
@@ -45,6 +48,7 @@ class Swagger2 extends Format
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
@@ -116,27 +120,47 @@ class Swagger2 extends Format
/** @var \Utopia\Route $route */
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
$method = $route->getLabel('sdk.method', \uniqid());
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
$routeSecurity = $route->getLabel('sdk.auth', []);
/** @var \Appwrite\SDK\Method $sdk */
$sdk = $route->getLabel('sdk', false);
if (empty($sdk)) {
continue;
}
$additionalMethods = null;
if (is_array($sdk)) {
$mainSdk = array_shift($sdk);
$additionalMethods = $sdk;
$sdk = $mainSdk;
}
$consumes = [$sdk->getRequestType()];
$method = $sdk->getMethodName() ?? \uniqid();
if (!empty($method) && is_array($method)) {
$method = array_keys($method)[0];
}
$desc = $sdk->getDescriptionFilePath();
$produces = ($sdk->getContentType())->value;
$routeSecurity = $sdk->getAuth() ?? [];
$sdkPlatforms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
case AuthType::SESSION:
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
case AuthType::KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
case AuthType::JWT:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
case AuthType::ADMIN:
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
break;
}
@@ -147,31 +171,30 @@ class Swagger2 extends Format
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
}
$namespace = $sdk->getNamespace() ?? 'default';
$temp = [
'summary' => $route->getDesc(),
'operationId' => $route->getLabel('sdk.namespace', 'default') . ucfirst($method),
'operationId' => $namespace . ucfirst($method),
'consumes' => [],
'produces' => [],
'tags' => [$route->getLabel('sdk.namespace', 'default')],
'tags' => [$namespace],
'description' => ($desc) ? \file_get_contents($desc) : '',
'responses' => [],
'x-appwrite' => [ // Appwrite related metadata
'method' => $method,
'weight' => $route->getOrder(),
'cookies' => $route->getLabel('sdk.cookies', false),
'type' => $route->getLabel('sdk.methodType', ''),
'deprecated' => $route->getLabel('sdk.deprecated', false),
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')) . '/' . Template::fromCamelCaseToDash($method) . '.md',
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
'type' => $sdk->getType()->value ?? '',
'deprecated' => $sdk->isDeprecated(),
'demo' => Template::fromCamelCaseToDash($namespace) . '/' . Template::fromCamelCaseToDash($method) . '.md',
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $sdk->getDescription() ?? '',
'rate-limit' => $route->getLabel('abuse-limit', 0),
'rate-time' => $route->getLabel('abuse-time', 3600),
'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
'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'),
'packaging' => $sdk->isPackaging()
],
];
@@ -179,71 +202,111 @@ class Swagger2 extends Format
$temp['produces'][] = $produces;
}
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
} else {
if ($value->getType() === $model) {
$model = $value;
break;
if (!empty($additionalMethods)) {
$temp['x-appwrite']['additional-methods'] = [];
foreach ($additionalMethods as $method) {
/** @var \Appwrite\SDK\Method $method */
$additionalMethod = [
'name' => $method->getMethodName(),
'parameters' => [],
'required' => [],
'responses' => [],
'description' => $method->getDescription(),
];
foreach ($method->getParameters() as $name => $param) {
$additionalMethod['parameters'][] = $name;
if (!$param['optional']) {
$additionalMethod['required'][] = $name;
}
}
foreach ($method->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/definitions/' . $response->getModel()
];
}
$temp['x-appwrite']['additional-methods'][] = $additionalMethod;
}
}
if (!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => in_array($produces, [
'image/*',
'image/jpeg',
'image/gif',
'image/png',
'image/webp',
'image/svg-x',
'image/x-icon',
'image/bmp',
]) ? 'Image' : 'File',
'schema' => [
'type' => 'file'
],
];
} else {
if (\is_array($model)) {
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
$usedModels[] = $m->getType();
// Handle Responses
foreach ($sdk->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$model = $response->getModel();
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
} else {
if ($value->getType() === $model) {
$model = $value;
break;
}
}
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $modelDescription,
}
if (!(\is_array($model)) && $model->isNone()) {
$temp['responses'][(string)$response->getCode() ?? '500'] = [
'description' => in_array($produces, [
'image/*',
'image/jpeg',
'image/gif',
'image/png',
'image/webp',
'image/svg-x',
'image/x-icon',
'image/bmp',
]) ? 'Image' : 'File',
'schema' => [
'x-oneOf' => \array_map(function ($m) {
return ['$ref' => '#/definitions/' . $m->getType()];
}, $model)
'type' => 'file'
],
];
} else {
// Response definition using one type
$usedModels[] = $model->getType();
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $model->getName(),
'schema' => [
'$ref' => '#/definitions/' . $model->getType(),
],
];
if (\is_array($model)) {
$modelDescription = \join(', or ', \array_map(fn ($m) => $m->getName(), $model));
// model has multiple possible responses, we will use oneOf
foreach ($model as $m) {
$usedModels[] = $m->getType();
}
$temp['responses'][(string)$response->getCode() ?? '500'] = [
'description' => $modelDescription,
'schema' => [
'x-oneOf' => \array_map(function ($m) {
return ['$ref' => '#/definitions/' . $m->getType()];
}, $model)
],
];
} else {
// Response definition using one type
$usedModels[] = $model->getType();
$temp['responses'][(string)$response->getCode() ?? '500'] = [
'description' => $model->getName(),
'schema' => [
'$ref' => '#/definitions/' . $model->getType(),
],
];
}
}
}
if (in_array($route->getLabel('sdk.response.code', 500), [204, 301, 302, 308], true)) {
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['description'] = 'No content';
unset($temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['schema']);
if (in_array($response->getCode() ?? 500, [204, 301, 302, 308], true)) {
$temp['responses'][(string)$response->getCode() ?? '500']['description'] = 'No content';
unset($temp['responses'][(string)$response->getCode() ?? '500']['schema']);
}
}
if ((!empty($scope))) { // && 'public' != $scope
$securities = ['Project' => []];
foreach ($route->getLabel('sdk.auth', []) as $security) {
if (array_key_exists($security, $this->keys)) {
$securities[$security] = [];
foreach ($sdk->getAuth() as $security) {
/** @var \Appwrite\SDK\AuthType $security */
if (array_key_exists($security->value, $this->keys)) {
$securities[$security->value] = [];
}
}
@@ -264,7 +327,7 @@ class Swagger2 extends Format
$parameters = \array_merge(
$route->getParams(),
$route->getLabel('sdk.parameters', []),
$sdk->getAdditionalParameters() ?? [],
);
foreach ($parameters as $name => $param) { // Set params
@@ -314,7 +377,7 @@ class Swagger2 extends Format
$node['x-example'] = false;
break;
case 'Appwrite\Utopia\Database\Validator\CustomId':
if ($route->getLabel('sdk.methodType', '') === 'upload') {
if ($sdk->getType() === MethodType::UPLOAD) {
$node['x-upload-id'] = true;
}
$node['type'] = $validator->getType();
@@ -347,7 +410,6 @@ class Swagger2 extends Format
case 'Utopia\Validator\JSON':
case 'Utopia\Validator\Mock':
case 'Utopia\Validator\Assoc':
case 'Appwrite\Functions\Validator\Payload':
$node['type'] = 'object';
$node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
$node['x-example'] = '{}';
@@ -422,7 +484,7 @@ class Swagger2 extends Format
//Iterate the blackList. If it matches with the current one, then it is blackListed
$allowed = true;
foreach ($this->enumBlacklist as $blacklist) {
if ($blacklist['namespace'] == $route->getLabel('sdk.namespace', '') && $blacklist['method'] == $method && $blacklist['parameter'] == $name) {
if ($blacklist['namespace'] == $namespace && $blacklist['method'] == $method && $blacklist['parameter'] == $name) {
$allowed = false;
break;
}
@@ -430,8 +492,8 @@ class Swagger2 extends Format
if ($allowed && $validator->getType() === 'string') {
$node['enum'] = $validator->getList();
$node['x-enum-name'] = $this->getEnumName($route->getLabel('sdk.namespace', ''), $method, $name);
$node['x-enum-keys'] = $this->getEnumKeys($route->getLabel('sdk.namespace', ''), $method, $name);
$node['x-enum-name'] = $this->getEnumName($namespace, $method, $name);
$node['x-enum-keys'] = $this->getEnumKeys($namespace, $method, $name);
}
if ($validator->getType() === 'integer') {
+24 -2
View File
@@ -28,8 +28,30 @@ class Request extends UtopiaRequest
$parameters = parent::getParams();
if ($this->hasFilters() && self::hasRoute()) {
$method = self::getRoute()->getLabel('sdk.method', 'unknown');
$endpointIdentifier = self::getRoute()->getLabel('sdk.namespace', 'unknown') . '.' . $method;
$methods = self::getRoute()->getLabel('sdk', null);
if (!\is_array($methods)) {
$methods = [$methods];
}
$params = [];
foreach ($methods as $method) {
/** @var \Appwrite\SDK\Method $method */
if (empty($method)) {
$endpointIdentifier = 'unknown.unknown';
} else {
$endpointIdentifier = $method->getNamespace() . '.' . $method->getMethodName();
}
$params += $method->getParameters();
}
if (!empty($params)) {
$parameters = array_filter($parameters, function ($key) use ($params) {
return array_key_exists($key, $params);
}, \ARRAY_FILTER_USE_KEY);
}
foreach ($this->getFilters() as $filter) {
$parameters = $filter->parse($parameters, $endpointIdentifier);