1
0
Fork 0
mirror of synced 2024-06-02 19:04:49 +12:00

Merge branch 'feat-database-indexing' of https://github.com/appwrite/appwrite into feat-database-controller-docs-refactor

This commit is contained in:
Torsten Dittmann 2021-12-14 12:16:05 +01:00
commit cb37a69c3e
55 changed files with 2065 additions and 1369 deletions

View file

@ -4,7 +4,7 @@ We would ❤️ for you to contribute to Appwrite and help make it better! We wa
## How to Start?
If you are worried or dont know where to start, check out our next section explaining what kind of help we could use and where can you get involved. You can reach out with questions to [Eldad Fux (@eldadfux)](https://twitter.com/eldadfux) or [@appwrite_io](https://twitter.com/appwrite_io) on Twitter, and anyone from the [Appwrite team on Discord](https://discord.gg/GSeTUeA). You can also submit an issue, and a maintainer can guide you!
If you are worried or dont know where to start, check out our next section explaining what kind of help we could use and where can you get involved. You can reach out with questions to [Eldad Fux (@eldadfux)](https://twitter.com/eldadfux) or [@appwrite](https://twitter.com/appwrite) on Twitter, and anyone from the [Appwrite team on Discord](https://discord.gg/GSeTUeA). You can also submit an issue, and a maintainer can guide you!
## Code of Conduct
@ -406,7 +406,7 @@ Pull requests are great, but there are many other areas where you can help Appwr
### Blogging & Speaking
Blogging, speaking about, or creating tutorials about one of Appwrites many features. Mention [@appwrite_io](https://twitter.com/appwrite_io) on Twitter and/or [email team@appwrite.io](mailto:team@appwrite.io) so we can give pointers and tips and help you spread the word by promoting your content on the different Appwrite communication channels. Please add your blog posts and videos of talks to our [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite) repo on GitHub.
Blogging, speaking about, or creating tutorials about one of Appwrites many features. Mention [@appwrite](https://twitter.com/appwrite) on Twitter and/or [email team@appwrite.io](mailto:team@appwrite.io) so we can give pointers and tips and help you spread the word by promoting your content on the different Appwrite communication channels. Please add your blog posts and videos of talks to our [Awesome Appwrite](https://github.com/appwrite/awesome-appwrite) repo on GitHub.
### Presenting at Meetups

View file

@ -9,10 +9,10 @@
</p>
[![Hacktoberfest](https://img.shields.io/static/v1?label=hacktoberfest&message=friendly&color=90a88b&style=flat-square)](https://hacktoberfest.appwrite.io)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github)
[![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite)
[![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite)
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite_io?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite_io)
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
[![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md)
<!-- [![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io) -->
@ -155,7 +155,7 @@ For security issues, kindly email us at [security@appwrite.io](mailto:security@a
## Follow Us
Join our growing community around the world! See our official [Blog](https://medium.com/appwrite-io). Follow us on [Twitter](https://twitter.com/appwrite_io), [Facebook Page](https://www.facebook.com/appwrite.io), [Facebook Group](https://www.facebook.com/groups/appwrite.developers/) , [Dev Community](https://dev.to/appwrite) or join our live [Discord server](https://discord.gg/GSeTUeA) for more help, ideas, and discussions.
Join our growing community around the world! See our official [Blog](https://medium.com/appwrite-io). Follow us on [Twitter](https://twitter.com/appwrite), [Facebook Page](https://www.facebook.com/appwrite.io), [Facebook Group](https://www.facebook.com/groups/appwrite.developers/) , [Dev Community](https://dev.to/appwrite) or join our live [Discord server](https://discord.gg/GSeTUeA) for more help, ideas, and discussions.
## License

View file

@ -648,8 +648,9 @@ App::post('/v1/account/sessions/magic-url')
throw new Exception('SMTP Disabled', 503);
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$user = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]);
@ -1780,8 +1781,9 @@ App::post('/v1/account/recovery')
throw new Exception('SMTP Disabled', 503);
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$email = \strtolower($email);
$profile = $dbForInternal->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
@ -1971,9 +1973,10 @@ App::post('/v1/account/verification')
if(empty(App::getEnv('_APP_SMTP_HOST'))) {
throw new Exception('SMTP Disabled', 503);
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$verificationSecret = Auth::tokenGenerator();

View file

@ -1640,16 +1640,19 @@ App::post('/v1/database/collections/:collectionId/documents')
$data['$read'] = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user
$data['$write'] = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? []; // By default set write permissions for user
// Users can only add their roles to documents, API keys can add any
$roles = \array_fill_keys(Authorization::getRoles(), true); // Auth::isAppUser expects roles to be keys, not values of assoc array
foreach ($data['$read'] as $read) {
if (!Auth::isAppUser($roles) && !Authorization::isRole($read)) {
throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400);
// Users can only add their roles to documents, API keys and Admin users can add any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($data['$read'] as $read) {
if (!Authorization::isRole($read)) {
throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
}
foreach ($data['$write'] as $write) {
if (!Auth::isAppUser($roles) && !Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400);
foreach ($data['$write'] as $write) {
if (!Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
}
@ -1998,16 +2001,19 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
$data['$read'] = (is_null($read)) ? ($document->getRead() ?? []) : $read; // By default inherit read permissions
$data['$write'] = (is_null($write)) ? ($document->getWrite() ?? []) : $write; // By default inherit write permissions
// Users can only add their roles to documents, API keys can add any
$roles = \array_fill_keys(Authorization::getRoles(), true); // Auth::isAppUser expects roles to be keys, not values of assoc array
foreach ($data['$read'] as $read) {
if (!Auth::isAppUser($roles) && !Authorization::isRole($read)) {
throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400);
// Users can only add their roles to documents, API keys and Admin users can add any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($data['$read'] as $read) {
if (!Authorization::isRole($read)) {
throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
}
foreach ($data['$write'] as $write) {
if (!Auth::isAppUser($roles) && !Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400);
foreach ($data['$write'] as $write) {
if (!Authorization::isRole($write)) {
throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
}

View file

@ -119,6 +119,34 @@ App::get('/v1/functions')
]), Response::MODEL_FUNCTION_LIST);
});
App::get('/v1/functions/runtimes')
->groups(['api', 'functions'])
->desc('List the currently active function runtimes.')
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listRuntimes')
->label('sdk.description', '/docs/references/functions/list-runtimes.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_RUNTIME_LIST)
->inject('response')
->action(function ($response) {
/** @var Appwrite\Utopia\Response $response */
$runtimes = Config::getParam('runtimes');
$runtimes = array_map(function ($key) use ($runtimes) {
$runtimes[$key]['$id'] = $key;
return $runtimes[$key];
}, array_keys($runtimes));
$response->dynamic(new Document([
'sum' => count($runtimes),
'runtimes' => $runtimes
]), Response::MODEL_RUNTIME_LIST);
});
App::get('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Get Function')

View file

@ -1,5 +1,6 @@
<?php
use Appwrite\Auth\Auth;
use Utopia\App;
use Utopia\Exception;
use Utopia\Validator\ArrayList;
@ -58,6 +59,25 @@ App::post('/v1/storage/files')
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$read = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user
$write = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [];
// Users can only add their roles to files, API keys and Admin users can add any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($read as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
foreach ($write as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
}
$file = $request->getFiles('file');
/*
@ -563,13 +583,34 @@ App::put('/v1/storage/files/:fileId')
->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->inject('response')
->inject('dbForInternal')
->inject('user')
->inject('audits')
->inject('usage')
->action(function ($fileId, $read, $write, $response, $dbForInternal, $audits, $usage) {
->action(function ($fileId, $read, $write, $response, $dbForInternal, $user, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
$read = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user
$write = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [];
// Users can only add their roles to files, API keys and Admin users can add any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($read as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Read permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
foreach ($write as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Write permissions must be one of: ('.\implode(', ', $roles).')', 400);
}
}
}
$file = $dbForInternal->getDocument('files', $fileId);
if (empty($file->getId())) {

View file

@ -49,8 +49,8 @@ App::post('/v1/teams')
Authorization::disable();
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$teamId = $teamId == 'unique()' ? $dbForInternal->getId() : $teamId;
$team = $dbForInternal->createDocument('teams', new Document([
@ -293,8 +293,8 @@ App::post('/v1/teams/:teamId/memberships')
throw new Exception('SMTP Disabled', 503);
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$email = \strtolower($email);
$name = (empty($name)) ? $email : $name;
@ -566,8 +566,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception('User not found', 404);
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$isOwner = Authorization::isRole('team:'.$team->getId().'/owner');;
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)

View file

@ -695,7 +695,7 @@ App::delete('/v1/users/:userId/sessions')
$dbForInternal->deleteDocument('sessions', $session->getId());
}
$dbForInternal->updateDocument('users', $user->getId(), $user->getAttribute('sessions', []));
$dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
$events
->setParam('eventData', $response->output($user, Response::MODEL_USER))

View file

@ -5,7 +5,7 @@ require_once __DIR__.'/../init.php';
use Utopia\App;
use Utopia\Swoole\Request;
use Appwrite\Utopia\Response;
use Utopia\View;
use Appwrite\Utopia\View;
use Utopia\Exception;
use Utopia\Config\Config;
use Utopia\Domains\Domain;
@ -254,7 +254,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
if(!empty($service)) {
if(array_key_exists($service, $project->getAttribute('services',[]))
&& !$project->getAttribute('services',[])[$service]
&& !Auth::isPrivilegedUser(Authorization::$roles)) {
&& !Auth::isPrivilegedUser(Authorization::getRoles())) {
throw new Exception('Service is disabled', 503);
}
}
@ -298,7 +298,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
/** @var Utopia\Database\Document $project */
if ($error instanceof PDOException) {

View file

@ -64,8 +64,9 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
;
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (($abuse->check() // Route is rate-limited
&& App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not disabled
@ -128,8 +129,8 @@ App::init(function ($utopia, $request, $project) {
$route = $utopia->match($request);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles);
$isAppUser = Auth::isAppUser(Authorization::$roles);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
if($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
@ -166,7 +167,7 @@ App::init(function ($utopia, $request, $project) {
throw new Exception('JWT authentication is disabled for this project', 501);
}
break;
default:
throw new Exception('Unsupported authentication route');
break;
@ -187,7 +188,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
/** @var bool $mode */
if (!empty($events->getParam('event'))) {
if(empty($events->getParam('eventData'))) {
if (empty($events->getParam('eventData'))) {
$events->setParam('eventData', $response->getPayload());
}
@ -206,10 +207,10 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
if ($project->getId() !== 'console') {
$payload = new Document($response->getPayload());
$target = Realtime::fromPayload($events->getParam('event'), $payload);
$target = Realtime::fromPayload($events->getParam('event'), $payload, $project);
Realtime::send(
$project->getId(),
$target['projectId'] ?? $project->getId(),
$response->getPayload(),
$events->getParam('event'),
$target['channels'],

View file

@ -7,7 +7,7 @@ App::init(function ($utopia, $request, $response, $layout) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
/* AJAX check */
if (!empty($request->getQuery('version', ''))) {

View file

@ -1,14 +1,14 @@
<?php
use Appwrite\Utopia\View;
use Utopia\App;
use Utopia\View;
use Utopia\Config\Config;
use Utopia\Domains\Domain;
use Utopia\Database\Validator\UID;
use Utopia\Storage\Storage;
App::init(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$layout
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
@ -18,7 +18,7 @@ App::init(function ($layout) {
App::shutdown(function ($response, $layout) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$header = new View(__DIR__.'/../../views/console/comps/header.phtml');
$footer = new View(__DIR__.'/../../views/console/comps/footer.phtml');
@ -43,7 +43,7 @@ App::get('/error/:code')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
->inject('layout')
->action(function ($code, $layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/error.phtml');
@ -62,7 +62,7 @@ App::get('/console')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/index.phtml');
@ -81,7 +81,7 @@ App::get('/console/account')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/account/index.phtml');
@ -102,7 +102,7 @@ App::get('/console/notifications')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/v1/console/notifications/index.phtml');
@ -117,7 +117,7 @@ App::get('/console/home')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/home/index.phtml');
$page
@ -133,7 +133,7 @@ App::get('/console/settings')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
@ -157,7 +157,7 @@ App::get('/console/webhooks')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/webhooks/index.phtml');
@ -176,7 +176,7 @@ App::get('/console/keys')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$scopes = array_keys(Config::getParam('scopes'));
$page = new View(__DIR__.'/../../views/console/keys/index.phtml');
@ -194,7 +194,7 @@ App::get('/console/database')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/database/index.phtml');
@ -212,7 +212,7 @@ App::get('/console/database/collection')
->inject('layout')
->action(function ($id, $response, $layout) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$logs = new View(__DIR__.'/../../views/console/comps/logs.phtml');
@ -247,7 +247,7 @@ App::get('/console/database/document')
->param('collection', '', new UID(), 'Collection unique ID.')
->inject('layout')
->action(function ($collection, $layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$logs = new View(__DIR__.'/../../views/console/comps/logs.phtml');
@ -280,7 +280,7 @@ App::get('/console/database/document/new')
->param('collection', '', new UID(), 'Collection unique ID.')
->inject('layout')
->action(function ($collection, $layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/database/document.phtml');
@ -301,7 +301,7 @@ App::get('/console/storage')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/storage/index.phtml');
$page
@ -321,7 +321,7 @@ App::get('/console/users')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/users/index.phtml');
@ -342,7 +342,7 @@ App::get('/console/users/user')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/users/user.phtml');
@ -357,7 +357,7 @@ App::get('/console/users/teams/team')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/users/team.phtml');

View file

@ -3,15 +3,15 @@
use Appwrite\Specification\Format\OpenAPI3;
use Appwrite\Specification\Format\Swagger2;
use Appwrite\Specification\Specification;
use Appwrite\Utopia\View;
use Utopia\App;
use Utopia\View;
use Utopia\Config\Config;
use Utopia\Exception;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
App::init(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$header = new View(__DIR__.'/../../views/home/comps/header.phtml');
$footer = new View(__DIR__.'/../../views/home/comps/footer.phtml');
@ -32,7 +32,7 @@ App::init(function ($layout) {
App::shutdown(function ($response, $layout) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$response->html($layout->render());
}, ['response', 'layout'], 'home');
@ -76,7 +76,7 @@ App::get('/auth/signin')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/signin.phtml');
@ -95,7 +95,7 @@ App::get('/auth/signup')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/signup.phtml');
$page
@ -113,7 +113,7 @@ App::get('/auth/recovery')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/recovery.phtml');
@ -132,7 +132,7 @@ App::get('/auth/confirm')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/confirm.phtml');
@ -147,7 +147,7 @@ App::get('/auth/join')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/join.phtml');
@ -162,7 +162,7 @@ App::get('/auth/recovery/reset')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/recovery/reset.phtml');
@ -177,7 +177,7 @@ App::get('/auth/oauth2/success')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
@ -195,7 +195,7 @@ App::get('/auth/magic-url')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/magicURL.phtml');
@ -213,7 +213,7 @@ App::get('/auth/oauth2/failure')
->label('scope', 'home')
->inject('layout')
->action(function ($layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
@ -232,7 +232,7 @@ App::get('/error/:code')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
->inject('layout')
->action(function ($code, $layout) {
/** @var Utopia\View $layout */
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/error.phtml');

View file

@ -21,17 +21,14 @@ use Appwrite\Extend\PDO;
use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Database\Database as DatabaseOld;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Event\Event;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\IP;
use Appwrite\Network\Validator\URL;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Stats\Stats;
use Appwrite\Utopia\View;
use Utopia\App;
use Utopia\View;
use Utopia\Config\Config;
use Utopia\Locale\Locale;
use Utopia\Registry\Registry;
@ -76,8 +73,8 @@ const APP_STORAGE_FUNCTIONS = '/storage/functions';
const APP_STORAGE_CACHE = '/storage/cache';
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
const APP_STORAGE_CONFIG = '/storage/config';
const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite_io';
const APP_SOCIAL_TWITTER_HANDLE = 'appwrite_io';
const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite';
const APP_SOCIAL_TWITTER_HANDLE = 'appwrite';
const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite';
const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
@ -86,7 +83,7 @@ const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite';
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
// Database Worker Types
const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
@ -156,43 +153,6 @@ if(!empty($user) || !empty($pass)) {
Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '').':'.App::getEnv('_APP_REDIS_PORT', ''));
}
/**
* Old DB Filters
*/
DatabaseOld::addFilter('json',
function($value) {
if(!is_array($value)) {
return $value;
}
return json_encode($value);
},
function($value) {
return json_decode($value, true);
}
);
DatabaseOld::addFilter('encrypt',
function($value) {
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
return json_encode([
'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
'iv' => bin2hex($iv),
'tag' => bin2hex($tag),
'version' => '1',
]);
},
function($value) {
$value = json_decode($value, true);
$key = App::getEnv('_APP_OPENSSL_KEY_V'.$value['version']);
return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
}
);
/**
* New DB Filters
*/

View file

@ -5,10 +5,10 @@ global $cli;
use Appwrite\Auth\Auth;
use Appwrite\Docker\Compose;
use Appwrite\Docker\Env;
use Appwrite\Utopia\View;
use Utopia\Analytics\GoogleAnalytics;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\View;
use Utopia\Validator\Text;
$cli

View file

@ -180,7 +180,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
->setShareText('Appwrite is a backend as a service for building web or mobile apps')
->setShareURL('http://appwrite.io')
->setShareTags('JS,javascript,reactjs,angular,ios,android,serverless')
->setShareVia('appwrite_io')
->setShareVia('appwrite')
->setWarning($warning)
->setReadme($readme)
->setGettingStarted($gettingStarted)

View file

@ -467,8 +467,8 @@ $logs = $this->getParam('logs', null);
<div class="col span-1"><input name="permission" value="document" type="radio" class="margin-top-no" data-ls-bind="{{project-collection.permission}}" /></div>
<div class="col span-11">
<b>Document Level</b>
<p class="text-fade margin-top-tiny">With Document Level permissions, you have granular access control over every document, and users will only be able to access documents for which they have explicit permissions.</p>
<p class="text-fade margin-top-tiny">Document permissions are required in this permission model.</p>
<p class="text-fade margin-top-tiny">With Document Level permissions, you have granular access control over every file. Users will only be able to access documents for which they have explicit permissions.</p>
<p class="text-fade margin-top-tiny">In this permission level, document permissions take precedence and bucket permissions are ignored.</p>
</div>
</div>
@ -476,8 +476,8 @@ $logs = $this->getParam('logs', null);
<div class="col span-1"><input name="permission" value="collection" type="radio" class="margin-top-tiny" data-ls-bind="{{project-collection.permission}}" /></div>
<div class="col span-11">
<b>Collection Level</b>
<p class="text-fade margin-top-tiny">With Collection Level permissions, you assign permissions once for every document in the collection - users with read access to the collection can see all documents.</p>
<p class="text-fade margin-top-tiny">Collection permissions are required in this permission model, and document permissions are optional.</p>
<p class="text-fade margin-top-tiny">With Collection Level permissions, you assign permissions only once in the collection.</p>
<p class="text-fade margin-top-tiny">In this permission level, permissions assigned to collection takes the precedence and documents permissions are ignored</p>
<div data-ls-if="{{project-collection.permission}} === 'collection'">
<label for="collection-read">Read Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label>
<input type="hidden" id="collection-read" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{project-collection.$read}}" placeholder="User ID, Team ID or Role" />

View file

@ -1,6 +1,6 @@
<?php
use Utopia\View;
use Appwrite\Utopia\View;
$collection = $this->getParam('collection', null);
$collections = $this->getParam('collections', []);

View file

@ -10,7 +10,7 @@ $litespeed = $this->getParam('litespeed', true);
$analytics = $this->getParam('analytics', 'UA-26264668-9');
$mode = $this->getParam('mode', '');
$canonical = $this->getParam('canonical', '');
$image = $this->getParam('image', '/images/logo.png');
$image = $this->getParam('image', '/images/logo.png');
$locale = $this->getParam('locale', null);
$runtimes = $this->getParam('runtimes', null);
@ -53,12 +53,17 @@ if(!empty($platforms)) {
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5" />
<meta name="theme-color" content="#f02e65">
<meta property="og:type" content="website" />
<meta property="og:site_name" content="<?php echo APP_NAME; ?>">
<meta property="og:title" content="<?php echo $this->escape($this->getParam('title', '')); ?>" />
<meta property="og:description" content="<?php echo $this->escape($this->getParam('description', '')); ?>" />
<?php if (!empty($canonical)): ?>
<meta property="og:url" content="<?php echo $this->escape($canonical); ?>" />
<?php endif; ?>
<meta property="og:image" content="<?php echo $this->escape($endpoint); ?><?php echo $this->escape($image); ?>?v=<?php echo APP_CACHE_BUSTER; ?>" />
<meta name="twitter:site" content="@<?php echo APP_SOCIAL_TWITTER_HANDLE; ?>">
<meta name="twitter:title" content="<?php echo $this->escape($this->getParam('title', '')); ?>">
<meta name="twitter:image:src" content="<?php echo $this->escape($endpoint); ?><?php echo $this->escape($image); ?>?v=<?php echo APP_CACHE_BUSTER; ?>">
<meta name="twitter:card" content="summary_large_image">
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){

View file

@ -1,5 +1,6 @@
<?php
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Resque\Worker;
use Utopia\CLI\Console;
use Utopia\Database\Document;
@ -33,7 +34,7 @@ class DatabaseV1 extends Worker
if($document->isEmpty()) {
throw new Exception('Missing document');
}
switch (strval($type)) {
case DATABASE_TYPE_CREATE_ATTRIBUTE:
$this->createAttribute($collection, $document, $projectId);
@ -67,9 +68,11 @@ class DatabaseV1 extends Worker
*/
protected function createAttribute(Document $collection, Document $attribute, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$event = 'database.attributes.update';
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');
$type = $attribute->getAttribute('type', '');
@ -81,6 +84,7 @@ class DatabaseV1 extends Worker
$format = $attribute->getAttribute('format', '');
$formatOptions = $attribute->getAttribute('formatOptions', []);
$filters = $attribute->getAttribute('filters', []);
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if(!$dbForExternal->createAttribute($collectionId, $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
@ -90,6 +94,20 @@ class DatabaseV1 extends Worker
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed'));
} finally {
$target = Realtime::fromPayload($event, $attribute, $project);
Realtime::send(
projectId: 'console',
payload: $attribute->getArrayCopy(),
event: $event,
channels: $target['channels'],
roles: $target['roles'],
options: [
'projectId' => $projectId,
'collectionId' => $collection->getId()
]
);
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
@ -102,11 +120,15 @@ class DatabaseV1 extends Worker
*/
protected function deleteAttribute(Document $collection, Document $attribute, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$event = 'database.attributes.delete';
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');
$status = $attribute->getAttribute('status', '');
$project = $dbForConsole->getDocument('projects', $projectId);
// possible states at this point:
// - available: should not land in queue; controller flips these to 'deleting'
@ -122,6 +144,20 @@ class DatabaseV1 extends Worker
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'stuck'));
} finally {
$target = Realtime::fromPayload($event, $attribute, $project);
Realtime::send(
projectId: 'console',
payload: $attribute->getArrayCopy(),
event: $event,
channels: $target['channels'],
roles: $target['roles'],
options: [
'projectId' => $projectId,
'collectionId' => $collection->getId()
]
);
}
// The underlying database removes/rebuilds indexes when attribute is removed
@ -185,15 +221,18 @@ class DatabaseV1 extends Worker
*/
protected function createIndex(Document $collection, Document $index, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$event = 'database.indexes.update';
$collectionId = $collection->getId();
$key = $index->getAttribute('key', '');
$type = $index->getAttribute('type', '');
$attributes = $index->getAttribute('attributes', []);
$lengths = $index->getAttribute('lengths', []);
$orders = $index->getAttribute('orders', []);
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if(!$dbForExternal->createIndex($collectionId, $key, $type, $attributes, $lengths, $orders)) {
@ -203,6 +242,20 @@ class DatabaseV1 extends Worker
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed'));
} finally {
$target = Realtime::fromPayload($event, $index, $project);
Realtime::send(
projectId: 'console',
payload: $index->getArrayCopy(),
event: $event,
channels: $target['channels'],
roles: $target['roles'],
options: [
'projectId' => $projectId,
'collectionId' => $collection->getId()
]
);
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
@ -215,12 +268,15 @@ class DatabaseV1 extends Worker
*/
protected function deleteIndex(Document $collection, Document $index, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $collection->getId();
$key = $index->getAttribute('key');
$status = $index->getAttribute('status', '');
$event = 'database.indexes.delete';
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if($status !== 'failed' && !$dbForExternal->deleteIndex($collectionId, $key)) {
@ -230,6 +286,20 @@ class DatabaseV1 extends Worker
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'stuck'));
} finally {
$target = Realtime::fromPayload($event, $index, $project);
Realtime::send(
projectId: 'console',
payload: $index->getArrayCopy(),
event: $event,
channels: $target['channels'],
roles: $target['roles'],
options: [
'projectId' => $projectId,
'collectionId' => $collection->getId()
]
);
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);

View file

@ -51,7 +51,7 @@
"utopia-php/registry": "0.5.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/domains": "1.1.*",
"utopia-php/swoole": "0.2.*",
"utopia-php/swoole": "0.3.*",
"utopia-php/storage": "0.5.*",
"utopia-php/websocket": "0.0.*",
"utopia-php/image": "0.5.*",

245
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7e24a95bc534ed39b042f19b27268de9",
"content-hash": "9dcf48d4173daea87c60b464b104cd22",
"packages": [
{
"name": "adhocore/jwt",
@ -489,16 +489,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.4.0",
"version": "7.4.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94"
"reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/868b3571a039f0ebc11ac8f344f4080babe2cb94",
"reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79",
"reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79",
"shasum": ""
},
"require": {
@ -507,7 +507,7 @@
"guzzlehttp/psr7": "^1.8.3 || ^2.1",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2"
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
@ -593,7 +593,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.4.0"
"source": "https://github.com/guzzle/guzzle/tree/7.4.1"
},
"funding": [
{
@ -609,7 +609,7 @@
"type": "tidelift"
}
],
"time": "2021-10-18T09:52:00+00:00"
"time": "2021-12-06T18:43:05+00:00"
},
{
"name": "guzzlehttp/promises",
@ -1591,25 +1591,25 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.5.0",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8"
"reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8",
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced",
"reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=8.0.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.5-dev"
"dev-main": "3.0-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1638,7 +1638,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0"
},
"funding": [
{
@ -1654,7 +1654,7 @@
"type": "tidelift"
}
],
"time": "2021-07-12T14:48:14+00:00"
"time": "2021-11-01T23:48:49+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -2138,16 +2138,16 @@
},
{
"name": "utopia-php/database",
"version": "0.12.0",
"version": "0.12.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "102ee1d21fd55fc92dc7a07b60672a98ae49be26"
"reference": "af512b7a00cc7c6e30fa03efbc5fd7e77a93e2df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/102ee1d21fd55fc92dc7a07b60672a98ae49be26",
"reference": "102ee1d21fd55fc92dc7a07b60672a98ae49be26",
"url": "https://api.github.com/repos/utopia-php/database/zipball/af512b7a00cc7c6e30fa03efbc5fd7e77a93e2df",
"reference": "af512b7a00cc7c6e30fa03efbc5fd7e77a93e2df",
"shasum": ""
},
"require": {
@ -2155,7 +2155,7 @@
"ext-pdo": "*",
"ext-redis": "*",
"mongodb/mongodb": "1.8.0",
"php": ">=7.1",
"php": ">=8.0",
"utopia-php/cache": "0.4.*",
"utopia-php/framework": "0.*.*"
},
@ -2195,9 +2195,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.12.0"
"source": "https://github.com/utopia-php/database/tree/0.12.1"
},
"time": "2021-11-24T14:53:22+00:00"
"time": "2021-12-13T14:57:32+00:00"
},
{
"name": "utopia-php/domains",
@ -2255,24 +2255,24 @@
},
{
"name": "utopia-php/framework",
"version": "0.19.1",
"version": "0.19.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "cc7629b5f7a8f45912ec2e069b7f14e361e41c34"
"reference": "49e4374b97c0f4d14bc84b424bdc9c3b7747e15f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/cc7629b5f7a8f45912ec2e069b7f14e361e41c34",
"reference": "cc7629b5f7a8f45912ec2e069b7f14e361e41c34",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/49e4374b97c0f4d14bc84b424bdc9c3b7747e15f",
"reference": "49e4374b97c0f4d14bc84b424bdc9c3b7747e15f",
"shasum": ""
},
"require": {
"php": ">=7.3.0"
"php": ">=8.0.0"
},
"require-dev": {
"phpunit/phpunit": "^9.4",
"vimeo/psalm": "4.0.1"
"phpunit/phpunit": "^9.5.10",
"vimeo/psalm": "4.13.1"
},
"type": "library",
"autoload": {
@ -2298,9 +2298,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.19.1"
"source": "https://github.com/utopia-php/framework/tree/0.19.2"
},
"time": "2021-11-25T16:11:40+00:00"
"time": "2021-12-07T09:29:35+00:00"
},
{
"name": "utopia-php/image",
@ -2567,16 +2567,16 @@
},
{
"name": "utopia-php/storage",
"version": "0.5.0",
"version": "0.5.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "92ae20c7a2ac329f573a58a82dc245134cc63408"
"reference": "e672aa3fc2a8ba689aff65f68ff29f1d608223b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/92ae20c7a2ac329f573a58a82dc245134cc63408",
"reference": "92ae20c7a2ac329f573a58a82dc245134cc63408",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/e672aa3fc2a8ba689aff65f68ff29f1d608223b8",
"reference": "e672aa3fc2a8ba689aff65f68ff29f1d608223b8",
"shasum": ""
},
"require": {
@ -2613,33 +2613,33 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.5.0"
"source": "https://github.com/utopia-php/storage/tree/0.5.1"
},
"time": "2021-04-15T16:43:12+00:00"
"time": "2021-12-13T15:17:14+00:00"
},
{
"name": "utopia-php/swoole",
"version": "0.2.4",
"version": "0.3.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/swoole.git",
"reference": "37d8c64b536d6bc7da4f0f5a934a0ec44885abf4"
"reference": "2b714eddf77cd5eda1889219c9656d7c0a63ce73"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/37d8c64b536d6bc7da4f0f5a934a0ec44885abf4",
"reference": "37d8c64b536d6bc7da4f0f5a934a0ec44885abf4",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/2b714eddf77cd5eda1889219c9656d7c0a63ce73",
"reference": "2b714eddf77cd5eda1889219c9656d7c0a63ce73",
"shasum": ""
},
"require": {
"ext-swoole": "*",
"php": ">=7.4",
"php": ">=8.0",
"utopia-php/framework": "0.*.*"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"swoole/ide-helper": "4.5.5",
"vimeo/psalm": "4.0.1"
"swoole/ide-helper": "4.8.3",
"vimeo/psalm": "4.15.0"
},
"type": "library",
"autoload": {
@ -2669,9 +2669,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/swoole/issues",
"source": "https://github.com/utopia-php/swoole/tree/0.2.4"
"source": "https://github.com/utopia-php/swoole/tree/0.3.2"
},
"time": "2021-06-22T10:49:24+00:00"
"time": "2021-12-13T15:37:41+00:00"
},
{
"name": "utopia-php/system",
@ -3061,6 +3061,77 @@
},
"time": "2021-11-12T11:09:38+00:00"
},
{
"name": "composer/pcre",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "3d322d715c43a1ac36c7fe215fa59336265500f2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/3d322d715c43a1ac36c7fe215fa59336265500f2",
"reference": "3d322d715c43a1ac36c7fe215fa59336265500f2",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1",
"phpstan/phpstan-strict-rules": "^1.1",
"symfony/phpunit-bridge": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/1.0.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2021-12-06T15:17:27+00:00"
},
{
"name": "composer/semver",
"version": "3.2.6",
@ -3144,25 +3215,27 @@
},
{
"name": "composer/xdebug-handler",
"version": "2.0.2",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
"reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339"
"reference": "6555461e76962fd0379c444c46fd558a0fcfb65e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/84674dd3a7575ba617f5a76d7e9e29a7d3891339",
"reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6555461e76962fd0379c444c46fd558a0fcfb65e",
"reference": "6555461e76962fd0379c444c46fd558a0fcfb65e",
"shasum": ""
},
"require": {
"composer/pcre": "^1",
"php": "^5.3.2 || ^7.0 || ^8.0",
"psr/log": "^1 || ^2 || ^3"
},
"require-dev": {
"phpstan/phpstan": "^0.12.55",
"symfony/phpunit-bridge": "^4.2 || ^5"
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-strict-rules": "^1.1",
"symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0"
},
"type": "library",
"autoload": {
@ -3188,7 +3261,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/xdebug-handler/issues",
"source": "https://github.com/composer/xdebug-handler/tree/2.0.2"
"source": "https://github.com/composer/xdebug-handler/tree/2.0.3"
},
"funding": [
{
@ -3204,7 +3277,7 @@
"type": "tidelift"
}
],
"time": "2021-07-31T17:03:58+00:00"
"time": "2021-12-08T13:07:32+00:00"
},
{
"name": "dnoegel/php-xdg-base-dir",
@ -4035,16 +4108,16 @@
},
{
"name": "phpspec/prophecy",
"version": "1.14.0",
"version": "v1.15.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e"
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
"reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
"shasum": ""
},
"require": {
@ -4096,22 +4169,22 @@
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
"source": "https://github.com/phpspec/prophecy/tree/1.14.0"
"source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
},
"time": "2021-09-10T09:02:12+00:00"
"time": "2021-12-08T12:19:24+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.9",
"version": "9.2.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b"
"reference": "d5850aaf931743067f4bfc1ae4cbd06468400687"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f301eb1453c9e7a1bc912ee8b0ea9db22c60223b",
"reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687",
"reference": "d5850aaf931743067f4bfc1ae4cbd06468400687",
"shasum": ""
},
"require": {
@ -4167,7 +4240,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.9"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10"
},
"funding": [
{
@ -4175,20 +4248,20 @@
"type": "github"
}
],
"time": "2021-11-19T15:21:02+00:00"
"time": "2021-12-05T09:12:13+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "3.0.5",
"version": "3.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "aa4be8575f26070b100fccb67faabb28f21f66f8"
"reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8",
"reference": "aa4be8575f26070b100fccb67faabb28f21f66f8",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
"reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
"shasum": ""
},
"require": {
@ -4227,7 +4300,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5"
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
},
"funding": [
{
@ -4235,7 +4308,7 @@
"type": "github"
}
],
"time": "2020-09-28T05:57:25+00:00"
"time": "2021-12-02T12:48:52+00:00"
},
{
"name": "phpunit/php-invoker",
@ -5592,23 +5665,23 @@
},
{
"name": "symfony/console",
"version": "v5.4.0",
"version": "v5.4.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "ec3661faca1d110d6c307e124b44f99ac54179e3"
"reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/ec3661faca1d110d6c307e124b44f99ac54179e3",
"reference": "ec3661faca1d110d6c307e124b44f99ac54179e3",
"url": "https://api.github.com/repos/symfony/console/zipball/9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4",
"reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.8",
"symfony/polyfill-php73": "^1.9",
"symfony/polyfill-php80": "^1.16",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/string": "^5.1|^6.0"
@ -5671,7 +5744,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.4.0"
"source": "https://github.com/symfony/console/tree/v5.4.1"
},
"funding": [
{
@ -5687,7 +5760,7 @@
"type": "tidelift"
}
],
"time": "2021-11-29T15:30:56+00:00"
"time": "2021-12-09T11:22:43+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
@ -6097,16 +6170,16 @@
},
{
"name": "symfony/string",
"version": "v6.0.0",
"version": "v6.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "ba727797426af0f587f4800566300bdc0cda0777"
"reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/ba727797426af0f587f4800566300bdc0cda0777",
"reference": "ba727797426af0f587f4800566300bdc0cda0777",
"url": "https://api.github.com/repos/symfony/string/zipball/0cfed595758ec6e0a25591bdc8ca733c1896af32",
"reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32",
"shasum": ""
},
"require": {
@ -6162,7 +6235,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.0.0"
"source": "https://github.com/symfony/string/tree/v6.0.1"
},
"funding": [
{
@ -6178,7 +6251,7 @@
"type": "tidelift"
}
],
"time": "2021-10-29T07:35:21+00:00"
"time": "2021-12-08T15:13:44+00:00"
},
{
"name": "textalk/websocket",

View file

@ -0,0 +1 @@
Get a list of all runtimes that are currently active in your project.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 495 KiB

View file

@ -57,10 +57,27 @@ window.addEventListener("load", async () => {
const realtime = window.ls.container.get('realtime');
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
let current = {};
window.ls.container.get('console').subscribe('project', event => {
for (let project in event.payload) {
current[project] = event.payload[project] ?? 0;
window.ls.container.get('console').subscribe(['project', 'console'], response => {
switch (response.event) {
case 'stats.connections':
for (let project in response.payload) {
current[project] = response.payload[project] ?? 0;
}
break;
case 'database.attributes.create':
case 'database.attributes.update':
case 'database.attributes.delete':
document.dispatchEvent(new CustomEvent('database.createAttribute'));
break;
case 'database.indexes.create':
case 'database.indexes.update':
case 'database.indexes.delete':
document.dispatchEvent(new CustomEvent('database.createIndex'));
break;
}
});
while (true) {

View file

@ -242,9 +242,9 @@ class Auth
public static function isPrivilegedUser(array $roles): bool
{
if (
array_key_exists('role:'.self::USER_ROLE_OWNER, $roles) ||
array_key_exists('role:'.self::USER_ROLE_DEVELOPER, $roles) ||
array_key_exists('role:'.self::USER_ROLE_ADMIN, $roles)
in_array('role:'.self::USER_ROLE_OWNER, $roles) ||
in_array('role:'.self::USER_ROLE_DEVELOPER, $roles) ||
in_array('role:'.self::USER_ROLE_ADMIN, $roles)
) {
return true;
}
@ -261,7 +261,7 @@ class Auth
*/
public static function isAppUser(array $roles): bool
{
if (array_key_exists('role:'.self::USER_ROLE_APP, $roles)) {
if (in_array('role:'.self::USER_ROLE_APP, $roles)) {
return true;
}
@ -278,7 +278,7 @@ class Auth
{
$roles = [];
if (!self::isPrivilegedUser(Authorization::$roles) && !self::isAppUser(Authorization::$roles)) {
if (!self::isPrivilegedUser(Authorization::getRoles()) && !self::isAppUser(Authorization::getRoles())) {
if ($user->getId()) {
$roles[] = 'user:'.$user->getId();
$roles[] = 'role:'.Auth::USER_ROLE_MEMBER;

View file

@ -18,7 +18,7 @@ class Password extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'Password must be at least 8 characters';
}
@ -30,7 +30,7 @@ class Password extends Validator
*
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
if (!\is_string($value)) {
return false;

View file

@ -46,7 +46,7 @@ class Authorization extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return $this->message;
}
@ -60,7 +60,7 @@ class Authorization extends Validator
*
* @return bool
*/
public function isValid($permissions)
public function isValid($permissions): bool
{
if (!self::$status) {
return true;

View file

@ -39,7 +39,7 @@ class Collection extends Structure
*
* @return bool
*/
public function isValid($document)
public function isValid($document): bool
{
$document = new Document(
\array_merge($this->merge, ($document instanceof Document) ? $document->getArrayCopy() : $document)

View file

@ -13,7 +13,7 @@ class CustomId extends Key {
*
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
return $value == 'unique()' || parent::isValid($value);

View file

@ -42,7 +42,7 @@ class DocumentId extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return $this->message;
}
@ -56,7 +56,7 @@ class DocumentId extends Validator
*
* @return bool
*/
public function isValid($id)
public function isValid($id): bool
{
$document = $this->database->getDocument($id);

View file

@ -18,7 +18,7 @@ class Key extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return $this->message;
}
@ -32,7 +32,7 @@ class Key extends Validator
*
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
if (!\is_string($value)) {
return false;

View file

@ -34,7 +34,7 @@ class Permissions extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return $this->message;
}
@ -48,7 +48,7 @@ class Permissions extends Validator
*
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
if (!\is_array($value) && !empty($value)) {
$this->message = 'Invalid permissions data structure';

View file

@ -109,7 +109,7 @@ class Structure extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'Invalid document structure: '.$this->message;
}
@ -123,7 +123,7 @@ class Structure extends Validator
*
* @return bool
*/
public function isValid($document)
public function isValid($document): bool
{
$document = (\is_array($document)) ? new Document($document) : $document;

View file

@ -13,7 +13,7 @@ class UID extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'Invalid UID format';
}
@ -27,7 +27,7 @@ class UID extends Validator
*
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
if ($value === 0) { // TODO Deprecate confition when we get the chance.
return true;

View file

@ -234,16 +234,18 @@ class Realtime extends Adapter
/**
* Create channels array based on the event name and payload.
*
*
* @param string $event
* @param Document $payload
* @param Document|null $project
* @return array
*/
public static function fromPayload(string $event, Document $payload): array
public static function fromPayload(string $event, Document $payload, Document $project = null): array
{
$channels = [];
$roles = [];
$permissionsChanged = false;
$projectId = null;
switch (true) {
case strpos($event, 'account.recovery.') === 0:
@ -279,6 +281,13 @@ class Realtime extends Adapter
$channels[] = 'collections.' . $payload->getId();
$roles = $payload->getRead();
break;
case strpos($event, 'database.attributes.') === 0:
case strpos($event, 'database.indexes.') === 0:
$channels[] = 'console';
$projectId = 'console';
$roles = ['team:' . $project->getAttribute('teamId')];
break;
case strpos($event, 'database.documents.') === 0:
$channels[] = 'documents';
@ -306,7 +315,8 @@ class Realtime extends Adapter
return [
'channels' => $channels,
'roles' => $roles,
'permissionsChanged' => $permissionsChanged
'permissionsChanged' => $permissionsChanged,
'projectId' => $projectId
];
}
}

View file

@ -22,7 +22,7 @@ class CNAME extends Validator
/**
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'Invalid CNAME record';
}
@ -34,7 +34,7 @@ class CNAME extends Validator
*
* @return bool
*/
public function isValid($domain)
public function isValid($domain): bool
{
if (!is_string($domain)) {
return false;

View file

@ -20,7 +20,7 @@ class Domain extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'Value must be a valid domain';
}
@ -35,7 +35,7 @@ class Domain extends Validator
* @param mixed $value
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
if (empty($value)) {
return false;

View file

@ -20,7 +20,7 @@ class Email extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'Value must be a valid email address';
}
@ -33,7 +33,7 @@ class Email extends Validator
* @param mixed $value
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
if (!\filter_var($value, FILTER_VALIDATE_EMAIL)) {
return false;

View file

@ -30,7 +30,7 @@ class Host extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'URL host must be one of: ' . \implode(', ', $this->whitelist);
}
@ -43,7 +43,7 @@ class Host extends Validator
* @param mixed $value
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
$urlValidator = new URL();

View file

@ -46,7 +46,7 @@ class IP extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'Value must be a valid IP address';
}
@ -59,7 +59,7 @@ class IP extends Validator
* @param mixed $value
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
switch ($this->type) {
case self::ALL:

View file

@ -84,7 +84,7 @@ class Origin extends Validator
}
}
public function getDescription()
public function getDescription(): string
{
if (!\array_key_exists($this->client, $this->platforms)) {
return 'Unsupported platform';
@ -102,7 +102,7 @@ class Origin extends Validator
*
* @return bool
*/
public function isValid($origin)
public function isValid($origin): bool
{
if (!is_string($origin)) {
return false;

View file

@ -20,7 +20,7 @@ class URL extends Validator
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return 'Value must be a valid URL';
}
@ -33,7 +33,7 @@ class URL extends Validator
* @param mixed $value
* @return bool
*/
public function isValid($value)
public function isValid($value): bool
{
if (\filter_var($value, FILTER_VALIDATE_URL) === false) {
return false;

View file

@ -2,8 +2,8 @@
namespace Appwrite\Template;
use Appwrite\Utopia\View;
use Exception;
use Utopia\View;
class Template extends View
{
@ -63,7 +63,7 @@ class Template extends View
*
* @throws Exception
*/
public function render($minify = true)
public function render($minify = true): string
{
if ($this->rendered) { // Don't render any template
return '';

View file

@ -33,7 +33,6 @@ use Appwrite\Utopia\Response\Model\Execution;
use Appwrite\Utopia\Response\Model\File;
use Appwrite\Utopia\Response\Model\Func;
use Appwrite\Utopia\Response\Model\Index;
use Appwrite\Utopia\Response\Model\FuncPermissions;
use Appwrite\Utopia\Response\Model\JWT;
use Appwrite\Utopia\Response\Model\Key;
use Appwrite\Utopia\Response\Model\Language;
@ -54,6 +53,7 @@ use Appwrite\Utopia\Response\Model\Token;
use Appwrite\Utopia\Response\Model\Webhook;
use Appwrite\Utopia\Response\Model\Preferences;
use Appwrite\Utopia\Response\Model\Mock; // Keep last
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
use Appwrite\Utopia\Response\Model\UsageDatabase;
@ -141,6 +141,8 @@ class Response extends SwooleResponse
// Functions
const MODEL_FUNCTION = 'function';
const MODEL_FUNCTION_LIST = 'functionList';
const MODEL_RUNTIME = 'runtime';
const MODEL_RUNTIME_LIST = 'runtimeList';
const MODEL_TAG = 'tag';
const MODEL_TAG_LIST = 'tagList';
const MODEL_EXECUTION = 'execution';
@ -201,6 +203,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM))
->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP))
->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION))
->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME))
->setModel(new BaseList('Tags List', self::MODEL_TAG_LIST, 'tags', self::MODEL_TAG))
->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION))
->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false))
@ -239,7 +242,7 @@ class Response extends SwooleResponse
->setModel(new Team())
->setModel(new Membership())
->setModel(new Func())
->setModel(new FuncPermissions())
->setModel(new Runtime())
->setModel(new Tag())
->setModel(new Execution())
->setModel(new Project())

View file

@ -17,10 +17,10 @@ class Func extends Model
'example' => '5e5ea5c16897e',
])
->addRule('execute', [
'type' => Response::MODEL_FUNC_PERMISSIONS,
'description' => 'Function permissions.',
'default' => new \stdClass,
'example' => new \stdClass,
'type' => self::TYPE_STRING,
'description' => 'Execution permissions.',
'default' => '',
'example' => 'role:member',
'array' => false,
])
->addRule('name', [

View file

@ -1,42 +0,0 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class FuncPermissions extends Model
{
public function __construct()
{
$this
->addRule('execute', [
'type' => self::TYPE_STRING,
'description' => 'Execution permissions.',
'default' => [],
'example' => 'user:5e5ea5c16897e',
'array' => true,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'FuncPermissions';
}
/**
* Get Collection
*
* @return string
*/
public function getType():string
{
return Response::MODEL_FUNC_PERMISSIONS;
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Runtime extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Runtime ID.',
'default' => '',
'example' => 'python-3.8',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Runtime Name.',
'default' => '',
'example' => 'Python'
])
->addRule('version', [
'type' => self::TYPE_STRING,
'description' => 'Runtime version.',
'default' => '',
'example' => '3.8',
])
->addRule('base', [
'type' => self::TYPE_STRING,
'description' => 'Base Docker image used to build the runtime.',
'default' => '',
'example' => 'python:3.8-alpine',
])
->addRule('image', [
'type' => self::TYPE_STRING,
'description' => 'Image name of Docker Hub.',
'default' => '',
'example' => 'appwrite\/runtime-for-python:3.8',
])
->addRule('logo', [
'type' => self::TYPE_STRING,
'description' => 'Name of the logo image.',
'default' => '',
'example' => 'python.png',
])
->addRule('supports', [
'type' => self::TYPE_STRING,
'description' => 'List of supported architectures.',
'default' => '',
'example' => 'amd64',
'array' => true,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'Runtime';
}
/**
* Get Type
*
* @return string
*/
public function getType():string
{
return Response::MODEL_RUNTIME;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Appwrite\Utopia;
use Utopia\View as OldView;
class View extends OldView
{
/**
* Escape
*
* Convert all applicable characters to HTML entities
*
* @param string $str
* @return string
* @deprecated Use print method with escape filter
*/
public function escape($str)
{
return \htmlentities($str, ENT_QUOTES, 'UTF-8');
}
}

View file

@ -795,4 +795,27 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']);
}
public function testGetRuntimes()
{
$runtimes = $this->client->call(Client::METHOD_GET, '/functions/runtimes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $runtimes['headers']['status-code']);
$this->assertGreaterThan(0, $runtimes['body']['sum']);
$runtime = $runtimes['body']['runtimes'][0];
$this->assertArrayHasKey('$id', $runtime);
$this->assertArrayHasKey('name', $runtime);
$this->assertArrayHasKey('version', $runtime);
$this->assertArrayHasKey('logo', $runtime);
$this->assertArrayHasKey('image', $runtime);
$this->assertArrayHasKey('base', $runtime);
$this->assertArrayHasKey('supports', $runtime);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,276 @@
<?php
namespace Tests\E2E\Services\Realtime;
use Exception;
use SebastianBergmann\RecursionContext\InvalidArgumentException;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\Exception as FrameworkException;
use Tests\E2E\Client;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\SideConsole;
use WebSocket\BadOpcodeException;
use WebSocket\ConnectionException;
use WebSocket\TimeoutException;
class RealtimeConsoleClientTest extends Scope
{
use RealtimeBase;
use ProjectCustom;
use SideConsole;
public function testAttributes()
{
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = 'console';
$client = $this->getWebsocket(['console'], [
'origin' => 'http://localhost',
'cookie' => 'a_session_console='. $this->getRoot()['session'],
], $projectId);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('connected', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
/**
* Test Attributes
*/
$actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'collectionId' => 'unique()',
'name' => 'Actors',
'read' => ['role:all'],
'write' => ['role:all'],
'permission' => 'collection'
]);
$data = ['actorsId' => $actors['body']['$id']];
$name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'attributeId' => 'name',
'size' => 256,
'required' => true,
]);
$this->assertEquals($name['headers']['status-code'], 201);
$this->assertEquals($name['body']['key'], 'name');
$this->assertEquals($name['body']['type'], 'string');
$this->assertEquals($name['body']['size'], 256);
$this->assertEquals($name['body']['required'], true);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertEquals('database.attributes.create', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertEquals('processing', $response['data']['payload']['status']);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertEquals('database.attributes.update', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertEquals('available', $response['data']['payload']['status']);
$client->close();
return $data;
}
/**
* @depends testAttributes
*/
public function testIndexes(array $data)
{
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = 'console';
$client = $this->getWebsocket(['console'], [
'origin' => 'http://localhost',
'cookie' => 'a_session_console='. $this->getRoot()['session'],
], $projectId);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('connected', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
/**
* Test Indexes
*/
$index = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/indexes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'indexId' => 'key_name',
'type' => 'key',
'attributes' => [
'name',
],
]);
$this->assertEquals($index['headers']['status-code'], 201);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertEquals('database.indexes.create', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertEquals('processing', $response['data']['payload']['status']);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertEquals('database.indexes.update', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertEquals('available', $response['data']['payload']['status']);
$client->close();
return $data;
}
/**
* @depends testIndexes
*/
public function testDeleteIndex(array $data)
{
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = 'console';
$client = $this->getWebsocket(['console'], [
'origin' => 'http://localhost',
'cookie' => 'a_session_console='. $this->getRoot()['session'],
], $projectId);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('connected', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
/**
* Test Delete Index
*/
$attribute = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/indexes/key_name', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($attribute['headers']['status-code'], 204);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertEquals('database.indexes.delete', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
$client->close();
return $data;
}
/**
* @depends testDeleteIndex
*/
public function testDeleteAttribute(array $data)
{
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = 'console';
$client = $this->getWebsocket(['console'], [
'origin' => 'http://localhost',
'cookie' => 'a_session_console='. $this->getRoot()['session'],
], $projectId);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('connected', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
/**
* Test Delete Attribute
*/
$attribute = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/attributes/name', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($attribute['headers']['status-code'], 204);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertEquals('database.attributes.delete', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
$client->close();
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,9 @@
namespace Tests\E2E\Services\Storage;
use CURLFile;
use Exception;
use SebastianBergmann\RecursionContext\InvalidArgumentException;
use PHPUnit\Framework\ExpectationFailedException;
use Tests\E2E\Client;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\ProjectCustom;
@ -14,7 +17,7 @@ class StorageCustomClientTest extends Scope
use ProjectCustom;
use SideClient;
public function testCreateFileDefaultPermissions():void
public function testCreateFileDefaultPermissions(): array
{
/**
* Test for SUCCESS
@ -36,5 +39,111 @@ class StorageCustomClientTest extends Scope
$this->assertEquals('permissions.png', $file['body']['name']);
$this->assertEquals('image/png', $file['body']['mimeType']);
$this->assertEquals(47218, $file['body']['sizeOriginal']);
return $file['body'];
}
public function testCreateFileAbusePermissions(): void
{
/**
* Test for FAILURE
*/
$file = $this->client->call(Client::METHOD_POST, '/storage/files', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'fileId' => 'unique()',
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
'folderId' => 'xyz',
'read' => ['user:notme']
]);
$this->assertEquals($file['headers']['status-code'], 400);
$this->assertStringStartsWith('Read permissions must be one of:', $file['body']['message']);
$this->assertStringContainsString('role:all', $file['body']['message']);
$this->assertStringContainsString('role:member', $file['body']['message']);
$this->assertStringContainsString('user:'.$this->getUser()['$id'], $file['body']['message']);
$file = $this->client->call(Client::METHOD_POST, '/storage/files', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'fileId' => 'unique()',
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
'folderId' => 'xyz',
'write' => ['user:notme']
]);
$this->assertEquals($file['headers']['status-code'], 400);
$this->assertStringStartsWith('Write permissions must be one of:', $file['body']['message']);
$this->assertStringContainsString('role:all', $file['body']['message']);
$this->assertStringContainsString('role:member', $file['body']['message']);
$this->assertStringContainsString('user:'.$this->getUser()['$id'], $file['body']['message']);
$file = $this->client->call(Client::METHOD_POST, '/storage/files', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'fileId' => 'unique()',
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
'folderId' => 'xyz',
'read' => ['user:notme'],
'write' => ['user:notme']
]);
$this->assertEquals($file['headers']['status-code'], 400);
$this->assertStringStartsWith('Read permissions must be one of:', $file['body']['message']);
$this->assertStringContainsString('role:all', $file['body']['message']);
$this->assertStringContainsString('role:member', $file['body']['message']);
$this->assertStringContainsString('user:'.$this->getUser()['$id'], $file['body']['message']);
}
/**
* @depends testCreateFileDefaultPermissions
*/
public function testUpdateFileAbusePermissions(array $data): void
{
/**
* Test for FAILURE
*/
$file = $this->client->call(Client::METHOD_PUT, '/storage/files/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'read' => ['user:notme']
]);
$this->assertEquals($file['headers']['status-code'], 400);
$this->assertStringStartsWith('Read permissions must be one of:', $file['body']['message']);
$this->assertStringContainsString('role:all', $file['body']['message']);
$this->assertStringContainsString('role:member', $file['body']['message']);
$this->assertStringContainsString('user:'.$this->getUser()['$id'], $file['body']['message']);
$file = $this->client->call(Client::METHOD_PUT, '/storage/files/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'write' => ['user:notme']
]);
$this->assertEquals($file['headers']['status-code'], 400);
$this->assertStringStartsWith('Write permissions must be one of:', $file['body']['message']);
$this->assertStringContainsString('role:all', $file['body']['message']);
$this->assertStringContainsString('role:member', $file['body']['message']);
$this->assertStringContainsString('user:'.$this->getUser()['$id'], $file['body']['message']);
$file = $this->client->call(Client::METHOD_PUT, '/storage/files/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'read' => ['user:notme'],
'write' => ['user:notme']
]);
$this->assertEquals($file['headers']['status-code'], 400);
$this->assertStringStartsWith('Read permissions must be one of:', $file['body']['message']);
$this->assertStringContainsString('role:all', $file['body']['message']);
$this->assertStringContainsString('role:member', $file['body']['message']);
$this->assertStringContainsString('user:'.$this->getUser()['$id'], $file['body']['message']);
}
}

View file

@ -172,35 +172,35 @@ class AuthTest extends TestCase
public function testIsPrivilegedUser()
{
$this->assertEquals(false, Auth::isPrivilegedUser([]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_GUEST => true]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_MEMBER => true]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_ADMIN => true]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_DEVELOPER => true]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_OWNER => true]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_APP => true]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_SYSTEM => true]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_GUEST]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_MEMBER]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_ADMIN]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_DEVELOPER]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_OWNER]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_APP]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_SYSTEM]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_APP => true, 'role:'.Auth::USER_ROLE_APP => true]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_APP => true, 'role:'.Auth::USER_ROLE_GUEST => true]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_OWNER => true, 'role:'.Auth::USER_ROLE_GUEST => true]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_OWNER => true, 'role:'.Auth::USER_ROLE_ADMIN => true, 'role:'.Auth::USER_ROLE_DEVELOPER => true]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_APP, 'role:'.Auth::USER_ROLE_APP]));
$this->assertEquals(false, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_APP, 'role:'.Auth::USER_ROLE_GUEST]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_OWNER, 'role:'.Auth::USER_ROLE_GUEST]));
$this->assertEquals(true, Auth::isPrivilegedUser(['role:'.Auth::USER_ROLE_OWNER, 'role:'.Auth::USER_ROLE_ADMIN, 'role:'.Auth::USER_ROLE_DEVELOPER]));
}
public function testIsAppUser()
{
$this->assertEquals(false, Auth::isAppUser([]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_GUEST => true]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_MEMBER => true]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_ADMIN => true]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_DEVELOPER => true]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER => true]));
$this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP => true]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_SYSTEM => true]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_GUEST]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_MEMBER]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_ADMIN]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_DEVELOPER]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER]));
$this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_SYSTEM]));
$this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP => true, 'role:'.Auth::USER_ROLE_APP => true]));
$this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP => true, 'role:'.Auth::USER_ROLE_GUEST => true]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER => true, 'role:'.Auth::USER_ROLE_GUEST => true]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER => true, 'role:'.Auth::USER_ROLE_ADMIN => true, 'role:'.Auth::USER_ROLE_DEVELOPER => true]));
$this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP, 'role:'.Auth::USER_ROLE_APP]));
$this->assertEquals(true, Auth::isAppUser(['role:'.Auth::USER_ROLE_APP, 'role:'.Auth::USER_ROLE_GUEST]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER, 'role:'.Auth::USER_ROLE_GUEST]));
$this->assertEquals(false, Auth::isAppUser(['role:'.Auth::USER_ROLE_OWNER, 'role:'.Auth::USER_ROLE_ADMIN, 'role:'.Auth::USER_ROLE_DEVELOPER]));
}
public function testGuestRoles()