1
0
Fork 0
mirror of synced 2024-06-02 10:54:44 +12:00

Merge branch 'feat-database-indexing' into feat-quest-filters

This commit is contained in:
Matej Baco 2022-01-03 10:52:35 +01:00
commit df97b67c6d
53 changed files with 747 additions and 766 deletions

View file

@ -1,17 +1,70 @@
# Version 1.0.0
# Version 0.12.0
## Features
- Grouped auth related attributes in project collection. Introduced new attribute `auths` and removed all attributes related to auth methods and `usersAuthLimit` as well, all these are grouped under `auths` attribute
- Grouped oAuth related attributes in project collection. Introduced new attribute `providers` and removed all attributes related to OAuth2 providers. All OAuth2 attributes are grouped under `providers`
- Project model changed, `userAuth<AuthMethod>` => `auth<AuthMethod>` example `userAuthEmailPassword` => `authEmailPassword`, also `userOauth2<Provider>...` => `provider<Provider>...` example `userOauth2GithubAppid` => `providerGithubAppid`
# Version 0.12.0
## Breaking Changes (Read before upgrading!)
- Completely rewritten Database service:
- Collection rules are now attributes
- Filters for have been replaced with a new, more powerful syntax
- Custom indexes for more performant queries
- Enum Attributes
- **DEPRECATED** Nested documents has been removed
- **DEPRECATED** Wildcard rule has been removed
- You can now set custom IDs when creating following resources:
- User
- Team
- Function
- Project
- File
- Collection
- Document
- Wildcard permissions `*` are now `role:all`
- Collections can be enabled and disabled
- Permissions are now found as top-level keys `$read` and `$write` instead of nested under `$permissions`
- Accessing collections with insufficient permissions now return a `401` isntead of `404` status code
- Added Cursor pagination to all endpoints that provide pagination by offset
- Added new Usage worker to aggregate usage statistics
- Added new Database worker to handle heavy database tasks in the background
- Added detailed Usage statistics to following services in the Console:
- Users
- Storage
- Database
- You can now disable/enable following services in the Console:
- Account
- Avatars
- Database
- Locale
- Health
- Storage
- Teams
- Users
- Functions
- Added Flutter Desktop Support
- Fixed several memory leaks in the Console
- Added pagination to account activities in the Console
- Added following events from User service to Webhooks and Functions:
- `users.update.email`
- `users.update.name`
- `users.update.password`
- Added new environment variable `_APP_USAGE_AGGREGATION_INTERVAL` to configure the usage worker interval
- Added negative rotation values to file preview endpoint
- Multiple HealthAPI response models were changed to new (better) schema
- Method `health.getAntiVirus()` has been renamed to `health.getAntivirus()`
- Added following langauges to the Locale service:
- Latin
- Sindhi
- Telugu
- **DEPRECATED** Tasks service
## Bugs
- Fixed `/v1/avatars/initials` when no space in the name, will try to split by `_`
- Fixed all audit logs now saving all relevant informations
- Fixed Health endpoints for `db` and `cache`
## Security
- Increased minimum password length to 8 and removed maximum length
- Upgraded Redis to 6.2
- Upgraded InfluxDB to 1.4.0
- Upgraded Telegraf to 1.3.0
# Version 0.11.0

View file

@ -288,7 +288,7 @@ The Runtimes for all supported cloud functions (multicore builds) can be found a
For generating a new console SDK follow the next steps:
1. Update the console spec file located at `app/config/specs/0.10.x.console.json` from the dynamic version located at `https://localhost/specs/swagger2?platform=console`
1. Update the console spec file located at `app/config/specs/swagger2-0.12.x.console.json` from the dynamic version located at `https://localhost/specs/swagger2?platform=console`
2. Generate a new SDK using the command `php app/cli.php sdks`
3. Change your working dir using `cd app/sdks/console-web`
4. Build the new SDK `npm run build`

View file

@ -259,6 +259,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/realtime && \
chmod +x /usr/local/bin/schedule && \
chmod +x /usr/local/bin/sdks && \
chmod +x /usr/local/bin/specs && \
chmod +x /usr/local/bin/ssl && \
chmod +x /usr/local/bin/test && \
chmod +x /usr/local/bin/vars && \

View file

@ -1,6 +1,6 @@
<?php
require_once __DIR__.'/init.php';
require_once __DIR__.'/controllers/general.php';
use Utopia\App;
use Utopia\CLI\CLI;
@ -13,6 +13,7 @@ include 'tasks/maintenance.php';
include 'tasks/install.php';
include 'tasks/migrate.php';
include 'tasks/sdks.php';
include 'tasks/specs.php';
include 'tasks/ssl.php';
include 'tasks/vars.php';
include 'tasks/usage.php';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -245,232 +245,6 @@ App::get('/error/:code')
->setParam('body', $page);
});
App::get('/specs/:format')
->groups(['web', 'home'])
->label('scope', 'public')
->label('docs', false)
->label('origin', '*')
->param('format', 'swagger2', new WhiteList(['swagger2', 'open-api3'], true), 'Spec format.', true)
->param('platform', APP_PLATFORM_CLIENT, new WhiteList([APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER, APP_PLATFORM_CONSOLE], true), 'Choose target platform.', true)
->param('tests', 0, function () {return new Range(0, 1);}, 'Include only test services.', true)
->inject('utopia')
->inject('request')
->inject('response')
->action(function ($format, $platform, $tests, $utopia, $request, $response) {
/** @var Utopia\App $utopia */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
$platforms = [
'client' => APP_PLATFORM_CLIENT,
'server' => APP_PLATFORM_SERVER,
'console' => APP_PLATFORM_CONSOLE,
];
$authCounts = [
'client' => 1,
'server' => 2,
'console' => 1,
];
$routes = [];
$models = [];
$services = [];
$keys = [
APP_PLATFORM_CLIENT => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
],
APP_PLATFORM_SERVER => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
],
APP_PLATFORM_CONSOLE => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
'Mode' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Mode',
'description' => '',
'in' => 'header',
],
],
];
foreach ($utopia->getRoutes() as $key => $method) {
foreach ($method as $route) { /** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if(empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
if ($route->getLabel('sdk.mock', false) && !$tests) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $tests) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
continue;
}
$routes[] = $route;
$modelLabel = $route->getLabel('sdk.response.model', 'none');
$model = \is_array($modelLabel) ? \array_map(function($m) use($response) {
return $response->getModel($m);
}, $modelLabel) : $response->getModel($modelLabel);
}
}
foreach (Config::getParam('services', []) as $service) {
if(!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']) {
continue;
}
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
];
}
$models = $response->getModels();
foreach ($models as $key => $value) {
if($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
}
}
switch ($format) {
case 'swagger2':
$format = new Swagger2($utopia, $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
case 'open-api3':
$format = new OpenAPI3($utopia, $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
default:
throw new Exception('Format not found', 404);
break;
}
$specs = new Specification($format);
$format
->setParam('name', APP_NAME)
->setParam('description', 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)')
->setParam('endpoint', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/v1')
->setParam('version', APP_VERSION_STABLE)
->setParam('terms', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/policy/terms')
->setParam('support.email', App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM))
->setParam('support.url', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/support')
->setParam('contact.name', APP_NAME.' Team')
->setParam('contact.email', App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM))
->setParam('contact.url', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/support')
->setParam('license.name', 'BSD-3-Clause')
->setParam('license.url', 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE')
->setParam('docs.description', 'Full API docs, specs and tutorials')
->setParam('docs.url', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/docs')
;
$response
->json($specs->parse());
});
App::get('/versions')
->desc('Get Version')
->groups(['web', 'home'])

View file

@ -510,7 +510,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
}
switch ($message['type']) {
/**
/**
* This type is used to authenticate.
*/
case 'authentication':

View file

@ -2,8 +2,6 @@
global $cli;
require_once __DIR__.'/../init.php';
use Appwrite\Event\Event;
use Utopia\App;
use Utopia\CLI\Console;

View file

@ -30,38 +30,38 @@ $cli
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
$message = ($git) ? Console::confirm('Please enter your commit message:') : '';
if(!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x'])) {
if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', 'latest'])) {
throw new Exception('Unknown version given');
}
foreach($platforms as $key => $platform) {
foreach($platform['languages'] as $language) {
if($selected !== $language['key'] && $selected !== '*') {
foreach ($platforms as $key => $platform) {
foreach ($platform['languages'] as $language) {
if ($selected !== $language['key'] && $selected !== '*') {
continue;
}
if(!$language['enabled']) {
Console::warning($language['name'].' for '.$platform['name'] . ' is disabled');
if (!$language['enabled']) {
Console::warning($language['name'] . ' for ' . $platform['name'] . ' is disabled');
continue;
}
Console::info('Fetching API Spec for '.$language['name'].' for '.$platform['name'] . ' (version: '.$version.')');
$spec = file_get_contents(__DIR__.'/../config/specs/'.$version.'.'.$language['family'].'.json');
Console::info('Fetching API Spec for ' . $language['name'] . ' for ' . $platform['name'] . ' (version: ' . $version . ')');
$spec = file_get_contents(__DIR__ . '/../config/specs/swagger2-' . $version . '-' . $language['family'] . '.json');
$cover = 'https://appwrite.io/images/github.png';
$result = \realpath(__DIR__.'/..').'/sdks/'.$key.'-'.$language['key'];
$resultExamples = \realpath(__DIR__.'/../..').'/docs/examples/'.$version.'/'.$key.'-'.$language['key'];
$target = \realpath(__DIR__.'/..').'/sdks/git/'.$language['key'].'/';
$readme = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/README.md');
$result = \realpath(__DIR__ . '/..') . '/sdks/' . $key . '-' . $language['key'];
$resultExamples = \realpath(__DIR__ . '/../..') . '/docs/examples/' . $version . '/' . $key . '-' . $language['key'];
$target = \realpath(__DIR__ . '/..') . '/sdks/git/' . $language['key'] . '/';
$readme = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/README.md');
$readme = ($readme) ? \file_get_contents($readme) : '';
$gettingStarted = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/GETTING_STARTED.md');
$gettingStarted = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/GETTING_STARTED.md');
$gettingStarted = ($gettingStarted) ? \file_get_contents($gettingStarted) : '';
$examples = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/EXAMPLES.md');
$examples = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/EXAMPLES.md');
$examples = ($examples) ? \file_get_contents($examples) : '';
$changelog = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/CHANGELOG.md');
$changelog = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/CHANGELOG.md');
$changelog = ($changelog) ? \file_get_contents($changelog) : '# Change Log';
$warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check [previous releases]('.$language['url'].'/releases).**';
$warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check [previous releases](' . $language['url'] . '/releases).**';
$license = 'BSD-3-Clause';
$licenseContent = 'Copyright (c) ' . date('Y') . ' Appwrite (https://appwrite.io) and individual contributors.
All rights reserved.
@ -105,7 +105,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$config = new Node();
$config->setNPMPackage('node-appwrite');
$config->setBowerPackage('appwrite');
$warning = $warning."\n\n > This is the Node.js SDK for integrating with Appwrite from your Node.js server-side code.
$warning = $warning . "\n\n > This is the Node.js SDK for integrating with Appwrite from your Node.js server-side code.
If you're looking to integrate from the browser, you should check [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web)";
break;
case 'deno':
@ -115,7 +115,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$config = new Python();
$config->setPipPackage('appwrite');
$license = 'BSD License'; // license edited due to classifiers in pypi
break;
break;
case 'ruby':
$config = new Ruby();
$config->setGemPackage('appwrite');
@ -131,14 +131,14 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
case 'dart':
$config = new Dart();
$config->setPackageName('dart_appwrite');
$warning = $warning."\n\n > This is the Dart SDK for integrating with Appwrite from your Dart server-side code. If you're looking for the Flutter SDK you should check [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter)";
$warning = $warning . "\n\n > This is the Dart SDK for integrating with Appwrite from your Dart server-side code. If you're looking for the Flutter SDK you should check [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter)";
break;
case 'go':
$config = new Go();
break;
case 'swift':
$config = new Swift();
$warning = $warning."\n\n > This is the Swift SDK for integrating with Appwrite from your Swift server-side code. If you're looking for the Apple SDK you should check [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)";
$warning = $warning . "\n\n > This is the Swift SDK for integrating with Appwrite from your Swift server-side code. If you're looking for the Apple SDK you should check [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)";
break;
case 'apple':
$config = new SwiftClient();
@ -152,10 +152,10 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
break;
case 'kotlin':
$config = new Kotlin();
$warning = $warning."\n\n > This is the Kotlin SDK for integrating with Appwrite from your Kotlin server-side code. If you're looking for the Android SDK you should check [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android)";
$warning = $warning . "\n\n > This is the Kotlin SDK for integrating with Appwrite from your Kotlin server-side code. If you're looking for the Android SDK you should check [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android)";
break;
default:
throw new Exception('Language "'.$language['key'].'" not supported');
throw new Exception('Language "' . $language['key'] . '" not supported');
break;
}
@ -189,10 +189,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
->setTwitter(APP_SOCIAL_TWITTER_HANDLE)
->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD)
->setDefaultHeaders([
'X-Appwrite-Response-Format' => '0.11.0',
])
;
'X-Appwrite-Response-Format' => '0.12.0',
]);
try {
$sdk->generate($result);
} catch (Exception $exception) {
@ -204,38 +203,43 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$gitUrl = $language['gitUrl'];
$gitBranch = $language['gitBranch'];
if(!$production) {
$gitUrl = 'git@github.com:aw-tests/'.$language['gitRepoName'].'.git';
if (!$production) {
$gitUrl = 'git@github.com:aw-tests/' . $language['gitRepoName'] . '.git';
}
if($git && !empty($gitUrl)) {
\exec('rm -rf '.$target.' && \
mkdir -p '.$target.' && \
cd '.$target.' && \
git init --initial-branch='.$gitBranch.' && \
git remote add origin '.$gitUrl.' && \
if ($git && !empty($gitUrl)) {
\exec('rm -rf ' . $target . ' && \
mkdir -p ' . $target . ' && \
cd ' . $target . ' && \
git init --initial-branch=' . $gitBranch . ' && \
git remote add origin ' . $gitUrl . ' && \
git fetch && \
git pull '.$gitUrl.' && \
rm -rf '.$target.'/* && \
cp -r '.$result.'/ '.$target.'/ && \
git pull ' . $gitUrl . ' && \
rm -rf ' . $target . '/* && \
cp -r ' . $result . '/ ' . $target . '/ && \
git add . && \
git commit -m "'.$message.'" && \
git push -u origin '.$gitBranch.'
git commit -m "' . $message . '" && \
git push -u origin ' . $gitBranch . '
');
Console::success("Pushed {$language['name']} SDK to {$gitUrl}");
\exec('rm -rf '.$target);
\exec('rm -rf ' . $target);
Console::success("Remove temp directory '{$target}' for {$language['name']} SDK");
}
$docDirectories = $language['docDirectories'] ?? [''];
if($version === 'latest') {
continue;
}
foreach ($docDirectories as $languageTitle => $path) {
$languagePath = strtolower($languageTitle !== 0 ? '/'.$languageTitle : '');
$languagePath = strtolower($languageTitle !== 0 ? '/' . $languageTitle : '');
\exec(
'mkdir -p '.$resultExamples.$languagePath.' && \
cp -r '.$result.'/docs/examples'.$languagePath.' '.$resultExamples
'mkdir -p ' . $resultExamples . $languagePath . ' && \
cp -r ' . $result . '/docs/examples' . $languagePath . ' ' . $resultExamples
);
Console::success("Copied code examples for {$language['name']} SDK to: {$resultExamples}");
}

266
app/tasks/specs.php Normal file
View file

@ -0,0 +1,266 @@
<?php
global $cli;
use Utopia\Validator\Text;
use Appwrite\Specification\Format\OpenAPI3;
use Appwrite\Specification\Format\Swagger2;
use Appwrite\Specification\Specification;
use Appwrite\Utopia\Response;
use Swoole\Http\Response as HttpResponse;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Request;
use Utopia\Validator\WhiteList;
$cli
->task('specs')
->param('version', 'latest', new Text(8), 'Spec version', true)
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
->action(function ($version, $mode) use ($register) {
$db = $register->get('db');
$redis = $register->get('cache');
$appRoutes = App::getRoutes();
$response = new Response(new HttpResponse());
$mocks = ($mode === 'mocks');
App::setResource('request', fn() => new Request);
App::setResource('response', fn() => $response);
App::setResource('db', fn() => $db);
App::setResource('cache', fn() => $redis);
$platforms = [
'client' => APP_PLATFORM_CLIENT,
'server' => APP_PLATFORM_SERVER,
'console' => APP_PLATFORM_CONSOLE,
];
$authCounts = [
'client' => 1,
'server' => 2,
'console' => 1,
];
$keys = [
APP_PLATFORM_CLIENT => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
],
APP_PLATFORM_SERVER => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
],
APP_PLATFORM_CONSOLE => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
'Mode' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Mode',
'description' => '',
'in' => 'header',
],
],
];
foreach (['swagger2', 'open-api3'] as $format) {
foreach ($platforms as $platform) {
$routes = [];
$models = [];
$services = [];
foreach ($appRoutes as $key => $method) {
foreach ($method as $route) { /** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if(empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
if ($route->getLabel('sdk.mock', false) && !$mocks) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $mocks) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
continue;
}
$routes[] = $route;
$modelLabel = $route->getLabel('sdk.response.model', 'none');
\is_array($modelLabel) ? \array_map(function($m) use($response) {
return $response->getModel($m);
}, $modelLabel) : $response->getModel($modelLabel);
}
}
foreach (Config::getParam('services', []) as $service) {
if(!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']) {
continue;
}
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
];
}
$models = $response->getModels();
foreach ($models as $key => $value) {
if($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
}
}
switch ($format) {
case 'swagger2':
$formatInstance = new Swagger2(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
case 'open-api3':
$formatInstance = new OpenAPI3(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
default:
throw new Exception('Format not found: '.$format);
break;
}
$specs = new Specification($formatInstance);
$endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]');
$email = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$formatInstance
->setParam('name', APP_NAME)
->setParam('description', 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)')
->setParam('endpoint', 'https://HOSTNAME/v1')
->setParam('version', APP_VERSION_STABLE)
->setParam('terms', $endpoint.'/policy/terms')
->setParam('support.email', $email)
->setParam('support.url', $endpoint.'/support')
->setParam('contact.name', APP_NAME.' Team')
->setParam('contact.email', $email)
->setParam('contact.url', $endpoint.'/support')
->setParam('license.name', 'BSD-3-Clause')
->setParam('license.url', 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE')
->setParam('docs.description', 'Full API docs, specs and tutorials')
->setParam('docs.url', $endpoint.'/docs')
;
if($mocks) {
$path = __DIR__.'/../config/specs/'.$format.'-mocks-'.$platform.'.json';
if(!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save mocks spec file: '.$path);
}
Console::success('Saved mocks spec file: ' . realpath($path));
continue;
}
$path = __DIR__.'/../config/specs/'.$format.'-'.$version.'-'.$platform.'.json';
if(!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save spec file: '.$path);
}
Console::success('Saved spec file: ' . realpath($path));
}
}
});

View file

@ -2,8 +2,6 @@
global $cli, $register;
require_once __DIR__ . '/../init.php';
use Utopia\App;
use Utopia\Cache\Adapter\Redis;
use Utopia\Cache\Cache;

View file

@ -155,10 +155,8 @@ $logs = $this->getParam('logs', null);
<th width="100"></th>
<th width="130">Attribute ID</th>
<th width="100">Type</th>
<th>Length</th>
<th>Default</th>
<th></th>
<th width="80"></th>
<th width="32"></th>
</tr>
</thead>
<tbody data-ls-loop="project-collection.attributes" data-ls-as="attribute">
@ -186,52 +184,116 @@ $logs = $this->getParam('logs', null);
<span class="text-size-small" data-ls-if="{{attribute.format}}" data-ls-bind="({{attribute.format}})"></span>
</td>
<td data-title="Length:">
<span class="text-size-small" data-ls-if="{{attribute.size}}" data-ls-bind="{{attribute.size}}"></span>
</td>
<td data-title="Default:">
<span class="text-size-small" data-ls-bind="{{attribute.default}}" data-ls-attr="title={{attribute.default}}"></span>
<span class="text-fade text-size-small" data-ls-if="{{attribute.default}} != ''">n/a</span>
</td>
<td data-title="">
<span class="text-size-small text-danger" data-ls-if="{{attribute.required}}">required</span>
</td>
<td data-title="">
<form
data-ls-if="{{attribute.status}} !== 'deleting'"
class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Collection Attribute"
data-service="database.deleteAttribute"
data-scope="sdk"
data-event="submit"
data-confirm="Are you sure you want to delete this attribute?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted attribute successfully"
data-success-param-trigger-events="database.deleteAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to delete attribute"
data-failure-param-alert-classname="error">
<div data-ui-modal class="box modal sticky-footer width-large close" data-button-class="icon-dot-3 link" data-button-element="i">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="key" data-ls-bind="{{attribute.key}}" />
<h1>
<i data-ls-if="!{{attribute.format}}" data-ls-attrs="class=icon-{{attribute.type}}"></i>
<i data-ls-if="{{attribute.format}}" data-ls-attrs="class=icon-{{attribute.format}}"></i>
<span data-ls-bind="{{attribute.key}}"></span>
</h1>
<button class="danger small">Delete</button>
</form>
<hr />
<div class="row responsive modalize">
<form>
<div class="col span-12">
<label>Key</label>
<div class="input-copy">
<input type="text" data-ls-bind="{{attribute.key}}" data-forms-copy disabled />
</div>
<label>Type</label>
<input type="text" data-ls-bind="{{attribute.type}}" disabled />
<div data-ls-if="{{attribute.format}}">
<label>Format</label>
<input type="text" data-ls-bind="{{attribute.format}}" class="full-width" disabled />
</div>
<div class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label>Required</label>
<input type="text" data-ls-bind="{{attribute.required}}" disabled />
</div>
<div class="col span-6 margin-bottom-small">
<label>Array</label>
<input type="text" data-ls-bind="{{attribute.array}}" disabled />
</div>
</div>
<div data-ls-if="{{attribute.default}}">
<label>Default Value</label>
<input type="text" data-ls-bind="{{attribute.default}}" class="full-width" disabled />
</div>
<div data-ls-if="{{attribute.size}}">
<label for="size">Length</label>
<input type="text" data-ls-bind="{{attribute.size}}" class="full-width" disabled />
</div>
<div data-ls-if="{{attribute.format}} == 'enum'">
<label>Elements</label>
<span data-ls-loop="attribute.elements" data-ls-as="element">
<input type="text" data-ls-bind="{{element}}" class="full-width" disabled />
</span>
</div>
<div data-ls-if="{{attribute.min}} && {{attribute.max}}" class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label>Min</label>
<input type="text" name="min" data-ls-bind="{{attribute.min}}" class="full-width" disabled />
</div>
<div class="col span-6 margin-bottom-small">
<label>Max</label>
<input type="text" name="max" data-ls-bind="{{attribute.max}}" class="full-width" disabled />
</div>
</div>
</div>
</form>
</div>
<footer>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
<form
data-ls-if="{{attribute.status}} !== 'deleting'"
class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Collection Attribute"
data-service="database.deleteAttribute"
data-scope="sdk"
data-event="submit"
data-confirm="Are you sure you want to delete this attribute?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted attribute successfully"
data-success-param-trigger-events="database.deleteAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to delete attribute"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="key" data-ls-bind="{{attribute.key}}" />
<button class="danger">Delete</button>
</form>
</footer>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="drop-list pull-start" data-ls-ui-open="" data-button-aria="Choose Platform" data-button-text="Add Attribute" data-button-class="button" data-blur="1">
<div class="drop-list pull-start" data-ls-ui-open="" data-button-aria="Choose Attribute" data-button-text="Add Attribute" data-button-class="button" data-blur="1">
<ul>
<li>
<div class="link new-attribute-string"><i class="avatar icon-string"></i> New String Attribute</div>
@ -543,22 +605,23 @@ $logs = $this->getParam('logs', null);
<h1>Add String Attribute</h1>
<form
id="add-string-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (string)"
data-service="database.createStringAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-string-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (string)"
data-service="database.createStringAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -571,48 +634,25 @@ $logs = $this->getParam('logs', null);
<input id="string-length" name="size" type="number" class="margin-bottom" autocomplete="off" required value="255" data-cast-to="integer" />
<div class="margin-bottom">
<input type="hidden" name="required" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input type="hidden" name="array" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<div>
<label for="xdefault">Default Value</label>
<label for="xdefault">Default Value</label>
<template x-if="!(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
</div>
</template>
<template x-if="(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-string-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-integer">
@ -620,22 +660,23 @@ $logs = $this->getParam('logs', null);
<h1>Add Integer Attribute</h1>
<form
id="add-integer-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (integer)"
data-service="database.createIntegerAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-integer-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (integer)"
data-service="database.createIntegerAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -645,11 +686,11 @@ $logs = $this->getParam('logs', null);
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input type="hidden" name="required" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input type="hidden" name="array" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<div class="row responsive thin">
@ -665,36 +706,17 @@ $logs = $this->getParam('logs', null);
<label for="integer-default">Default Value</label>
<input id="integer-default" name="xdefault" type="number" step="1" class="margin-bottom-large" autocomplete="off" data-cast-to="integer">
<template x-if="!(array || required)">
<input name="xdefault" type="number" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="number" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-integer-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-float">
@ -702,22 +724,23 @@ $logs = $this->getParam('logs', null);
<h1>Add Float Attribute</h1>
<form
id="add-float-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (float)"
data-service="database.createFloatAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-float-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (float)"
data-service="database.createFloatAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -727,11 +750,11 @@ $logs = $this->getParam('logs', null);
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input type="hidden" name="required" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input type="hidden" name="array" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<div class="row responsive thin">
@ -746,36 +769,18 @@ $logs = $this->getParam('logs', null);
</div>
<label for="float-default">Default Value</label>
<input id="float-default" name="xdefault" type="number" step="0.01" class="margin-bottom-large" autocomplete="off" data-cast-to="float">
<template x-if="!(array || required)">
<input name="xdefault" type="number" step="0.01" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="number" step="0.01" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-float-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-email">
@ -783,22 +788,23 @@ $logs = $this->getParam('logs', null);
<h1>Add Email Attribute</h1>
<form
id="add-email-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (email)"
data-service="database.createEmailAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-email-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (email)"
data-service="database.createEmailAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -808,44 +814,25 @@ $logs = $this->getParam('logs', null);
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="string-default">Default Value</label>
<input id="string-default" name="xdefault" type="email" class="margin-bottom-large" autocomplete="off">
<template x-if="!(array || required)">
<input name="xdefault" type="email" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="email" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-email-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-boolean">
@ -853,22 +840,23 @@ $logs = $this->getParam('logs', null);
<h1>Add Boolean Attribute</h1>
<form
id="add-boolean-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (boolean)"
data-service="database.createBooleanAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-boolean-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (boolean)"
data-service="database.createBooleanAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -878,46 +866,28 @@ $logs = $this->getParam('logs', null);
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom-large">
<input name="xdefault" type="hidden" data-forms-switch data-cast-to="boolean" autocomplete="off" /> &nbsp; Default Value <span class="tooltip" data-tooltip="Whether this attribute is set to true or false on creation"><i class="icon-info-circled"></i></span>
<template x-if="!(array || required)">
<input name="xdefault" class="button switch" type="checkbox" />
</template>
<template x-if="(array || required)">
<input name="xdefault" class="button switch" type="checkbox" disabled />
</template>
&nbsp; Default Value <span class="tooltip" data-tooltip="Whether this attribute is set to true or false on creation"><i class="icon-info-circled"></i></span>
</div>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-boolean-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["uncheck", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
@ -926,22 +896,23 @@ $logs = $this->getParam('logs', null);
<h1>Add IP Attribute</h1>
<form
id="add-ip-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (IP)"
data-service="database.createIpAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-ip-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (IP)"
data-service="database.createIpAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -951,44 +922,25 @@ $logs = $this->getParam('logs', null);
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="string-default">Default Value</label>
<input id="string-default" name="xdefault" type="text" data-forms-ip-address class="margin-bottom-large" autocomplete="off">
<template x-if="!(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-ip-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-url">
@ -1011,7 +963,8 @@ $logs = $this->getParam('logs', null);
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -1021,44 +974,25 @@ $logs = $this->getParam('logs', null);
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="string-default">Default Value</label>
<input id="string-default" name="xdefault" type="url" title="Valid URL address" class="margin-bottom-large" autocomplete="off">
<template x-if="!(array || required)">
<input name="xdefault" type="url" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="url" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-url-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-enum">
@ -1081,7 +1015,8 @@ $logs = $this->getParam('logs', null);
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
@ -1108,44 +1043,25 @@ $logs = $this->getParam('logs', null);
</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="enunm-default">Default Value</label>
<input id="enum-default" name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
<template x-if="!(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-enum-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-index">
@ -1234,4 +1150,4 @@ $logs = $this->getParam('logs', null);
</script>
<script type="text/html" id="template-attribute-body-first">
<span class="text-fade" data-ls-bind="{{node.$id}}" data-general-copy data-class="icon-docs note copy text-fade pull-end"></span>
</script>
</script>

View file

@ -88,7 +88,7 @@ $logs = $this->getParam('logs', null);
<fieldset name="data" data-cast-to="object" data-ls-attrs="x-init=doc = {{project-document}}" x-data="{doc: {}}">
<ul data-ls-attrs="x-init=attributes = {{project-collection.attributes}}" x-data="{attributes: []}">
<template x-for="attr in attributes">
<template x-for="attr in attributes.filter(a => a.status === 'available')">
<li>
<label>
<div x-text="attr.key" class="margin-bottom-tiny"></div>

View file

@ -138,6 +138,7 @@ services:
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV

3
bin/specs Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php specs $@

View file

@ -158,6 +158,7 @@ services:
- ./src:/usr/src/code/src
# - ./vendor:/usr/src/code/vendor
depends_on:
- mariadb
- redis
environment:
- _APP_ENV

View file

@ -38,7 +38,7 @@
class Appwrite {
constructor() {
this.config = {
endpoint: 'https://appwrite.io/v1',
endpoint: 'https://HOSTNAME/v1',
endpointRealtime: '',
project: '',
key: '',
@ -48,7 +48,7 @@
};
this.headers = {
'x-sdk-version': 'appwrite:web:4.0.4',
'X-Appwrite-Response-Format': '0.11.0',
'X-Appwrite-Response-Format': '0.12.0',
};
this.realtime = {
socket: undefined,
@ -2647,14 +2647,14 @@
}, payload);
}),
/**
* Get Anti virus
* Get Antivirus
*
* Check the Appwrite Anti Virus server is up and connection is successful.
* Check the Appwrite Antivirus server is up and connection is successful.
*
* @throws {AppwriteException}
* @returns {Promise}
*/
getAntiVirus: () => __awaiter(this, void 0, void 0, function* () {
getAntivirus: () => __awaiter(this, void 0, void 0, function* () {
let path = '/health/anti-virus';
let payload = {};
const uri = new URL(this.config.endpoint + path);
@ -4214,10 +4214,11 @@
/**
* List Teams
*
* Get a list of all the current user teams. You can use the query params to
* filter your results. On admin mode, this endpoint will return a list of all
* of the project's teams. [Learn more about different API
* modes](/docs/admin).
* Get a list of all the teams in which the current user is a member. You can
* use the parameters to filter your results.
*
* In admin mode, this endpoint returns a list of all the teams in the current
* project. [Learn more about different API modes](/docs/admin).
*
* @param {string} search
* @param {number} limit
@ -4258,9 +4259,8 @@
* Create Team
*
* Create a new team. The user who creates the team will automatically be
* assigned as the owner of the team. The team owner can invite new members,
* who will be able add new owners and update or delete the team from your
* project.
* assigned as the owner of the team. Only the users with the owner role can
* invite new members, add new owners and delete or update the team.
*
* @param {string} teamId
* @param {string} name
@ -4294,8 +4294,7 @@
/**
* Get Team
*
* Get a team by its unique ID. All team members have read access for this
* resource.
* Get a team by its ID. All team members have read access for this resource.
*
* @param {string} teamId
* @throws {AppwriteException}
@ -4315,8 +4314,8 @@
/**
* Update Team
*
* Update a team by its unique ID. Only team owners have write access for this
* resource.
* Update a team using its ID. Only members with the owner role can update the
* team.
*
* @param {string} teamId
* @param {string} name
@ -4343,8 +4342,8 @@
/**
* Delete Team
*
* Delete a team by its unique ID. Only team owners have write access for this
* resource.
* Delete a team using its ID. Only team members with the owner role can
* delete the team.
*
* @param {string} teamId
* @throws {AppwriteException}
@ -4364,8 +4363,8 @@
/**
* Get Team Memberships
*
* Get a team members by the team unique ID. All team members have read access
* for this list of resources.
* Use this endpoint to list a team's members using the team's ID. All team
* members have read access to this endpoint.
*
* @param {string} teamId
* @param {string} search
@ -4409,22 +4408,21 @@
/**
* Create Team Membership
*
* Use this endpoint to invite a new member to join your team. If initiated
* from Client SDK, an email with a link to join the team will be sent to the
* new member's email address if the member doesn't exist in the project it
* will be created automatically. If initiated from server side SDKs, new
* member will automatically be added to the team.
* Invite a new member to join your team. If initiated from the client SDK, an
* email with a link to join the team will be sent to the member's email
* address and an account will be created for them should they not be signed
* up already. If initiated from server-side SDKs, the new member will
* automatically be added to the team.
*
* Use the 'URL' parameter to redirect the user from the invitation email back
* Use the 'url' parameter to redirect the user from the invitation email back
* to your app. When the user is redirected, use the [Update Team Membership
* Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow
* the user to accept the invitation to the team. While calling from side
* SDKs the redirect url can be empty string.
* the user to accept the invitation to the team.
*
* Please note that in order to avoid a [Redirect
* Attacks](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
* Please note that to avoid a [Redirect
* Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
* the only valid redirect URL's are the once from domains you have set when
* added your platforms in the console interface.
* adding your platforms in the console interface.
*
* @param {string} teamId
* @param {string} email
@ -4494,6 +4492,9 @@
/**
* Update Membership Roles
*
* Modify the roles of a team member. Only team members with the owner role
* have access to this endpoint. Learn more about [roles and
* permissions](/docs/permissions).
*
* @param {string} teamId
* @param {string} membershipId

View file

@ -140,75 +140,6 @@ window.addEventListener("load", async () => {
}
});
window.formValidation = (form, fields) => {
const elements = Array.from(form.querySelectorAll('[name]')).reduce((prev, curr) => {
if(!curr.name) {
return prev;
}
prev[curr.name] = curr;
return prev;
}, {});
const actionHandler = (action, attribute) => {
switch (action) {
case "disable":
elements[attribute].setAttribute("disabled", true);
elements[attribute].dispatchEvent(new Event('change'));
break;
case "enable":
elements[attribute].removeAttribute("disabled");
elements[attribute].dispatchEvent(new Event('change'));
break;
case "unvalue":
elements[attribute].value = "";
break;
case "check":
elements[attribute].value = "true";
break;
case "uncheck":
elements[attribute].value = "false";
break;
}
};
for (const field in fields) {
for (const attribute in fields[field]) {
const attr = fields[field][attribute];
if (Array.isArray(attr)) {
attr.forEach(action => {
if (elements[field].value === "true") {
actionHandler(action, attribute);
}
})
} else {
const condition = attr.if.some(c => {
return elements[c].value === "true";
});
if (condition) {
for (const thenAction in attr.then) {
attr.then[thenAction].forEach(action => {
actionHandler(action, thenAction);
});
}
} else {
for (const elseAction in attr.else) {
attr.else[elseAction].forEach(action => {
actionHandler(action, elseAction);
});
}
}
}
}
}
form.addEventListener("reset", () => {
for (const key in fields) {
if (Object.hasOwnProperty.call(fields, key)) {
const element = elements[key];
element.setAttribute("value", "");
element.removeAttribute("disabled");
element.dispatchEvent(new Event("change"));
}
}
});
};
/**
* Method to add attribute for the UI on array attributes.
*

View file

@ -396,7 +396,14 @@ class OpenAPI3 extends Format
foreach ($this->models as $model) {
foreach ($model->getRules() as $rule) {
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])) {
$usedModels[] = $rule['type'];
if(\is_array($rule['type'])) {
foreach ($rule['type'] as $value) {
$usedModels[] = $value;
}
}
else {
$usedModels[] = $rule['type'];
}
}
}
}

View file

@ -198,7 +198,7 @@ class Swagger2 extends Format
$temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [
'description' => $modelDescription,
'schema' => [
'oneOf' => \array_map(function($m) {
'x-oneOf' => \array_map(function($m) {
return ['$ref' => '#/definitions/'.$m->getType()];
}, $model)
],
@ -393,7 +393,14 @@ class Swagger2 extends Format
foreach ($this->models as $model) {
foreach ($model->getRules() as $rule) {
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])) {
$usedModels[] = $rule['type'];
if(\is_array($rule['type'])) {
foreach ($rule['type'] as $value) {
$usedModels[] = $value;
}
}
else {
$usedModels[] = $rule['type'];
}
}
}
}
@ -460,13 +467,13 @@ class Swagger2 extends Format
if(\is_array($rule['type'])) {
if($rule['array']) {
$items = [
'anyOf' => \array_map(function($type) {
'x-anyOf' => \array_map(function($type) {
return ['$ref' => '#/definitions/'.$type];
}, $rule['type'])
];
} else {
$items = [
'oneOf' => \array_map(function($type) {
'x-oneOf' => \array_map(function($type) {
return ['$ref' => '#/definitions/'.$type];
}, $rule['type'])
];
@ -514,7 +521,7 @@ class Swagger2 extends Format
}
}
if (!in_array($name, $required)) {
$output['definitions'][$model->getType()]['properties'][$name]['nullable'] = true;
$output['definitions'][$model->getType()]['properties'][$name]['x-nullable'] = true;
}
}
}

View file

@ -128,26 +128,36 @@ class HTTPTest extends Scope
'content-type' => 'application/json',
], []);
if(!file_put_contents(__DIR__ . '/../../resources/open-api3.json', json_encode($response['body']))) {
throw new Exception('Failed to save spec file');
}
$directory = __DIR__ . '/../../../app/config/specs/';
$files = scandir($directory);
$client = new Client();
$client->setEndpoint('https://validator.swagger.io');
/**
* Test for SUCCESS
*/
$response = $client->call(Client::METHOD_POST, '/validator/debug', [
'content-type' => 'application/json',
], json_decode(file_get_contents(realpath(__DIR__ . '/../../resources/open-api3.json')), true));
foreach($files as $file) {
if(in_array($file, ['.', '..'])) {
continue;
}
$response['body'] = json_decode($response['body'], true);
if(
(strpos($file, 'latest') === false) &&
(strpos($file, '0.12.x') === false)
) {
continue;
}
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertTrue(empty($response['body']));
/**
* Test for SUCCESS
*/
$response = $client->call(Client::METHOD_POST, '/validator/debug', [
'content-type' => 'application/json',
], json_decode(file_get_contents($directory.$file), true));
unlink(realpath(__DIR__ . '/../../resources/open-api3.json'));
$response['body'] = json_decode($response['body'], true);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertTrue(empty($response['body']));
}
}
public function testResponseHeader() {