Merge pull request #2637 from appwrite/feat-migration-0-13
feat: migration for 0.13
This commit is contained in:
commit
6796aabf9c
58 changed files with 361 additions and 7920 deletions
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,6 @@
|
|||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Database\Validator\CustomId;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
|
@ -12,6 +11,7 @@ use Appwrite\OpenSSL\OpenSSL;
|
|||
use Appwrite\Template\Template;
|
||||
use Appwrite\URL\URL as URLParser;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
|
|
|
@ -27,10 +27,10 @@ use Utopia\Database\Exception\Duplicate as DuplicateException;
|
|||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Database\Validator\CustomId;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Event;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Storage\Validator\File;
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Database\Validator\CustomId;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\Network\Validator\Domain as DomainValidator;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\App;
|
||||
|
@ -537,7 +537,7 @@ App::delete('/v1/projects/:projectId')
|
|||
->inject('deletes')
|
||||
->action(function ($projectId, $password, $response, $user, $dbForConsole, $deletes) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Appwrite\Database\Document $user */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Appwrite\Event\Event $deletes */
|
||||
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\HexColor;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Appwrite\Database\Validator\CustomId;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Storage\Validator\File;
|
||||
use Utopia\Storage\Validator\FileSize;
|
||||
use Utopia\Storage\Validator\Upload;
|
||||
use Utopia\Storage\Compression\Algorithms\GZIP;
|
||||
use Utopia\Image\Image;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\HexColor;
|
||||
|
||||
App::post('/v1/storage/files')
|
||||
->desc('Create File')
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Database\Validator\CustomId;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
|
@ -22,6 +17,11 @@ use Utopia\Database\Query;
|
|||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::post('/v1/teams')
|
||||
->desc('Create Team')
|
||||
|
|
|
@ -2,25 +2,25 @@
|
|||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Database\Validator\CustomId;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
App::post('/v1/users')
|
||||
->desc('Create User')
|
||||
|
|
|
@ -13,9 +13,6 @@ use Utopia\Config\Config;
|
|||
use Utopia\Domains\Domain;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Response\Filters\V06;
|
||||
use Appwrite\Utopia\Response\Filters\V07;
|
||||
use Appwrite\Utopia\Response\Filters\V08;
|
||||
use Appwrite\Utopia\Response\Filters\V11;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Document;
|
||||
|
@ -159,15 +156,6 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
$responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
switch($responseFormat) {
|
||||
case version_compare ($responseFormat , '0.6.2', '<=') :
|
||||
Response::setFilter(new V06());
|
||||
break;
|
||||
case version_compare ($responseFormat , '0.7.2', '<=') :
|
||||
Response::setFilter(new V07());
|
||||
break;
|
||||
case version_compare ($responseFormat , '0.8.0', '<=') :
|
||||
Response::setFilter(new V08());
|
||||
break;
|
||||
case version_compare ($responseFormat , '0.11.0', '<=') :
|
||||
Response::setFilter(new V11());
|
||||
break;
|
||||
|
@ -337,8 +325,8 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
if($logger) {
|
||||
if($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
/** @var Appwrite\Database\Document $user */
|
||||
} catch(\Throwable $th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Database\Validator\Authorization;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
|
||||
|
|
|
@ -193,8 +193,8 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$logger = $app->getResource("logger");
|
||||
if($logger) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $app->getResource('user');
|
||||
/** @var Appwrite\Database\Document $user */
|
||||
} catch(\Throwable $_th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
|
40
app/init.php
40
app/init.php
|
@ -21,7 +21,6 @@ use Appwrite\Extend\PDO;
|
|||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Database\Database as DatabaseOld;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
|
@ -62,7 +61,7 @@ const APP_PAGING_LIMIT = 12;
|
|||
const APP_LIMIT_COUNT = 5000;
|
||||
const APP_LIMIT_USERS = 10000;
|
||||
const APP_CACHE_BUSTER = 201;
|
||||
const APP_VERSION_STABLE = '0.12.1';
|
||||
const APP_VERSION_STABLE = '0.13.0';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
@ -158,43 +157,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
|
||||
*/
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
<?php
|
||||
|
||||
global $cli, $register, $projectDB, $console;
|
||||
global $cli, $register;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Validator\Authorization;
|
||||
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
|
||||
use Appwrite\Database\Adapter\Redis as RedisAdapter;
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Config::load('collections.old', __DIR__ . '/../config/collections.old.php');
|
||||
|
||||
$cli
|
||||
->task('migrate')
|
||||
->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true)
|
||||
|
@ -23,86 +22,36 @@ $cli
|
|||
Console::exit(1);
|
||||
return;
|
||||
}
|
||||
$options = [];
|
||||
if (str_starts_with($version, '0.12.')) {
|
||||
Console::error('--------------------');
|
||||
Console::error('WARNING');
|
||||
Console::error('--------------------');
|
||||
Console::warning('Migrating to Version 0.12.x introduces a major breaking change within the Database Service!');
|
||||
Console::warning('Before migrating, please read about the breaking changes here:');
|
||||
Console::info('https://dev.to/appwrite/appwrite-012-migration-post-3cha');
|
||||
$confirm = Console::confirm("If you want to proceed, type 'yes':");
|
||||
if ($confirm != 'yes') {
|
||||
Console::exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
Console::log('');
|
||||
Console::log('Collections');
|
||||
Console::log('--------------------');
|
||||
Console::warning('Be aware that following actions will happen during the migration:');
|
||||
Console::warning('- Nested Document rules will be migrated to String attributes');
|
||||
Console::warning('- Numeric rules will be migrated to float attributes');
|
||||
Console::warning('- Wildcard and Markdown rules will be converted to string attributes');
|
||||
Console::info("Do you want to migrate your Database Collections?");
|
||||
$options['migrateCollections'] = Console::confirm("Type 'yes' or 'no':");
|
||||
|
||||
if ($options['migrateCollections'] === 'yes') {
|
||||
Console::log('');
|
||||
Console::log('Documents');
|
||||
Console::log('------------------');
|
||||
Console::warning('Be aware that following actions will happen during the migration:');
|
||||
Console::warning('- Nested Documents will be stored as JSON values');
|
||||
Console::warning('- All Numeric values will be converted to float');
|
||||
Console::warning('- All Wildcard and Markdown values will be converted to string');
|
||||
Console::info("Do you want to migrate your Database Documents?");
|
||||
$options['migrateDocuments'] = Console::confirm("Type 'yes' or 'no':");
|
||||
} else {
|
||||
$options['migrateDocuments'] = 'no';
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
!in_array($options['migrateDocuments'], ['yes', 'no'])
|
||||
|| !in_array($options['migrateCollections'], ['yes', 'no'])
|
||||
) {
|
||||
Console::error("You must reply with 'yes' or 'no'!");
|
||||
Console::exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Config::load('collectionsold', __DIR__ . '/../config/collections.old.php');
|
||||
$app = new App('UTC');
|
||||
|
||||
Console::success('Starting Data Migration to version ' . $version);
|
||||
|
||||
$db = $register->get('db', true);
|
||||
$cache = $register->get('cache', true);
|
||||
$cache->flushAll();
|
||||
|
||||
$consoleDB = new Database();
|
||||
$consoleDB
|
||||
->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache))
|
||||
->setNamespace('app_console') // Main DB
|
||||
->setMocks(Config::getParam('collectionsold', []));
|
||||
$cache = new Cache(new RedisCache($cache));
|
||||
|
||||
$projectDB = new Database();
|
||||
$projectDB
|
||||
->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache))
|
||||
->setMocks(Config::getParam('collectionsold', []));
|
||||
$projectDB = new Database(new MariaDB($db), $cache);
|
||||
$projectDB->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
|
||||
$console = $consoleDB->getDocument('console');
|
||||
$consoleDB = new Database(new MariaDB($db), $cache);
|
||||
$consoleDB->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$consoleDB->setNamespace('_project_console');
|
||||
|
||||
$console = $app->getResource('console');
|
||||
|
||||
$limit = 30;
|
||||
$sum = 30;
|
||||
$offset = 0;
|
||||
$projects = [$console];
|
||||
$count = 0;
|
||||
$totalProjects = $consoleDB->count('projects') + 1;
|
||||
|
||||
$class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version];
|
||||
$migration = new $class($register->get('db'), $register->get('cache'), $options);
|
||||
$migration = new $class();
|
||||
|
||||
while ($sum > 0) {
|
||||
while (!empty($projects)) {
|
||||
foreach ($projects as $project) {
|
||||
try {
|
||||
$migration
|
||||
|
@ -114,23 +63,14 @@ $cli
|
|||
}
|
||||
}
|
||||
|
||||
$projects = $consoleDB->getCollection([
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
'filters' => [
|
||||
'$collection=' . Database::SYSTEM_COLLECTION_PROJECTS,
|
||||
],
|
||||
]);
|
||||
|
||||
$sum = \count($projects);
|
||||
$projects = $consoleDB->find('projects', limit: $limit, offset: $offset);
|
||||
|
||||
$offset = $offset + $limit;
|
||||
$count = $count + $sum;
|
||||
|
||||
if ($sum > 0) {
|
||||
Console::log('Fetched ' . $count . '/' . $consoleDB->getSum() . ' projects...');
|
||||
}
|
||||
Console::log('Migrated ' . $count . '/' . $totalProjects . ' projects...');
|
||||
}
|
||||
$cache->flushAll();
|
||||
|
||||
Swoole\Event::wait(); // Wait for Coroutines to finish
|
||||
Console::success('Data Migration Completed');
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database;
|
||||
|
||||
use Exception;
|
||||
|
||||
abstract class Adapter
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = '';
|
||||
|
||||
/**
|
||||
* Set Namespace.
|
||||
*
|
||||
* Set namespace to divide different scope of data sets
|
||||
*
|
||||
* @param $namespace
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setNamespace($namespace)
|
||||
{
|
||||
if (empty($namespace)) {
|
||||
throw new Exception('Missing namespace');
|
||||
}
|
||||
|
||||
$this->namespace = $namespace;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Namespace.
|
||||
*
|
||||
* Get namespace of current set scope
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNamespace()
|
||||
{
|
||||
if (empty($this->namespace)) {
|
||||
throw new Exception('Missing namespace');
|
||||
}
|
||||
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Document.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getDocument($id);
|
||||
|
||||
/**
|
||||
* Create Document
|
||||
**.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function createDocument(array $data = [], array $unique = []);
|
||||
|
||||
/**
|
||||
* Update Document.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function updateDocument(array $data = []);
|
||||
|
||||
/**
|
||||
* Delete Node.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function deleteDocument(string $id);
|
||||
|
||||
/**
|
||||
* Delete Unique Key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function deleteUniqueKey($key);
|
||||
|
||||
/**
|
||||
* Add Unique Key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function addUniqueKey($key);
|
||||
|
||||
/**
|
||||
* Create Namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function createNamespace($namespace);
|
||||
|
||||
/**
|
||||
* Delete Namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function deleteNamespace($namespace);
|
||||
|
||||
/**
|
||||
* Filter.
|
||||
*
|
||||
* Filter data sets using chosen queries
|
||||
*
|
||||
* @param array $options
|
||||
* @param array $filterTypes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getCollection(array $options, array $filterTypes = []);
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function getCount(array $options);
|
||||
|
||||
/**
|
||||
* Last Modified.
|
||||
*
|
||||
* Return Unix timestamp of last time a node queried in corrent session has been changed
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function lastModified();
|
||||
|
||||
/**
|
||||
* Get Debug Data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getDebug();
|
||||
}
|
|
@ -1,986 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Adapter;
|
||||
|
||||
use Appwrite\Database\Adapter;
|
||||
use Appwrite\Database\Exception\Duplicate;
|
||||
use Appwrite\Database\Validator\Authorization;
|
||||
use Exception;
|
||||
use PDO;
|
||||
use Redis;
|
||||
|
||||
class MySQL extends Adapter
|
||||
{
|
||||
const DATA_TYPE_STRING = 'string';
|
||||
const DATA_TYPE_INTEGER = 'integer';
|
||||
const DATA_TYPE_FLOAT = 'float';
|
||||
const DATA_TYPE_BOOLEAN = 'boolean';
|
||||
const DATA_TYPE_OBJECT = 'object';
|
||||
const DATA_TYPE_DICTIONARY = 'dictionary';
|
||||
const DATA_TYPE_ARRAY = 'array';
|
||||
const DATA_TYPE_NULL = 'null';
|
||||
|
||||
const OPTIONS_LIMIT_ATTRIBUTES = 1000;
|
||||
|
||||
/**
|
||||
* Last modified.
|
||||
*
|
||||
* Read node with most recent changes
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $lastModified = -1;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $debug = [];
|
||||
|
||||
/**
|
||||
* @var PDO
|
||||
*/
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* @var Redis
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Set connection and settings
|
||||
*
|
||||
* @param PDO $pdo
|
||||
* @param Redis $redis
|
||||
*/
|
||||
public function __construct($pdo, Redis $redis)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
$this->redis = $redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Document.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getDocument($id)
|
||||
{
|
||||
// Get fields abstraction
|
||||
$st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.documents` a
|
||||
WHERE a.uid = :uid AND a.status = 0
|
||||
ORDER BY a.updatedAt DESC LIMIT 10;
|
||||
');
|
||||
|
||||
$st->bindValue(':uid', $id, PDO::PARAM_STR);
|
||||
|
||||
$st->execute();
|
||||
|
||||
$document = $st->fetch();
|
||||
|
||||
if (empty($document)) { // Not Found
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get fields abstraction
|
||||
$st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.properties` a
|
||||
WHERE a.documentUid = :documentUid AND a.documentRevision = :documentRevision
|
||||
ORDER BY `order`
|
||||
');
|
||||
|
||||
$st->bindParam(':documentUid', $document['uid'], PDO::PARAM_STR, 32);
|
||||
$st->bindParam(':documentRevision', $document['revision'], PDO::PARAM_STR, 32);
|
||||
|
||||
$st->execute();
|
||||
|
||||
$properties = $st->fetchAll();
|
||||
|
||||
$output = [
|
||||
'$id' => null,
|
||||
'$collection' => null,
|
||||
'$permissions' => (!empty($document['permissions'])) ? \json_decode($document['permissions'], true) : [],
|
||||
];
|
||||
|
||||
foreach ($properties as &$property) {
|
||||
\settype($property['value'], $property['primitive']);
|
||||
|
||||
if ($property['array']) {
|
||||
$output[$property['key']][] = $property['value'];
|
||||
} else {
|
||||
$output[$property['key']] = $property['value'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get fields abstraction
|
||||
$st = $this->getPDO()->prepare('SELECT * FROM `'.$this->getNamespace().'.database.relationships` a
|
||||
WHERE a.start = :start AND revision = :revision
|
||||
ORDER BY `order`
|
||||
');
|
||||
|
||||
$st->bindParam(':start', $document['uid'], PDO::PARAM_STR, 32);
|
||||
$st->bindParam(':revision', $document['revision'], PDO::PARAM_STR, 32);
|
||||
|
||||
$st->execute();
|
||||
|
||||
$output['temp-relations'] = $st->fetchAll();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Document.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function createDocument(array $data = [], array $unique = [])
|
||||
{
|
||||
$order = 0;
|
||||
$data = \array_merge(['$id' => null, '$permissions' => []], $data); // Merge data with default params
|
||||
$signature = \md5(\json_encode($data));
|
||||
$revision = \uniqid('', true);
|
||||
$data['$id'] = (empty($data['$id'])) ? null : $data['$id'];
|
||||
|
||||
/*
|
||||
* When updating node, check if there are any changes to update
|
||||
* by comparing data md5 signatures
|
||||
*/
|
||||
if (null !== $data['$id']) {
|
||||
$st = $this->getPDO()->prepare('SELECT signature FROM `'.$this->getNamespace().'.database.documents` a
|
||||
WHERE a.uid = :uid AND a.status = 0
|
||||
ORDER BY a.updatedAt DESC LIMIT 1;
|
||||
');
|
||||
|
||||
$st->bindValue(':uid', $data['$id'], PDO::PARAM_STR);
|
||||
|
||||
$st->execute();
|
||||
|
||||
$result = $st->fetch();
|
||||
|
||||
if ($result && isset($result['signature'])) {
|
||||
$oldSignature = $result['signature'];
|
||||
|
||||
if ($signature === $oldSignature) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Unique Keys
|
||||
*/
|
||||
foreach ($unique as $key => $value) {
|
||||
$st = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.unique`
|
||||
SET `key` = :key;
|
||||
');
|
||||
|
||||
$st->bindValue(':key', \md5($data['$collection'].':'.$key.'='.$value), PDO::PARAM_STR);
|
||||
|
||||
if (!$st->execute()) {
|
||||
throw new Duplicate('Duplicated Property: '.$key.'='.$value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add or update fields abstraction level
|
||||
$st1 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.documents`
|
||||
SET uid = :uid, createdAt = :createdAt, updatedAt = :updatedAt, signature = :signature, revision = :revision, permissions = :permissions, status = 0
|
||||
ON DUPLICATE KEY UPDATE uid = :uid, updatedAt = :updatedAt, signature = :signature, revision = :revision, permissions = :permissions;
|
||||
');
|
||||
|
||||
// Adding fields properties
|
||||
if (null === $data['$id'] || !isset($data['$id'])) { // Get new fields UID
|
||||
$data['$id'] = $this->getId();
|
||||
}
|
||||
|
||||
$st1->bindValue(':uid', $data['$id'], PDO::PARAM_STR);
|
||||
$st1->bindValue(':revision', $revision, PDO::PARAM_STR);
|
||||
$st1->bindValue(':signature', $signature, PDO::PARAM_STR);
|
||||
$st1->bindValue(':createdAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR);
|
||||
$st1->bindValue(':updatedAt', \date('Y-m-d H:i:s', \time()), PDO::PARAM_STR);
|
||||
$st1->bindValue(':permissions', \json_encode($data['$permissions']), PDO::PARAM_STR);
|
||||
|
||||
$st1->execute();
|
||||
|
||||
// Delete old properties
|
||||
$rms1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.properties` WHERE documentUid = :documentUid AND documentRevision != :documentRevision');
|
||||
$rms1->bindValue(':documentUid', $data['$id'], PDO::PARAM_STR);
|
||||
$rms1->bindValue(':documentRevision', $revision, PDO::PARAM_STR);
|
||||
$rms1->execute();
|
||||
|
||||
// Delete old relationships
|
||||
$rms2 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.relationships` WHERE start = :start AND revision != :revision');
|
||||
$rms2->bindValue(':start', $data['$id'], PDO::PARAM_STR);
|
||||
$rms2->bindValue(':revision', $revision, PDO::PARAM_STR);
|
||||
$rms2->execute();
|
||||
|
||||
// Create new properties
|
||||
$st2 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.properties`
|
||||
(`documentUid`, `documentRevision`, `key`, `value`, `primitive`, `array`, `order`)
|
||||
VALUES (:documentUid, :documentRevision, :key, :value, :primitive, :array, :order)');
|
||||
|
||||
$props = [];
|
||||
|
||||
foreach ($data as $key => $value) { // Prepare properties data
|
||||
|
||||
if (\in_array($key, ['$permissions'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->getDataType($value);
|
||||
|
||||
// Handle array of relations
|
||||
if (self::DATA_TYPE_ARRAY === $type) {
|
||||
if (!is_array($value)) { // Property should be of type array, if not = skip
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($value as $i => $child) {
|
||||
if (self::DATA_TYPE_DICTIONARY !== $this->getDataType($child)) { // not dictionary
|
||||
|
||||
$props[] = [
|
||||
'type' => $this->getDataType($child),
|
||||
'key' => $key,
|
||||
'value' => $child,
|
||||
'array' => true,
|
||||
'order' => $order++,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$key][$i] = $this->createDocument($child);
|
||||
|
||||
$this->createRelationship($revision, $data['$id'], $data[$key][$i]['$id'], $key, true, $i);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle relation
|
||||
if (self::DATA_TYPE_DICTIONARY === $type) {
|
||||
$value = $this->createDocument($value);
|
||||
$this->createRelationship($revision, $data['$id'], $value['$id'], $key); //xxx
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle empty values
|
||||
if (self::DATA_TYPE_NULL === $type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$props[] = [
|
||||
'type' => $type,
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
'array' => false,
|
||||
'order' => $order++,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($props as $prop) {
|
||||
if (\is_array($prop['value'])) {
|
||||
throw new Exception('Value can\'t be an array: '.\json_encode($prop['value']));
|
||||
}
|
||||
if (\is_bool($prop['value'])) {
|
||||
$prop['value'] = (int) $prop['value'];
|
||||
}
|
||||
$st2->bindValue(':documentUid', $data['$id'], PDO::PARAM_STR);
|
||||
$st2->bindValue(':documentRevision', $revision, PDO::PARAM_STR);
|
||||
|
||||
$st2->bindValue(':key', $prop['key'], PDO::PARAM_STR);
|
||||
$st2->bindValue(':value', $prop['value'], PDO::PARAM_STR);
|
||||
$st2->bindValue(':primitive', $prop['type'], PDO::PARAM_STR);
|
||||
$st2->bindValue(':array', $prop['array'], PDO::PARAM_BOOL);
|
||||
$st2->bindValue(':order', $prop['order'], PDO::PARAM_STR);
|
||||
|
||||
$st2->execute();
|
||||
}
|
||||
|
||||
// TODO remove this dependency (check if related to nested documents)
|
||||
$this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0);
|
||||
$this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Document.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function updateDocument(array $data = [])
|
||||
{
|
||||
return $this->createDocument($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Document.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function deleteDocument(string $id)
|
||||
{
|
||||
$st1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.documents`
|
||||
WHERE uid = :id
|
||||
');
|
||||
|
||||
$st1->bindValue(':id', $id, PDO::PARAM_STR);
|
||||
|
||||
$st1->execute();
|
||||
|
||||
$st2 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.properties`
|
||||
WHERE documentUid = :id
|
||||
');
|
||||
|
||||
$st2->bindValue(':id', $id, PDO::PARAM_STR);
|
||||
|
||||
$st2->execute();
|
||||
|
||||
$st3 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.relationships`
|
||||
WHERE start = :id OR end = :id
|
||||
');
|
||||
|
||||
$st3->bindValue(':id', $id, PDO::PARAM_STR);
|
||||
|
||||
$st3->execute();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Unique Key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function deleteUniqueKey($key)
|
||||
{
|
||||
$st1 = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.database.unique` WHERE `key` = :key');
|
||||
|
||||
$st1->bindValue(':key', $key, PDO::PARAM_STR);
|
||||
|
||||
$st1->execute();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Unique Key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function addUniqueKey($key)
|
||||
{
|
||||
$st = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.unique`
|
||||
SET `key` = :key;
|
||||
');
|
||||
|
||||
$st->bindValue(':key', $key, PDO::PARAM_STR);
|
||||
|
||||
if (!$st->execute()) {
|
||||
throw new Duplicate('Duplicated Property: '.$key);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Relation.
|
||||
*
|
||||
* Adds a new relationship between different nodes
|
||||
*
|
||||
* @param string $revision
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
* @param string $key
|
||||
* @param bool $isArray
|
||||
* @param int $order
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function createRelationship($revision, $start, $end, $key, $isArray = false, $order = 0)
|
||||
{
|
||||
$st2 = $this->getPDO()->prepare('INSERT INTO `'.$this->getNamespace().'.database.relationships`
|
||||
(`revision`, `start`, `end`, `key`, `array`, `order`)
|
||||
VALUES (:revision, :start, :end, :key, :array, :order)');
|
||||
|
||||
$st2->bindValue(':revision', $revision, PDO::PARAM_STR);
|
||||
$st2->bindValue(':start', $start, PDO::PARAM_STR);
|
||||
$st2->bindValue(':end', $end, PDO::PARAM_STR);
|
||||
$st2->bindValue(':key', $key, PDO::PARAM_STR);
|
||||
$st2->bindValue(':array', $isArray, PDO::PARAM_INT);
|
||||
$st2->bindValue(':order', $order, PDO::PARAM_INT);
|
||||
|
||||
$st2->execute();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Namespace.
|
||||
*
|
||||
* @param $namespace
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function createNamespace($namespace)
|
||||
{
|
||||
if (empty($namespace)) {
|
||||
throw new Exception('Empty namespace');
|
||||
}
|
||||
|
||||
$documents = 'app_'.$namespace.'.database.documents';
|
||||
$properties = 'app_'.$namespace.'.database.properties';
|
||||
$relationships = 'app_'.$namespace.'.database.relationships';
|
||||
$unique = 'app_'.$namespace.'.database.unique';
|
||||
$audit = 'app_'.$namespace.'.audit.audit';
|
||||
$abuse = 'app_'.$namespace.'.abuse.abuse';
|
||||
|
||||
try {
|
||||
$this->getPDO()->prepare('CREATE TABLE `'.$documents.'` LIKE `template.database.documents`;')->execute();
|
||||
$this->getPDO()->prepare('CREATE TABLE `'.$properties.'` LIKE `template.database.properties`;')->execute();
|
||||
$this->getPDO()->prepare('CREATE TABLE `'.$relationships.'` LIKE `template.database.relationships`;')->execute();
|
||||
$this->getPDO()->prepare('CREATE TABLE `'.$unique.'` LIKE `template.database.unique`;')->execute();
|
||||
$this->getPDO()->prepare('CREATE TABLE `'.$audit.'` LIKE `template.audit.audit`;')->execute();
|
||||
$this->getPDO()->prepare('CREATE TABLE `'.$abuse.'` LIKE `template.abuse.abuse`;')->execute();
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Namespace.
|
||||
*
|
||||
* @param $namespace
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteNamespace($namespace)
|
||||
{
|
||||
if (empty($namespace)) {
|
||||
throw new Exception('Empty namespace');
|
||||
}
|
||||
|
||||
$unique = 'app_'.$namespace.'.database.unique';
|
||||
$documents = 'app_'.$namespace.'.database.documents';
|
||||
$properties = 'app_'.$namespace.'.database.properties';
|
||||
$relationships = 'app_'.$namespace.'.database.relationships';
|
||||
$audit = 'app_'.$namespace.'.audit.audit';
|
||||
$abuse = 'app_'.$namespace.'.abuse.abuse';
|
||||
|
||||
try {
|
||||
$this->getPDO()->prepare('DROP TABLE `'.$unique.'`;')->execute();
|
||||
$this->getPDO()->prepare('DROP TABLE `'.$documents.'`;')->execute();
|
||||
$this->getPDO()->prepare('DROP TABLE `'.$properties.'`;')->execute();
|
||||
$this->getPDO()->prepare('DROP TABLE `'.$relationships.'`;')->execute();
|
||||
$this->getPDO()->prepare('DROP TABLE `'.$audit.'`;')->execute();
|
||||
$this->getPDO()->prepare('DROP TABLE `'.$abuse.'`;')->execute();
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Collection.
|
||||
*
|
||||
* @param array $options
|
||||
* @param array $filterTypes
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCollection(array $options, array $filterTypes = [])
|
||||
{
|
||||
$start = \microtime(true);
|
||||
$orderCastMap = [
|
||||
'int' => 'UNSIGNED',
|
||||
'string' => 'CHAR',
|
||||
'date' => 'DATE',
|
||||
'time' => 'TIME',
|
||||
'datetime' => 'DATETIME',
|
||||
];
|
||||
$orderTypeMap = ['DESC', 'ASC'];
|
||||
|
||||
$options['orderField'] = (empty($options['orderField'])) ? '' : $options['orderField']; // Set default order field
|
||||
$options['orderCast'] = (empty($options['orderCast'])) ? 'string' : $options['orderCast']; // Set default order field
|
||||
|
||||
if (!\array_key_exists($options['orderCast'], $orderCastMap)) {
|
||||
throw new Exception('Invalid order cast');
|
||||
}
|
||||
|
||||
if (!\in_array($options['orderType'], $orderTypeMap)) {
|
||||
throw new Exception('Invalid order type');
|
||||
}
|
||||
|
||||
$where = [];
|
||||
$join = [];
|
||||
$sorts = [];
|
||||
$search = '';
|
||||
|
||||
// Filters
|
||||
foreach ($options['filters'] as $i => $filter) {
|
||||
$filter = $this->parseFilter($filter);
|
||||
$key = $filter['key'];
|
||||
$value = $filter['value'];
|
||||
$operator = $filter['operator'];
|
||||
|
||||
$path = \explode('.', $key);
|
||||
$original = $path;
|
||||
|
||||
if (1 < \count($path)) {
|
||||
$key = \array_pop($path);
|
||||
} else {
|
||||
$path = [];
|
||||
}
|
||||
|
||||
//$path = implode('.', $path);
|
||||
|
||||
if (array_key_exists($key, $filterTypes) && $filterTypes[$key] === 'numeric') {
|
||||
$value = (float) $value;
|
||||
} else {
|
||||
$value = $this->getPDO()->quote($value, PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
$key = $this->getPDO()->quote($key, PDO::PARAM_STR);
|
||||
|
||||
//$path = $this->getPDO()->quote($path, PDO::PARAM_STR);
|
||||
$options['offset'] = (int) $options['offset'];
|
||||
$options['limit'] = (int) $options['limit'];
|
||||
|
||||
if (empty($path)) {
|
||||
//if($path == "''") { // Handle direct attributes queries
|
||||
$where[] = 'JOIN `'.$this->getNamespace().".database.properties` b{$i} ON a.uid IS NOT NULL AND b{$i}.documentUid = a.uid AND (b{$i}.key = {$key} AND b{$i}.value {$operator} {$value})";
|
||||
} else { // Handle direct child attributes queries
|
||||
$len = \count($original);
|
||||
$prev = 'c'.$i;
|
||||
|
||||
foreach ($original as $y => $part) {
|
||||
$part = $this->getPDO()->quote($part, PDO::PARAM_STR);
|
||||
|
||||
if (0 === $y) { // First key
|
||||
$join[$i] = 'JOIN `'.$this->getNamespace().".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$part}";
|
||||
} elseif ($y == $len - 1) { // Last key
|
||||
$join[$i] .= 'JOIN `'.$this->getNamespace().".database.properties` e{$i} ON e{$i}.documentUid = {$prev}.end AND e{$i}.key = {$part} AND e{$i}.value {$operator} {$value}";
|
||||
} else {
|
||||
$join[$i] .= 'JOIN `'.$this->getNamespace().".database.relationships` d{$i}{$y} ON d{$i}{$y}.start = {$prev}.end AND d{$i}{$y}.key = {$part}";
|
||||
$prev = 'd'.$i.$y;
|
||||
}
|
||||
}
|
||||
|
||||
//$join[] = "JOIN `" . $this->getNamespace() . ".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$path}
|
||||
// JOIN `" . $this->getNamespace() . ".database.properties` d{$i} ON d{$i}.documentUid = c{$i}.end AND d{$i}.key = {$key} AND d{$i}.value {$operator} {$value}";
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting
|
||||
if (!empty($options['orderField'])) {
|
||||
$orderPath = \explode('.', $options['orderField']);
|
||||
$len = \count($orderPath);
|
||||
$orderKey = 'order_b';
|
||||
$part = $this->getPDO()->quote(\implode('', $orderPath), PDO::PARAM_STR);
|
||||
$orderSelect = "CASE WHEN {$orderKey}.key = {$part} THEN CAST({$orderKey}.value AS {$orderCastMap[$options['orderCast']]}) END AS sort_ff";
|
||||
|
||||
if (1 === $len) {
|
||||
//if($path == "''") { // Handle direct attributes queries
|
||||
$sorts[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` order_b ON a.uid IS NOT NULL AND order_b.documentUid = a.uid AND (order_b.key = {$part})";
|
||||
} else { // Handle direct child attributes queries
|
||||
$prev = 'c';
|
||||
$orderKey = 'order_e';
|
||||
|
||||
foreach ($orderPath as $y => $part) {
|
||||
$part = $this->getPDO()->quote($part, PDO::PARAM_STR);
|
||||
$x = $y - 1;
|
||||
|
||||
if (0 === $y) { // First key
|
||||
$sorts[] = 'JOIN `'.$this->getNamespace().".database.relationships` order_c{$y} ON a.uid IS NOT NULL AND order_c{$y}.start = a.uid AND order_c{$y}.key = {$part}";
|
||||
} elseif ($y == $len - 1) { // Last key
|
||||
$sorts[] .= 'JOIN `'.$this->getNamespace().".database.properties` order_e ON order_e.documentUid = order_{$prev}{$x}.end AND order_e.key = {$part}";
|
||||
} else {
|
||||
$sorts[] .= 'JOIN `'.$this->getNamespace().".database.relationships` order_d{$y} ON order_d{$y}.start = order_{$prev}{$x}.end AND order_d{$y}.key = {$part}";
|
||||
$prev = 'd';
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$orderSelect = 'a.uid AS sort_ff';
|
||||
}
|
||||
|
||||
/*
|
||||
* Workaround for a MySQL bug as reported here:
|
||||
* https://bugs.mysql.com/bug.php?id=78485
|
||||
*/
|
||||
$options['search'] = ($options['search'] === '*') ? '' : $options['search'];
|
||||
|
||||
// Search
|
||||
if (!empty($options['search'])) { // Handle free search
|
||||
$where[] = 'LEFT JOIN `'.$this->getNamespace().".database.properties` b_search ON a.uid IS NOT NULL AND b_search.documentUid = a.uid AND b_search.primitive = 'string'
|
||||
LEFT JOIN
|
||||
`".$this->getNamespace().'.database.relationships` c_search ON c_search.start = b_search.documentUid
|
||||
LEFT JOIN
|
||||
`'.$this->getNamespace().".database.properties` d_search ON d_search.documentUid = c_search.end AND d_search.primitive = 'string'
|
||||
\n";
|
||||
|
||||
$search = "AND (MATCH (b_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE)
|
||||
OR MATCH (d_search.value) AGAINST ({$this->getPDO()->quote($options['search'], PDO::PARAM_STR)} IN BOOLEAN MODE)
|
||||
)";
|
||||
}
|
||||
|
||||
$select = 'DISTINCT a.uid';
|
||||
$where = \implode("\n", $where);
|
||||
$join = \implode("\n", $join);
|
||||
$sorts = \implode("\n", $sorts);
|
||||
$range = "LIMIT {$options['offset']}, {$options['limit']}";
|
||||
$roles = [];
|
||||
|
||||
foreach (Authorization::getRoles() as $role) {
|
||||
$roles[] = 'JSON_CONTAINS(REPLACE(a.permissions, \'{self}\', a.uid), \'"'.$role.'"\', \'$.read\')';
|
||||
}
|
||||
|
||||
if (false === Authorization::$status) { // FIXME temporary solution (hopefully)
|
||||
$roles = ['1=1'];
|
||||
}
|
||||
|
||||
$query = "SELECT %s, {$orderSelect}
|
||||
FROM `".$this->getNamespace().".database.documents` a {$where}{$join}{$sorts}
|
||||
WHERE status = 0
|
||||
{$search}
|
||||
AND (".\implode('||', $roles).")
|
||||
ORDER BY sort_ff {$options['orderType']} %s";
|
||||
|
||||
$st = $this->getPDO()->prepare(\sprintf($query, $select, $range));
|
||||
|
||||
$st->execute();
|
||||
|
||||
$results = ['data' => []];
|
||||
|
||||
// Get entire fields data for each id
|
||||
foreach ($st->fetchAll() as $node) {
|
||||
$results['data'][] = $node['uid'];
|
||||
}
|
||||
|
||||
$count = $this->getPDO()->prepare(\sprintf($query, 'count(DISTINCT a.uid) as sum', ''));
|
||||
|
||||
$count->execute();
|
||||
|
||||
$count = $count->fetch();
|
||||
|
||||
$this->resetDebug();
|
||||
|
||||
$this
|
||||
->setDebug('query', \preg_replace('/\s+/', ' ', \sprintf($query, $select, $range)))
|
||||
->setDebug('time', \microtime(true) - $start)
|
||||
->setDebug('filters', \count($options['filters']))
|
||||
->setDebug('joins', \substr_count($query, 'JOIN'))
|
||||
->setDebug('count', \count($results['data']))
|
||||
->setDebug('sum', (int) $count['sum'])
|
||||
;
|
||||
|
||||
return $results['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Collection.
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount(array $options)
|
||||
{
|
||||
$start = \microtime(true);
|
||||
$where = [];
|
||||
$join = [];
|
||||
|
||||
$options = array_merge([
|
||||
'attribute' => '',
|
||||
'filters' => [],
|
||||
], $options);
|
||||
|
||||
// Filters
|
||||
foreach ($options['filters'] as $i => $filter) {
|
||||
$filter = $this->parseFilter($filter);
|
||||
$key = $filter['key'];
|
||||
$value = $filter['value'];
|
||||
$operator = $filter['operator'];
|
||||
$path = \explode('.', $key);
|
||||
$original = $path;
|
||||
|
||||
if (1 < \count($path)) {
|
||||
$key = \array_pop($path);
|
||||
} else {
|
||||
$path = [];
|
||||
}
|
||||
|
||||
$key = $this->getPDO()->quote($key, PDO::PARAM_STR);
|
||||
$value = $this->getPDO()->quote($value, PDO::PARAM_STR);
|
||||
|
||||
if (empty($path)) {
|
||||
//if($path == "''") { // Handle direct attributes queries
|
||||
$where[] = 'JOIN `'.$this->getNamespace().".database.properties` b{$i} ON a.uid IS NOT NULL AND b{$i}.documentUid = a.uid AND (b{$i}.key = {$key} AND b{$i}.value {$operator} {$value})";
|
||||
} else { // Handle direct child attributes queries
|
||||
$len = \count($original);
|
||||
$prev = 'c'.$i;
|
||||
|
||||
foreach ($original as $y => $part) {
|
||||
$part = $this->getPDO()->quote($part, PDO::PARAM_STR);
|
||||
|
||||
if (0 === $y) { // First key
|
||||
$join[$i] = 'JOIN `'.$this->getNamespace().".database.relationships` c{$i} ON a.uid IS NOT NULL AND c{$i}.start = a.uid AND c{$i}.key = {$part}";
|
||||
} elseif ($y == $len - 1) { // Last key
|
||||
$join[$i] .= 'JOIN `'.$this->getNamespace().".database.properties` e{$i} ON e{$i}.documentUid = {$prev}.end AND e{$i}.key = {$part} AND e{$i}.value {$operator} {$value}";
|
||||
} else {
|
||||
$join[$i] .= 'JOIN `'.$this->getNamespace().".database.relationships` d{$i}{$y} ON d{$i}{$y}.start = {$prev}.end AND d{$i}{$y}.key = {$part}";
|
||||
$prev = 'd'.$i.$y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$where = \implode("\n", $where);
|
||||
$join = \implode("\n", $join);
|
||||
$attribute = $this->getPDO()->quote($options['attribute'], PDO::PARAM_STR);
|
||||
$func = 'JOIN `'.$this->getNamespace().".database.properties` b_func ON a.uid IS NOT NULL
|
||||
AND a.uid = b_func.documentUid
|
||||
AND (b_func.key = {$attribute})";
|
||||
$roles = [];
|
||||
|
||||
foreach (Authorization::getRoles() as $role) {
|
||||
$roles[] = 'JSON_CONTAINS(REPLACE(a.permissions, \'{self}\', a.uid), \'"'.$role.'"\', \'$.read\')';
|
||||
}
|
||||
|
||||
if (false === Authorization::$status) { // FIXME temporary solution (hopefully)
|
||||
$roles = ['1=1'];
|
||||
}
|
||||
|
||||
$query = "SELECT SUM(b_func.value) as result
|
||||
FROM `".$this->getNamespace().".database.documents` a {$where}{$join}{$func}
|
||||
WHERE status = 0
|
||||
AND (".\implode('||', $roles).')';
|
||||
|
||||
$st = $this->getPDO()->prepare(\sprintf($query));
|
||||
|
||||
$st->execute();
|
||||
|
||||
$result = $st->fetch();
|
||||
|
||||
$this->resetDebug();
|
||||
|
||||
$this
|
||||
->setDebug('query', \preg_replace('/\s+/', ' ', \sprintf($query)))
|
||||
->setDebug('time', \microtime(true) - $start)
|
||||
->setDebug('filters', \count($options['filters']))
|
||||
->setDebug('joins', \substr_count($query, 'JOIN'))
|
||||
;
|
||||
|
||||
return (isset($result['result'])) ? (int)$result['result'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Unique Document ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
$unique = \uniqid();
|
||||
$attempts = 5;
|
||||
|
||||
for ($i = 1; $i <= $attempts; ++$i) {
|
||||
$document = $this->getDocument($unique);
|
||||
|
||||
if (empty($document) || $document['$id'] !== $unique) {
|
||||
return $unique;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception('Failed to create a unique ID ('.$attempts.' attempts)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Last Modified.
|
||||
*
|
||||
* Return Unix timestamp of last time a node queried in corrent session has been changed
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function lastModified()
|
||||
{
|
||||
return $this->lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Filter.
|
||||
*
|
||||
* @param string $filter
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function parseFilter($filter)
|
||||
{
|
||||
$operatorsMap = ['!=', '>=', '<=', '=', '>', '<']; // Do not edit order of this array
|
||||
|
||||
//FIXME bug with >= <= operators
|
||||
|
||||
$operator = null;
|
||||
|
||||
foreach ($operatorsMap as $node) {
|
||||
if (\strpos($filter, $node) !== false) {
|
||||
$operator = $node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($operator)) {
|
||||
throw new Exception('Invalid operator');
|
||||
}
|
||||
|
||||
$filter = \explode($operator, $filter);
|
||||
|
||||
if (\count($filter) != 2) {
|
||||
throw new Exception('Invalid filter expression');
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => $filter[0],
|
||||
'value' => $filter[1],
|
||||
'operator' => $operator,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Data Type.
|
||||
*
|
||||
* Check value data type. return value can be on of the following:
|
||||
* string, integer, float, boolean, object, list or null
|
||||
*
|
||||
* @param $value
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getDataType($value)
|
||||
{
|
||||
switch (\gettype($value)) {
|
||||
|
||||
case 'string':
|
||||
return self::DATA_TYPE_STRING;
|
||||
break;
|
||||
|
||||
case 'integer':
|
||||
return self::DATA_TYPE_INTEGER;
|
||||
break;
|
||||
|
||||
case 'double':
|
||||
return self::DATA_TYPE_FLOAT;
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
return self::DATA_TYPE_BOOLEAN;
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
if ((bool) \count(\array_filter(\array_keys($value), 'is_string'))) {
|
||||
return self::DATA_TYPE_DICTIONARY;
|
||||
}
|
||||
|
||||
return self::DATA_TYPE_ARRAY;
|
||||
break;
|
||||
|
||||
case 'NULL':
|
||||
return self::DATA_TYPE_NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
throw new Exception('Unknown data type: '.$value.' ('.\gettype($value).')');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDebug(string $key, $value): self
|
||||
{
|
||||
$this->debug[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDebug(): array
|
||||
{
|
||||
return $this->debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* return $this;.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetDebug(): void
|
||||
{
|
||||
$this->debug = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PDO
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getPDO()
|
||||
{
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Redis
|
||||
*/
|
||||
protected function getRedis(): Redis
|
||||
{
|
||||
return $this->redis;
|
||||
}
|
||||
}
|
|
@ -1,300 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Adapter;
|
||||
|
||||
use Appwrite\Database\Adapter;
|
||||
use Exception;
|
||||
use Redis as Client;
|
||||
|
||||
class Redis extends Adapter
|
||||
{
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
/**
|
||||
* @var Adapter
|
||||
*/
|
||||
protected $adapter;
|
||||
|
||||
/**
|
||||
* Redis constructor.
|
||||
*
|
||||
* @param Adapter $adapter
|
||||
* @param Client $redis
|
||||
*/
|
||||
public function __construct(Adapter $adapter, Client $redis)
|
||||
{
|
||||
$this->redis = $redis;
|
||||
$this->adapter = $adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Document.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getDocument($id)
|
||||
{
|
||||
$output = \json_decode($this->getRedis()->get($this->getNamespace().':document-'.$id), true);
|
||||
|
||||
if (!$output) {
|
||||
$output = $this->adapter->getDocument($id);
|
||||
$this->getRedis()->set($this->getNamespace().':document-'.$id, \json_encode($output, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
$output = $this->parseRelations($output);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $output
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function parseRelations($output)
|
||||
{
|
||||
$keys = [];
|
||||
|
||||
if (empty($output) || !isset($output['temp-relations'])) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
foreach ($output['temp-relations'] as $relationship) {
|
||||
$keys[] = $this->getNamespace().':document-'.$relationship['end'];
|
||||
}
|
||||
|
||||
$nodes = (!empty($keys)) ? $this->getRedis()->mget($keys) : [];
|
||||
|
||||
foreach ($output['temp-relations'] as $i => $relationship) {
|
||||
$node = $relationship['end'];
|
||||
|
||||
$node = (!empty($nodes[$i])) ? $this->parseRelations(\json_decode($nodes[$i], true)) : $this->getDocument($node);
|
||||
|
||||
if (empty($node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($relationship['array']) {
|
||||
$output[$relationship['key']][] = $node;
|
||||
} else {
|
||||
$output[$relationship['key']] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
unset($output['temp-relations']);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Document.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createDocument(array $data = [], array $unique = [])
|
||||
{
|
||||
$data = $this->adapter->createDocument($data, $unique);
|
||||
|
||||
$this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0);
|
||||
$this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Document.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function updateDocument(array $data = [])
|
||||
{
|
||||
$data = $this->adapter->updateDocument($data);
|
||||
|
||||
$this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0);
|
||||
$this->getRedis()->expire($this->getNamespace().':document-'.$data['$id'], 0);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Document.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function deleteDocument(string $id)
|
||||
{
|
||||
$data = $this->adapter->deleteDocument($id);
|
||||
|
||||
$this->getRedis()->expire($this->getNamespace().':document-'.$id, 0);
|
||||
$this->getRedis()->expire($this->getNamespace().':document-'.$id, 0);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Unique Key.
|
||||
*
|
||||
* @param $key
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function deleteUniqueKey($key)
|
||||
{
|
||||
$data = $this->adapter->deleteUniqueKey($key);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Unique Key.
|
||||
*
|
||||
* @param $key
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function addUniqueKey($key)
|
||||
{
|
||||
$data = $this->adapter->addUniqueKey($key);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function createNamespace($namespace)
|
||||
{
|
||||
return $this->adapter->createNamespace($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteNamespace($namespace)
|
||||
{
|
||||
return $this->adapter->deleteNamespace($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @param array $filterTypes
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getCollection(array $options, array $filterTypes = [])
|
||||
{
|
||||
$data = $this->adapter->getCollection($options, $filterTypes);
|
||||
$keys = [];
|
||||
|
||||
foreach ($data as $node) {
|
||||
$keys[] = $this->getNamespace().':document-'.$node;
|
||||
}
|
||||
|
||||
$nodes = (!empty($keys)) ? $this->getRedis()->mget($keys) : [];
|
||||
|
||||
foreach ($data as $i => &$node) {
|
||||
$temp = (!empty($nodes[$i])) ? $this->parseRelations(\json_decode($nodes[$i], true)) : $this->getDocument($node);
|
||||
|
||||
if (!empty($temp)) {
|
||||
$node = $temp;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getCount(array $options)
|
||||
{
|
||||
return $this->adapter->getCount($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Last Modified.
|
||||
*
|
||||
* Return Unix timestamp of last time a node queried in current session has been changed
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function lastModified()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDebug()
|
||||
{
|
||||
return $this->adapter->getDebug();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Client
|
||||
*/
|
||||
protected function getRedis(): Client
|
||||
{
|
||||
return $this->redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Namespace.
|
||||
*
|
||||
* Set namespace to divide different scope of data sets
|
||||
*
|
||||
* @param $namespace
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setNamespace($namespace)
|
||||
{
|
||||
$this->adapter->setNamespace($namespace);
|
||||
|
||||
return parent::setNamespace($namespace);
|
||||
}
|
||||
}
|
|
@ -1,628 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database;
|
||||
|
||||
use Exception;
|
||||
use Appwrite\Database\Validator\Authorization;
|
||||
use Appwrite\Database\Validator\Structure;
|
||||
use Appwrite\Database\Exception\Authorization as AuthorizationException;
|
||||
use Appwrite\Database\Exception\Structure as StructureException;
|
||||
|
||||
class Database
|
||||
{
|
||||
// System Core
|
||||
const SYSTEM_COLLECTION_COLLECTIONS = 0;
|
||||
const SYSTEM_COLLECTION_RULES = 'rules';
|
||||
|
||||
// Project
|
||||
const SYSTEM_COLLECTION_PROJECTS = 'projects';
|
||||
const SYSTEM_COLLECTION_WEBHOOKS = 'webhooks';
|
||||
const SYSTEM_COLLECTION_KEYS = 'keys';
|
||||
const SYSTEM_COLLECTION_TASKS = 'tasks';
|
||||
const SYSTEM_COLLECTION_PLATFORMS = 'platforms';
|
||||
const SYSTEM_COLLECTION_USAGES = 'usages'; // TODO add structure
|
||||
const SYSTEM_COLLECTION_DOMAINS = 'domains';
|
||||
const SYSTEM_COLLECTION_CERTIFICATES = 'certificates';
|
||||
const SYSTEM_COLLECTION_RESERVED = 'reserved';
|
||||
|
||||
// Auth, Account and Users (private to user)
|
||||
const SYSTEM_COLLECTION_USERS = 'users';
|
||||
const SYSTEM_COLLECTION_SESSIONS = 'sessions';
|
||||
const SYSTEM_COLLECTION_TOKENS = 'tokens';
|
||||
|
||||
// Teams (shared among team members)
|
||||
const SYSTEM_COLLECTION_MEMBERSHIPS = 'memberships';
|
||||
const SYSTEM_COLLECTION_TEAMS = 'teams';
|
||||
|
||||
// Storage
|
||||
const SYSTEM_COLLECTION_FILES = 'files';
|
||||
|
||||
// Functions
|
||||
const SYSTEM_COLLECTION_FUNCTIONS = 'functions';
|
||||
const SYSTEM_COLLECTION_TAGS = 'tags';
|
||||
const SYSTEM_COLLECTION_EXECUTIONS = 'executions';
|
||||
|
||||
// Realtime
|
||||
const SYSTEM_COLLECTION_CONNECTIONS = 'connections';
|
||||
|
||||
// Var Types
|
||||
const SYSTEM_VAR_TYPE_TEXT = 'text';
|
||||
const SYSTEM_VAR_TYPE_NUMERIC = 'numeric';
|
||||
const SYSTEM_VAR_TYPE_BOOLEAN = 'boolean';
|
||||
const SYSTEM_VAR_TYPE_DOCUMENT = 'document';
|
||||
const SYSTEM_VAR_TYPE_WILDCARD = 'wildcard';
|
||||
const SYSTEM_VAR_TYPE_EMAIL = 'email';
|
||||
const SYSTEM_VAR_TYPE_IP = 'ip';
|
||||
const SYSTEM_VAR_TYPE_URL = 'url';
|
||||
const SYSTEM_VAR_TYPE_KEY = 'key';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $filters = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $statusFilters = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $mocks = [];
|
||||
|
||||
/**
|
||||
* @var Adapter
|
||||
*/
|
||||
protected $adapter;
|
||||
|
||||
/**
|
||||
* Set Adapter.
|
||||
*
|
||||
* @param Adapter $adapter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAdapter(Adapter $adapter)
|
||||
{
|
||||
$this->adapter = $adapter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Namespace.
|
||||
*
|
||||
* Set namespace to divide different scope of data sets
|
||||
*
|
||||
* @param $namespace
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setNamespace($namespace)
|
||||
{
|
||||
$this->adapter->setNamespace($namespace);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Namespace.
|
||||
*
|
||||
* Get namespace of current set scope
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getNamespace()
|
||||
{
|
||||
return $this->adapter->getNamespace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function createNamespace($namespace)
|
||||
{
|
||||
return $this->adapter->createNamespace($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Namespace.
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteNamespace($namespace)
|
||||
{
|
||||
return $this->adapter->deleteNamespace($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @param array $filterTypes
|
||||
*
|
||||
* @return Document[]
|
||||
*/
|
||||
public function getCollection(array $options, array $filterTypes = [])
|
||||
{
|
||||
$options = \array_merge([
|
||||
'offset' => 0,
|
||||
'limit' => 15,
|
||||
'search' => '',
|
||||
'relations' => true,
|
||||
'orderField' => '',
|
||||
'orderType' => 'ASC',
|
||||
'orderCast' => 'int',
|
||||
'filters' => [],
|
||||
], $options);
|
||||
|
||||
$results = $this->adapter->getCollection($options, $filterTypes);
|
||||
|
||||
foreach ($results as &$node) {
|
||||
$node = $this->decode(new Document($node));
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getCollectionFirst(array $options)
|
||||
{
|
||||
$results = $this->getCollection($options);
|
||||
return \reset($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getCollectionLast(array $options)
|
||||
{
|
||||
$results = $this->getCollection($options);
|
||||
return \end($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param bool $mock is mocked data allowed?
|
||||
* @param bool $decode enable decoding?
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getDocument($id, bool $mock = true, bool $decode = true)
|
||||
{
|
||||
if (\is_null($id)) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
$document = new Document((isset($this->mocks[$id]) && $mock) ? $this->mocks[$id] : $this->adapter->getDocument($id));
|
||||
$validator = new Authorization($document, 'read');
|
||||
|
||||
if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document
|
||||
return new Document();
|
||||
}
|
||||
|
||||
$document = ($decode) ? $this->decode($document) : $document;
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return Document
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
* @throws StructureException
|
||||
*/
|
||||
public function createDocument(array $data, array $unique = [])
|
||||
{
|
||||
$document = new Document($data);
|
||||
|
||||
$validator = new Authorization($document, 'write');
|
||||
|
||||
if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document
|
||||
throw new AuthorizationException($validator->getDescription());
|
||||
}
|
||||
|
||||
$validator = new Structure($this);
|
||||
|
||||
$document = $this->encode($document);
|
||||
|
||||
if (!$validator->isValid($document)) {
|
||||
throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false;
|
||||
}
|
||||
|
||||
$document = new Document($this->adapter->createDocument($document->getArrayCopy(), $unique));
|
||||
|
||||
$document = $this->decode($document);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return Document|false
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function updateDocument(array $data)
|
||||
{
|
||||
if (!isset($data['$id'])) {
|
||||
throw new Exception('Must define $id attribute');
|
||||
}
|
||||
|
||||
$document = $this->getDocument($data['$id']); // TODO make sure user don\'t need read permission for write operations
|
||||
|
||||
// Make sure reserved keys stay constant
|
||||
$data['$id'] = $document->getId();
|
||||
$data['$collection'] = $document->getCollection();
|
||||
|
||||
$validator = new Authorization($document, 'write');
|
||||
|
||||
if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document
|
||||
throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false;
|
||||
}
|
||||
|
||||
$new = new Document($data);
|
||||
|
||||
if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document
|
||||
throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false;
|
||||
}
|
||||
|
||||
$new = $this->encode($new);
|
||||
|
||||
$validator = new Structure($this);
|
||||
|
||||
if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any)
|
||||
throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false;
|
||||
}
|
||||
|
||||
$new = new Document($this->adapter->updateDocument($new->getArrayCopy()));
|
||||
|
||||
$new = $this->decode($new);
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return Document|false
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function overwriteDocument(array $data)
|
||||
{
|
||||
if (!isset($data['$id'])) {
|
||||
throw new Exception('Must define $id attribute');
|
||||
}
|
||||
|
||||
$document = $this->getDocument($data['$id']); // TODO make sure user don\'t need read permission for write operations
|
||||
|
||||
$validator = new Authorization($document, 'write');
|
||||
|
||||
if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document
|
||||
throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false;
|
||||
}
|
||||
|
||||
$new = new Document($data);
|
||||
|
||||
if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document
|
||||
throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false;
|
||||
}
|
||||
|
||||
$new = $this->encode($new);
|
||||
|
||||
$validator = new Structure($this);
|
||||
|
||||
if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any)
|
||||
throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false;
|
||||
}
|
||||
|
||||
$new = new Document($this->adapter->updateDocument($new->getArrayCopy()));
|
||||
|
||||
$new = $this->decode($new);
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*
|
||||
* @return Document|false
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function deleteDocument(string $id)
|
||||
{
|
||||
$document = $this->getDocument($id);
|
||||
|
||||
$validator = new Authorization($document, 'write');
|
||||
|
||||
if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document
|
||||
throw new AuthorizationException($validator->getDescription());
|
||||
}
|
||||
|
||||
return new Document($this->adapter->deleteDocument($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $key
|
||||
*
|
||||
* @return Document|false
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function deleteUniqueKey($key)
|
||||
{
|
||||
return new Document($this->adapter->deleteUniqueKey($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $key
|
||||
*
|
||||
* @return Document|false
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function addUniqueKey($key)
|
||||
{
|
||||
return new Document($this->adapter->addUniqueKey($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDebug()
|
||||
{
|
||||
return $this->adapter->getDebug();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSum()
|
||||
{
|
||||
$debug = $this->getDebug();
|
||||
|
||||
return (isset($debug['sum'])) ? $debug['sum'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount(array $options)
|
||||
{
|
||||
$options = \array_merge([
|
||||
'filters' => [],
|
||||
], $options);
|
||||
|
||||
$results = $this->adapter->getCount($options);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMock($key, $value): self
|
||||
{
|
||||
$this->mocks[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $mocks
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMocks(array $mocks): self
|
||||
{
|
||||
$this->mocks = $mocks;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMocks()
|
||||
{
|
||||
return $this->mocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Attribute Filter
|
||||
*
|
||||
* @param string $name
|
||||
* @param callable $encode
|
||||
* @param callable $decode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addFilter(string $name, callable $encode, callable $decode): void
|
||||
{
|
||||
self::$filters[$name] = [
|
||||
'encode' => $encode,
|
||||
'decode' => $decode,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Attribute decoding
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function disableFilters(): void
|
||||
{
|
||||
self::$statusFilters = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Attribute decoding
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enableFilters(): void
|
||||
{
|
||||
self::$statusFilters = true;
|
||||
}
|
||||
|
||||
public function encode(Document $document):Document
|
||||
{
|
||||
if (!self::$statusFilters) {
|
||||
return $document;
|
||||
}
|
||||
|
||||
$collection = $this->getDocument($document->getCollection(), true, false);
|
||||
$rules = $collection->getAttribute('rules', []);
|
||||
|
||||
foreach ($rules as $key => $rule) {
|
||||
$key = $rule->getAttribute('key', null);
|
||||
$type = $rule->getAttribute('type', null);
|
||||
$array = $rule->getAttribute('array', false);
|
||||
$filters = $rule->getAttribute('filter', []);
|
||||
$value = $document->getAttribute($key, null);
|
||||
|
||||
if (($value !== null)) {
|
||||
if ($type === self::SYSTEM_VAR_TYPE_DOCUMENT) {
|
||||
if ($array) {
|
||||
$list = [];
|
||||
foreach ($value as $child) {
|
||||
$list[] = $this->encode($child);
|
||||
}
|
||||
|
||||
$document->setAttribute($key, $list);
|
||||
} else {
|
||||
$document->setAttribute($key, $this->encode($value));
|
||||
}
|
||||
} else {
|
||||
foreach ($filters as $filter) {
|
||||
$value = $this->encodeAttribute($filter, $value);
|
||||
$document->setAttribute($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
public function decode(Document $document):Document
|
||||
{
|
||||
if (!self::$statusFilters) {
|
||||
return $document;
|
||||
}
|
||||
|
||||
$collection = $this->getDocument($document->getCollection(), true, false);
|
||||
$rules = $collection->getAttribute('rules', []);
|
||||
|
||||
foreach ($rules as $key => $rule) {
|
||||
$key = $rule->getAttribute('key', null);
|
||||
$type = $rule->getAttribute('type', null);
|
||||
$array = $rule->getAttribute('array', false);
|
||||
$filters = $rule->getAttribute('filter', []);
|
||||
$value = $document->getAttribute($key, null);
|
||||
|
||||
if (($value !== null)) {
|
||||
if ($type === self::SYSTEM_VAR_TYPE_DOCUMENT) {
|
||||
if ($array) {
|
||||
$list = [];
|
||||
foreach ($value as $child) {
|
||||
$list[] = $this->decode($child);
|
||||
}
|
||||
|
||||
$document->setAttribute($key, $list);
|
||||
} else {
|
||||
$document->setAttribute($key, $this->decode($value));
|
||||
}
|
||||
} else {
|
||||
foreach (array_reverse($filters) as $filter) {
|
||||
$value = $this->decodeAttribute($filter, $value);
|
||||
$document->setAttribute($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode Attribute
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected static function encodeAttribute(string $name, $value)
|
||||
{
|
||||
if (!isset(self::$filters[$name])) {
|
||||
return $value;
|
||||
throw new Exception("Filter '{$name}' not found");
|
||||
}
|
||||
|
||||
try {
|
||||
$value = self::$filters[$name]['encode']($value);
|
||||
} catch (\Throwable $th) {
|
||||
$value = null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Attribute
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected static function decodeAttribute(string $name, $value)
|
||||
{
|
||||
if (!isset(self::$filters[$name])) {
|
||||
return $value;
|
||||
throw new Exception("Filter '{$name}' not found");
|
||||
}
|
||||
|
||||
try {
|
||||
$value = self::$filters[$name]['decode']($value);
|
||||
} catch (\Throwable $th) {
|
||||
$value = null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Last Modified.
|
||||
*
|
||||
* Return Unix timestamp of last time a node queried in current session has been changed
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function lastModified()
|
||||
{
|
||||
return $this->adapter->lastModified();
|
||||
}
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database;
|
||||
|
||||
use ArrayObject;
|
||||
|
||||
class Document extends ArrayObject
|
||||
{
|
||||
const SET_TYPE_ASSIGN = 'assign';
|
||||
const SET_TYPE_PREPEND = 'prepend';
|
||||
const SET_TYPE_APPEND = 'append';
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* Construct a new fields object
|
||||
*
|
||||
* @see ArrayObject::__construct
|
||||
*
|
||||
* @param array $input
|
||||
* @param int $flags
|
||||
* @param string $iterator_class
|
||||
*/
|
||||
public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator')
|
||||
{
|
||||
foreach ($input as $key => &$value) {
|
||||
if (\is_array($value)) {
|
||||
if ((isset($value['$id']) || isset($value['$collection'])) && (!$value instanceof self)) {
|
||||
$input[$key] = new self($value);
|
||||
} else {
|
||||
foreach ($value as $childKey => $child) {
|
||||
if ((isset($child['$id']) || isset($child['$collection'])) && (!$child instanceof self)) {
|
||||
$value[$childKey] = new self($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($input, $flags, $iterator_class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->getAttribute('$id', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCollection()
|
||||
{
|
||||
return $this->getAttribute('$collection', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPermissions()
|
||||
{
|
||||
return $this->getAttribute('$permissions', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Attribute.
|
||||
*
|
||||
* Method for getting a specific fields attribute. If $name is not found $default value will be returned.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute($name, $default = null)
|
||||
{
|
||||
$name = \explode('.', $name);
|
||||
|
||||
$temp = &$this;
|
||||
|
||||
foreach ($name as $key) {
|
||||
if (!isset($temp[$key])) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$temp = &$temp[$key];
|
||||
}
|
||||
|
||||
return $temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Document Attributes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes(): array
|
||||
{
|
||||
$attributes = [];
|
||||
|
||||
foreach ($this as $attribute => $value) {
|
||||
if(array_key_exists($attribute, ['$id' => true, '$permissions' => true, '$collection' => true, '$execute' => []])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes[$attribute] = $value;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Attribute.
|
||||
*
|
||||
* Method for setting a specific field attribute
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param string $type
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setAttribute($key, $value, $type = self::SET_TYPE_ASSIGN)
|
||||
{
|
||||
switch ($type) {
|
||||
case self::SET_TYPE_ASSIGN:
|
||||
$this[$key] = $value;
|
||||
break;
|
||||
case self::SET_TYPE_APPEND:
|
||||
$this[$key] = (!isset($this[$key]) || !\is_array($this[$key])) ? [] : $this[$key];
|
||||
\array_push($this[$key], $value);
|
||||
break;
|
||||
case self::SET_TYPE_PREPEND:
|
||||
$this[$key] = (!isset($this[$key]) || !\is_array($this[$key])) ? [] : $this[$key];
|
||||
\array_unshift($this[$key], $value);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Attribute.
|
||||
*
|
||||
* Method for removing a specific field attribute
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param string $type
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function removeAttribute($key)
|
||||
{
|
||||
if (isset($this[$key])) {
|
||||
unset($this[$key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search.
|
||||
*
|
||||
* Get array child by key and value match
|
||||
*
|
||||
* @param $key
|
||||
* @param $value
|
||||
* @param array|null $scope
|
||||
*
|
||||
* @return Document|Document[]|mixed|null|array
|
||||
*/
|
||||
public function search($key, $value, $scope = null)
|
||||
{
|
||||
$array = (!\is_null($scope)) ? $scope : $this;
|
||||
|
||||
if (\is_array($array) || $array instanceof self) {
|
||||
if (isset($array[$key]) && $array[$key] == $value) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
foreach ($array as $k => $v) {
|
||||
if ((\is_array($v) || $v instanceof self) && (!empty($v))) {
|
||||
$result = $this->search($key, $value, $v);
|
||||
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
if ($k === $key && $v === $value) {
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($array === $value) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if document has data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a document key is set.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSet($key)
|
||||
{
|
||||
return isset($this[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Array Copy.
|
||||
*
|
||||
* Outputs entity as a PHP array
|
||||
*
|
||||
* @param array $whitelist
|
||||
* @param array $blacklist
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getArrayCopy(array $whitelist = [], array $blacklist = []): array
|
||||
{
|
||||
$array = parent::getArrayCopy();
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($array as $key => &$value) {
|
||||
if (!empty($whitelist) && !\in_array($key, $whitelist)) { // Export only whitelisted fields
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($blacklist) && \in_array($key, $blacklist)) { // Don't export blacklisted fields
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value instanceof self) {
|
||||
$output[$key] = $value->getArrayCopy($whitelist, $blacklist);
|
||||
} elseif (\is_array($value)) {
|
||||
foreach ($value as $childKey => &$child) {
|
||||
if ($child instanceof self) {
|
||||
$output[$key][$childKey] = $child->getArrayCopy($whitelist, $blacklist);
|
||||
} else {
|
||||
$output[$key][$childKey] = $child;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
} else {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Exception;
|
||||
|
||||
class Authorization extends \Exception
|
||||
{
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Exception;
|
||||
|
||||
class Duplicate extends \Exception
|
||||
{
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Exception;
|
||||
|
||||
class Structure extends \Exception
|
||||
{
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Validator;
|
||||
|
||||
use Appwrite\Database\Document;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Authorization extends Validator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $roles = ['*' => true];
|
||||
|
||||
/**
|
||||
* @var Document
|
||||
*/
|
||||
protected $document;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Authorization Error';
|
||||
|
||||
/**
|
||||
* Structure constructor.
|
||||
*
|
||||
* @param Document $document
|
||||
* @param string $action
|
||||
*/
|
||||
public function __construct(Document $document, $action)
|
||||
{
|
||||
$this->document = $document;
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if valid or false if not.
|
||||
*
|
||||
* @param mixed $permissions
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($permissions): bool
|
||||
{
|
||||
if (!self::$status) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isset($permissions[$this->action])) {
|
||||
$this->message = 'Missing action key: "'.$this->action.'"';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$permission = null;
|
||||
|
||||
foreach ($permissions[$this->action] as $permission) {
|
||||
$permission = \str_replace(':{self}', ':'.$this->document->getId(), $permission);
|
||||
|
||||
if (\array_key_exists($permission, self::$roles)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->message = 'Missing "'.$this->action.'" permission for role "'.$permission.'". Only this scopes "'.\json_encode(self::getRoles()).'" are given and only this are allowed "'.\json_encode($permissions[$this->action]).'".';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setRole(string $role): void
|
||||
{
|
||||
self::$roles[$role] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unsetRole(string $role): void
|
||||
{
|
||||
unset(self::$roles[$role]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getRoles(): array
|
||||
{
|
||||
return \array_keys(self::$roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function cleanRoles(): void
|
||||
{
|
||||
self::$roles = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isRole(string $role): bool
|
||||
{
|
||||
return (\array_key_exists($role, self::$roles));
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public static $status = true;
|
||||
|
||||
/**
|
||||
* Default value in case we need
|
||||
* to reset Authorization status
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $statusDefault = true;
|
||||
|
||||
/**
|
||||
* Change default status.
|
||||
* This will be used for the
|
||||
* value set on the self::reset() method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setDefaultStatus($status): void
|
||||
{
|
||||
self::$statusDefault = $status;
|
||||
self::$status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Authorization checks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enable(): void
|
||||
{
|
||||
self::$status = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Authorization checks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function disable(): void
|
||||
{
|
||||
self::$status = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Authorization checks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset(): void
|
||||
{
|
||||
self::$status = self::$statusDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_ARRAY;
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Validator;
|
||||
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
|
||||
class Collection extends Structure
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $collections = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $merge = [];
|
||||
|
||||
/**
|
||||
* @param Database $database
|
||||
* @param array $collections
|
||||
* @param array $merge
|
||||
*/
|
||||
public function __construct(Database $database, array $collections, array $merge = [])
|
||||
{
|
||||
$this->collections = $collections;
|
||||
$this->merge = $merge;
|
||||
|
||||
return parent::__construct($database);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if valid or false if not.
|
||||
*
|
||||
* @param mixed $document
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($document): bool
|
||||
{
|
||||
$document = new Document(
|
||||
\array_merge($this->merge, ($document instanceof Document) ? $document->getArrayCopy() : $document)
|
||||
);
|
||||
|
||||
if (\is_null($document->getCollection())) {
|
||||
$this->message = 'Missing collection attribute $collection';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\in_array($document->getCollection(), $this->collections)) {
|
||||
$this->message = 'Collection is not allowed';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::isValid($document);
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Validator;
|
||||
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
use Utopia\Validator;
|
||||
|
||||
class DocumentId extends Validator
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Document not found.';
|
||||
|
||||
/**
|
||||
* @var Database
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $collection = '';
|
||||
|
||||
/**
|
||||
* Structure constructor.
|
||||
*
|
||||
* @param Database $database
|
||||
* @param string $collection
|
||||
*/
|
||||
public function __construct(Database $database, string $collection = '')
|
||||
{
|
||||
$this->database = $database;
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if valid or false if not.
|
||||
*
|
||||
* @param $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($id): bool
|
||||
{
|
||||
$document = $this->database->getDocument($id);
|
||||
|
||||
if (!$document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$document instanceof Document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$document->getId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($document->getCollection() !== $this->collection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class Key extends Validator
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Parameter must contain only letters with no spaces or special chars and be shorter than 32 chars';
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if valid or false if not.
|
||||
*
|
||||
* @param $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!\is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\preg_match('/[^A-Za-z0-9\-\_]/', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\mb_strlen($value) > 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Validator;
|
||||
|
||||
use Appwrite\Database\Document;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Permissions extends Validator
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Permissions Error';
|
||||
|
||||
/**
|
||||
* @var Document
|
||||
*/
|
||||
protected $document;
|
||||
|
||||
/**
|
||||
* Structure constructor.
|
||||
*
|
||||
* @param Document $document
|
||||
*/
|
||||
public function __construct(Document $document)
|
||||
{
|
||||
$this->document = $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if valid or false if not.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!\is_array($value) && !empty($value)) {
|
||||
$this->message = 'Invalid permissions data structure';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($value as $action => $roles) {
|
||||
if (!\in_array($action, ['read', 'write', 'execute'])) {
|
||||
$this->message = 'Unknown action ("'.$action.'")';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($roles as $role) {
|
||||
if (!\is_string($role)) {
|
||||
$this->message = 'Permissions role must be of type string.';
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_ARRAY;
|
||||
}
|
||||
}
|
|
@ -1,313 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Validator;
|
||||
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
use Appwrite\Network\Validator as NetworkValidator;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Structure extends Validator
|
||||
{
|
||||
const RULE_TYPE_ID = 'id';
|
||||
const RULE_TYPE_PERMISSIONS = 'permissions';
|
||||
const RULE_TYPE_KEY = 'key';
|
||||
const RULE_TYPE_TEXT = 'text';
|
||||
const RULE_TYPE_MARKDOWN = 'markdown';
|
||||
const RULE_TYPE_NUMERIC = 'numeric';
|
||||
const RULE_TYPE_BOOLEAN = 'boolean';
|
||||
const RULE_TYPE_EMAIL = 'email';
|
||||
const RULE_TYPE_URL = 'url';
|
||||
const RULE_TYPE_IP = 'ip';
|
||||
const RULE_TYPE_WILDCARD = 'wildcard';
|
||||
const RULE_TYPE_DOCUMENT = 'document';
|
||||
const RULE_TYPE_DOCUMENTID = 'documentId';
|
||||
const RULE_TYPE_FILEID = 'fileId';
|
||||
|
||||
/**
|
||||
* @var Database
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id = '';
|
||||
|
||||
/**
|
||||
* Basic rules to apply on all documents.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rules = [
|
||||
[
|
||||
'label' => '$id',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_RULES,
|
||||
'key' => '$id',
|
||||
'type' => 'id',
|
||||
'default' => null,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'label' => '$collection',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_RULES,
|
||||
'key' => '$collection',
|
||||
'type' => 'id',
|
||||
'default' => null,
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'label' => '$permissions',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_RULES,
|
||||
'key' => '$permissions',
|
||||
'type' => 'permissions',
|
||||
'default' => null,
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'label' => '$createdAt',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_RULES,
|
||||
'key' => '$createdAt',
|
||||
'type' => 'numeric',
|
||||
'default' => null,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'label' => '$updatedAt',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_RULES,
|
||||
'key' => '$updatedAt',
|
||||
'type' => 'numeric',
|
||||
'default' => null,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'General Error';
|
||||
|
||||
/**
|
||||
* Structure constructor.
|
||||
*
|
||||
* @param Database $database
|
||||
*/
|
||||
public function __construct(Database $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Invalid document structure: '.$this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if valid or false if not.
|
||||
*
|
||||
* @param mixed $document
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($document): bool
|
||||
{
|
||||
$document = (\is_array($document)) ? new Document($document) : $document;
|
||||
|
||||
$this->id = $document->getId();
|
||||
|
||||
if (\is_null($document->getCollection())) {
|
||||
$this->message = 'Missing collection attribute $collection';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$collection = $this->getCollection($document->getCollection());
|
||||
|
||||
if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
|
||||
$this->message = 'Collection not found';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$array = $document->getArrayCopy();
|
||||
$rules = \array_merge($this->rules, $collection->getAttribute('rules', []));
|
||||
|
||||
foreach ($rules as $rule) { // Check all required keys are set
|
||||
if (isset($rule['key']) && !isset($array[$rule['key']])
|
||||
&& isset($rule['required']) && true == $rule['required']) {
|
||||
$this->message = 'Missing required key "'.$rule['key'].'"';
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
$rule = $collection->search('key', $key, $rules);
|
||||
|
||||
if (!$rule) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ruleType = $rule['type'] ?? '';
|
||||
$ruleRequired = $rule['required'] ?? true;
|
||||
$ruleArray = $rule['array'] ?? false;
|
||||
$validator = null;
|
||||
|
||||
switch ($ruleType) {
|
||||
case self::RULE_TYPE_ID:
|
||||
$validator = new UID();
|
||||
break;
|
||||
case self::RULE_TYPE_PERMISSIONS:
|
||||
$validator = new Permissions($document); //$validator = ($this->forcePermissions) ? new Authorization($original, 'write') : new Validator\Mock();
|
||||
break;
|
||||
case self::RULE_TYPE_KEY:
|
||||
$validator = new Key();
|
||||
break;
|
||||
case self::RULE_TYPE_TEXT:
|
||||
case self::RULE_TYPE_MARKDOWN:
|
||||
$validator = new Validator\Text(0);
|
||||
break;
|
||||
case self::RULE_TYPE_NUMERIC:
|
||||
$validator = new Validator\Numeric();
|
||||
break;
|
||||
case self::RULE_TYPE_BOOLEAN:
|
||||
$validator = new Validator\Boolean();
|
||||
break;
|
||||
case self::RULE_TYPE_EMAIL:
|
||||
$validator = new NetworkValidator\Email();
|
||||
break;
|
||||
case self::RULE_TYPE_URL:
|
||||
$validator = new NetworkValidator\URL();
|
||||
break;
|
||||
case self::RULE_TYPE_IP:
|
||||
$validator = new NetworkValidator\IP();
|
||||
break;
|
||||
case self::RULE_TYPE_WILDCARD:
|
||||
$validator = new Validator\Wildcard();
|
||||
break;
|
||||
case self::RULE_TYPE_DOCUMENT:
|
||||
$validator = new Collection($this->database, (isset($rule['list'])) ? $rule['list'] : []);
|
||||
$value = $document->getAttribute($key);
|
||||
break;
|
||||
case self::RULE_TYPE_DOCUMENTID:
|
||||
$validator = new DocumentId($this->database, (isset($rule['list']) && isset($rule['list'][0])) ? $rule['list'][0] : '');
|
||||
$value = $document->getAttribute($key);
|
||||
break;
|
||||
case self::RULE_TYPE_FILEID:
|
||||
$validator = new DocumentId($this->database, Database::SYSTEM_COLLECTION_FILES);
|
||||
$value = $document->getAttribute($key);
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($validator)) { // Error creating validator for property
|
||||
$this->message = 'Unknown rule type "'.$ruleType.'" for property "'.\htmlspecialchars($key, ENT_QUOTES, 'UTF-8').'"';
|
||||
|
||||
if (empty($ruleType)) {
|
||||
$this->message = 'Unknown property "'.$key.'" type'.
|
||||
'. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($ruleRequired && ('' === $value || null === $value)) {
|
||||
$this->message = 'Required property "'.$key.'" has no value';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$ruleRequired && empty($value)) {
|
||||
unset($array[$key]);
|
||||
unset($rule);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($ruleArray) { // Array of values validation
|
||||
if (!\is_array($value)) {
|
||||
$this->message = 'Property "'.$key.'" must be an array';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO add is required check here
|
||||
|
||||
foreach ($value as $node) {
|
||||
if (!$validator->isValid($node)) { // Check if property is valid, if not required can also be empty
|
||||
$this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else { // Single value validation
|
||||
if ((!$validator->isValid($value)) && !('' === $value && !$ruleRequired)) { // Error when value is not valid, and is not optional and empty
|
||||
$this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unset($array[$key]);
|
||||
unset($rule);
|
||||
}
|
||||
|
||||
if (!empty($array)) { // No fields should be left unvalidated
|
||||
$this->message = 'Unknown properties are not allowed ('.\implode(', ', \array_keys($array)).') for this collection'.
|
||||
'. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Collection
|
||||
*
|
||||
* Get Collection by unique ID
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
protected function getCollection($id): Document
|
||||
{
|
||||
return $this->database->getDocument($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_OBJECT;
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Database\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class UID extends Validator
|
||||
{
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Invalid UID format';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if valid or false if not.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if ($value === 0) { // TODO Deprecate confition when we get the chance.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mb_strlen($value) > 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
|
@ -2,107 +2,58 @@
|
|||
|
||||
namespace Appwrite\Migration;
|
||||
|
||||
use Appwrite\Database\Document as OldDocument;
|
||||
use Appwrite\Database\Database as OldDatabase;
|
||||
use PDO;
|
||||
use Redis;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Exception;
|
||||
|
||||
abstract class Migration
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $options;
|
||||
|
||||
/**
|
||||
* @var PDO
|
||||
*/
|
||||
protected PDO $db;
|
||||
|
||||
/**
|
||||
* @var Redis
|
||||
*/
|
||||
protected Redis $cache;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $limit = 500;
|
||||
protected int $limit = 100;
|
||||
|
||||
/**
|
||||
* @var OldDocument
|
||||
* @var Document
|
||||
*/
|
||||
protected OldDocument $project;
|
||||
protected Document $project;
|
||||
|
||||
/**
|
||||
* @var OldDatabase
|
||||
* @var Database
|
||||
*/
|
||||
protected OldDatabase $oldProjectDB;
|
||||
protected Database $projectDB;
|
||||
|
||||
/**
|
||||
* @var OldDatabase
|
||||
* @var Database
|
||||
*/
|
||||
protected OldDatabase $oldConsoleDB;
|
||||
protected Database $consoleDB;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static array $versions = [
|
||||
'0.6.0' => 'V05',
|
||||
'0.7.0' => 'V06',
|
||||
'0.8.0' => 'V07',
|
||||
'0.9.0' => 'V08',
|
||||
'0.9.1' => 'V08',
|
||||
'0.9.2' => 'V08',
|
||||
'0.9.3' => 'V08',
|
||||
'0.9.4' => 'V08',
|
||||
'0.10.0' => 'V09',
|
||||
'0.10.1' => 'V09',
|
||||
'0.10.2' => 'V09',
|
||||
'0.10.3' => 'V09',
|
||||
'0.10.4' => 'V09',
|
||||
'0.11.0' => 'V10',
|
||||
'0.12.0' => 'V11',
|
||||
'0.12.1' => 'V11',
|
||||
'0.13.0' => 'V12',
|
||||
];
|
||||
|
||||
/**
|
||||
* Migration constructor.
|
||||
*
|
||||
* @param PDO $db
|
||||
* @param Redis|null $cache
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(PDO $db, Redis $cache = null, array $options = [])
|
||||
{
|
||||
$this->options = $options;
|
||||
$this->db = $db;
|
||||
if (!is_null($cache)) {
|
||||
$this->cache = $cache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set project for migration.
|
||||
*
|
||||
* @param OldDocument $project
|
||||
* @param OldDatabase $projectDB
|
||||
* @param OldDatabase $oldConsoleDB
|
||||
* @param Document $project
|
||||
* @param Database $projectDB
|
||||
* @param Database $oldConsoleDB
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setProject(OldDocument $project, OldDatabase $projectDB, OldDatabase $oldConsoleDB): self
|
||||
public function setProject(Document $project, Database $projectDB, Database $consoleDB): self
|
||||
{
|
||||
$this->project = $project;
|
||||
$this->projectDB = $projectDB;
|
||||
$this->projectDB->setNamespace('_project_' . $this->project->getId());
|
||||
|
||||
$this->oldProjectDB = $projectDB;
|
||||
$this->oldProjectDB->setNamespace('app_' . $project->getId());
|
||||
|
||||
$this->oldConsoleDB = $oldConsoleDB;
|
||||
$this->consoleDB = $consoleDB;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -114,52 +65,72 @@ abstract class Migration
|
|||
*/
|
||||
public function forEachDocument(callable $callback): void
|
||||
{
|
||||
$sum = $this->limit;
|
||||
$offset = 0;
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
while ($sum >= $this->limit) {
|
||||
$all = $this->projectDB->getCollection([
|
||||
'limit' => $this->limit,
|
||||
'offset' => $offset,
|
||||
'orderType' => 'DESC',
|
||||
]);
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
$sum = \count($all);
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
foreach ($collections as $collection) {
|
||||
$sum = 0;
|
||||
$nextDocument = null;
|
||||
$collectionCount = $this->projectDB->count($collection['$id']);
|
||||
Console::log('Migrating Collection ' . $collection['$id'] . ':');
|
||||
|
||||
Console::log('Migrating: ' . $offset . ' / ' . $this->projectDB->getSum());
|
||||
\Co\run(function () use ($all, $callback) {
|
||||
foreach ($all as $document) {
|
||||
go(function () use ($document, $callback) {
|
||||
if (empty($document->getId()) || empty($document->getCollection())) {
|
||||
if ($document->getCollection() !== 0) {
|
||||
Console::warning('Skipped Document due to missing ID or Collection.');
|
||||
do {
|
||||
$documents = $this->projectDB->find($collection['$id'], limit: $this->limit, cursor: $nextDocument);
|
||||
$count = count($documents);
|
||||
$sum += $count;
|
||||
|
||||
Console::log($sum . ' / ' . $collectionCount);
|
||||
|
||||
\Co\run(function (array $documents, callable $callback) {
|
||||
foreach ($documents as $document) {
|
||||
go(function (Document $document, callable $callback) {
|
||||
if (empty($document->getId()) || empty($document->getCollection())) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$old = $document->getArrayCopy();
|
||||
$new = call_user_func($callback, $document);
|
||||
$old = $document->getArrayCopy();
|
||||
$new = call_user_func($callback, $document);
|
||||
|
||||
if (!$this->check_diff_multi($new->getArrayCopy(), $old)) {
|
||||
return;
|
||||
}
|
||||
foreach ($document as &$attr) {
|
||||
if ($attr instanceof Document) {
|
||||
$attr = call_user_func($callback, $attr);
|
||||
}
|
||||
|
||||
try {
|
||||
$new = $this->projectDB->overwriteDocument($new->getArrayCopy());
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to update document: ' . $th->getMessage());
|
||||
return;
|
||||
|
||||
if ($document && $new->getId() !== $document->getId()) {
|
||||
throw new Exception('Duplication Error');
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as &$child) {
|
||||
if ($child instanceof Document) {
|
||||
$child = call_user_func($callback, $child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!$this->check_diff_multi($new->getArrayCopy(), $old)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$new = $this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to update document: ' . $th->getMessage());
|
||||
return;
|
||||
|
||||
if ($document && $new->getId() !== $document->getId()) {
|
||||
throw new Exception('Duplication Error');
|
||||
}
|
||||
}
|
||||
}, $document, $callback);
|
||||
}
|
||||
}, $documents, $callback);
|
||||
|
||||
if ($count !== $this->limit) {
|
||||
$nextDocument = null;
|
||||
} else {
|
||||
$nextDocument = end($documents);
|
||||
}
|
||||
});
|
||||
|
||||
$offset += $this->limit;
|
||||
} while (!is_null($nextDocument));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
|
||||
class V05 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
$db = $this->db;
|
||||
$project = $this->project;
|
||||
Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
|
||||
// Update all documents $uid -> $id
|
||||
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
|
||||
$schema = $_SERVER['_APP_DB_SCHEMA'] ?? '';
|
||||
|
||||
try {
|
||||
$statement = $db->prepare("
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `template.database.unique` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`key` varchar(128) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `index1` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `{$schema}`.`app_{$project->getId()}.database.unique` LIKE `template.database.unique`;
|
||||
ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` DROP COLUMN IF EXISTS `userType`;
|
||||
ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` DROP INDEX IF EXISTS `index_1`;
|
||||
ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` ADD INDEX IF NOT EXISTS `index_1` (`userId` ASC);
|
||||
");
|
||||
|
||||
$statement->closeCursor();
|
||||
|
||||
$statement->execute();
|
||||
} catch (\Exception $e) {
|
||||
Console::error('Failed to alter table for project: ' . $project->getId() . ' with message: ' . $e->getMessage() . '/');
|
||||
}
|
||||
}
|
||||
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
$providers = Config::getParam('providers');
|
||||
|
||||
switch ($document->getAttribute('$collection')) {
|
||||
case Database::SYSTEM_COLLECTION_PROJECTS:
|
||||
foreach ($providers as $key => $provider) {
|
||||
if (!empty($document->getAttribute('usersOauth' . \ucfirst($key) . 'Appid'))) {
|
||||
$document
|
||||
->setAttribute('usersOauth2' . \ucfirst($key) . 'Appid', $document->getAttribute('usersOauth' . \ucfirst($key) . 'Appid', ''))
|
||||
->removeAttribute('usersOauth' . \ucfirst($key) . 'Appid');
|
||||
}
|
||||
|
||||
if (!empty($document->getAttribute('usersOauth' . \ucfirst($key) . 'Secret'))) {
|
||||
$document
|
||||
->setAttribute('usersOauth2' . \ucfirst($key) . 'Secret', $document->getAttribute('usersOauth' . \ucfirst($key) . 'Secret', ''))
|
||||
->removeAttribute('usersOauth' . \ucfirst($key) . 'Secret');
|
||||
}
|
||||
}
|
||||
$document->setAttribute('security', $document->getAttribute('security') ? true : false);
|
||||
break;
|
||||
|
||||
case Database::SYSTEM_COLLECTION_TASKS:
|
||||
$document->setAttribute('security', $document->getAttribute('security') ? true : false);
|
||||
break;
|
||||
|
||||
case Database::SYSTEM_COLLECTION_USERS:
|
||||
foreach ($providers as $key => $provider) {
|
||||
if (!empty($document->getAttribute('oauth' . \ucfirst($key)))) {
|
||||
$document
|
||||
->setAttribute('oauth2' . \ucfirst($key), $document->getAttribute('oauth' . \ucfirst($key), ''))
|
||||
->removeAttribute('oauth' . \ucfirst($key));
|
||||
}
|
||||
|
||||
if (!empty($document->getAttribute('oauth' . \ucfirst($key) . 'AccessToken'))) {
|
||||
$document
|
||||
->setAttribute('oauth2' . \ucfirst($key) . 'AccessToken', $document->getAttribute('oauth' . \ucfirst($key) . 'AccessToken', ''))
|
||||
->removeAttribute('oauth' . \ucfirst($key) . 'AccessToken');
|
||||
}
|
||||
}
|
||||
|
||||
if ($document->getAttribute('confirm', null) !== null) {
|
||||
$document
|
||||
->setAttribute('emailVerification', $document->getAttribute('confirm', $document->getAttribute('emailVerification', false)))
|
||||
->removeAttribute('confirm');
|
||||
}
|
||||
break;
|
||||
|
||||
case Database::SYSTEM_COLLECTION_PLATFORMS:
|
||||
if ($document->getAttribute('url', null) !== null) {
|
||||
$document
|
||||
->setAttribute('hostname', \parse_url($document->getAttribute('url', $document->getAttribute('hostname', '')), PHP_URL_HOST))
|
||||
->removeAttribute('url');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$document
|
||||
->setAttribute('$id', $document->getAttribute('$uid', $document->getAttribute('$id')))
|
||||
->removeAttribute('$uid');
|
||||
|
||||
foreach ($document as &$attr) { // Handle child documents
|
||||
if ($attr instanceof Document) {
|
||||
$attr = $this->fixDocument($attr);
|
||||
}
|
||||
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as &$child) {
|
||||
if ($child instanceof Document) {
|
||||
$child = $this->fixDocument($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
use Appwrite\Migration\Migration;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
|
||||
class V06 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
$project = $this->project;
|
||||
Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
|
||||
$this->projectDB->disableFilters();
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
$this->projectDB->enableFilters();
|
||||
}
|
||||
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
switch ($document->getAttribute('$collection')) {
|
||||
case Database::SYSTEM_COLLECTION_USERS:
|
||||
if ($document->isSet('password-update')) {
|
||||
$document
|
||||
->setAttribute('passwordUpdate', $document->getAttribute('password-update', $document->getAttribute('passwordUpdate', '')))
|
||||
->removeAttribute('password-update');
|
||||
}
|
||||
break;
|
||||
case Database::SYSTEM_COLLECTION_KEYS:
|
||||
if ($document->getAttribute('secret', null)) {
|
||||
$json = \json_decode($document->getAttribute('secret'), true);
|
||||
if (is_array($json)) {
|
||||
Console::log('Secret already encrypted. Skipped: ' . $document->getId());
|
||||
break;
|
||||
}
|
||||
|
||||
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
|
||||
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
|
||||
$tag = null;
|
||||
|
||||
$document->setAttribute('secret', json_encode([
|
||||
'data' => OpenSSL::encrypt($document->getAttribute('secret'), OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
|
||||
'method' => OpenSSL::CIPHER_AES_128_GCM,
|
||||
'iv' => \bin2hex($iv),
|
||||
'tag' => \bin2hex($tag ?? ''),
|
||||
'version' => '1',
|
||||
]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $document;
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
|
||||
class V07 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
$project = $this->project;
|
||||
Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
$providers = Config::getParam('providers');
|
||||
|
||||
switch ($document->getAttribute('$collection')) {
|
||||
case Database::SYSTEM_COLLECTION_USERS:
|
||||
/**
|
||||
* Remove deprecated OAuth2 properties in the Users Documents.
|
||||
*/
|
||||
foreach ($providers as $key => $provider) {
|
||||
if (!empty($document->getAttribute('oauth2' . \ucfirst($key)))) {
|
||||
$document->removeAttribute('oauth2' . \ucfirst($key));
|
||||
}
|
||||
|
||||
if (!empty($document->getAttribute('oauth2' . \ucfirst($key) . 'AccessToken'))) {
|
||||
$document->removeAttribute('oauth2' . \ucfirst($key) . 'AccessToken');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invalidate all Login Tokens, since they can't be migrated to the new structure.
|
||||
* Reason for it is the missing distinction between E-Mail and OAuth2 tokens.
|
||||
*/
|
||||
$tokens = array_filter($document->getAttribute('tokens', []), function ($token) {
|
||||
return ($token->getAttribute('type') != Auth::TOKEN_TYPE_LOGIN);
|
||||
});
|
||||
$document->setAttribute('tokens', array_values($tokens));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($document as &$attr) { // Handle child documents
|
||||
if ($attr instanceof Document) {
|
||||
$attr = $this->fixDocument($attr);
|
||||
}
|
||||
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as &$child) {
|
||||
if ($child instanceof Document) {
|
||||
$child = $this->fixDocument($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
|
||||
class V08 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
$project = $this->project;
|
||||
Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
switch ($document->getAttribute('$collection')) {
|
||||
/**
|
||||
* Rename env attribute to runtime.
|
||||
*/
|
||||
case Database::SYSTEM_COLLECTION_FUNCTIONS:
|
||||
if ($document->isSet('env')) {
|
||||
$document
|
||||
->setAttribute('runtime', $document->getAttribute('env', $document->getAttribute('env', '')))
|
||||
->removeAttribute('env');
|
||||
}
|
||||
|
||||
break;
|
||||
/**
|
||||
* Add version reference to database.
|
||||
*/
|
||||
case Database::SYSTEM_COLLECTION_PROJECTS:
|
||||
$document->setAttribute('version', '0.9.0');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($document as &$attr) {
|
||||
if ($attr instanceof Document) {
|
||||
$attr = $this->fixDocument($attr);
|
||||
}
|
||||
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as &$child) {
|
||||
if ($child instanceof Document) {
|
||||
$child = $this->fixDocument($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
|
||||
class V09 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
$project = $this->project;
|
||||
Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
switch ($document->getAttribute('$collection')) {
|
||||
/**
|
||||
* Add version reference to database.
|
||||
*/
|
||||
case Database::SYSTEM_COLLECTION_PROJECTS:
|
||||
$document->setAttribute('version', '0.10.0');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($document as &$attr) {
|
||||
if ($attr instanceof Document) {
|
||||
$attr = $this->fixDocument($attr);
|
||||
}
|
||||
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as &$child) {
|
||||
if ($child instanceof Document) {
|
||||
$child = $this->fixDocument($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
|
||||
class V10 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
$project = $this->project;
|
||||
Console::log('Migrating project: ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
switch ($document->getAttribute('$collection')) {
|
||||
/**
|
||||
* Add version reference to database.
|
||||
*/
|
||||
case Database::SYSTEM_COLLECTION_PROJECTS:
|
||||
$document->setAttribute('version', '0.11.0');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($document as &$attr) {
|
||||
if ($attr instanceof Document) {
|
||||
$attr = $this->fixDocument($attr);
|
||||
}
|
||||
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as &$child) {
|
||||
if ($child instanceof Document) {
|
||||
$child = $this->fixDocument($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
|
@ -1,821 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Database\Database as OldDatabase;
|
||||
use Appwrite\Database\Document as OldDocument;
|
||||
use Appwrite\Migration\Migration;
|
||||
use Exception;
|
||||
use PDO;
|
||||
use Redis;
|
||||
use Swoole\Runtime;
|
||||
use Throwable;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Limit;
|
||||
use Utopia\Database\Exception\Authorization as ExceptionAuthorization;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
global $register;
|
||||
|
||||
class V11 extends Migration
|
||||
{
|
||||
protected Database $dbProject;
|
||||
protected Database $dbConsole;
|
||||
|
||||
protected array $oldCollections;
|
||||
protected array $newCollections;
|
||||
|
||||
public function __construct(PDO $db, Redis $cache = null, array $options = [])
|
||||
{
|
||||
parent::__construct($db, $cache, $options);
|
||||
$this->options = array_map(fn ($option) => $option === 'yes' ? true : false, $this->options);
|
||||
|
||||
if (!is_null($cache)) {
|
||||
$this->cache->flushAll();
|
||||
$cacheAdapter = new Cache(new RedisCache($this->cache));
|
||||
$this->dbProject = new Database(new MariaDB($this->db), $cacheAdapter); // namespace is set on execution
|
||||
$this->dbConsole = new Database(new MariaDB($this->db), $cacheAdapter);
|
||||
|
||||
$this->dbProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$this->dbConsole->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$this->dbConsole->setNamespace('_project_console');
|
||||
}
|
||||
|
||||
$this->newCollections = Config::getParam('collections', []);
|
||||
$this->oldCollections = Config::getParam('collectionsold', []);
|
||||
}
|
||||
|
||||
public function execute(): void
|
||||
{
|
||||
Authorization::disable();
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
$oldProject = $this->project;
|
||||
|
||||
$this->dbProject->setNamespace('_project_' . $oldProject->getId());
|
||||
$this->dbConsole->setNamespace('_project_console');
|
||||
|
||||
Console::info('');
|
||||
Console::info('------------------------------------');
|
||||
Console::info('Migrating project ' . $oldProject->getAttribute('name'));
|
||||
Console::info('------------------------------------');
|
||||
|
||||
/**
|
||||
* Create internal/external structure for projects and skip the console project.
|
||||
*/
|
||||
if ($oldProject->getId() !== 'console') {
|
||||
try {
|
||||
$project = $this->dbConsole->getDocument(collection: 'projects', id: $oldProject->getId());
|
||||
} catch (\Throwable $th) {
|
||||
Console::error($th->getTraceAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Project Document.
|
||||
*/
|
||||
if ($project->isEmpty()) {
|
||||
$newProject = $this->fixDocument($oldProject);
|
||||
$newProject->setAttribute('version', '0.12.0');
|
||||
$project = $this->dbConsole->createDocument('projects', $newProject);
|
||||
Console::log('Created project document: ' . $oldProject->getAttribute('name') . ' (' . $oldProject->getId() . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create internal tables
|
||||
*/
|
||||
try {
|
||||
Console::log('Created internal tables for : ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
$this->dbProject->createMetadata();
|
||||
} catch (\Throwable $th) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Audit tables
|
||||
*/
|
||||
if ($this->dbProject->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($this->dbProject);
|
||||
$audit->setup();
|
||||
Console::log('Created audit tables for : ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Abuse tables
|
||||
*/
|
||||
if ($this->dbProject->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
|
||||
$adapter = new TimeLimit("", 0, 1, $this->dbProject);
|
||||
$adapter->setup();
|
||||
Console::log('Created abuse tables for : ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create internal collections for Project
|
||||
*/
|
||||
foreach ($this->newCollections as $key => $collection) {
|
||||
if (!$this->dbProject->getCollection($key)->isEmpty()) continue; // Skip if project collection already exists
|
||||
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
|
||||
foreach ($collection['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => $attribute['$id'],
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($collection['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => $index['$id'],
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->dbProject->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
if ($this->options['migrateCollections']) {
|
||||
$this->migrateExternalCollections();
|
||||
}
|
||||
} else {
|
||||
Console::log('Skipped console project migration.');
|
||||
}
|
||||
|
||||
$sum = $this->limit;
|
||||
$offset = 0;
|
||||
$total = 0;
|
||||
|
||||
/**
|
||||
* Migrate internal documents
|
||||
*/
|
||||
while ($sum >= $this->limit) {
|
||||
$all = $this->oldProjectDB->getCollection([
|
||||
'limit' => $this->limit,
|
||||
'offset' => $offset,
|
||||
'orderType' => 'DESC',
|
||||
'filters' => [
|
||||
'$collection!=' . OldDatabase::SYSTEM_COLLECTION_COLLECTIONS,
|
||||
'$collection!=' . OldDatabase::SYSTEM_COLLECTION_RULES,
|
||||
'$collection!=' . OldDatabase::SYSTEM_COLLECTION_TASKS,
|
||||
'$collection!=' . OldDatabase::SYSTEM_COLLECTION_PROJECTS,
|
||||
'$collection!=' . OldDatabase::SYSTEM_COLLECTION_CONNECTIONS,
|
||||
'$collection!=' . OldDatabase::SYSTEM_COLLECTION_RESERVED,
|
||||
'$collection!=' . OldDatabase::SYSTEM_COLLECTION_TOKENS,
|
||||
]
|
||||
]);
|
||||
|
||||
$sum = \count($all);
|
||||
|
||||
Console::log('Migrating Internal Documents: ' . $offset . ' / ' . $this->oldProjectDB->getSum());
|
||||
|
||||
foreach ($all as $document) {
|
||||
if (
|
||||
!array_key_exists($document->getCollection(), $this->oldCollections)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new = $this->fixDocument($document);
|
||||
|
||||
if (is_null($new) || empty($new->getId())) {
|
||||
Console::warning('Skipped Document due to missing ID.');
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->dbProject->getDocument($new->getCollection(), $new->getId())->isEmpty()) {
|
||||
$this->dbProject->createDocument($new->getCollection(), $new);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::error("Failed to migrate document ({$new->getId()}) from collection ({$new->getCollection()}): " . $th->getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$offset += $this->limit;
|
||||
$total += $sum;
|
||||
}
|
||||
Console::log('Migrated ' . $total . ' Internal Documents.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate external collections for Project
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
* @throws Limit
|
||||
* @throws ExceptionAuthorization
|
||||
* @throws Structure
|
||||
*/
|
||||
protected function migrateExternalCollections(): void
|
||||
{
|
||||
$sum = $this->limit;
|
||||
$offset = 0;
|
||||
|
||||
while ($sum >= $this->limit) {
|
||||
$databaseCollections = $this->oldProjectDB->getCollection([
|
||||
'limit' => $this->limit,
|
||||
'offset' => $offset,
|
||||
'orderType' => 'DESC',
|
||||
'filters' => [
|
||||
'$collection=' . OldDatabase::SYSTEM_COLLECTION_COLLECTIONS,
|
||||
]
|
||||
]);
|
||||
|
||||
$sum = \count($databaseCollections);
|
||||
Console::log('Migrating Collections: ' . $offset . ' / ' . $this->oldProjectDB->getSum());
|
||||
|
||||
foreach ($databaseCollections as $oldCollection) {
|
||||
$id = $oldCollection->getId();
|
||||
$permissions = $oldCollection->getPermissions();
|
||||
$name = $oldCollection->getAttribute('name');
|
||||
$newCollection = $this->dbProject->getCollection('collection_' . $id);
|
||||
|
||||
if ($newCollection->isEmpty()) {
|
||||
$this->dbProject->createCollection('collection_' . $id);
|
||||
/**
|
||||
* Migrate permissions
|
||||
*/
|
||||
$read = $this->migrateWildcardPermissions($permissions['read'] ?? []);
|
||||
$write = $this->migrateWildcardPermissions($permissions['write'] ?? []);
|
||||
|
||||
/**
|
||||
* Suffix collection name with a subsequent number to make it unique if possible.
|
||||
*/
|
||||
$suffix = 1;
|
||||
while ($this->dbProject->findOne('collections', [
|
||||
new Query('name', Query::TYPE_EQUAL, [$name])
|
||||
])) {
|
||||
$name .= ' - ' . $suffix++;
|
||||
}
|
||||
|
||||
$this->dbProject->createDocument('collections', new Document([
|
||||
'$id' => $id,
|
||||
'$read' => [],
|
||||
'$write' => [],
|
||||
'permission' => 'document',
|
||||
'dateCreated' => time(),
|
||||
'dateUpdated' => time(),
|
||||
'name' => substr($name, 0, 256),
|
||||
'enabled' => true,
|
||||
'search' => implode(' ', [$id, $name]),
|
||||
]));
|
||||
} else {
|
||||
Console::warning('Skipped Collection ' . $newCollection->getId() . ' from ' . $newCollection->getCollection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate collection rules to attributes
|
||||
*/
|
||||
$attributes = $this->getCollectionAttributes($oldCollection);
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
try {
|
||||
$this->dbProject->createAttribute(
|
||||
collection: 'collection_' . $attribute['$collection'],
|
||||
id: $attribute['$id'],
|
||||
type: $attribute['type'],
|
||||
size: $attribute['size'],
|
||||
required: $attribute['required'],
|
||||
default: $attribute['default'],
|
||||
signed: $attribute['signed'],
|
||||
array: $attribute['array'],
|
||||
format: $attribute['format'] ?? null,
|
||||
formatOptions: $attribute['formatOptions'] ?? [],
|
||||
filters: $attribute['filters']
|
||||
);
|
||||
$this->dbProject->createDocument('attributes', new Document([
|
||||
'$id' => $attribute['$collection'] . '_' . $attribute['$id'],
|
||||
'key' => $attribute['$id'],
|
||||
'collectionId' => $attribute['$collection'],
|
||||
'type' => $attribute['type'],
|
||||
'status' => 'available',
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'default' => $attribute['default'],
|
||||
'array' => $attribute['array'],
|
||||
'format' => $attribute['format'] ?? null,
|
||||
'formatOptions' => $attribute['formatOptions'] ?? null,
|
||||
'filters' => $attribute['filters']
|
||||
]));
|
||||
|
||||
Console::log('Created "' . $attribute['$id'] . '" attribute in collection: ' . $name);
|
||||
} catch (\Throwable $th) {
|
||||
Console::log($th->getMessage() . ' - ("' . $attribute['$id'] . '" attribute in collection ' . $name . ')');
|
||||
}
|
||||
}
|
||||
if ($this->options['migrateDocuments']) {
|
||||
$this->migrateExternalDocuments(collection: $id);
|
||||
}
|
||||
}
|
||||
$offset += $this->limit;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Migrate all external documents
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
* @throws ExceptionAuthorization
|
||||
* @throws Structure
|
||||
*/
|
||||
protected function migrateExternalDocuments(string $collection): void
|
||||
{
|
||||
$sum = $this->limit;
|
||||
$offset = 0;
|
||||
|
||||
while ($sum >= $this->limit) {
|
||||
$allDocs = $this->oldProjectDB->getCollection([
|
||||
'limit' => $this->limit,
|
||||
'offset' => $offset,
|
||||
'orderType' => 'DESC',
|
||||
'filters' => [
|
||||
'$collection=' . $collection
|
||||
]
|
||||
]);
|
||||
|
||||
$sum = \count($allDocs);
|
||||
|
||||
Console::log('Migrating External Documents for Collection ' . $collection . ': ' . $offset . ' / ' . $this->oldProjectDB->getSum());
|
||||
|
||||
foreach ($allDocs as $document) {
|
||||
if (!$this->dbProject->getDocument('collection_' . $collection, $document->getId())->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
go(function ($document) {
|
||||
foreach ($document as $key => $attr) {
|
||||
/**
|
||||
* Convert nested Document to JSON strings.
|
||||
*/
|
||||
if ($document->getAttribute($key) instanceof OldDocument) {
|
||||
$document[$key] = json_encode($this->fixDocument($attr)->getArrayCopy());
|
||||
}
|
||||
/**
|
||||
* Convert numeric Attributes to float.
|
||||
*/
|
||||
if (!is_string($attr) && is_numeric($attr)) {
|
||||
$document[$key] = floatval($attr);
|
||||
}
|
||||
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as $index => $child) {
|
||||
/**
|
||||
* Convert array of nested Document to array JSON strings.
|
||||
*/
|
||||
if ($document->getAttribute($key)[$index] instanceof OldDocument) {
|
||||
$document[$key][$index] = json_encode($this->fixDocument($child)->getArrayCopy());
|
||||
}
|
||||
/**
|
||||
* Convert array of numeric Attributes to array float.
|
||||
*/
|
||||
if (!is_string($child) && is_numeric($child)) {
|
||||
$document[$key][$index] = floatval($child); // Convert any numeric to float
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, $document);
|
||||
$document = new Document($document->getArrayCopy());
|
||||
$document = $this->migratePermissions($document);
|
||||
|
||||
try {
|
||||
$this->dbProject->createDocument('collection_' . $collection, $document);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error("Failed to migrate document ({$document->getId()}): " . $th->getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$offset += $this->limit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates single docuemnt.
|
||||
*
|
||||
* @param OldDocument $oldDocument
|
||||
* @return Document|null
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function fixDocument(OldDocument $oldDocument): Document|null
|
||||
{
|
||||
$document = new Document($oldDocument->getArrayCopy());
|
||||
$document = $this->migratePermissions($document);
|
||||
|
||||
/**
|
||||
* Check attributes and set their default values.
|
||||
*/
|
||||
if (array_key_exists($document->getCollection(), $this->oldCollections)) {
|
||||
foreach ($this->newCollections[$document->getCollection()]['attributes'] ?? [] as $attr) {
|
||||
if (
|
||||
(!$attr['array'] ||
|
||||
($attr['array'] && array_key_exists('filter', $attr)
|
||||
&& in_array('json', $attr['filter'])))
|
||||
&& empty($document->getAttribute($attr['$id'], null))
|
||||
) {
|
||||
$document->setAttribute($attr['$id'], $attr['default'] ?? null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch ($document->getAttribute('$collection')) {
|
||||
case OldDatabase::SYSTEM_COLLECTION_PROJECTS:
|
||||
$newProviders = [];
|
||||
$newAuths = [];
|
||||
$providers = Config::getParam('providers', []);
|
||||
$auths = Config::getParam('auth', []);
|
||||
|
||||
/**
|
||||
* Remove Tasks
|
||||
*/
|
||||
$document->removeAttribute('tasks');
|
||||
|
||||
/*
|
||||
* Add enabled OAuth2 providers to default data rules
|
||||
*/
|
||||
foreach ($providers as $index => $provider) {
|
||||
$appId = $document->getAttribute('usersOauth2' . \ucfirst($index) . 'Appid');
|
||||
$appSecret = $document->getAttribute('usersOauth2' . \ucfirst($index) . 'Secret');
|
||||
|
||||
if (!is_null($appId) || !is_null($appId)) {
|
||||
$newProviders[$appId] = $appSecret;
|
||||
}
|
||||
|
||||
$document
|
||||
->removeAttribute('usersOauth2' . \ucfirst($index) . 'Appid')
|
||||
->removeAttribute('usersOauth2' . \ucfirst($index) . 'Secret');
|
||||
}
|
||||
|
||||
$document->setAttribute('providers', $newProviders);
|
||||
|
||||
/*
|
||||
* Migrate User providers settings
|
||||
*/
|
||||
$oldAuths = [
|
||||
'email-password' => 'usersAuthEmailPassword',
|
||||
'magic-url' => 'usersAuthMagicURL',
|
||||
'anonymous' => 'usersAuthAnonymous',
|
||||
'invites' => 'usersAuthInvites',
|
||||
'jwt' => 'usersAuthJWT',
|
||||
'phone' => 'usersAuthPhone'
|
||||
];
|
||||
|
||||
foreach ($oldAuths as $index => $auth) {
|
||||
$enabled = $document->getAttribute($auth, true);
|
||||
$newAuths['auth' . \ucfirst($auths[$index]['key'])] = $enabled;
|
||||
$document->removeAttribute($auth);
|
||||
}
|
||||
|
||||
if (!empty($document->getAttribute('usersAuthLimit'))) {
|
||||
$newAuths['limit'] = $document->getAttribute('usersAuthLimit');
|
||||
}
|
||||
|
||||
$document->removeAttribute('usersAuthLimit');
|
||||
|
||||
$document->setAttribute('auths', $newProviders);
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_PLATFORMS:
|
||||
$projectId = $this->getProjectIdFromReadPermissions($document);
|
||||
|
||||
if (is_null($projectId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Project ID
|
||||
*/
|
||||
if ($document->getAttribute('projectId') === null) {
|
||||
$document->setAttribute('projectId', $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set empty key and store if null
|
||||
*/
|
||||
if ($document->getAttribute('key') === null) {
|
||||
$document->setAttribute('key', '');
|
||||
}
|
||||
if ($document->getAttribute('store') === null) {
|
||||
$document->setAttribute('store', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Permissions
|
||||
*/
|
||||
$document->setAttribute('$read', ['role:all']);
|
||||
$document->setAttribute('$write', ['role:all']);
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_CERTIFICATES:
|
||||
/**
|
||||
* Replace certificateId attribute.
|
||||
*/
|
||||
if ($document->getAttribute('certificateId') !== null) {
|
||||
$document->setAttribute('$id', $document->getAttribute('certificateId'));
|
||||
$document->removeAttribute('certificateId');
|
||||
}
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_DOMAINS:
|
||||
$projectId = $this->getProjectIdFromReadPermissions($document);
|
||||
|
||||
if (is_null($projectId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Project ID
|
||||
*/
|
||||
if ($document->getAttribute('projectId') === null) {
|
||||
$document->setAttribute('projectId', $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set empty verification if null
|
||||
*/
|
||||
if ($document->getAttribute('verification') === null) {
|
||||
$document->setAttribute('verification', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Permissions
|
||||
*/
|
||||
$document->setAttribute('$read', ['role:all']);
|
||||
$document->setAttribute('$write', ['role:all']);
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_KEYS:
|
||||
$projectId = $this->getProjectIdFromReadPermissions($document);
|
||||
|
||||
if (is_null($projectId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Project ID
|
||||
*/
|
||||
if ($document->getAttribute('projectId') === null) {
|
||||
$document->setAttribute('projectId', $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set scopes if empty
|
||||
*/
|
||||
if (empty($document->getAttribute('scopes', []))) {
|
||||
$document->setAttribute('scopes', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Permissions
|
||||
*/
|
||||
$document->setAttribute('$read', ['role:all']);
|
||||
$document->setAttribute('$write', ['role:all']);
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_FUNCTIONS:
|
||||
$document->setAttribute('events', $document->getAttribute('events', []));
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_WEBHOOKS:
|
||||
$projectId = $this->getProjectIdFromReadPermissions($document);
|
||||
|
||||
if (is_null($projectId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Project ID
|
||||
*/
|
||||
if ($document->getAttribute('projectId') === null) {
|
||||
$document->setAttribute('projectId', $projectId);
|
||||
}
|
||||
|
||||
$document->setAttribute('events', $document->getAttribute('events', []));
|
||||
|
||||
/**
|
||||
* Reset Permissions
|
||||
*/
|
||||
$document->setAttribute('$read', ['role:all']);
|
||||
$document->setAttribute('$write', ['role:all']);
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_USERS:
|
||||
/**
|
||||
* Set deleted attribute to false
|
||||
*/
|
||||
if ($document->getAttribute('deleted') === null) {
|
||||
$document->setAttribute('deleted', false);
|
||||
}
|
||||
/**
|
||||
* Remove deprecated user status 0 and replace with boolean.
|
||||
*/
|
||||
if ($document->getAttribute('status') === 2) {
|
||||
$document->setAttribute('status', false);
|
||||
} else {
|
||||
$document->setAttribute('status', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default values for arrays if not set.
|
||||
*/
|
||||
if (empty($document->getAttribute('prefs', []))) {
|
||||
$document->setAttribute('prefs', new \stdClass());
|
||||
}
|
||||
if (empty($document->getAttribute('sessions', []))) {
|
||||
$document->setAttribute('sessions', []);
|
||||
}
|
||||
if (empty($document->getAttribute('tokens', []))) {
|
||||
$document->setAttribute('tokens', []);
|
||||
}
|
||||
if (empty($document->getAttribute('memberships', []))) {
|
||||
$document->setAttribute('memberships', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace user:{self} with user:USER_ID
|
||||
*/
|
||||
$write = $document->getWrite();
|
||||
$document->setAttribute('$write', str_replace('user:{self}', "user:{$document->getId()}", $write));
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_TEAMS:
|
||||
|
||||
/**
|
||||
* Replace team:{self} with team:TEAM_ID
|
||||
*/
|
||||
$read = $document->getWrite();
|
||||
$write = $document->getWrite();
|
||||
|
||||
$document->setAttribute('$read', str_replace('team:{self}', "team:{$document->getId()}", $read));
|
||||
$document->setAttribute('$write', str_replace('team:{self}', "team:{$document->getId()}", $write));
|
||||
|
||||
break;
|
||||
case OldDatabase::SYSTEM_COLLECTION_FILES:
|
||||
/**
|
||||
* Migrating breakind changes on Files.
|
||||
*/
|
||||
if (!empty($document->getAttribute('fileOpenSSLVersion', null))) {
|
||||
$document
|
||||
->setAttribute('openSSLVersion', $document->getAttribute('fileOpenSSLVersion'))
|
||||
->removeAttribute('fileOpenSSLVersion');
|
||||
}
|
||||
if (!empty($document->getAttribute('fileOpenSSLCipher', null))) {
|
||||
$document
|
||||
->setAttribute('openSSLCipher', $document->getAttribute('fileOpenSSLCipher'))
|
||||
->removeAttribute('fileOpenSSLCipher');
|
||||
}
|
||||
if (!empty($document->getAttribute('fileOpenSSLTag', null))) {
|
||||
$document
|
||||
->setAttribute('openSSLTag', $document->getAttribute('fileOpenSSLTag'))
|
||||
->removeAttribute('fileOpenSSLTag');
|
||||
}
|
||||
if (!empty($document->getAttribute('fileOpenSSLIV', null))) {
|
||||
$document
|
||||
->setAttribute('openSSLIV', $document->getAttribute('fileOpenSSLIV'))
|
||||
->removeAttribute('fileOpenSSLIV');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove deprecated attributes.
|
||||
*/
|
||||
$document->removeAttribute('folderId');
|
||||
$document->removeAttribute('token');
|
||||
break;
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates $permissions to independent $read and $write.
|
||||
* @param Document $document
|
||||
* @return Document
|
||||
*/
|
||||
protected function migratePermissions(Document $document): Document
|
||||
{
|
||||
if ($document->isSet('$permissions')) {
|
||||
$permissions = $document->getAttribute('$permissions', []);
|
||||
$read = $this->migrateWildcardPermissions($permissions['read'] ?? []);
|
||||
$write = $this->migrateWildcardPermissions($permissions['write'] ?? []);
|
||||
$document->setAttribute('$read', $read);
|
||||
$document->setAttribute('$write', $write);
|
||||
$document->removeAttribute('$permissions');
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a permissions array and replaces wildcard * with role:all.
|
||||
* @param array $permissions
|
||||
* @return array
|
||||
*/
|
||||
protected function migrateWildcardPermissions(array $permissions): array
|
||||
{
|
||||
return array_map(function ($permission) {
|
||||
if ($permission === '*') return 'role:all';
|
||||
return $permission;
|
||||
}, $permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get new collection attributes from old collection rules.
|
||||
* @param OldDocument $collection
|
||||
* @return array
|
||||
*/
|
||||
protected function getCollectionAttributes(OldDocument $collection): array
|
||||
{
|
||||
$attributes = [];
|
||||
foreach ($collection->getAttribute('rules', []) as $key => $value) {
|
||||
$collectionId = $collection->getId();
|
||||
$id = $value['key'];
|
||||
$array = $value['array'] ?? false;
|
||||
$required = $value['required'] ?? false;
|
||||
$default = $value['default'] ?? null;
|
||||
$default = match ($value['type']) {
|
||||
OldDatabase::SYSTEM_VAR_TYPE_NUMERIC => floatval($default),
|
||||
default => $default
|
||||
};
|
||||
$type = match ($value['type']) {
|
||||
OldDatabase::SYSTEM_VAR_TYPE_TEXT => Database::VAR_STRING,
|
||||
OldDatabase::SYSTEM_VAR_TYPE_EMAIL => Database::VAR_STRING,
|
||||
OldDatabase::SYSTEM_VAR_TYPE_DOCUMENT => Database::VAR_STRING,
|
||||
OldDatabase::SYSTEM_VAR_TYPE_IP => Database::VAR_STRING,
|
||||
OldDatabase::SYSTEM_VAR_TYPE_URL => Database::VAR_STRING,
|
||||
OldDatabase::SYSTEM_VAR_TYPE_WILDCARD => Database::VAR_STRING,
|
||||
OldDatabase::SYSTEM_VAR_TYPE_NUMERIC => Database::VAR_FLOAT,
|
||||
OldDatabase::SYSTEM_VAR_TYPE_BOOLEAN => Database::VAR_BOOLEAN,
|
||||
default => Database::VAR_STRING
|
||||
};
|
||||
|
||||
$size = $type === Database::VAR_STRING ? 65_535 : 0; // Max size of text in MariaDB
|
||||
|
||||
if ($required) {
|
||||
$default = null;
|
||||
}
|
||||
|
||||
$attributes[$key] = [
|
||||
'$collection' => $collectionId,
|
||||
'$id' => $id,
|
||||
'type' => $type,
|
||||
'size' => $size,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
'signed' => true,
|
||||
'filters' => []
|
||||
];
|
||||
|
||||
if ($type === Database::VAR_FLOAT) {
|
||||
$attributes[$key]['format'] = APP_DATABASE_ATTRIBUTE_FLOAT_RANGE;
|
||||
$attributes[$key]['formatOptions'] = [];
|
||||
$attributes[$key]['formatOptions']['min'] = -PHP_FLOAT_MAX;
|
||||
$attributes[$key]['formatOptions']['max'] = PHP_FLOAT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document
|
||||
* @return string|null
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getProjectIdFromReadPermissions(Document $document): string|null
|
||||
{
|
||||
$readPermissions = $document->getRead();
|
||||
$teamId = str_replace('team:', '', reset($readPermissions));
|
||||
$project = $this->oldConsoleDB->getCollectionFirst([
|
||||
'filters' => [
|
||||
'$collection=' . OldDatabase::SYSTEM_COLLECTION_PROJECTS,
|
||||
'teamId=' . $teamId
|
||||
]
|
||||
]);
|
||||
|
||||
if (!$project) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $project->getId();
|
||||
}
|
||||
}
|
113
src/Appwrite/Migration/Version/V12.php
Normal file
113
src/Appwrite/Migration/Version/V12.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class V12 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
Console::log('Migrating project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
|
||||
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
switch ($document->getCollection()) {
|
||||
case 'projects':
|
||||
/**
|
||||
* Bump Project version number.
|
||||
*/
|
||||
$document->setAttribute('version', '0.13.0');
|
||||
|
||||
/**
|
||||
* Populate search string from Migration to 0.12.
|
||||
*/
|
||||
if (empty($document->getAttribute('search'))) {
|
||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'name'], $document));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'users':
|
||||
/**
|
||||
* Populate search string from Migration to 0.12.
|
||||
*/
|
||||
if (empty($document->getAttribute('search'))) {
|
||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'email', 'name'], $document));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'teams':
|
||||
/**
|
||||
* Populate search string from Migration to 0.12.
|
||||
*/
|
||||
if (empty($document->getAttribute('search'))) {
|
||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'name'], $document));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'files':
|
||||
/**
|
||||
* Populate search string from Migration to 0.12.
|
||||
*/
|
||||
if (empty($document->getAttribute('search'))) {
|
||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'name'], $document));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'functions':
|
||||
/**
|
||||
* Populate search string from Migration to 0.12.
|
||||
*/
|
||||
if (empty($document->getAttribute('search'))) {
|
||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'name', 'runtime'], $document));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'tags':
|
||||
/**
|
||||
* Populate search string from Migration to 0.12.
|
||||
*/
|
||||
if (empty($document->getAttribute('search'))) {
|
||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'command'], $document));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'executions':
|
||||
/**
|
||||
* Populate search string from Migration to 0.12.
|
||||
*/
|
||||
if (empty($document->getAttribute('search'))) {
|
||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'functionId'], $document));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a search string for a fulltext index.
|
||||
*
|
||||
* @param array $values
|
||||
* @param Document $document
|
||||
* @return string
|
||||
*/
|
||||
private function buildSearchAttribute(array $values, Document $document): string
|
||||
{
|
||||
$values = array_filter(array_map(fn (string $value) => $document->getAttribute($value) ?? '', $values));
|
||||
|
||||
return implode(' ', $values);
|
||||
}
|
||||
}
|
|
@ -275,7 +275,7 @@ class OpenAPI3 extends Format
|
|||
$node['schema']['x-example'] = false;
|
||||
break;
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case 'Appwrite\Database\Validator\CustomId':
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
|
||||
break;
|
||||
|
|
|
@ -263,7 +263,7 @@ class Swagger2 extends Format
|
|||
$node['x-example'] = false;
|
||||
break;
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case 'Appwrite\Database\Validator\CustomId':
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
$node['type'] = $validator->getType();
|
||||
$node['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace Appwrite\Database\Validator;
|
||||
namespace Appwrite\Utopia\Database\Validator;
|
||||
|
||||
use Utopia\Database\Validator\Key;
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Validator\Authorization;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
use Exception;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Locale\Locale as Locale;
|
||||
|
||||
class V06 extends Filter
|
||||
{
|
||||
// Convert 0.7 Data format to 0.6 format
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
$parsedResponse = [];
|
||||
|
||||
switch ($model) {
|
||||
|
||||
case Response::MODEL_DOCUMENT_LIST:
|
||||
$parsedResponse = $content;
|
||||
break;
|
||||
|
||||
case Response::MODEL_COLLECTION:
|
||||
$parsedResponse = $this->parseCollection($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_COLLECTION_LIST:
|
||||
$parsedResponse = $this->parseCollectionList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_FILE:
|
||||
$parsedResponse = $this->parseFile($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_FILE_LIST:
|
||||
$parsedResponse = $content;
|
||||
break;
|
||||
|
||||
case Response::MODEL_USER:
|
||||
$parsedResponse = $this->parseUser($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_USER_LIST:
|
||||
$parsedResponse = $this->parseUserList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_TEAM:
|
||||
$parsedResponse = $this->parseTeam($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_TEAM_LIST:
|
||||
$parsedResponse = $this->parseTeamList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_MEMBERSHIP:
|
||||
$parsedResponse = $content;
|
||||
break;
|
||||
|
||||
case Response::MODEL_MEMBERSHIP_LIST:
|
||||
$parsedResponse = $content['memberships'];
|
||||
break;
|
||||
|
||||
case Response::MODEL_SESSION:
|
||||
$parsedResponse = $this->parseSession($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_SESSION_LIST:
|
||||
$parsedResponse = $this->parseSessionList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_LOG_LIST:
|
||||
$parsedResponse = $this->parseLogList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_TOKEN:
|
||||
$parsedResponse = $this->parseToken($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_LOCALE:
|
||||
$parsedResponse = $this->parseLocale($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_COUNTRY_LIST:
|
||||
$parsedResponse = $this->parseCountryList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_PHONE_LIST:
|
||||
$parsedResponse = $this->parsePhoneList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_CONTINENT_LIST:
|
||||
$parsedResponse = $this->parseContinentList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_CURRENCY_LIST:
|
||||
$parsedResponse = $this->parseCurrencyList($content);
|
||||
break;
|
||||
|
||||
case Response::MODEL_LANGUAGE_LIST:
|
||||
$parsedResponse = $content;
|
||||
break;
|
||||
|
||||
case Response::MODEL_ANY:
|
||||
case Response::MODEL_DOCUMENT:
|
||||
case Response::MODEL_PREFERENCES:
|
||||
$parsedResponse = $content;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Received invalid model : ' . $model);
|
||||
}
|
||||
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
private function parseCollectionList(array $content)
|
||||
{
|
||||
foreach ($content['collections'] as $key => $collection) {
|
||||
$content['collections'][$key] = $this->parseCollection($collection);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseCollection(array $content)
|
||||
{
|
||||
$content['$collection'] = Database::SYSTEM_COLLECTION_COLLECTIONS;
|
||||
$content['structure'] = true;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseFile(array $content)
|
||||
{
|
||||
$content['$collection'] = Database::SYSTEM_COLLECTION_FILES;
|
||||
$content['algorithm'] = 'gzip';
|
||||
$content['comment'] = '';
|
||||
$content['fileOpenSSLCipher'] = OpenSSL::CIPHER_AES_128_GCM;
|
||||
$content['fileOpenSSLIV'] = '';
|
||||
$content['fileOpenSSLTag'] = '';
|
||||
$content['fileOpenSSLVersion'] = '';
|
||||
$content['folderId'] = '';
|
||||
$content['path'] = '';
|
||||
$content['sizeActual'] = $content['sizeOriginal'];
|
||||
$content['token'] = '';
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseCurrencyList(array $content)
|
||||
{
|
||||
$content['locations'] = [];
|
||||
$currencies = $content['currencies'];
|
||||
$parsedResponse = [];
|
||||
foreach ($currencies as $currency) {
|
||||
$currency['locations'] = [];
|
||||
$parsedResponse[] = $currency;
|
||||
}
|
||||
$content['currencies'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseContinentList(array $content)
|
||||
{
|
||||
$continents = $content['continents'];
|
||||
$parsedResponse = [];
|
||||
foreach ($continents as $continent) {
|
||||
$parsedResponse[$continent['code']] = $continent['name'];
|
||||
}
|
||||
$content['continents'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parsePhoneList(array $content)
|
||||
{
|
||||
$phones = $content['phones'];
|
||||
$parsedResponse = [];
|
||||
foreach ($phones as $phone) {
|
||||
$parsedResponse[$phone['countryCode']] = $phone['code'];
|
||||
}
|
||||
$content['phones'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseCountryList(array $content)
|
||||
{
|
||||
$countries = $content['countries'];
|
||||
$parsedResponse = [];
|
||||
foreach ($countries as $country) {
|
||||
$parsedResponse[$country['code']] = $country['name'];
|
||||
}
|
||||
$content['countries'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseLocale(array $content)
|
||||
{
|
||||
$content['ip'] = $content['ip'] ?? '';
|
||||
$content['countryCode'] = $content['countryCode'] ?? '--';
|
||||
$content['country'] = $content['country'] ?? Locale::getText('locale.country.unknown');
|
||||
$content['continent'] = $content['continent'] ?? Locale::getText('locale.country.unknown');
|
||||
$content['continentCode'] = $content['continentCode'] ?? '--';
|
||||
$content['eu'] = $content['eu'] ?? false;
|
||||
$content['currency'] = $content['currency'] ?? null;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseToken(array $content)
|
||||
{
|
||||
$content['type'] = Auth::TOKEN_TYPE_RECOVERY;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseTeam(array $content)
|
||||
{
|
||||
$content['$collection'] = Database::SYSTEM_COLLECTION_TEAMS;
|
||||
$content['$permissions'] = [];
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseTeamList(array $content)
|
||||
{
|
||||
$teams = $content['teams'];
|
||||
$parsedResponse = [];
|
||||
foreach ($teams as $team) {
|
||||
$parsedResponse[] = $this->parseTeam($team);
|
||||
}
|
||||
$content['teams'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseLogList(array $content)
|
||||
{
|
||||
$logs = $content['logs'];
|
||||
$parsedResponse = [];
|
||||
foreach ($logs as $log) {
|
||||
$parsedResponse[] = [
|
||||
'brand' => $log['deviceBrand'],
|
||||
'device' => $log['deviceName'],
|
||||
'event' => $log['event'],
|
||||
'ip' => $log['ip'],
|
||||
'model' => $log['deviceModel'],
|
||||
'time' => $log['time'],
|
||||
'geo' => [
|
||||
'isoCode' => empty($log['countryCode']) ? '---' : $log['countryCode'],
|
||||
'country' => empty($log['countryName']) ? Locale::getText('locale.country.unknown') : $log['countryName']
|
||||
],
|
||||
'OS' => [
|
||||
'name' => $log['osName'],
|
||||
'platform' => '',
|
||||
'short_name' => $log['osCode'],
|
||||
'version' => $log['osVersion']
|
||||
],
|
||||
'client' => [
|
||||
'engine' => $log['clientEngine'],
|
||||
'name' => $log['clientName'],
|
||||
'short_name' => $log['clientCode'],
|
||||
'type' => $log['clientType'],
|
||||
'version' => $log['clientVersion']
|
||||
]
|
||||
];
|
||||
}
|
||||
$content['logs'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseSessionList(array $content)
|
||||
{
|
||||
$sessions = $content['sessions'];
|
||||
$parsedResponse = [];
|
||||
foreach ($sessions as $session) {
|
||||
$parsedResponse[] = [
|
||||
'$id' => $session['$id'],
|
||||
'brand' => $session['deviceBrand'],
|
||||
'current' => $session['current'],
|
||||
'device' => $session['deviceName'],
|
||||
'ip' => $session['ip'],
|
||||
'model' => $session['deviceModel'],
|
||||
'geo' => [
|
||||
'isoCode' => empty($session['countryCode']) ? '---' : $session['countryCode'],
|
||||
'country' => empty($session['countryName']) ? Locale::getText('locale.country.unknown') : $session['countryName']
|
||||
],
|
||||
'OS' => [
|
||||
'name' => $session['osName'],
|
||||
'platform' => '',
|
||||
'short_name' => $session['osCode'],
|
||||
'version' => $session['osVersion']
|
||||
],
|
||||
'client' => [
|
||||
'engine' => $session['clientEngine'],
|
||||
'name' => $session['clientName'],
|
||||
'short_name' => $session['clientCode'],
|
||||
'type' => $session['clientType'],
|
||||
'version' => $session['clientVersion']
|
||||
]
|
||||
];
|
||||
}
|
||||
$content['sessions'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseSession(array $content)
|
||||
{
|
||||
$content['type'] = Auth::TOKEN_TYPE_LOGIN;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseUserList(array $content)
|
||||
{
|
||||
$users = $content['users'];
|
||||
$parsedResponse = [];
|
||||
foreach ($users as $user) {
|
||||
$parsedResponse[] = $this->parseUser($user);
|
||||
}
|
||||
$content['users'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function parseUser(array $content)
|
||||
{
|
||||
foreach (Config::getParam('providers', []) as $key => $provider) {
|
||||
if (!$provider['enabled']) {
|
||||
continue;
|
||||
}
|
||||
$content['oauth2' . ucfirst($key)] = '';
|
||||
$content['oauth2' . ucfirst($key) . 'AccessToken'] = '';
|
||||
}
|
||||
$content['status'] = $content['status'] ? 0 : 2;
|
||||
$content['roles'] = Authorization::getRoles() ?? [];
|
||||
return $content;
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
use Exception;
|
||||
|
||||
class V07 extends Filter
|
||||
{
|
||||
|
||||
// Convert 0.8 Data format to 0.7 format
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
$parsedResponse = [];
|
||||
|
||||
switch ($model) {
|
||||
|
||||
case Response::MODEL_DOCUMENT_LIST: /** ANY was replaced by DOCUMENT in 0.8.x but this is backward compatible with 0.7.x */
|
||||
// no break
|
||||
case Response::MODEL_DOCUMENT: /** ANY was replaced by DOCUMENT in 0.8.x but this is backward compatible with 0.7.x */
|
||||
// no break
|
||||
case Response::MODEL_USER_LIST: /** [FIELDS ADDED in 0.8.x] passwordUpdate */
|
||||
// no break
|
||||
case Response::MODEL_USER: /** [FIELDS ADDED in 0.8.x] passwordUpdate */
|
||||
// no break
|
||||
case Response::MODEL_COLLECTION_LIST:
|
||||
case Response::MODEL_COLLECTION:
|
||||
case Response::MODEL_FILE_LIST:
|
||||
case Response::MODEL_FILE:
|
||||
case Response::MODEL_TAG_LIST:
|
||||
case Response::MODEL_TAG:
|
||||
case Response::MODEL_EXECUTION_LIST:
|
||||
case Response::MODEL_EXECUTION:
|
||||
case Response::MODEL_TEAM_LIST:
|
||||
case Response::MODEL_TEAM:
|
||||
case Response::MODEL_MEMBERSHIP_LIST:
|
||||
case Response::MODEL_MEMBERSHIP:
|
||||
case Response::MODEL_SESSION_LIST: /** [FIELDS ADDED in 0.8.x] provider, providerUid, providerToken */
|
||||
// no break
|
||||
case Response::MODEL_SESSION: /** [FIELDS ADDED in 0.8.x] provider, providerUid, providerToken */
|
||||
// no break
|
||||
case Response::MODEL_JWT:
|
||||
case Response::MODEL_LOG:
|
||||
case Response::MODEL_LOG_LIST:
|
||||
case Response::MODEL_TOKEN:
|
||||
case Response::MODEL_LOCALE:
|
||||
case Response::MODEL_COUNTRY:
|
||||
case Response::MODEL_COUNTRY_LIST:
|
||||
case Response::MODEL_PHONE:
|
||||
case Response::MODEL_PHONE_LIST:
|
||||
case Response::MODEL_CONTINENT:
|
||||
case Response::MODEL_CONTINENT_LIST:
|
||||
case Response::MODEL_CURRENCY:
|
||||
case Response::MODEL_CURRENCY_LIST:
|
||||
case Response::MODEL_LANGUAGE:
|
||||
case Response::MODEL_LANGUAGE_LIST:
|
||||
case Response::MODEL_PROJECT:
|
||||
case Response::MODEL_PROJECT_LIST:
|
||||
case Response::MODEL_PLATFORM:
|
||||
case Response::MODEL_PLATFORM_LIST:
|
||||
case Response::MODEL_DOMAIN:
|
||||
case Response::MODEL_DOMAIN_LIST:
|
||||
case Response::MODEL_KEY:
|
||||
case Response::MODEL_KEY_LIST:
|
||||
case Response::MODEL_PERMISSIONS:
|
||||
case Response::MODEL_RULE:
|
||||
case Response::MODEL_TASK:
|
||||
case Response::MODEL_WEBHOOK:
|
||||
case Response::MODEL_WEBHOOK_LIST:
|
||||
case Response::MODEL_MOCK:
|
||||
case Response::MODEL_ANY:
|
||||
case Response::MODEL_PREFERENCES: /** ANY was replaced by PREFERENCES in 0.8.x but this is backward compatible with 0.7.x */
|
||||
// no break
|
||||
case Response::MODEL_NONE:
|
||||
case Response::MODEL_ERROR:
|
||||
case Response::MODEL_ERROR_DEV:
|
||||
$parsedResponse = $content;
|
||||
break;
|
||||
case Response::MODEL_FUNCTION_LIST: /** Function property env was renamed to runtime in 0.9.x */
|
||||
$parsedResponse = $this->parseFunctionList($content);
|
||||
break;
|
||||
case Response::MODEL_FUNCTION:
|
||||
$parsedResponse = $this->parseFunctionList($content); /** Function property env was renamed to runtime in 0.9.x */
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Received invalid model : '.$model);
|
||||
}
|
||||
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
protected function parseFunction(array $content)
|
||||
{
|
||||
$content['env'] = $content['runtime'];
|
||||
unset($content['runtime']);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseFunctionList(array $content)
|
||||
{
|
||||
$functions = $content['functions'];
|
||||
$parsedResponse = [];
|
||||
foreach ($functions as $function) {
|
||||
$parsedResponse[] = $this->parseFunction($function);
|
||||
}
|
||||
$content['functions'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
use Exception;
|
||||
|
||||
class V08 extends Filter
|
||||
{
|
||||
|
||||
// Convert 0.9 Data format to 0.8 format
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
$parsedResponse = [];
|
||||
|
||||
switch ($model) {
|
||||
|
||||
case Response::MODEL_DOCUMENT_LIST:
|
||||
case Response::MODEL_DOCUMENT:
|
||||
case Response::MODEL_USER_LIST:
|
||||
case Response::MODEL_USER:
|
||||
case Response::MODEL_COLLECTION_LIST:
|
||||
case Response::MODEL_COLLECTION:
|
||||
case Response::MODEL_FILE_LIST:
|
||||
case Response::MODEL_FILE:
|
||||
case Response::MODEL_TAG_LIST:
|
||||
case Response::MODEL_TAG:
|
||||
case Response::MODEL_EXECUTION_LIST:
|
||||
case Response::MODEL_EXECUTION:
|
||||
case Response::MODEL_TEAM_LIST:
|
||||
case Response::MODEL_TEAM:
|
||||
case Response::MODEL_MEMBERSHIP_LIST:
|
||||
case Response::MODEL_MEMBERSHIP:
|
||||
case Response::MODEL_SESSION_LIST:
|
||||
case Response::MODEL_SESSION:
|
||||
case Response::MODEL_JWT:
|
||||
case Response::MODEL_LOG:
|
||||
case Response::MODEL_LOG_LIST:
|
||||
case Response::MODEL_TOKEN:
|
||||
case Response::MODEL_LOCALE:
|
||||
case Response::MODEL_COUNTRY:
|
||||
case Response::MODEL_COUNTRY_LIST:
|
||||
case Response::MODEL_PHONE:
|
||||
case Response::MODEL_PHONE_LIST:
|
||||
case Response::MODEL_CONTINENT:
|
||||
case Response::MODEL_CONTINENT_LIST:
|
||||
case Response::MODEL_CURRENCY:
|
||||
case Response::MODEL_CURRENCY_LIST:
|
||||
case Response::MODEL_LANGUAGE:
|
||||
case Response::MODEL_LANGUAGE_LIST:
|
||||
case Response::MODEL_PROJECT:
|
||||
case Response::MODEL_PROJECT_LIST:
|
||||
case Response::MODEL_PLATFORM:
|
||||
case Response::MODEL_PLATFORM_LIST:
|
||||
case Response::MODEL_DOMAIN:
|
||||
case Response::MODEL_DOMAIN_LIST:
|
||||
case Response::MODEL_KEY:
|
||||
case Response::MODEL_KEY_LIST:
|
||||
case Response::MODEL_PERMISSIONS:
|
||||
case Response::MODEL_RULE:
|
||||
case Response::MODEL_TASK:
|
||||
case Response::MODEL_WEBHOOK:
|
||||
case Response::MODEL_WEBHOOK_LIST:
|
||||
case Response::MODEL_MOCK:
|
||||
case Response::MODEL_ANY:
|
||||
case Response::MODEL_PREFERENCES:
|
||||
case Response::MODEL_NONE:
|
||||
case Response::MODEL_ERROR:
|
||||
case Response::MODEL_ERROR_DEV:
|
||||
$parsedResponse = $content;
|
||||
break;
|
||||
case Response::MODEL_FUNCTION_LIST: /** Function property env was renamed to runtime in 0.9.x */
|
||||
$parsedResponse = $this->parseFunctionList($content);
|
||||
break;
|
||||
case Response::MODEL_FUNCTION: /** Function property env was renamed to runtime in 0.9.x */
|
||||
$parsedResponse = $this->parseFunctionList($content);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Received invalid model : '.$model);
|
||||
}
|
||||
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
protected function parseFunction(array $content)
|
||||
{
|
||||
$content['env'] = $content['runtime'];
|
||||
unset($content['runtime']);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseFunctionList(array $content)
|
||||
{
|
||||
$functions = $content['functions'];
|
||||
$parsedResponse = [];
|
||||
foreach ($functions as $function) {
|
||||
$parsedResponse[] = $this->parseFunction($function);
|
||||
}
|
||||
$content['functions'] = $parsedResponse;
|
||||
return $content;
|
||||
}
|
||||
}
|
|
@ -160,45 +160,7 @@ class HTTPTest extends Scope
|
|||
}
|
||||
}
|
||||
|
||||
public function testResponseHeader() {
|
||||
|
||||
/**
|
||||
* Test without header
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/locale/continents', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], $this->getHeaders()));
|
||||
|
||||
$body = $response['body'];
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($body['sum'], 7);
|
||||
$this->assertEquals($body['continents'][0]['name'], 'Africa');
|
||||
$this->assertEquals($body['continents'][0]['code'], 'AF');
|
||||
$this->assertEquals($body['continents'][1]['name'], 'Antarctica');
|
||||
$this->assertEquals($body['continents'][1]['code'], 'AN');
|
||||
$this->assertEquals($body['continents'][2]['name'], 'Asia');
|
||||
$this->assertEquals($body['continents'][2]['code'], 'AS');
|
||||
|
||||
/**
|
||||
* Test with header
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/locale/continents', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'x-appwrite-response-format' => '0.6.2'
|
||||
], $this->getHeaders()));
|
||||
|
||||
$body = $response['body'];
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($body['sum'], 7);
|
||||
$this->assertEquals($body['continents']['AF'], 'Africa');
|
||||
$this->assertEquals($body['continents']['AN'], 'Antarctica');
|
||||
$this->assertEquals($body['continents']['AS'], 'Asia');
|
||||
}
|
||||
|
||||
public function testVersions() {
|
||||
|
||||
/**
|
||||
* Test without header
|
||||
*/
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use Appwrite\Database\Validator\Key;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class KeyTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Key
|
||||
*/
|
||||
protected $object = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->object = new Key();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValues()
|
||||
{
|
||||
$this->assertEquals($this->object->isValid('dasda asdasd'), false);
|
||||
$this->assertEquals($this->object->isValid('asdasdasdas'), true);
|
||||
$this->assertEquals($this->object->isValid('as$$5dasdasdas'), false);
|
||||
$this->assertEquals($this->object->isValid(false), false);
|
||||
$this->assertEquals($this->object->isValid(null), false);
|
||||
$this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscribers'), false);
|
||||
$this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscriber'), false);
|
||||
$this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscribe'), true);
|
||||
$this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscrib'), true);
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use Appwrite\Database\Validator\UID;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UIDTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var UID
|
||||
*/
|
||||
protected $object = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->object = new UID();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValues()
|
||||
{
|
||||
$this->assertEquals($this->object->isValid('5f058a8925807'), true);
|
||||
$this->assertEquals($this->object->isValid('5f058a89258075f058a89258075f058t'), true);
|
||||
$this->assertEquals($this->object->isValid('5f058a89258075f058a89258075f058tx'), false);
|
||||
}
|
||||
}
|
|
@ -2,18 +2,13 @@
|
|||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use Appwrite\Database\Document;
|
||||
use Appwrite\Migration\Migration;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionMethod;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
abstract class MigrationTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var PDO
|
||||
*/
|
||||
protected \PDO $pdo;
|
||||
|
||||
/**
|
||||
* @var Migration
|
||||
*/
|
||||
|
@ -43,7 +38,7 @@ abstract class MigrationTest extends TestCase
|
|||
{
|
||||
require_once __DIR__.'/../../../app/init.php';
|
||||
|
||||
foreach (Migration::$versions as $version => $class) {
|
||||
foreach (Migration::$versions as $class) {
|
||||
$this->assertTrue(class_exists('Appwrite\\Migration\\Version\\'.$class));
|
||||
}
|
||||
// Test if current version exists
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Migration\Version\V05;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
use Utopia\Config\Config;
|
||||
|
||||
class MigrationV05Test extends MigrationTest
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
Config::load('providers', __DIR__ . '/../../../app/config/providers.php');
|
||||
|
||||
$this->pdo = new \PDO('sqlite::memory:');
|
||||
$this->migration = new V05($this->pdo);
|
||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V05');
|
||||
$this->method = $reflector->getMethod('fixDocument');
|
||||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigration()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$uid' => 'unique',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_PROJECTS,
|
||||
'usersOauthGithubAppid' => 123,
|
||||
'usersOauthGithubSecret' => 456
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('$uid', null), null);
|
||||
$this->assertEquals($document->getAttribute('$id', null), 'unique');
|
||||
|
||||
$this->assertEquals($document->getAttribute('usersOauthGithubAppid', null), null);
|
||||
$this->assertEquals($document->getAttribute('usersOauth2GithubAppid', null), 123);
|
||||
|
||||
$this->assertEquals($document->getAttribute('usersOauthGithubSecret', null), null);
|
||||
$this->assertEquals($document->getAttribute('usersOauth2GithubSecret', null), 456);
|
||||
|
||||
$this->assertEquals($document->getAttribute('security', true), false);
|
||||
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$uid' => 'unique',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_TASKS
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('$uid', null), null);
|
||||
$this->assertEquals($document->getAttribute('$id', null), 'unique');
|
||||
|
||||
$this->assertEquals($document->getAttribute('security', true), false);
|
||||
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$uid' => 'unique',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_USERS,
|
||||
'oauthGithub' => 'id',
|
||||
'oauthGithubAccessToken' => 'token',
|
||||
'confirm' => false
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('$uid', null), null);
|
||||
$this->assertEquals($document->getAttribute('$id', null), 'unique');
|
||||
|
||||
$this->assertEquals($document->getAttribute('confirm', null), null);
|
||||
$this->assertEquals($document->getAttribute('emailVerification', true), false);
|
||||
|
||||
$this->assertEquals($document->getAttribute('oauthGithub', null), null);
|
||||
$this->assertEquals($document->getAttribute('oauth2Github', null), 'id');
|
||||
|
||||
$this->assertEquals($document->getAttribute('oauthGithubAccessToken', null), null);
|
||||
$this->assertEquals($document->getAttribute('oauth2GithubAccessToken', null), 'token');
|
||||
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$uid' => 'unique',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
|
||||
'url' => 'https://appwrite.io'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('$uid', null), null);
|
||||
$this->assertEquals($document->getAttribute('$id', null), 'unique');
|
||||
|
||||
$this->assertEquals($document->getAttribute('url', null), null);
|
||||
$this->assertEquals($document->getAttribute('hostname', null), 'appwrite.io');
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Migration\Version\V06;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
|
||||
class MigrationV06Test extends MigrationTest
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->pdo = new \PDO('sqlite::memory:');
|
||||
|
||||
$this->migration = new V06($this->pdo);
|
||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V06');
|
||||
$this->method = $reflector->getMethod('fixDocument');
|
||||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigration()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => uniqid(),
|
||||
'$collection' => Database::SYSTEM_COLLECTION_USERS,
|
||||
'password-update' => 123
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('password-update', null), null);
|
||||
$this->assertEquals($document->getAttribute('passwordUpdate', null), 123);
|
||||
|
||||
$document = $this->fixDocument(
|
||||
new Document([
|
||||
'$id' => uniqid(),
|
||||
'$collection' => Database::SYSTEM_COLLECTION_KEYS,
|
||||
'secret' => 123
|
||||
])
|
||||
);
|
||||
|
||||
$encrypted = json_decode($document->getAttribute('secret', null));
|
||||
$this->assertObjectHasAttribute('data', $encrypted);
|
||||
$this->assertObjectHasAttribute('method', $encrypted);
|
||||
$this->assertObjectHasAttribute('iv', $encrypted);
|
||||
$this->assertObjectHasAttribute('tag', $encrypted);
|
||||
$this->assertObjectHasAttribute('version', $encrypted);
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Migration\Version\V07;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Utopia\Config\Config;
|
||||
|
||||
class MigrationV07Test extends MigrationTest
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
Config::load('providers', __DIR__ . '/../../../app/config/providers.php');
|
||||
|
||||
$this->pdo = new \PDO('sqlite::memory:');
|
||||
$this->migration = new V07($this->pdo);
|
||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V07');
|
||||
$this->method = $reflector->getMethod('fixDocument');
|
||||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigration()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'unique',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_USERS,
|
||||
'oauth2Github' => 123,
|
||||
'oauth2GithubAccessToken' => 456,
|
||||
'tokens' => [
|
||||
new Document([
|
||||
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
|
||||
'userId' => 'unique',
|
||||
'type' => Auth::TOKEN_TYPE_LOGIN,
|
||||
'secret' => 'login',
|
||||
]),
|
||||
new Document([
|
||||
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
|
||||
'userId' => 'unique',
|
||||
'type' => Auth::TOKEN_TYPE_INVITE,
|
||||
'secret' => 'invite',
|
||||
]),
|
||||
new Document([
|
||||
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
|
||||
'userId' => 'unique',
|
||||
'type' => Auth::TOKEN_TYPE_RECOVERY,
|
||||
'secret' => 'recovery',
|
||||
]),
|
||||
new Document([
|
||||
'$collection' => Database::SYSTEM_COLLECTION_TOKENS,
|
||||
'userId' => 'unique',
|
||||
'type' => Auth::TOKEN_TYPE_VERIFICATION,
|
||||
'secret' => 'verification',
|
||||
]),
|
||||
]
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('oauth2Github', null), null);
|
||||
$this->assertEquals($document->getAttribute('oauth2GithubAccessToken', null), null);
|
||||
|
||||
$this->assertCount(3, $document->getAttribute('tokens', []));
|
||||
$this->assertEquals(Auth::TOKEN_TYPE_INVITE, $document->getAttribute('tokens', [])[0]['type']);
|
||||
$this->assertEquals(Auth::TOKEN_TYPE_RECOVERY, $document->getAttribute('tokens', [])[1]['type']);
|
||||
$this->assertEquals(Auth::TOKEN_TYPE_VERIFICATION, $document->getAttribute('tokens', [])[2]['type']);
|
||||
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Migration\Version\V08;
|
||||
|
||||
class MigrationV08Test extends MigrationTest
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->pdo = new \PDO('sqlite::memory:');
|
||||
$this->migration = new V08($this->pdo);
|
||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V08');
|
||||
$this->method = $reflector->getMethod('fixDocument');
|
||||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigration()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'unique',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_FUNCTIONS,
|
||||
'env' => 'node-16'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('env', null), null);
|
||||
$this->assertEquals($document->getAttribute('runtime', null), 'node-16');
|
||||
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'project',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_PROJECTS
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('version', null), '0.9.0');
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
use Appwrite\Migration\Version\V09;
|
||||
|
||||
class MigrationV09Test extends MigrationTest
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->pdo = new \PDO('sqlite::memory:');
|
||||
$this->migration = new V09($this->pdo);
|
||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V09');
|
||||
$this->method = $reflector->getMethod('fixDocument');
|
||||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigration()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'project',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_PROJECTS,
|
||||
'version' => '0.9.0'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('version', '0.9.0'), '0.10.0');
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Document;
|
||||
use Appwrite\Migration\Version\V10;
|
||||
|
||||
class MigrationV10Test extends MigrationTest
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->pdo = new \PDO('sqlite::memory:');
|
||||
$this->migration = new V10($this->pdo);
|
||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V10');
|
||||
$this->method = $reflector->getMethod('fixDocument');
|
||||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigration()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'project',
|
||||
'$collection' => Database::SYSTEM_COLLECTION_PROJECTS,
|
||||
'version' => '0.10.0'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('version', '0.10.0'), '0.11.0');
|
||||
}
|
||||
}
|
100
tests/unit/Migration/MigrationV12Test.php
Normal file
100
tests/unit/Migration/MigrationV12Test.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Migration\Version\V12;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class MigrationV12Test extends MigrationTest
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->migration = new V12();
|
||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V12');
|
||||
$this->method = $reflector->getMethod('fixDocument');
|
||||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigrationProjects()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'project',
|
||||
'$collection' => 'projects',
|
||||
'name' => 'Appwrite',
|
||||
'version' => '0.12.0',
|
||||
'search' => ''
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('version'), '0.13.0');
|
||||
$this->assertEquals($document->getAttribute('search'), 'project Appwrite');
|
||||
}
|
||||
|
||||
public function testMigrationUsers()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'user',
|
||||
'$collection' => 'users',
|
||||
'email' => 'test@appwrite.io',
|
||||
'name' => 'Torsten Dittmann'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('search'), 'user test@appwrite.io Torsten Dittmann');
|
||||
}
|
||||
|
||||
public function testMigrationTeams()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'team',
|
||||
'$collection' => 'teams',
|
||||
'name' => 'Appwrite'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('search'), 'team Appwrite');
|
||||
}
|
||||
|
||||
public function testMigrationFiles()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'file',
|
||||
'$collection' => 'files',
|
||||
'name' => 'Dog.jpeg'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('search'), 'file Dog.jpeg');
|
||||
}
|
||||
|
||||
public function testMigrationFunctions()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'function',
|
||||
'$collection' => 'functions',
|
||||
'name' => 'My Function',
|
||||
'runtime' => 'php-8.0'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('search'), 'function My Function php-8.0');
|
||||
}
|
||||
|
||||
public function testMigrationTags()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'tag',
|
||||
'$collection' => 'tags',
|
||||
'command' => 'php main.php'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('search'), 'tag php main.php');
|
||||
}
|
||||
|
||||
public function testMigrationExecutions()
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'execution',
|
||||
'$collection' => 'executions',
|
||||
'functionId' => 'function'
|
||||
]));
|
||||
|
||||
$this->assertEquals($document->getAttribute('search'), 'execution function');
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use Appwrite\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CustomIdTest extends TestCase
|
|
@ -1,518 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Database\Database;
|
||||
use Appwrite\Database\Validator\Authorization;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V06;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Config\Config;
|
||||
|
||||
class V06Test extends TestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Filter
|
||||
*/
|
||||
protected $filter = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->filter = new V06();
|
||||
}
|
||||
|
||||
public function testParseUser()
|
||||
{
|
||||
$content = [
|
||||
'$id' => '5e5ea5c16897e',
|
||||
'name' => 'John Doe',
|
||||
'registration' => 1592981250,
|
||||
'status' => true,
|
||||
'email' => 'john@appwrite.io',
|
||||
'emailVerification' => false,
|
||||
'prefs' => [
|
||||
'theme' => 'pink',
|
||||
'timezone' => 'UTC'
|
||||
]
|
||||
];
|
||||
|
||||
Config::load('providers', __DIR__.'/../../../../app/config/providers.php');
|
||||
|
||||
$model = Response::MODEL_USER;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['$id'], '5e5ea5c16897e');
|
||||
$this->assertEquals($parsedResponse['name'], 'John Doe');
|
||||
$this->assertEquals($parsedResponse['registration'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['status'], 0);
|
||||
$this->assertEquals($parsedResponse['email'], 'john@appwrite.io');
|
||||
$this->assertEquals($parsedResponse['emailVerification'], false);
|
||||
$this->assertEquals($parsedResponse['prefs'], ['theme' => 'pink', 'timezone' => 'UTC']);
|
||||
$this->assertEquals($parsedResponse['roles'], Authorization::getRoles() ?? []);
|
||||
}
|
||||
|
||||
public function testParseUserList()
|
||||
{
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'users' => [
|
||||
0 => [
|
||||
'$id' => '5e5ea5c16897e',
|
||||
'name' => 'John Doe',
|
||||
'registration' => 1592981250,
|
||||
'status' => true,
|
||||
'email' => 'john@appwrite.io',
|
||||
'emailVerification' => false,
|
||||
'prefs' => [
|
||||
'theme' => 'pink',
|
||||
'timezone' => 'UTC'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
Config::load('providers', __DIR__.'/../../../../app/config/providers.php');
|
||||
|
||||
$model = Response::MODEL_USER_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['users'][0]['$id'], '5e5ea5c16897e');
|
||||
$this->assertEquals($parsedResponse['users'][0]['name'], 'John Doe');
|
||||
$this->assertEquals($parsedResponse['users'][0]['registration'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['users'][0]['status'], 0);
|
||||
$this->assertEquals($parsedResponse['users'][0]['email'], 'john@appwrite.io');
|
||||
$this->assertEquals($parsedResponse['users'][0]['emailVerification'], false);
|
||||
$this->assertEquals($parsedResponse['users'][0]['prefs'], ['theme' => 'pink', 'timezone' => 'UTC']);
|
||||
$this->assertEquals($parsedResponse['users'][0]['roles'], Authorization::getRoles() ?? []);
|
||||
}
|
||||
|
||||
public function testParseSession()
|
||||
{
|
||||
$content = [
|
||||
'$id' => '5e5ea5c16897e',
|
||||
'userId' => '5e5bb8c16897e',
|
||||
'expire' => 1592981250,
|
||||
'ip' => '127.0.0.1',
|
||||
'osCode' => 'Mac',
|
||||
'osName' => 'Mac',
|
||||
'osVersion' => 'Mac',
|
||||
'clientType' => 'browser',
|
||||
'clientCode' => 'CM',
|
||||
'clientName' => 'Chrome Mobile iOS',
|
||||
'clientVersion' => '84.0',
|
||||
'clientEngine' => 'WebKit',
|
||||
'clientEngineVersion' => '605.1.15',
|
||||
'deviceName' => 'smartphone',
|
||||
'deviceBrand' => 'Google',
|
||||
'deviceModel' => 'Nexus 5',
|
||||
'countryCode' => 'US',
|
||||
'countryName' => 'United States',
|
||||
'current' => true
|
||||
];
|
||||
|
||||
$model = Response::MODEL_SESSION;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['$id'], '5e5ea5c16897e');
|
||||
$this->assertEquals($parsedResponse['userId'], '5e5bb8c16897e');
|
||||
$this->assertEquals($parsedResponse['expire'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['ip'], '127.0.0.1');
|
||||
$this->assertEquals($parsedResponse['osCode'], 'Mac');
|
||||
$this->assertEquals($parsedResponse['osName'], 'Mac');
|
||||
$this->assertEquals($parsedResponse['osVersion'], 'Mac');
|
||||
$this->assertEquals($parsedResponse['clientType'], 'browser');
|
||||
$this->assertEquals($parsedResponse['clientCode'], 'CM');
|
||||
$this->assertEquals($parsedResponse['clientName'], 'Chrome Mobile iOS');
|
||||
$this->assertEquals($parsedResponse['clientVersion'], '84.0');
|
||||
$this->assertEquals($parsedResponse['clientEngine'], 'WebKit');
|
||||
$this->assertEquals($parsedResponse['clientEngineVersion'], '605.1.15');
|
||||
$this->assertEquals($parsedResponse['deviceName'], 'smartphone');
|
||||
$this->assertEquals($parsedResponse['deviceBrand'], 'Google');
|
||||
$this->assertEquals($parsedResponse['deviceModel'], 'Nexus 5');
|
||||
$this->assertEquals($parsedResponse['countryCode'], 'US');
|
||||
$this->assertEquals($parsedResponse['countryName'], 'United States');
|
||||
$this->assertEquals($parsedResponse['current'], true);
|
||||
$this->assertEquals($parsedResponse['type'], Auth::TOKEN_TYPE_LOGIN);
|
||||
}
|
||||
|
||||
public function testParseSessionList()
|
||||
{
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'sessions' => [
|
||||
0 => [
|
||||
'$id' => '5e5ea5c16897e',
|
||||
'userId' => '5e5bb8c16897e',
|
||||
'expire' => 1592981250,
|
||||
'ip' => '127.0.0.1',
|
||||
'osCode' => 'Mac',
|
||||
'osName' => 'Mac',
|
||||
'osVersion' => 'Mac',
|
||||
'clientType' => 'browser',
|
||||
'clientCode' => 'CM',
|
||||
'clientName' => 'Chrome Mobile iOS',
|
||||
'clientVersion' => '84.0',
|
||||
'clientEngine' => 'WebKit',
|
||||
'clientEngineVersion' => '605.1.15',
|
||||
'deviceName' => 'smartphone',
|
||||
'deviceBrand' => 'Google',
|
||||
'deviceModel' => 'Nexus 5',
|
||||
'countryCode' => 'US',
|
||||
'countryName' => 'United States',
|
||||
'current' => true
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$model = Response::MODEL_SESSION_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['$id'], '5e5ea5c16897e');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['brand'], 'Google');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['current'], true);
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['device'], 'smartphone');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['ip'], '127.0.0.1');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['model'], 'Nexus 5');
|
||||
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['OS']['name'], 'Mac');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['OS']['platform'], '');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['OS']['short_name'], 'Mac');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['OS']['version'], 'Mac');
|
||||
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['client']['engine'], 'WebKit');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['client']['name'], 'Chrome Mobile iOS');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['client']['short_name'], 'CM');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['client']['type'], 'browser');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['client']['version'], '84.0');
|
||||
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['geo']['isoCode'], 'US');
|
||||
$this->assertEquals($parsedResponse['sessions'][0]['geo']['country'], 'United States');
|
||||
}
|
||||
|
||||
public function testParseLogList()
|
||||
{
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'logs' => [
|
||||
0 => [
|
||||
'event' => 'account.sessions.create',
|
||||
'ip' => '127.0.0.1',
|
||||
'time' => 1592981250,
|
||||
'osCode' => 'Mac',
|
||||
'osName' => 'Mac',
|
||||
'osVersion' => 'Mac',
|
||||
'clientType' => 'browser',
|
||||
'clientCode' => 'CM',
|
||||
'clientName' => 'Chrome Mobile iOS',
|
||||
'clientVersion' => '84.0',
|
||||
'clientEngine' => 'WebKit',
|
||||
'clientEngineVersion' => '605.1.15',
|
||||
'deviceName' => 'smartphone',
|
||||
'deviceBrand' => 'Google',
|
||||
'deviceModel' => 'Nexus 5',
|
||||
'countryCode' => 'US',
|
||||
'countryName' => 'United States'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$model = Response::MODEL_LOG_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['logs'][0]['brand'], 'Google');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['device'], 'smartphone');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['event'], 'account.sessions.create');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['ip'], '127.0.0.1');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['model'], 'Nexus 5');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['time'], 1592981250);
|
||||
|
||||
$this->assertEquals($parsedResponse['logs'][0]['OS']['name'], 'Mac');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['OS']['platform'], '');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['OS']['short_name'], 'Mac');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['OS']['version'], 'Mac');
|
||||
|
||||
$this->assertEquals($parsedResponse['logs'][0]['client']['engine'], 'WebKit');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['client']['name'], 'Chrome Mobile iOS');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['client']['short_name'], 'CM');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['client']['type'], 'browser');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['client']['version'], '84.0');
|
||||
|
||||
$this->assertEquals($parsedResponse['logs'][0]['geo']['isoCode'], 'US');
|
||||
$this->assertEquals($parsedResponse['logs'][0]['geo']['country'], 'United States');
|
||||
}
|
||||
|
||||
public function testParseTeam()
|
||||
{
|
||||
$content = [
|
||||
'$id' => '5ff45ef261829',
|
||||
'name' => 'test',
|
||||
'dateCreated' => 1592981250,
|
||||
'sum' => 7
|
||||
];
|
||||
|
||||
$model = Response::MODEL_TEAM;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['$id'], '5ff45ef261829');
|
||||
$this->assertEquals($parsedResponse['name'], 'test');
|
||||
$this->assertEquals($parsedResponse['dateCreated'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['sum'], 7);
|
||||
$this->assertEquals($parsedResponse['$collection'], 'teams');
|
||||
$this->assertEquals($parsedResponse['$permissions'], []);
|
||||
}
|
||||
|
||||
public function testParseTeamList()
|
||||
{
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'teams' => [
|
||||
0 => [
|
||||
'$id' => '5ff45ef261829',
|
||||
'name' => 'test',
|
||||
'dateCreated' => 1592981250,
|
||||
'sum' => 7
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$model = Response::MODEL_TEAM_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['teams'][0]['$id'], '5ff45ef261829');
|
||||
$this->assertEquals($parsedResponse['teams'][0]['name'], 'test');
|
||||
$this->assertEquals($parsedResponse['teams'][0]['dateCreated'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['teams'][0]['sum'], 7);
|
||||
$this->assertEquals($parsedResponse['teams'][0]['$collection'], 'teams');
|
||||
$this->assertEquals($parsedResponse['teams'][0]['$permissions'], []);
|
||||
|
||||
}
|
||||
|
||||
public function testParseToken()
|
||||
{
|
||||
$content = [
|
||||
'$id' => 'bb8ea5c16897e',
|
||||
'userId' => '5e5ea5c168bb8',
|
||||
'secret' => '',
|
||||
'expire' => 1592981250
|
||||
];
|
||||
|
||||
$model = Response::MODEL_TOKEN;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['$id'], 'bb8ea5c16897e');
|
||||
$this->assertEquals($parsedResponse['userId'], '5e5ea5c168bb8');
|
||||
$this->assertEquals($parsedResponse['expire'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['secret'], '');
|
||||
$this->assertEquals($parsedResponse['type'], Auth::TOKEN_TYPE_RECOVERY);
|
||||
}
|
||||
|
||||
public function testParseLocale()
|
||||
{
|
||||
$content = [
|
||||
'ip' => '127.0.0.1',
|
||||
'countryCode' => 'US',
|
||||
'country' => 'United States',
|
||||
'continentCode' => 'NA',
|
||||
'continent' => 'North America',
|
||||
'eu' => false,
|
||||
'currency' => 'USD'
|
||||
];
|
||||
|
||||
$model = Response::MODEL_LOCALE;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['ip'], '127.0.0.1');
|
||||
$this->assertEquals($parsedResponse['countryCode'], 'US');
|
||||
$this->assertEquals($parsedResponse['country'], 'United States');
|
||||
$this->assertEquals($parsedResponse['continentCode'], 'NA');
|
||||
$this->assertEquals($parsedResponse['continent'], 'North America');
|
||||
$this->assertEquals($parsedResponse['eu'], false);
|
||||
$this->assertEquals($parsedResponse['currency'], 'USD');
|
||||
}
|
||||
|
||||
public function testParseCountryList()
|
||||
{
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'countries' => [
|
||||
0 => [
|
||||
'name' => 'United States',
|
||||
'code' => 'US'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$model = Response::MODEL_COUNTRY_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['countries']['US'], 'United States');
|
||||
}
|
||||
|
||||
public function testParsePhoneList()
|
||||
{
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'phones' => [
|
||||
0 => [
|
||||
'code' => '+1',
|
||||
'countryCode' => 'US',
|
||||
'countryName' => 'United States'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$model = Response::MODEL_PHONE_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['phones']['US'], '+1');
|
||||
}
|
||||
|
||||
public function testParseContinentList()
|
||||
{
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'continents' => [
|
||||
0 => [
|
||||
'name' => 'Europe',
|
||||
'code' => 'EU',
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$model = Response::MODEL_CONTINENT_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['continents']['EU'], 'Europe');
|
||||
}
|
||||
|
||||
public function testParseCurrencyList()
|
||||
{
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'currencies' => [
|
||||
0 => [
|
||||
'symbol' => '$',
|
||||
'name' => 'US dollar',
|
||||
'symbolNative' => '$',
|
||||
'decimalDigits' => 2,
|
||||
'rounding' => 0,
|
||||
'code' => 'USD',
|
||||
'namePlural' => 'US Dollars'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$model = Response::MODEL_CURRENCY_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['currencies'][0]['symbol'], '$');
|
||||
$this->assertEquals($parsedResponse['currencies'][0]['name'], 'US dollar');
|
||||
$this->assertEquals($parsedResponse['currencies'][0]['symbolNative'], '$');
|
||||
$this->assertEquals($parsedResponse['currencies'][0]['decimalDigits'], 2);
|
||||
$this->assertEquals($parsedResponse['currencies'][0]['rounding'], 0);
|
||||
$this->assertEquals($parsedResponse['currencies'][0]['code'], 'USD');
|
||||
$this->assertEquals($parsedResponse['currencies'][0]['namePlural'], 'US Dollars');
|
||||
$this->assertEquals($parsedResponse['currencies'][0]['locations'], []);
|
||||
}
|
||||
|
||||
public function testParseFile()
|
||||
{
|
||||
$content = [
|
||||
'$id' => '5e5ea5c16897e',
|
||||
'$permissions' => ['read' => ['role:all'], 'write' => ['role:all']],
|
||||
'name' => 'Pink.png',
|
||||
'dateCreated' => 1592981250,
|
||||
'signature' => '5d529fd02b544198ae075bd57c1762bb',
|
||||
'mimeType' => 'image/png',
|
||||
'sizeOriginal' => 17890
|
||||
];
|
||||
|
||||
$model = Response::MODEL_FILE;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['$id'], '5e5ea5c16897e');
|
||||
$this->assertEquals($parsedResponse['$permissions'], ['read' => ['role:all'], 'write' => ['role:all']]);
|
||||
$this->assertEquals($parsedResponse['name'], 'Pink.png');
|
||||
$this->assertEquals($parsedResponse['dateCreated'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['signature'], '5d529fd02b544198ae075bd57c1762bb');
|
||||
$this->assertEquals($parsedResponse['mimeType'], 'image/png');
|
||||
$this->assertEquals($parsedResponse['sizeOriginal'], 17890);
|
||||
$this->assertEquals($parsedResponse['$collection'], Database::SYSTEM_COLLECTION_FILES);
|
||||
$this->assertEquals($parsedResponse['algorithm'], 'gzip');
|
||||
$this->assertEquals($parsedResponse['comment'], '');
|
||||
$this->assertEquals($parsedResponse['fileOpenSSLCipher'], OpenSSL::CIPHER_AES_128_GCM);
|
||||
$this->assertEquals($parsedResponse['fileOpenSSLIV'], '');
|
||||
$this->assertEquals($parsedResponse['fileOpenSSLTag'], '');
|
||||
$this->assertEquals($parsedResponse['fileOpenSSLVersion'], '');
|
||||
$this->assertEquals($parsedResponse['folderId'], '');
|
||||
$this->assertEquals($parsedResponse['path'], '');
|
||||
$this->assertEquals($parsedResponse['sizeActual'], $content['sizeOriginal']);
|
||||
$this->assertEquals($parsedResponse['token'], '');
|
||||
}
|
||||
|
||||
public function testParseCollection()
|
||||
{
|
||||
$content = [
|
||||
'$id' => '5e5ea5c16897e',
|
||||
'$permissions' => ['read' => ['role:all'], 'write' => ['role:all']],
|
||||
'name' => 'Movies',
|
||||
'dateCreated' => 1592981250,
|
||||
'dateUpdated' => '5d529fd02b544198ae075bd57c1762bb',
|
||||
'rules' => []
|
||||
];
|
||||
|
||||
$model = Response::MODEL_COLLECTION;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['$id'], '5e5ea5c16897e');
|
||||
$this->assertEquals($parsedResponse['$permissions'], ['read' => ['role:all'], 'write' => ['role:all']]);
|
||||
$this->assertEquals($parsedResponse['name'], 'Movies');
|
||||
$this->assertEquals($parsedResponse['dateCreated'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['dateUpdated'], '5d529fd02b544198ae075bd57c1762bb');
|
||||
$this->assertEquals($parsedResponse['rules'], []);
|
||||
$this->assertEquals($parsedResponse['$collection'], Database::SYSTEM_COLLECTION_COLLECTIONS);
|
||||
$this->assertEquals($parsedResponse['structure'], true);
|
||||
}
|
||||
|
||||
public function testParseCollectionList()
|
||||
{
|
||||
|
||||
$content = [
|
||||
'sum' => 1,
|
||||
'collections' => [
|
||||
0 => [
|
||||
'$id' => '5e5ea5c16897e',
|
||||
'$permissions' => ['read' => ['role:all'], 'write' => ['role:all']],
|
||||
'name' => 'Movies',
|
||||
'dateCreated' => 1592981250,
|
||||
'dateUpdated' => '5d529fd02b544198ae075bd57c1762bb',
|
||||
'rules' => []
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$model = Response::MODEL_COLLECTION_LIST;
|
||||
$parsedResponse = $this->filter->parse($content, $model);
|
||||
|
||||
$this->assertEquals($parsedResponse['sum'], 1);
|
||||
$this->assertEquals($parsedResponse['collections'][0]['$id'], '5e5ea5c16897e');
|
||||
$this->assertEquals($parsedResponse['collections'][0]['$permissions'], ['read' => ['role:all'], 'write' => ['role:all']]);
|
||||
$this->assertEquals($parsedResponse['collections'][0]['name'], 'Movies');
|
||||
$this->assertEquals($parsedResponse['collections'][0]['dateCreated'], 1592981250);
|
||||
$this->assertEquals($parsedResponse['collections'][0]['dateUpdated'], '5d529fd02b544198ae075bd57c1762bb');
|
||||
$this->assertEquals($parsedResponse['collections'][0]['rules'], []);
|
||||
$this->assertEquals($parsedResponse['collections'][0]['$collection'], Database::SYSTEM_COLLECTION_COLLECTIONS);
|
||||
$this->assertEquals($parsedResponse['collections'][0]['structure'], true);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
namespace Appwrite\Tests;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V06;
|
||||
use Appwrite\Utopia\Response\Filters\V11;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
|
||||
|
@ -25,7 +25,7 @@ class ResponseTest extends TestCase
|
|||
$this->assertEquals($this->object->hasFilter(), false);
|
||||
$this->assertEquals($this->object->getFilter(), null);
|
||||
|
||||
$filter = new V06();
|
||||
$filter = new V11();
|
||||
$this->object->setFilter($filter);
|
||||
|
||||
$this->assertEquals($this->object->hasFilter(), true);
|
||||
|
|
Loading…
Reference in a new issue