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

merge feat-database-indexing

This commit is contained in:
Damodar Lohani 2021-07-18 11:29:43 +05:45
commit 57fa41c240
33 changed files with 2081 additions and 1045 deletions

View file

@ -212,6 +212,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/vars && \
chmod +x /usr/local/bin/worker-audits && \
chmod +x /usr/local/bin/worker-certificates && \
chmod +x /usr/local/bin/worker-database && \
chmod +x /usr/local/bin/worker-deletes && \
chmod +x /usr/local/bin/worker-functions && \
chmod +x /usr/local/bin/worker-mails && \

View file

@ -19,6 +19,7 @@ $collections = [
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -29,6 +30,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -39,6 +41,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -49,6 +52,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -59,6 +63,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -69,6 +74,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -79,6 +85,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -89,6 +96,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -99,6 +107,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -109,6 +118,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -119,6 +129,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -129,6 +140,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -139,6 +151,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => ['json'],
],
@ -149,6 +162,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => ['json'],
],
@ -159,6 +173,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => ['json'],
],
@ -169,6 +184,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => ['json'],
],
@ -179,6 +195,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => ['json'],
],
@ -206,6 +223,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -216,6 +234,7 @@ $collections = [
'size' => 1024,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -226,6 +245,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -236,6 +256,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -246,6 +267,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -256,6 +278,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['json']
],
@ -266,6 +289,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -276,6 +300,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -286,6 +311,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -296,6 +322,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => ['json'],
],
@ -306,6 +333,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => ['json'],
],
@ -316,6 +344,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => ['json'],
],
@ -343,6 +372,7 @@ $collections = [
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -353,6 +383,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -363,6 +394,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -390,6 +422,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -400,6 +433,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -410,6 +444,7 @@ $collections = [
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
@ -420,6 +455,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -430,6 +466,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -440,6 +477,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -450,6 +488,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -491,6 +530,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -502,6 +542,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -512,6 +553,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -522,6 +564,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -532,6 +575,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -542,6 +586,7 @@ $collections = [
'size' => 127, // https://tools.ietf.org/html/rfc4288#section-4.2
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -552,6 +597,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -562,6 +608,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -572,6 +619,7 @@ $collections = [
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -582,6 +630,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -592,6 +641,7 @@ $collections = [
'size' => 64,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -602,6 +652,7 @@ $collections = [
'size' => 64,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -612,6 +663,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -622,6 +674,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -656,6 +709,7 @@ $collections = [
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
@ -666,6 +720,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -676,6 +731,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -687,6 +743,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -697,6 +754,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -707,6 +765,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -717,6 +776,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -727,6 +787,7 @@ $collections = [
'size' => 8192,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['json', 'encrypt'],
],
@ -737,6 +798,7 @@ $collections = [
'size' => 256,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
@ -747,6 +809,7 @@ $collections = [
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -757,6 +820,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -767,6 +831,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -777,6 +842,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -804,6 +870,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -814,6 +881,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -825,6 +893,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -835,6 +904,7 @@ $collections = [
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -845,6 +915,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -872,6 +943,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -882,6 +954,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -892,6 +965,7 @@ $collections = [
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -903,6 +977,7 @@ $collections = [
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -913,6 +988,7 @@ $collections = [
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -923,6 +999,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -933,6 +1010,7 @@ $collections = [
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -943,6 +1021,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -953,6 +1032,7 @@ $collections = [
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@ -967,6 +1047,91 @@ $collections = [
]
],
],
'certificates' => [
'$collection' => Database::COLLECTIONS,
'$id' => 'certificates',
'name' => 'Certificates',
'attributes' => [
[
'$id' => 'domain',
'type' => Database::VAR_STRING,
'format' => '',
// The maximum total length of a domain name or number is 255 characters.
// https://datatracker.ietf.org/doc/html/rfc2821#section-4.5.3.1
// https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.2
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'issueDate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'renewDate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'attempts',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'log',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'updated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_domain',
'type' => Database::INDEX_KEY,
'attributes' => ['domain'],
'lengths' => [255],
'orders' => [Database::ORDER_ASC],
],
],
],
'buckets' => [
'$collection' => Database::COLLECTIONS,
'$id' => 'buckets',
@ -1103,6 +1268,7 @@ foreach ($providers as $index => $provider) {
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
];
@ -1114,6 +1280,7 @@ foreach ($providers as $index => $provider) {
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
];
@ -1127,6 +1294,7 @@ foreach ($auth as $index => $method) {
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
];

View file

@ -82,6 +82,26 @@ return [
'model' => Response::MODEL_COLLECTION,
'note' => '',
],
'database.attributes.create' => [
'description' => 'This event triggers when a collection attribute is created.',
'model' => Response::MODEL_ATTRIBUTE,
'note' => '',
],
'database.attributes.delete' => [
'description' => 'This event triggers when a collection attribute is deleted.',
'model' => Response::MODEL_ATTRIBUTE,
'note' => '',
],
'database.indexes.create' => [
'description' => 'This event triggers when a collection index is created.',
'model' => Response::MODEL_INDEX,
'note' => '',
],
'database.indexes.delete' => [
'description' => 'This event triggers when a collection index is deleted.',
'model' => Response::MODEL_INDEX,
'note' => '',
],
'database.documents.create' => [
'description' => 'This event triggers when a database document is created.',
'model' => Response::MODEL_DOCUMENT,

View file

@ -36,6 +36,10 @@ $admins = [
'users.write',
'collections.read',
'collections.write',
'attributes.read',
'attributes.write',
'indexes.read',
'indexes.write',
'platforms.read',
'platforms.write',
'keys.read',

View file

@ -19,6 +19,18 @@ return [ // List of publicly visible scopes
'collections.write' => [
'description' => 'Access to create, update, and delete your project\'s database collections',
],
'attributes.read' => [
'description' => 'Access to read your project\'s database collection\'s attributes',
],
'attributes.write' => [
'description' => 'Access to create, update, and delete your project\'s database collection\'s attributes',
],
'indexes.read' => [
'description' => 'Access to read your project\'s database collection\'s indexes',
],
'indexes.write' => [
'description' => 'Access to create, update, and delete your project\'s database collection\'s indexes',
],
'documents.read' => [
'description' => 'Access to read your project\'s database documents',
],

View file

@ -858,7 +858,6 @@ App::get('/v1/account/logs')
/** @var Utopia\Database\Database $dbForInternal */
$audit = new Audit($dbForInternal);
$countries = $locale->getText('countries');
$logs = $audit->getLogsByUserAndEvents($user->getId(), [

File diff suppressed because it is too large Load diff

View file

@ -1006,6 +1006,7 @@ App::post('/v1/projects/:projectId/tasks')
$task = new Document([
'$id' => $dbForConsole->getId(),
'projectId' => $project->getId(),
'name' => $name,
'status' => $status,
'schedule' => $schedule,

View file

@ -222,8 +222,7 @@ App::get('/v1/users/:userId/logs')
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
/** @var Utopia\App $app */
$user = $dbForInternal->getDocument('users', $userId);
if ($user->isEmpty()) {

View file

@ -16,17 +16,19 @@ use Appwrite\Utopia\Response\Filters\V06;
use Appwrite\Utopia\Response\Filters\V07;
use Appwrite\Utopia\Response\Filters\V08;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization as Authorization2;
Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
App::init(function ($utopia, $request, $response, $console, $project, $consoleDB, $user, $locale, $clients) {
App::init(function ($utopia, $request, $response, $console, $project, $dbForConsole, $user, $locale, $clients) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Database $consoleDB */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Document $console */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
@ -42,36 +44,29 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB
$domains[$domain->get()] = false;
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
} else {
Authorization::disable();
$dbDomain = $consoleDB->getCollectionFirst([
'limit' => 1,
'offset' => 0,
'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_CERTIFICATES,
'domain=' . $domain->get(),
],
]);
Authorization2::disable();
if (empty($dbDomain)) {
$dbDomain = [
'$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES,
'$permissions' => [
'read' => [],
'write' => [],
],
$certificate = $dbForConsole->findFirst('certificates', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
], /*limit*/ 1);
if (empty($certificate)) {
$certificate = new Document([
'domain' => $domain->get(),
];
$dbDomain = $consoleDB->createDocument($dbDomain);
Authorization::enable();
]);
$certificate = $dbForConsole->createDocument('certificates', $certificate);
Authorization2::enable();
Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in a few seconds...'); // TODO move this to installation script
Resque::enqueue('v1-certificates', 'CertificatesV1', [
'document' => $dbDomain,
'document' => $certificate,
'domain' => $domain->get(),
'validateTarget' => false,
'validateCNAME' => false,
]);
} else {
Authorization2::enable(); // ensure authorization is reenabled
}
$domains[$domain->get()] = true;
}
@ -286,7 +281,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB
throw new Exception('Password reset is required', 412);
}
}, ['utopia', 'request', 'response', 'console', 'project', 'consoleDB', 'user', 'locale', 'clients']);
}, ['utopia', 'request', 'response', 'console', 'project', 'dbForConsole', 'user', 'locale', 'clients']);
App::options(function ($request, $response) {
/** @var Utopia\Swoole\Request $request */

View file

@ -9,7 +9,7 @@ use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes, $dbForInternal) {
App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes, $database, $dbForInternal) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
@ -20,6 +20,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $usage */
/** @var Appwrite\Event\Event $deletes */
/** @var Appwrite\Event\Event $database */
/** @var Appwrite\Event\Event $functions */
/** @var Utopia\Database\Database $dbForInternal */
@ -36,7 +37,6 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e
* Abuse Check
*/
$timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForInternal);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
@ -110,7 +110,11 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e
->setParam('projectId', $project->getId())
;
}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes', 'dbForInternal'], 'api');
$database
->setParam('projectId', $project->getId())
;
}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes', 'database', 'dbForInternal'], 'api');
App::init(function ($utopia, $request, $response, $project, $user) {
/** @var Utopia\App $utopia */
@ -166,7 +170,7 @@ App::init(function ($utopia, $request, $response, $project, $user) {
}, ['utopia', 'request', 'response', 'project', 'user'], 'auth');
App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $mode) {
App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $database, $mode) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
@ -175,6 +179,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $usage */
/** @var Appwrite\Event\Event $deletes */
/** @var Appwrite\Event\Event $database */
/** @var Appwrite\Event\Event $functions */
/** @var bool $mode */
@ -204,6 +209,10 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
if (!empty($deletes->getParam('type')) && !empty($deletes->getParam('document'))) {
$deletes->trigger();
}
if (!empty($database->getParam('type')) && !empty($database->getParam('document'))) {
$database->trigger();
}
$route = $utopia->match($request);
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
@ -218,4 +227,4 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
;
}
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'mode'], 'api');
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode'], 'api');

View file

@ -54,14 +54,6 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
$app = new App('UTC');
go(function() use ($register, $app) {
// Only retry connection once before throwing exception
try {
$db = $register->get('dbPool')->get();
} catch (\Exception $exception) {
Console::warning('[Setup] - Database not ready. Waiting for five seconds...');
sleep(5);
}
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
@ -77,6 +69,9 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
return $app;
});
// wait for database to be ready
sleep(5);
$dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */
if(!$dbForConsole->exists()) {

View file

@ -75,7 +75,12 @@ const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
// Creation Types
const CREATE_TYPE_ATTRIBUTE = 'newAttribute';
const CREATE_TYPE_INDEX = 'newIndex';
// Deletion Types
const DELETE_TYPE_ATTRIBUTE = 'attribute';
const DELETE_TYPE_INDEX = 'index';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_AUDIT = 'audit';
@ -389,6 +394,10 @@ App::setResource('deletes', function($register) {
return new Event(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME);
}, ['register']);
App::setResource('database', function($register) {
return new Event(Event::DATABASE_QUEUE_NAME, Event::DATABASE_CLASS_NAME);
}, ['register']);
// Test Mock
App::setResource('clients', function($request, $console, $project) {
$console->setAttribute('platforms', [ // Allways allow current host

View file

@ -1,14 +1,16 @@
<?php
use Appwrite\Database\Database;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Database\Validator\Authorization;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Resque\Worker;
use Utopia\App;
use Utopia\Cache\Adapter\Redis;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Validator\Authorization;
use Utopia\Domains\Domain;
require_once __DIR__.'/../workers.php';
@ -28,183 +30,178 @@ class CertificatesV1 extends Worker
{
global $register;
$db = $register->get('db');
$cache = $register->get('cache');
go(function() use ($register) {
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
$consoleDB = new Database();
$consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));
$consoleDB->setNamespace('app_console'); // Main DB
$consoleDB->setMocks(Config::getParam('collections', []));
$cache = new Cache(new Redis($redis));
$dbForConsole = new Database(new MariaDB($db), $cache);
$dbForConsole->setNamespace('project_console_internal');
/**
* 1. Get new domain document - DONE
* 1.1. Validate domain is valid, public suffix is known and CNAME records are verified - DONE
* 2. Check if a certificate already exists - DONE
* 3. Check if certificate is about to expire, if not - skip it
* 3.1. Create / renew certificate
* 3.2. Update loadblancer
* 3.3. Update database (domains, change date, expiry)
* 3.4. Set retry on failure
* 3.5. Schedule to renew certificate in 60 days
*/
/**
* 1. Get new domain document - DONE
* 1.1. Validate domain is valid, public suffix is known and CNAME records are verified - DONE
* 2. Check if a certificate already exists - DONE
* 3. Check if certificate is about to expire, if not - skip it
* 3.1. Create / renew certificate
* 3.2. Update loadblancer
* 3.3. Update database (domains, change date, expiry)
* 3.4. Set retry on failure
* 3.5. Schedule to renew certificate in 60 days
*/
Authorization::disable();
Authorization::disable();
// Args
$document = $this->args['document'];
$domain = $this->args['domain'];
// Args
$document = $this->args['document'];
$domain = $this->args['domain'];
// Validation Args
$validateTarget = $this->args['validateTarget'] ?? true;
$validateCNAME = $this->args['validateCNAME'] ?? true;
// Validation Args
$validateTarget = $this->args['validateTarget'] ?? true;
$validateCNAME = $this->args['validateCNAME'] ?? true;
// Options
$domain = new Domain((!empty($domain)) ? $domain : '');
$expiry = 60 * 60 * 24 * 30 * 2; // 60 days
$safety = 60 * 60; // 1 hour
$renew = (\time() + $expiry);
if(empty($domain->get())) {
throw new Exception('Missing domain');
}
if(!$domain->isKnown() || $domain->isTest()) {
throw new Exception('Unknown public suffix for domain');
}
if($validateTarget) {
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
// Options
$domain = new Domain((!empty($domain)) ? $domain : '');
$expiry = 60 * 60 * 24 * 30 * 2; // 60 days
$safety = 60 * 60; // 1 hour
$renew = (\time() + $expiry);
if(empty($domain->get())) {
throw new Exception('Missing domain');
}
if(!$domain->isKnown() || $domain->isTest()) {
throw new Exception('Unknown public suffix for domain');
}
if($validateTarget) {
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
if(!$target->isKnown() || $target->isTest()) {
throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.');
if(!$target->isKnown() || $target->isTest()) {
throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.');
}
}
}
if($validateCNAME) {
$validator = new CNAME($target->get()); // Verify Domain with DNS records
if(!$validator->isValid($domain->get())) {
throw new Exception('Failed to verify domain DNS records');
}
}
$certificate = $consoleDB->getCollectionFirst([
'limit' => 1,
'offset' => 0,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_CERTIFICATES,
'domain='.$domain->get(),
],
]);
// $condition = ($certificate
// && $certificate instanceof Document
// && isset($certificate['issueDate'])
// && (($certificate['issueDate'] + ($expiry)) > time())) ? 'true' : 'false';
// throw new Exception('cert issued at'.date('d.m.Y H:i', $certificate['issueDate']).' | renew date is: '.date('d.m.Y H:i', ($certificate['issueDate'] + ($expiry))).' | condition is '.$condition);
$certificate = (!empty($certificate) && $certificate instanceof $certificate) ? $certificate->getArrayCopy() : [];
if(!empty($certificate)
&& isset($certificate['issueDate'])
&& (($certificate['issueDate'] + ($expiry)) > \time())) { // Check last issue time
throw new Exception('Renew isn\'t required');
}
$staging = (App::isProduction()) ? '' : ' --dry-run';
$email = App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS');
if(empty($email)) {
throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate');
}
$stdout = '';
$stderr = '';
$exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
." --email ".$email
." -w ".APP_STORAGE_CERTIFICATES
." -d {$domain->get()}", '', $stdout, $stderr);
if($exit !== 0) {
throw new Exception('Failed to issue a certificate with message: '.$stderr);
}
$path = APP_STORAGE_CERTIFICATES.'/'.$domain->get();
if(!\is_readable($path)) {
if (!\mkdir($path, 0755, true)) {
throw new Exception('Failed to create path...');
}
}
if($validateCNAME) {
$validator = new CNAME($target->get()); // Verify Domain with DNS records
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/cert.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/cert.pem')) {
throw new Exception('Failed to rename certificate cert.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/chain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/chain.pem')) {
throw new Exception('Failed to rename certificate chain.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/fullchain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/fullchain.pem')) {
throw new Exception('Failed to rename certificate fullchain.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/privkey.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/privkey.pem')) {
throw new Exception('Failed to rename certificate privkey.pem: '.\json_encode($stdout));
}
$certificate = \array_merge($certificate, [
'$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES,
'$permissions' => [
'read' => [],
'write' => [],
],
'domain' => $domain->get(),
'issueDate' => \time(),
'renewDate' => $renew,
'attempts' => 0,
'log' => \json_encode($stdout),
]);
$certificate = $consoleDB->createDocument($certificate);
if(!$certificate) {
throw new Exception('Failed saving certificate to DB');
}
if(!empty($document)) {
$document = \array_merge($document, [
'updated' => \time(),
'certificateId' => $certificate->getId(),
]);
$document = $consoleDB->updateDocument($document);
if(!$document) {
throw new Exception('Failed saving domain to DB');
if(!$validator->isValid($domain->get())) {
throw new Exception('Failed to verify domain DNS records');
}
}
}
$certificate = $dbForConsole->findFirst('certificates', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
], /*limit*/ 1);
// $condition = ($certificate
// && $certificate instanceof Document
// && isset($certificate['issueDate'])
// && (($certificate['issueDate'] + ($expiry)) > time())) ? 'true' : 'false';
// throw new Exception('cert issued at'.date('d.m.Y H:i', $certificate['issueDate']).' | renew date is: '.date('d.m.Y H:i', ($certificate['issueDate'] + ($expiry))).' | condition is '.$condition);
$certificate = (!empty($certificate) && $certificate instanceof $certificate) ? $certificate->getArrayCopy() : [];
if(!empty($certificate)
&& isset($certificate['issueDate'])
&& (($certificate['issueDate'] + ($expiry)) > \time())) { // Check last issue time
throw new Exception('Renew isn\'t required');
}
$staging = (App::isProduction()) ? '' : ' --dry-run';
$email = App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS');
if(empty($email)) {
throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate');
}
$stdout = '';
$stderr = '';
$exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
." --email ".$email
." -w ".APP_STORAGE_CERTIFICATES
." -d {$domain->get()}", '', $stdout, $stderr);
if($exit !== 0) {
throw new Exception('Failed to issue a certificate with message: '.$stderr);
}
$path = APP_STORAGE_CERTIFICATES.'/'.$domain->get();
if(!\is_readable($path)) {
if (!\mkdir($path, 0755, true)) {
throw new Exception('Failed to create path...');
}
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/cert.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/cert.pem')) {
throw new Exception('Failed to rename certificate cert.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/chain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/chain.pem')) {
throw new Exception('Failed to rename certificate chain.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/fullchain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/fullchain.pem')) {
throw new Exception('Failed to rename certificate fullchain.pem: '.\json_encode($stdout));
}
if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/privkey.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/privkey.pem')) {
throw new Exception('Failed to rename certificate privkey.pem: '.\json_encode($stdout));
}
$certificate = new Document(\array_merge($certificate, [
'domain' => $domain->get(),
'issueDate' => \time(),
'renewDate' => $renew,
'attempts' => 0,
'log' => \json_encode($stdout),
]));
$certificate = $dbForConsole->createDocument('certificates', $certificate);
if(!$certificate) {
throw new Exception('Failed saving certificate to DB');
}
if(!empty($document)) {
$certificate = new Document(\array_merge($document, [
'updated' => \time(),
'certificateId' => $certificate->getId(),
]));
$config =
"tls:
certificates:
- certFile: /storage/certificates/{$domain->get()}/fullchain.pem
keyFile: /storage/certificates/{$domain->get()}/privkey.pem";
$certificate = $dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate);
if(!$certificate) {
throw new Exception('Failed saving domain to DB');
}
}
$config =
"tls:
certificates:
- certFile: /storage/certificates/{$domain->get()}/fullchain.pem
keyFile: /storage/certificates/{$domain->get()}/privkey.pem";
if(!\file_put_contents(APP_STORAGE_CONFIG.'/'.$domain->get().'.yml', $config)) {
throw new Exception('Failed to save SSL configuration');
}
if(!\file_put_contents(APP_STORAGE_CONFIG.'/'.$domain->get().'.yml', $config)) {
throw new Exception('Failed to save SSL configuration');
}
ResqueScheduler::enqueueAt($renew + $safety, 'v1-certificates', 'CertificatesV1', [
'document' => [],
'domain' => $domain->get(),
'validateTarget' => $validateTarget,
'validateCNAME' => $validateCNAME,
]); // Async task rescheduale
ResqueScheduler::enqueueAt($renew + $safety, 'v1-certificates', 'CertificatesV1', [
'document' => [],
'domain' => $domain->get(),
'validateTarget' => $validateTarget,
'validateCNAME' => $validateCNAME,
]); // Async task rescheduale
Authorization::reset();
Authorization::reset();
// Return db connections to pool
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
});
}
public function shutdown(): void

183
app/workers/database.php Normal file
View file

@ -0,0 +1,183 @@
<?php
use Appwrite\Resque\Worker;
use Utopia\Cache\Cache;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Adapter\MariaDB;
require_once __DIR__.'/../init.php';
Console::title('Database V1 Worker');
Console::success(APP_NAME.' database worker v1 has started'."\n");
class DatabaseV1 extends Worker
{
public $args = [];
public function init(): void
{
}
public function run(): void
{
$projectId = $this->args['projectId'] ?? '';
$type = $this->args['type'] ?? '';
switch (strval($type)) {
case CREATE_TYPE_ATTRIBUTE:
$attribute = $this->args['document'] ?? '';
$attribute = new Document($attribute);
$this->createAttribute($attribute, $projectId);
break;
case DELETE_TYPE_ATTRIBUTE:
$attribute = $this->args['document'] ?? '';
$attribute = new Document($attribute);
$this->deleteAttribute($attribute, $projectId);
break;
case CREATE_TYPE_INDEX:
$index = $this->args['document'] ?? '';
$index = new Document($index);
$this->createIndex($index, $projectId);
break;
case DELETE_TYPE_INDEX:
$index = $this->args['document'] ?? '';
$index = new Document($index);
$this->deleteIndex($index, $projectId);
break;
default:
Console::error('No database operation for type: '.$type);
break;
}
}
public function shutdown(): void
{
}
/**
* @param Document $attribute
* @param string $projectId
*/
protected function createAttribute($attribute, $projectId): void
{
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $attribute->getCollection();
$id = $attribute->getAttribute('$id', '');
$type = $attribute->getAttribute('type', '');
$size = $attribute->getAttribute('size', 0);
$required = $attribute->getAttribute('required', false);
$default = $attribute->getAttribute('default', null);
$signed = $attribute->getAttribute('signed', true);
$array = $attribute->getAttribute('array', false);
$filters = $attribute->getAttribute('filters', []);
$success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $default, $signed, $array, $filters);
if ($success) {
$removed = $dbForExternal->removeAttributeInQueue($collectionId, $id);
}
}
/**
* @param Document $attribute
* @param string $projectId
*/
protected function deleteAttribute($attribute, $projectId): void
{
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $attribute->getCollection();
$id = $attribute->getAttribute('$id');
$success = $dbForExternal->deleteAttribute($collectionId, $id);
}
/**
* @param Document $index
* @param string $projectId
*/
protected function createIndex($index, $projectId): void
{
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $index->getCollection();
$id = $index->getAttribute('$id', '');
$type = $index->getAttribute('type', '');
$attributes = $index->getAttribute('attributes', []);
$lengths = $index->getAttribute('lengths', []);
$orders = $index->getAttribute('orders', []);
$success = $dbForExternal->createIndex($collectionId, $id, $type, $attributes, $lengths, $orders);
if ($success) {
$dbForExternal->removeIndexInQueue($collectionId, $id);
}
}
/**
* @param Document $index
* @param string $projectId
*/
protected function deleteIndex($index, $projectId): void
{
$dbForExternal = $this->getExternalDB($projectId);
$collectionId = $index->getCollection();
$id = $index->getAttribute('$id');
$success = $dbForExternal->deleteIndex($collectionId, $id);
}
/**
* @param string $projectId
*
* @return Database
*/
protected function getInternalDB($projectId): Database
{
global $register;
$dbForInternal = null;
go(function() use ($register, $projectId, &$dbForInternal) {
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
$cache = new Cache(new RedisCache($redis));
$dbForInternal = new Database(new MariaDB($db), $cache);
$dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB
});
return $dbForInternal;
}
/**
* @param string $projectId
*
* @return Database
*/
protected function getExternalDB($projectId): Database
{
global $register;
/** @var Database $dbForExternal */
$dbForExternal = null;
go(function() use ($register, $projectId, &$dbForExternal) {
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
$cache = new Cache(new RedisCache($redis));
$dbForExternal = new Database(new MariaDB($db), $cache);
$dbForExternal->setNamespace('project_'.$projectId.'_external'); // Main DB
});
return $dbForExternal;
}
}

View file

@ -1,12 +1,10 @@
<?php
use Appwrite\Database\Database;
use Utopia\Database\Database as Database2;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Database\Document;
use Appwrite\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization;
use Appwrite\Resque\Worker;
use Utopia\Storage\Device\Local;
use Utopia\Abuse\Abuse;
@ -43,19 +41,17 @@ class DeletesV1 extends Worker
$document = new Document($document);
switch ($document->getCollection()) {
case Database::SYSTEM_COLLECTION_PROJECTS:
// TODO@kodumbeats define these as constants somewhere
case 'projects':
$this->deleteProject($document);
break;
case Database::SYSTEM_COLLECTION_FUNCTIONS:
case 'functions':
$this->deleteFunction($document, $projectId);
break;
case Database::SYSTEM_COLLECTION_USERS:
case 'users':
$this->deleteUser($document, $projectId);
break;
case Database::SYSTEM_COLLECTION_COLLECTIONS:
$this->deleteDocuments($document, $projectId);
break;
case Database::SYSTEM_COLLECTION_TEAMS:
case 'teams':
$this->deleteMemberships($document, $projectId);
break;
default:
@ -91,97 +87,91 @@ class DeletesV1 extends Worker
{
}
protected function deleteDocuments(Document $document, $projectId)
{
$collectionId = $document->getId();
// Delete Documents in the deleted collection
$this->deleteByGroup([
'$collection='.$collectionId
], $this->getProjectDB($projectId));
}
/**
* @param Document $document teams document
* @param string $projectId
*/
protected function deleteMemberships(Document $document, $projectId) {
$teamId = $document->getAttribute('teamId', '');
// Delete Memberships
$this->deleteByGroup([
'$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'teamId='.$document->getId(),
], $this->getProjectDB($projectId));
$this->deleteByGroup('memberships', [
new Query('teamId', Query::TYPE_EQUAL, [$teamId])
], $this->getInternalDB($projectId));
}
/**
* @param Document $document project document
*/
protected function deleteProject(Document $document)
{
$projectId = $document->getId();
// Delete all DBs
$this->getConsoleDB()->deleteNamespace($document->getId());
$this->getExternalDB($projectId)->delete();
$this->getInternalDB($projectId)->delete();
// Delete all storage directories
$uploads = new Local(APP_STORAGE_UPLOADS.'/app-'.$document->getId());
$cache = new Local(APP_STORAGE_CACHE.'/app-'.$document->getId());
// Delete all storage directories
$uploads->delete($uploads->getRoot(), true);
$cache->delete($cache->getRoot(), true);
}
/**
* @param Document $document user document
* @param string $projectId
*/
protected function deleteUser(Document $document, $projectId)
{
$tokens = $document->getAttribute('tokens', []);
foreach ($tokens as $token) {
if (!$this->getProjectDB($projectId)->deleteDocument($token->getId())) {
throw new Exception('Failed to remove token from DB');
}
}
$sessions = $document->getAttribute('sessions', []);
foreach ($sessions as $session) {
if (!$this->getProjectDB($projectId)->deleteDocument($session->getId())) {
throw new Exception('Failed to remove session from DB');
}
}
$userId = $document->getId();
// Tokens and Sessions removed with user document
// Delete Memberships and decrement team membership counts
$this->deleteByGroup([
'$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'userId='.$document->getId(),
], $this->getProjectDB($projectId), function(Document $document) use ($projectId) {
$this->deleteByGroup('memberships', [
new Query('userId', Query::TYPE_EQUAL, [$userId])
], $this->getInternalDB($projectId), function(Document $document) use ($projectId, $userId) {
if ($document->getAttribute('confirm')) { // Count only confirmed members
$teamId = $document->getAttribute('teamId');
$team = $this->getProjectDB($projectId)->getDocument($teamId);
$team = $this->getInternalDB($projectId)->getDocument('teams', $teamId);
if(!$team->isEmpty()) {
$team = $this->getProjectDB($projectId)->updateDocument(\array_merge($team->getArrayCopy(), [
$team = $this->getInternalDB($projectId)->updateDocument('teams', $teamId, new Document(\array_merge($team->getArrayCopy(), [
'sum' => \max($team->getAttribute('sum', 0) - 1, 0), // Ensure that sum >= 0
]));
])));
}
}
});
}
/**
* @param int $timestamp
*/
protected function deleteExecutionLogs($timestamp)
{
$this->deleteForProjectIds(function($projectId) use ($timestamp) {
if (!($projectDB = $this->getProjectDB($projectId))) {
if (!($dbForInternal = $this->getInternalDB($projectId))) {
throw new Exception('Failed to get projectDB for project '.$projectId);
}
// Delete Executions
$this->deleteByGroup([
'$collection='.Database::SYSTEM_COLLECTION_EXECUTIONS,
'dateCreated<'.$timestamp
], $projectDB);
$this->deleteByGroup('executions', [
new Query('dateCreated', Query::TYPE_LESSER, [$timestamp])
], $dbForInternal);
});
}
/**
* @param int $timestamp
*/
protected function deleteAbuseLogs($timestamp)
{
global $register;
if($timestamp == 0) {
throw new Exception('Failed to delete audit logs. No timestamp provided');
}
$this->deleteForProjectIds(function($projectId) use ($timestamp){
$timeLimit = new TimeLimit("", 0, 1, $this->getInternalDB($projectId));
$abuse = new Abuse($timeLimit);
$status = $abuse->cleanup($timestamp);
@ -191,13 +181,15 @@ class DeletesV1 extends Worker
});
}
/**
* @param int $timestamp
*/
protected function deleteAuditLogs($timestamp)
{
global $register;
if($timestamp == 0) {
throw new Exception('Failed to delete audit logs. No timestamp provided');
}
$this->deleteForProjectIds(function($projectId) use ($register, $timestamp){
$this->deleteForProjectIds(function($projectId) use ($timestamp){
$audit = new Audit($this->getInternalDB($projectId));
$status = $audit->cleanup($timestamp);
if (!$status) {
@ -206,16 +198,19 @@ class DeletesV1 extends Worker
});
}
/**
* @param Document $document function document
* @param string $projectId
*/
protected function deleteFunction(Document $document, $projectId)
{
$projectDB = $this->getProjectDB($projectId);
$dbForInternal = $this->getInternalDB($projectId);
$device = new Local(APP_STORAGE_FUNCTIONS.'/app-'.$projectId);
// Delete Tags
$this->deleteByGroup([
'$collection='.Database::SYSTEM_COLLECTION_TAGS,
'functionId='.$document->getId(),
], $projectDB, function(Document $document) use ($device) {
$this->deleteByGroup('tags', [
new Query('functionId', Query::TYPE_EQUAL, [$document->getId()])
], $dbForInternal, function(Document $document) use ($device) {
if ($device->delete($document->getAttribute('path', ''))) {
Console::success('Delete code tag: '.$document->getAttribute('path', ''));
@ -226,17 +221,25 @@ class DeletesV1 extends Worker
});
// Delete Executions
$this->deleteByGroup([
'$collection='.Database::SYSTEM_COLLECTION_EXECUTIONS,
'functionId='.$document->getId(),
], $projectDB);
$this->deleteByGroup('executions', [
new Query('functionId', Query::TYPE_EQUAL, [$document->getId()])
], $dbForInternal);
}
/**
* @param Document $document to be deleted
* @param Database $database to delete it from
* @param callable $callback to perform after document is deleted
*
* @return bool
*/
protected function deleteById(Document $document, Database $database, callable $callback = null): bool
{
Authorization::disable();
if($database->deleteDocument($document->getId())) {
// TODO@kodumbeats is it better to pass objects or ID strings?
if($database->deleteDocument($document->getCollection(), $document->getId())) {
Console::success('Deleted document "'.$document->getId().'" successfully');
if(is_callable($callback)) {
@ -253,6 +256,9 @@ class DeletesV1 extends Worker
Authorization::reset();
}
/**
* @param callable $callback
*/
protected function deleteForProjectIds(callable $callback)
{
$count = 0;
@ -262,19 +268,12 @@ class DeletesV1 extends Worker
$sum = $limit;
$executionStart = \microtime(true);
while($sum === $limit) {
$chunk++;
Authorization::disable();
$projects = $this->getConsoleDB()->getCollection([
'limit' => $limit,
'orderType' => 'ASC',
'orderCast' => 'string',
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_PROJECTS,
],
]);
$projects = $this->getConsoleDB()->find('projects', [], $limit);
Authorization::reset();
$projectIds = array_map (function ($project) {
@ -294,7 +293,13 @@ class DeletesV1 extends Worker
Console::info("Found {$count} projects " . ($executionEnd - $executionStart) . " seconds");
}
protected function deleteByGroup(array $filters, Database $database, callable $callback = null)
/**
* @param string $collection collectionID
* @param Query[] $queries
* @param Database $database
* @param callable $callback
*/
protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null)
{
$count = 0;
$chunk = 0;
@ -303,19 +308,13 @@ class DeletesV1 extends Worker
$sum = $limit;
$executionStart = \microtime(true);
while($sum === $limit) {
$chunk++;
Authorization::disable();
$results = $database->getCollection([
'limit' => $limit,
'orderField' => '$id',
'orderType' => 'ASC',
'orderCast' => 'string',
'filters' => $filters,
]);
$results = $database->find($collection, $queries, $limit, 0);
Authorization::reset();
@ -334,6 +333,10 @@ class DeletesV1 extends Worker
Console::info("Deleted {$count} document by group in " . ($executionEnd - $executionStart) . " seconds");
}
/**
* @param Document $document certificates document
* @return Database
*/
protected function deleteCertificates(Document $document)
{
$domain = $document->getAttribute('domain');
@ -348,56 +351,48 @@ class DeletesV1 extends Worker
Console::info("No certificate files found for {$domain}");
}
}
/**
* @param string $projectId
* @return Database
*/
protected function getInternalDB($projectId): Database
{
global $register;
$cache = new Cache(new RedisCache($register->get('cache')));
$dbForInternal = new Database(new MariaDB($register->get('db')), $cache);
$dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB
return $dbForInternal;
}
/**
* @return Database;
* @param string $projectId
* @return Database
*/
protected function getExternalDB($projectId): Database
{
global $register;
$cache = new Cache(new RedisCache($register->get('cache')));
$dbForExternal = new Database(new MariaDB($register->get('db')), $cache);
$dbForExternal->setNamespace('project_'.$projectId.'_external'); // Main DB
return $dbForExternal;
}
/**
* @return Database
*/
protected function getConsoleDB(): Database
{
global $register;
$db = $register->get('db');
$cache = $register->get('cache');
if($this->consoleDB === null) {
$this->consoleDB = new Database();
$this->consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));;
$this->consoleDB->setNamespace('app_console'); // Main DB
$this->consoleDB->setMocks(Config::getParam('collections', []));
}
return $this->consoleDB;
}
/**
* @return Database;
*/
protected function getProjectDB($projectId): Database
{
global $register;
$db = $register->get('db');
$cache = $register->get('cache');
$projectDB = new Database();
$projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));
$projectDB->setNamespace('app_'.$projectId); // Main DB
$projectDB->setMocks(Config::getParam('collections', []));
return $projectDB;
}
/**
* @return Database2
*/
protected function getInternalDB($projectId): Database2
{
global $register;
$cache = new Cache(new RedisCache($register->get('cache')));
$dbForInternal = new Database2(new MariaDB($register->get('db')), $cache);
$dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB
$dbForConsole = new Database(new MariaDB($register->get('db')), $cache);
$dbForConsole->setNamespace('project_console_internal'); // Main DB
return $dbForInternal;
return $dbForConsole;
}
}

View file

@ -465,10 +465,6 @@ class FunctionsV1 extends Worker
Authorization::reset();
if (false === $function) {
throw new Exception('Failed saving execution to DB', 500);
}
$executionModel = new Execution();
$executionUpdate = new Event('v1-webhooks', 'WebhooksV1');

View file

@ -1,14 +1,14 @@
<?php
use Appwrite\Database\Database;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Database\Validator\Authorization;
use Appwrite\Resque\Worker;
use Cron\CronExpression;
use Utopia\App;
use Utopia\Cache\Adapter\Redis;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Validator\Authorization;
require_once __DIR__.'/../workers.php';
@ -33,10 +33,20 @@ class TasksV1 extends Worker
$db = $register->get('db');
$cache = $register->get('cache');
$consoleDB = new Database();
$consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));
$consoleDB->setNamespace('app_console'); // Main DB
$consoleDB->setMocks(Config::getParam('collections', []));
$projectId = $this->args['projectId'] ?? null;
$taskId = $this->args['$id'] ?? null;
$updated = $this->args['updated'] ?? null;
$next = $this->args['next'] ?? null;
$delay = \time() - $next;
$errors = [];
$timeout = 60 * 5; // 5 minutes
$errorLimit = 5;
$logLimit = 5;
$alert = '';
$cache = new Cache(new Redis($cache));
$dbForConsole = new Database(new MariaDB($db), $cache);
$dbForConsole->setNamespace('project_console_internal');
/*
* 1. Get Original Task
@ -51,30 +61,25 @@ class TasksV1 extends Worker
* If error count bigger than allowed change status to pause
*/
$taskId = $this->args['$id'] ?? null;
$updated = $this->args['updated'] ?? null;
$next = $this->args['next'] ?? null;
$delay = \time() - $next;
$errors = [];
$timeout = 60 * 5; // 5 minutes
$errorLimit = 5;
$logLimit = 5;
$alert = '';
if (empty($taskId)) {
throw new Exception('Missing task $id');
}
Authorization::disable();
$task = $consoleDB->getDocument($taskId);
$project = $dbForConsole->getDocument('projects', $projectId);
Authorization::reset();
if (\is_null($task->getId()) || Database::SYSTEM_COLLECTION_TASKS !== $task->getCollection()) {
// Find the task in the $project->getAttribute('tasks') array
$taskIndex = array_search($taskId, array_column($project->getAttributes()['tasks'], '$id'));
if ($taskIndex === false) {
throw new Exception('Task Not Found');
}
$task = $project->getAttribute('tasks')[$taskIndex];
if ($task->getAttribute('updated') !== $updated) { // Task have already been rescheduled by owner
return;
}
@ -193,9 +198,11 @@ class TasksV1 extends Worker
->setAttribute('delay', $delay)
;
Authorization::disable();
$project->findAndReplace('$id', $task->getId(), $task);
if (false === $consoleDB->updateDocument($task->getArrayCopy())) {
Authorization::disable();
if (false === $dbForConsole->updateDocument('projects', $project->getId(), $project)) {
throw new Exception('Failed saving tasks to DB');
}

10
bin/worker-database Normal file
View file

@ -0,0 +1,10 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
QUEUE='v1-database' APP_INCLUDE='/usr/src/code/app/workers/database.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

2
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "14172d7cbfa5a5b7353d3f8b24e44b81",
"content-hash": "ca8cf356c884e8d332b04bebf9d3b11b",
"packages": [
{
"name": "adhocore/jwt",

View file

@ -240,6 +240,31 @@ services:
- _APP_DB_USER
- _APP_DB_PASS
appwrite-worker-database:
entrypoint: worker-database
container_name: appwrite-worker-database
build:
context: .
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-worker-certificates:
entrypoint: worker-certificates
container_name: appwrite-worker-certificates

View file

@ -7,6 +7,9 @@ use Resque;
class Event
{
const DATABASE_QUEUE_NAME= 'v1-database';
const DATABASE_CLASS_NAME = 'DatabaseV1';
const DELETE_QUEUE_NAME = 'v1-deletes';
const DELETE_CLASS_NAME = 'DeletesV1';

View file

@ -10,6 +10,7 @@ use Appwrite\Utopia\Response\Filter;
use Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\Any;
use Appwrite\Utopia\Response\Model\Attribute;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Collection;
use Appwrite\Utopia\Response\Model\Continent;
@ -23,6 +24,7 @@ use Appwrite\Utopia\Response\Model\Execution;
use Appwrite\Utopia\Response\Model\File;
use Appwrite\Utopia\Response\Model\Bucket;
use Appwrite\Utopia\Response\Model\Func;
use Appwrite\Utopia\Response\Model\Index;
use Appwrite\Utopia\Response\Model\JWT;
use Appwrite\Utopia\Response\Model\Key;
use Appwrite\Utopia\Response\Model\Language;
@ -64,6 +66,10 @@ class Response extends SwooleResponse
// Database
const MODEL_COLLECTION = 'collection';
const MODEL_COLLECTION_LIST = 'collectionList';
const MODEL_ATTRIBUTE = 'attribute';
const MODEL_ATTRIBUTE_LIST = 'attributeList';
const MODEL_INDEX = 'index';
const MODEL_INDEX_LIST = 'indexList';
const MODEL_RULE = 'rule';
const MODEL_DOCUMENT = 'document';
const MODEL_DOCUMENT_LIST = 'documentList';
@ -152,6 +158,8 @@ class Response extends SwooleResponse
->setModel(new ErrorDev())
// Lists
->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION))
->setModel(new BaseList('Attributes List', self::MODEL_ATTRIBUTE_LIST, 'attributes', self::MODEL_ATTRIBUTE))
->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX))
->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT))
->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
@ -177,6 +185,8 @@ class Response extends SwooleResponse
// Entities
->setModel(new Permissions())
->setModel(new Collection())
->setModel(new Attribute())
->setModel(new Index())
->setModel(new ModelDocument())
->setModel(new Rule())
->setModel(new Log())

View file

@ -0,0 +1,87 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Attribute extends Model
{
public function __construct()
{
$this
->addRule('$collection', [
'type' => self::TYPE_STRING,
'description' => 'Collection ID.',
'default' => '',
'example' => '5e5ea5c16d55',
])
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Attribute ID.',
'default' => '',
'example' => '60ccf71b98a2d',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Attribute type.',
'default' => '',
'example' => 'string',
])
->addRule('size', [
'type' => self::TYPE_STRING,
'description' => 'Attribute size.',
'default' => 0,
'example' => 128,
])
->addRule('required', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is attribute required?',
'default' => false,
'example' => true,
])
->addRule('signed', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is attribute signed?',
'default' => true,
'example' => true,
'required' => false,
])
->addRule('array', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is attribute an array?',
'default' => false,
'example' => false,
'required' => false
])
->addRule('filters', [
'type' => self::TYPE_STRING,
'description' => 'Attribute filters.',
'default' => [],
'example' => [],
'array' => true,
'required' => false,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'Attribute';
}
/**
* Get Collection
*
* @return string
*/
public function getType():string
{
return Response::MODEL_ATTRIBUTE;
}
}

View file

@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use stdClass;
class Collection extends Model
{
@ -16,36 +17,53 @@ class Collection extends Model
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('$permissions', [
'type' => Response::MODEL_PERMISSIONS,
'description' => 'Collection permissions.',
'default' => new \stdClass,
'example' => new \stdClass,
'array' => false,
->addRule('$read', [
'type' => self::TYPE_STRING,
'description' => 'Collection read permissions.',
'default' => '',
'example' => 'role:all',
'array' => true
])
->addRule('$write', [
'type' => self::TYPE_STRING,
'description' => 'Collection write permissions.',
'default' => '',
'example' => 'user:608f9da25e7e1',
'array' => true
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Collection name.',
'default' => '',
'example' => 'Movies',
'example' => '',
])
->addRule('dateCreated', [
'type' => self::TYPE_INTEGER,
'description' => 'Collection creation date in Unix timestamp.',
'default' => 0,
'example' => 1592981250,
])
->addRule('dateUpdated', [
'type' => self::TYPE_INTEGER,
'description' => 'Collection creation date in Unix timestamp.',
'default' => 0,
'example' => 1592981550,
])
->addRule('rules', [
'type' => Response::MODEL_RULE,
'description' => 'Collection rules.',
->addRule('attributes', [
'type' => Response::MODEL_ATTRIBUTE,
'description' => 'Collection attributes.',
'default' => [],
'array' => true,
'example' => new stdClass,
'array' => true
])
->addRule('indexes', [
'type' => Response::MODEL_INDEX,
'description' => 'Collection indexes.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('attributesInQueue', [
'type' => Response::MODEL_ATTRIBUTE,
'description' => 'Collection attributes in creation queue.',
'default' => [],
'example' => new stdClass,
'array' => true
])
->addRule('indexesInQueue', [
'type' => Response::MODEL_INDEX,
'description' => 'Collection indexes in creation queue.',
'default' => [],
'example' => new stdClass,
'array' => true
])
;
}
@ -69,4 +87,4 @@ class Collection extends Model
{
return Response::MODEL_COLLECTION;
}
}
}

View file

@ -41,12 +41,20 @@ class Document extends Any
'default' => '',
'example' => '5e5ea5c15117e',
])
->addRule('$permissions', [
'type' => Response::MODEL_PERMISSIONS,
'description' => 'Document permissions.',
'default' => new \stdClass,
'example' => new \stdClass,
'array' => false,
]);
->addRule('$read', [
'type' => self::TYPE_STRING,
'description' => 'Document read permissions.',
'default' => '',
'example' => 'role:all',
'array' => true,
])
->addRule('$write', [
'type' => self::TYPE_STRING,
'description' => 'Document write permissions.',
'default' => '',
'example' => 'user:608f9da25e7e1',
'array' => true,
])
;
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Index extends Model
{
public function __construct()
{
$this
->addRule('$collection', [
'type' => self::TYPE_STRING,
'description' => 'Collection ID.',
'default' => '',
'example' => '5e5ea5c16d55',
])
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Index ID.',
'default' => '',
'example' => '',
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Index type.',
'default' => '',
'example' => '',
])
->addRule('attributes', [
'type' => self::TYPE_STRING,
'description' => 'Index attributes.',
'default' => [],
'example' => [],
'array' => true,
])
->addRule('lengths', [
'type' => self::TYPE_STRING,
'description' => 'Index lengths.',
'default' => [],
'example' => [],
'array' => true,
'required' => false,
])
->addRule('orders', [
'type' => self::TYPE_STRING,
'description' => 'Index orders.',
'default' => [],
'example' => [],
'array' => true,
'required' => false,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName():string
{
return 'Index';
}
/**
* Get Collection
*
* @return string
*/
public function getType():string
{
return Response::MODEL_INDEX;
}
}

View file

@ -21,6 +21,12 @@ class Task extends Model
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('projectId', [
'type' => self::TYPE_STRING,
'description' => 'Project ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Task name.',

View file

@ -69,6 +69,10 @@ trait ProjectCustom
'teams.write',
'collections.read',
'collections.write',
'attributes.read',
'attributes.write',
'indexes.read',
'indexes.write',
'documents.read',
'documents.write',
'files.read',
@ -112,6 +116,10 @@ trait ProjectCustom
'database.collections.create',
'database.collections.update',
'database.collections.delete',
'database.attributes.create',
'database.attributes.delete',
'database.indexes.create',
'database.indexes.delete',
'database.documents.create',
'database.documents.update',
'database.documents.delete',

View file

@ -11,42 +11,6 @@ trait DatabaseBase
/**
* Test for SUCCESS
*/
$actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Actors',
'read' => ['role:all'],
'write' => ['role:member', 'role:admin'],
'rules' => [
[
'label' => 'First Name',
'key' => 'firstName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
[
'label' => 'Last Name',
'key' => 'lastName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
],
]);
$this->assertEquals($actors['headers']['status-code'], 201);
$this->assertEquals($actors['body']['name'], 'Actors');
$this->assertIsArray($actors['body']['$permissions']);
$this->assertIsArray($actors['body']['$permissions']['read']);
$this->assertIsArray($actors['body']['$permissions']['write']);
$this->assertCount(1, $actors['body']['$permissions']['read']);
$this->assertCount(2, $actors['body']['$permissions']['write']);
$movies = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -54,50 +18,142 @@ trait DatabaseBase
]), [
'name' => 'Movies',
'read' => ['role:all'],
'write' => ['role:member', 'role:admin'],
'rules' => [
[
'label' => 'Name',
'key' => 'name',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
[
'label' => 'Release Year',
'key' => 'releaseYear',
'type' => 'numeric',
'default' => 0,
'required' => false,
'array' => false
],
[
'label' => 'Actors',
'key' => 'actors',
'type' => 'document',
'default' => [],
'required' => false,
'array' => true,
'list' => [$actors['body']['$id']],
],
],
'write' => ['role:all'],
]);
$this->assertEquals($movies['headers']['status-code'], 201);
$this->assertEquals($movies['body']['name'], 'Movies');
$this->assertIsArray($movies['body']['$permissions']);
$this->assertIsArray($movies['body']['$permissions']['read']);
$this->assertIsArray($movies['body']['$permissions']['write']);
$this->assertCount(1, $movies['body']['$permissions']['read']);
$this->assertCount(2, $movies['body']['$permissions']['write']);
return array_merge(['moviesId' => $movies['body']['$id'], 'actorsId' => $actors['body']['$id']]);
return ['moviesId' => $movies['body']['$id']];
}
/**
* @depends testCreateCollection
*/
public function testCreateAttributes(array $data): array
{
$title = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/attributes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'title',
'type' => 'string',
'size' => 256,
'required' => true,
]);
$releaseYear = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/attributes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'releaseYear',
'type' => 'integer',
'size' => 0,
'required' => true,
]);
$actors = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/attributes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'actors',
'type' => 'string',
'size' => 256,
'required' => false,
'default' => null,
'array' => true,
]);
$this->assertEquals($title['headers']['status-code'], 201);
$this->assertEquals($title['body']['$collection'], $data['moviesId']);
$this->assertEquals($title['body']['$id'], 'title');
$this->assertEquals($title['body']['type'], 'string');
$this->assertEquals($title['body']['size'], 256);
$this->assertEquals($title['body']['required'], true);
$this->assertEquals($releaseYear['headers']['status-code'], 201);
$this->assertEquals($releaseYear['body']['$collection'], $data['moviesId']);
$this->assertEquals($releaseYear['body']['$id'], 'releaseYear');
$this->assertEquals($releaseYear['body']['type'], 'integer');
$this->assertEquals($releaseYear['body']['size'], 0);
$this->assertEquals($releaseYear['body']['required'], true);
$this->assertEquals($actors['headers']['status-code'], 201);
$this->assertEquals($actors['body']['$collection'], $data['moviesId']);
$this->assertEquals($actors['body']['$id'], 'actors');
$this->assertEquals($actors['body']['type'], 'string');
$this->assertEquals($actors['body']['size'], 256);
$this->assertEquals($actors['body']['required'], false);
$this->assertEquals($actors['body']['array'], true);
// wait for database worker to create attributes
sleep(10);
$movies = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), []);
$this->assertEquals($movies['body']['$id'], $title['body']['$collection']);
$this->assertEquals($movies['body']['$id'], $releaseYear['body']['$collection']);
$this->assertEquals($movies['body']['$id'], $actors['body']['$collection']);
$this->assertIsArray($movies['body']['attributesInQueue']);
$this->assertCount(0, $movies['body']['attributesInQueue']);
$this->assertIsArray($movies['body']['attributes']);
$this->assertCount(3, $movies['body']['attributes']);
$this->assertEquals($movies['body']['attributes'][0]['$id'], $title['body']['$id']);
$this->assertEquals($movies['body']['attributes'][1]['$id'], $releaseYear['body']['$id']);
$this->assertEquals($movies['body']['attributes'][2]['$id'], $actors['body']['$id']);
return $data;
}
/**
* @depends testCreateAttributes
*/
public function testCreateIndexes(array $data): array
{
$titleIndex = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/indexes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'titleIndex',
'type' => 'fulltext',
'attributes' => ['title'],
]);
$this->assertEquals($titleIndex['headers']['status-code'], 201);
$this->assertEquals($titleIndex['body']['$collection'], $data['moviesId']);
$this->assertEquals($titleIndex['body']['$id'], 'titleIndex');
$this->assertEquals($titleIndex['body']['type'], 'fulltext');
$this->assertCount(1, $titleIndex['body']['attributes']);
$this->assertEquals($titleIndex['body']['attributes'][0], 'title');
// wait for database worker to create index
sleep(5);
$movies = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), []);
$this->assertEquals($movies['body']['$id'], $titleIndex['body']['$collection']);
$this->assertIsArray($movies['body']['indexes']);
$this->assertCount(1, $movies['body']['indexes']);
$this->assertEquals($movies['body']['indexes'][0]['$id'], $titleIndex['body']['$id']);
return $data;
}
/**
* @depends testCreateIndexes
*/
public function testCreateDocument(array $data):array
{
$document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
@ -105,21 +161,11 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Captain America',
'title' => 'Captain America',
'releaseYear' => 1944,
'actors' => [
[
'$collection' => $data['actorsId'],
'$permissions' => ['read' => [], 'write' => []],
'firstName' => 'Chris',
'lastName' => 'Evans',
],
[
'$collection' => $data['actorsId'],
'$permissions' => ['read' => [], 'write' => []],
'firstName' => 'Samuel',
'lastName' => 'Jackson',
],
'Chris Evans',
'Samuel Jackson',
]
],
'read' => ['user:'.$this->getUser()['$id']],
@ -131,27 +177,12 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Spider-Man: Far From Home',
'title' => 'Spider-Man: Far From Home',
'releaseYear' => 2019,
'actors' => [
[
'$collection' => $data['actorsId'],
'$permissions' => ['read' => [], 'write' => []],
'firstName' => 'Tom',
'lastName' => 'Holland',
],
[
'$collection' => $data['actorsId'],
'$permissions' => ['read' => [], 'write' => []],
'firstName' => 'Zendaya',
'lastName' => 'Maree Stoermer',
],
[
'$collection' => $data['actorsId'],
'$permissions' => ['read' => [], 'write' => []],
'firstName' => 'Samuel',
'lastName' => 'Jackson',
],
'Tom Holland',
'Zendaya Maree Stoermer',
'Samuel Jackson',
]
],
'read' => ['user:'.$this->getUser()['$id']],
@ -163,21 +194,11 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Spider-Man: Homecoming',
'title' => 'Spider-Man: Homecoming',
'releaseYear' => 2017,
'actors' => [
[
'$collection' => $data['actorsId'],
'$permissions' => ['read' => [], 'write' => []],
'firstName' => 'Tom',
'lastName' => 'Holland',
],
[
'$collection' => $data['actorsId'],
'$permissions' => ['read' => [], 'write' => []],
'firstName' => 'Zendaya',
'lastName' => 'Maree Stoermer',
],
'Tom Holland',
'Zendaya Maree Stoermer',
],
],
'read' => ['user:'.$this->getUser()['$id']],
@ -197,46 +218,40 @@ trait DatabaseBase
$this->assertEquals($document1['headers']['status-code'], 201);
$this->assertEquals($document1['body']['$collection'], $data['moviesId']);
$this->assertEquals($document1['body']['name'], 'Captain America');
$this->assertEquals($document1['body']['title'], 'Captain America');
$this->assertEquals($document1['body']['releaseYear'], 1944);
$this->assertIsArray($document1['body']['$permissions']);
$this->assertIsArray($document1['body']['$permissions']['read']);
$this->assertIsArray($document1['body']['$permissions']['write']);
$this->assertCount(1, $document1['body']['$permissions']['read']);
$this->assertCount(1, $document1['body']['$permissions']['write']);
$this->assertIsArray($document1['body']['$read']);
$this->assertIsArray($document1['body']['$write']);
$this->assertCount(1, $document1['body']['$read']);
$this->assertCount(1, $document1['body']['$write']);
$this->assertCount(2, $document1['body']['actors']);
$this->assertEquals($document1['body']['actors'][0], 'Chris Evans');
$this->assertEquals($document1['body']['actors'][1], 'Samuel Jackson');
$this->assertEquals($document2['headers']['status-code'], 201);
$this->assertEquals($document2['body']['$collection'], $data['moviesId']);
$this->assertEquals($document2['body']['name'], 'Spider-Man: Far From Home');
$this->assertEquals($document2['body']['title'], 'Spider-Man: Far From Home');
$this->assertEquals($document2['body']['releaseYear'], 2019);
$this->assertIsArray($document2['body']['$permissions']);
$this->assertIsArray($document2['body']['$permissions']['read']);
$this->assertIsArray($document2['body']['$permissions']['write']);
$this->assertCount(1, $document2['body']['$permissions']['read']);
$this->assertCount(1, $document2['body']['$permissions']['write']);
$this->assertIsArray($document2['body']['$read']);
$this->assertIsArray($document2['body']['$write']);
$this->assertCount(1, $document2['body']['$read']);
$this->assertCount(1, $document2['body']['$write']);
$this->assertCount(3, $document2['body']['actors']);
$this->assertEquals($document2['body']['actors'][0]['firstName'], 'Tom');
$this->assertEquals($document2['body']['actors'][0]['lastName'], 'Holland');
$this->assertEquals($document2['body']['actors'][1]['firstName'], 'Zendaya');
$this->assertEquals($document2['body']['actors'][1]['lastName'], 'Maree Stoermer');
$this->assertEquals($document2['body']['actors'][2]['firstName'], 'Samuel');
$this->assertEquals($document2['body']['actors'][2]['lastName'], 'Jackson');
$this->assertEquals($document2['body']['actors'][0], 'Tom Holland');
$this->assertEquals($document2['body']['actors'][1], 'Zendaya Maree Stoermer');
$this->assertEquals($document2['body']['actors'][2], 'Samuel Jackson');
$this->assertEquals($document3['headers']['status-code'], 201);
$this->assertEquals($document3['body']['$collection'], $data['moviesId']);
$this->assertEquals($document3['body']['name'], 'Spider-Man: Homecoming');
$this->assertEquals($document3['body']['title'], 'Spider-Man: Homecoming');
$this->assertEquals($document3['body']['releaseYear'], 2017);
$this->assertIsArray($document3['body']['$permissions']);
$this->assertIsArray($document3['body']['$permissions']['read']);
$this->assertIsArray($document3['body']['$permissions']['write']);
$this->assertCount(1, $document3['body']['$permissions']['read']);
$this->assertCount(1, $document3['body']['$permissions']['write']);
$this->assertIsArray($document3['body']['$read']);
$this->assertIsArray($document3['body']['$write']);
$this->assertCount(1, $document3['body']['$read']);
$this->assertCount(1, $document3['body']['$write']);
$this->assertCount(2, $document3['body']['actors']);
$this->assertEquals($document3['body']['actors'][0]['firstName'], 'Tom');
$this->assertEquals($document3['body']['actors'][0]['lastName'], 'Holland');
$this->assertEquals($document3['body']['actors'][1]['firstName'], 'Zendaya');
$this->assertEquals($document3['body']['actors'][1]['lastName'], 'Maree Stoermer');
$this->assertEquals($document2['body']['actors'][0], 'Tom Holland');
$this->assertEquals($document2['body']['actors'][1], 'Zendaya Maree Stoermer');
$this->assertEquals($document4['headers']['status-code'], 400);
@ -252,9 +267,8 @@ trait DatabaseBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderField' => 'releaseYear',
'orderType' => 'ASC',
'orderCast' => 'int',
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
]);
$this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
@ -266,9 +280,8 @@ trait DatabaseBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'orderField' => 'releaseYear',
'orderType' => 'DESC',
'orderCast' => 'int',
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['DESC'],
]);
$this->assertEquals(1944, $documents['body']['documents'][2]['releaseYear']);
@ -289,9 +302,8 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'limit' => 1,
'orderField' => 'releaseYear',
'orderType' => 'ASC',
'orderCast' => 'int',
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
]);
$this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
@ -303,9 +315,8 @@ trait DatabaseBase
], $this->getHeaders()), [
'limit' => 2,
'offset' => 1,
'orderField' => 'releaseYear',
'orderType' => 'ASC',
'orderCast' => 'int',
'orderAttributes' => ['releaseYear'],
'orderTypes' => ['ASC'],
]);
$this->assertEquals(2017, $documents['body']['documents'][0]['releaseYear']);
@ -324,7 +335,7 @@ trait DatabaseBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'Captain America',
'queries' => ['title.search("Captain America")'],
]);
$this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
@ -334,7 +345,7 @@ trait DatabaseBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'Homecoming',
'queries' => ['title.search("Homecoming")'],
]);
$this->assertEquals(2017, $documents['body']['documents'][0]['releaseYear']);
@ -344,7 +355,7 @@ trait DatabaseBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'spider',
'queries' => ['title.search("spider")'],
]);
$this->assertEquals(2019, $documents['body']['documents'][0]['releaseYear']);
@ -353,52 +364,53 @@ trait DatabaseBase
return [];
}
// TODO@kodumbeats test for empty searches and misformatted queries
/**
* @depends testCreateDocument
*/
public function testListDocumentsFilters(array $data):array
{
$documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'filters' => [
'actors.firstName=Tom'
],
]);
// public function testListDocumentsFilters(array $data):array
// {
// $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'filters' => [
// 'actors.firstName=Tom'
// ],
// ]);
$this->assertCount(2, $documents['body']['documents']);
$this->assertEquals('Spider-Man: Far From Home', $documents['body']['documents'][0]['name']);
$this->assertEquals('Spider-Man: Homecoming', $documents['body']['documents'][1]['name']);
// $this->assertCount(2, $documents['body']['documents']);
// $this->assertEquals('Spider-Man: Far From Home', $documents['body']['documents'][0]['name']);
// $this->assertEquals('Spider-Man: Homecoming', $documents['body']['documents'][1]['name']);
$documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'filters' => [
'releaseYear=1944'
],
]);
// $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'filters' => [
// 'releaseYear=1944'
// ],
// ]);
$this->assertCount(1, $documents['body']['documents']);
$this->assertEquals('Captain America', $documents['body']['documents'][0]['name']);
// $this->assertCount(1, $documents['body']['documents']);
// $this->assertEquals('Captain America', $documents['body']['documents'][0]['name']);
$documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'filters' => [
'releaseYear!=1944'
],
]);
// $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'filters' => [
// 'releaseYear!=1944'
// ],
// ]);
$this->assertCount(2, $documents['body']['documents']);
$this->assertEquals('Spider-Man: Far From Home', $documents['body']['documents'][0]['name']);
$this->assertEquals('Spider-Man: Homecoming', $documents['body']['documents'][1]['name']);
// $this->assertCount(2, $documents['body']['documents']);
// $this->assertEquals('Spider-Man: Far From Home', $documents['body']['documents'][0]['name']);
// $this->assertEquals('Spider-Man: Homecoming', $documents['body']['documents'][1]['name']);
return [];
}
// return [];
// }
/**
* @depends testCreateDocument
@ -410,35 +422,34 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Thor: Ragnaroc',
'title' => 'Thor: Ragnaroc',
'releaseYear' => 2017,
'actors' => [],
],
'read' => ['user:'.$this->getUser()['$id'], 'testx'],
'write' => ['user:'.$this->getUser()['$id'], 'testy'],
'read' => ['user:'.$this->getUser()['$id'], 'user:testx'],
'write' => ['user:'.$this->getUser()['$id'], 'user:testy'],
]);
$id = $document['body']['$id'];
$collection = $document['body']['$collection'];
$this->assertEquals($document['headers']['status-code'], 201);
$this->assertEquals($document['body']['name'], 'Thor: Ragnaroc');
$this->assertEquals($document['body']['title'], 'Thor: Ragnaroc');
$this->assertEquals($document['body']['releaseYear'], 2017);
$this->assertEquals($document['body']['$permissions']['read'][1], 'testx');
$this->assertEquals($document['body']['$permissions']['write'][1], 'testy');
$this->assertEquals($document['body']['$read'][1], 'user:testx');
$this->assertEquals($document['body']['$write'][1], 'user:testy');
$document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $collection . '/documents/' . $id, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Thor: Ragnarok'
'title' => 'Thor: Ragnarok',
],
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
]);
$this->assertEquals($document['headers']['status-code'], 200);
$this->assertEquals($document['body']['name'], 'Thor: Ragnarok');
$this->assertEquals($document['body']['title'], 'Thor: Ragnarok');
$this->assertEquals($document['body']['releaseYear'], 2017);
$document = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collection . '/documents/' . $id, array_merge([
@ -450,7 +461,7 @@ trait DatabaseBase
$collection = $document['body']['$collection'];
$this->assertEquals($document['headers']['status-code'], 200);
$this->assertEquals($document['body']['name'], 'Thor: Ragnarok');
$this->assertEquals($document['body']['title'], 'Thor: Ragnarok');
$this->assertEquals($document['body']['releaseYear'], 2017);
return [];
@ -466,8 +477,9 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Thor: Ragnarok',
'title' => 'Thor: Ragnarok',
'releaseYear' => 2017,
'actors' => [],
],
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
@ -512,7 +524,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Captain America',
'title' => 'Captain America',
'releaseYear' => 1944,
'actors' => [],
],
@ -522,24 +534,23 @@ trait DatabaseBase
$this->assertEquals($document['headers']['status-code'], 201);
$this->assertEquals($document['body']['$collection'], $data['moviesId']);
$this->assertEquals($document['body']['name'], 'Captain America');
$this->assertEquals($document['body']['title'], 'Captain America');
$this->assertEquals($document['body']['releaseYear'], 1944);
$this->assertIsArray($document['body']['$permissions']);
$this->assertIsArray($document['body']['$permissions']['read']);
$this->assertIsArray($document['body']['$permissions']['write']);
$this->assertIsArray($document['body']['$read']);
$this->assertIsArray($document['body']['$write']);
if($this->getSide() == 'client') {
$this->assertCount(1, $document['body']['$permissions']['read']);
$this->assertCount(1, $document['body']['$permissions']['write']);
$this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['read']);
$this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['write']);
$this->assertCount(1, $document['body']['$read']);
$this->assertCount(1, $document['body']['$write']);
$this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$read']);
$this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$write']);
}
if($this->getSide() == 'server') {
$this->assertCount(0, $document['body']['$permissions']['read']);
$this->assertCount(0, $document['body']['$permissions']['write']);
$this->assertEquals([], $document['body']['$permissions']['read']);
$this->assertEquals([], $document['body']['$permissions']['write']);
$this->assertCount(0, $document['body']['$read']);
$this->assertCount(0, $document['body']['$write']);
$this->assertEquals([], $document['body']['$read']);
$this->assertEquals([], $document['body']['$write']);
}
// Updated and Inherit Permissions
@ -549,7 +560,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Captain America 2',
'title' => 'Captain America 2',
'releaseYear' => 1945,
'actors' => [],
],
@ -557,21 +568,21 @@ trait DatabaseBase
]);
$this->assertEquals($document['headers']['status-code'], 200);
$this->assertEquals($document['body']['name'], 'Captain America 2');
$this->assertEquals($document['body']['title'], 'Captain America 2');
$this->assertEquals($document['body']['releaseYear'], 1945);
if($this->getSide() == 'client') {
$this->assertCount(1, $document['body']['$permissions']['read']);
$this->assertCount(1, $document['body']['$permissions']['write']);
$this->assertEquals(['role:all'], $document['body']['$permissions']['read']);
$this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['write']);
$this->assertCount(1, $document['body']['$read']);
$this->assertCount(1, $document['body']['$write']);
$this->assertEquals(['role:all'], $document['body']['$read']);
$this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$write']);
}
if($this->getSide() == 'server') {
$this->assertCount(1, $document['body']['$permissions']['read']);
$this->assertCount(0, $document['body']['$permissions']['write']);
$this->assertEquals(['role:all'], $document['body']['$permissions']['read']);
$this->assertEquals([], $document['body']['$permissions']['write']);
$this->assertCount(1, $document['body']['$read']);
$this->assertCount(0, $document['body']['$write']);
$this->assertEquals(['role:all'], $document['body']['$read']);
$this->assertEquals([], $document['body']['$write']);
}
$document = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([
@ -580,21 +591,21 @@ trait DatabaseBase
], $this->getHeaders()));
$this->assertEquals($document['headers']['status-code'], 200);
$this->assertEquals($document['body']['name'], 'Captain America 2');
$this->assertEquals($document['body']['title'], 'Captain America 2');
$this->assertEquals($document['body']['releaseYear'], 1945);
if($this->getSide() == 'client') {
$this->assertCount(1, $document['body']['$permissions']['read']);
$this->assertCount(1, $document['body']['$permissions']['write']);
$this->assertEquals(['role:all'], $document['body']['$permissions']['read']);
$this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['write']);
$this->assertCount(1, $document['body']['$read']);
$this->assertCount(1, $document['body']['$write']);
$this->assertEquals(['role:all'], $document['body']['$read']);
$this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$write']);
}
if($this->getSide() == 'server') {
$this->assertCount(1, $document['body']['$permissions']['read']);
$this->assertCount(0, $document['body']['$permissions']['write']);
$this->assertEquals(['role:all'], $document['body']['$permissions']['read']);
$this->assertEquals([], $document['body']['$permissions']['write']);
$this->assertCount(1, $document['body']['$read']);
$this->assertCount(0, $document['body']['$write']);
$this->assertEquals(['role:all'], $document['body']['$read']);
$this->assertEquals([], $document['body']['$write']);
}
// Reset Permissions
@ -604,7 +615,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Captain America 3',
'title' => 'Captain America 3',
'releaseYear' => 1946,
'actors' => [],
],
@ -618,12 +629,12 @@ trait DatabaseBase
if($this->getSide() == 'server') {
$this->assertEquals($document['headers']['status-code'], 200);
$this->assertEquals($document['body']['name'], 'Captain America 3');
$this->assertEquals($document['body']['title'], 'Captain America 3');
$this->assertEquals($document['body']['releaseYear'], 1946);
$this->assertCount(0, $document['body']['$permissions']['read']);
$this->assertCount(0, $document['body']['$permissions']['write']);
$this->assertEquals([], $document['body']['$permissions']['read']);
$this->assertEquals([], $document['body']['$permissions']['write']);
$this->assertCount(0, $document['body']['$read']);
$this->assertCount(0, $document['body']['$write']);
$this->assertEquals([], $document['body']['$read']);
$this->assertEquals([], $document['body']['$write']);
}
return $data;

View file

@ -27,34 +27,49 @@ class DatabaseCustomServerTest extends Scope
]), [
'name' => 'Actors',
'read' => ['role:all'],
'write' => ['role:member', 'role:admin'],
'rules' => [
[
'label' => 'First Name',
'key' => 'firstName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
[
'label' => 'Last Name',
'key' => 'lastName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
],
'write' => ['role:all'],
]);
$this->assertEquals($actors['headers']['status-code'], 201);
$this->assertEquals($actors['body']['name'], 'Actors');
$this->assertIsArray($actors['body']['$permissions']);
$this->assertIsArray($actors['body']['$permissions']['read']);
$this->assertIsArray($actors['body']['$permissions']['write']);
$this->assertCount(1, $actors['body']['$permissions']['read']);
$this->assertCount(2, $actors['body']['$permissions']['write']);
$firstName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/attributes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'firstName',
'type' => 'string',
'size' => 256,
'required' => true,
]);
$lastName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/attributes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'lastName',
'type' => 'string',
'size' => 256,
'required' => true,
]);
// wait for database worker to finish creating attributes
sleep(5);
$collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $actors['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), []);
$this->assertEquals($collection['body']['$id'], $firstName['body']['$collection']);
$this->assertEquals($collection['body']['$id'], $lastName['body']['$collection']);
$this->assertIsArray($collection['body']['attributes']);
$this->assertCount(2, $collection['body']['attributes']);
$this->assertEquals($collection['body']['attributes'][0]['$id'], $firstName['body']['$id']);
$this->assertEquals($collection['body']['attributes'][1]['$id'], $lastName['body']['$id']);
// Add Documents to the collection
$document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/documents', array_merge([
@ -80,24 +95,22 @@ class DatabaseCustomServerTest extends Scope
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
]);
$this->assertEquals($document1['headers']['status-code'], 201);
$this->assertEquals($document1['body']['$collection'], $actors['body']['$id']);
$this->assertIsArray($document1['body']['$permissions']);
$this->assertIsArray($document1['body']['$permissions']['read']);
$this->assertIsArray($document1['body']['$permissions']['write']);
$this->assertCount(1, $document1['body']['$permissions']['read']);
$this->assertCount(1, $document1['body']['$permissions']['write']);
$this->assertIsArray($document1['body']['$read']);
$this->assertIsArray($document1['body']['$write']);
$this->assertCount(1, $document1['body']['$read']);
$this->assertCount(1, $document1['body']['$write']);
$this->assertEquals($document1['body']['firstName'], 'Tom');
$this->assertEquals($document1['body']['lastName'], 'Holland');
$this->assertEquals($document2['headers']['status-code'], 201);
$this->assertEquals($document2['body']['$collection'], $actors['body']['$id']);
$this->assertIsArray($document2['body']['$permissions']);
$this->assertIsArray($document2['body']['$permissions']['read']);
$this->assertIsArray($document2['body']['$permissions']['write']);
$this->assertCount(1, $document2['body']['$permissions']['read']);
$this->assertCount(1, $document2['body']['$permissions']['write']);
$this->assertIsArray($document2['body']['$read']);
$this->assertIsArray($document2['body']['$write']);
$this->assertCount(1, $document2['body']['$read']);
$this->assertCount(1, $document2['body']['$write']);
$this->assertEquals($document2['body']['firstName'], 'Samuel');
$this->assertEquals($document2['body']['lastName'], 'Jackson');

View file

@ -20,24 +20,6 @@ trait WebhooksBase
'name' => 'Actors',
'read' => ['role:all'],
'write' => ['role:all'],
'rules' => [
[
'label' => 'First Name',
'key' => 'firstName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
[
'label' => 'Last Name',
'key' => 'lastName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
],
]);
$this->assertEquals($actors['headers']['status-code'], 201);
@ -55,12 +37,10 @@ trait WebhooksBase
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true);
$this->assertNotEmpty($webhook['data']['$id']);
$this->assertEquals($webhook['data']['name'], 'Actors');
$this->assertIsArray($webhook['data']['$permissions']);
$this->assertIsArray($webhook['data']['$read']);
$this->assertIsArray($webhook['data']['$write']);
$this->assertCount(1, $webhook['data']['$read']);
$this->assertCount(1, $webhook['data']['$write']);
$this->assertCount(2, $webhook['data']['rules']);
return array_merge(['actorsId' => $actors['body']['$id']]);
}
@ -68,6 +48,60 @@ trait WebhooksBase
/**
* @depends testCreateCollection
*/
public function testCreateAttributes(array $data): array
{
$firstName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'firstName',
'type' => 'string',
'size' => 256,
'required' => true,
]);
$lastName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'lastName',
'type' => 'string',
'size' => 256,
'required' => true,
]);
$this->assertEquals($firstName['headers']['status-code'], 201);
$this->assertEquals($firstName['body']['$collection'], $data['actorsId']);
$this->assertEquals($firstName['body']['$id'], 'firstName');
$this->assertEquals($lastName['headers']['status-code'], 201);
$this->assertEquals($lastName['body']['$collection'], $data['actorsId']);
$this->assertEquals($lastName['body']['$id'], 'lastName');
// wait for database worker to kick in
sleep(10);
$webhook = $this->getLastRequest();
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.attributes.create');
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented');
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
$this->assertNotEmpty($webhook['data']['$id']);
$this->assertEquals($webhook['data']['$id'], 'lastName');
// TODO@kodumbeats test webhook for removing attribute
return $data;
}
/**
* @depends testCreateAttributes
*/
public function testCreateDocument(array $data): array
{
$document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([

View file

@ -15,7 +15,7 @@ class WebhooksCustomServerTest extends Scope
use SideServer;
/**
* @depends testCreateCollection
* @depends testCreateAttributes
*/
public function testUpdateCollection($data): array
{
@ -28,26 +28,6 @@ class WebhooksCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Actors1',
'read' => ['role:all'],
'write' => ['role:all'],
'rules' => [
[
'label' => 'First Name',
'key' => 'firstName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
[
'label' => 'Last Name',
'key' => 'lastName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
],
]);
$this->assertEquals($actors['headers']['status-code'], 200);
@ -65,16 +45,73 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true);
$this->assertNotEmpty($webhook['data']['$id']);
$this->assertEquals($webhook['data']['name'], 'Actors1');
$this->assertIsArray($webhook['data']['$permissions']);
$this->assertIsArray($webhook['data']['$permissions']['read']);
$this->assertIsArray($webhook['data']['$permissions']['write']);
$this->assertCount(1, $webhook['data']['$permissions']['read']);
$this->assertCount(1, $webhook['data']['$permissions']['write']);
$this->assertCount(2, $webhook['data']['rules']);
$this->assertIsArray($webhook['data']['$read']);
$this->assertIsArray($webhook['data']['$write']);
$this->assertCount(1, $webhook['data']['$read']);
$this->assertCount(1, $webhook['data']['$write']);
return array_merge(['actorsId' => $actors['body']['$id']]);
}
/**
* @depends testCreateAttributes
*/
public function testCreateDeleteIndexes($data): array
{
$index = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/indexes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'id' => 'fullname',
'type' => 'text',
'attributes' => ['lastName', 'firstName'],
'orders' => ['ASC', 'ASC'],
]);
$this->assertEquals($index['headers']['status-code'], 201);
$this->assertEquals($index['body']['$collection'], $data['actorsId']);
$this->assertEquals($index['body']['$id'], 'fullname');
// wait for database worker to create index
sleep(5);
$webhook = $this->getLastRequest();
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.indexes.create');
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented');
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true);
// TODO@kodumbeats test for indexes.delete
// Remove index
// $index = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/indexes/' . $index['body']['$id'], array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// 'x-appwrite-key' => $this->getProject()['apiKey']
// ]));
// // wait for database worker to remove index
// sleep(5);
// $webhook = $this->getLastRequest();
// // $this->assertEquals($webhook['method'], 'DELETE');
// $this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
// $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
// $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.indexes.delete');
// $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented');
// $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
// $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
// $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true);
return $data;
}
public function testDeleteCollection(): array
{
/**
@ -88,24 +125,6 @@ class WebhooksCustomServerTest extends Scope
'name' => 'Demo',
'read' => ['role:all'],
'write' => ['role:all'],
'rules' => [
[
'label' => 'First Name',
'key' => 'firstName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
[
'label' => 'Last Name',
'key' => 'lastName',
'type' => 'text',
'default' => '',
'required' => true,
'array' => false
],
],
]);
$this->assertEquals($actors['headers']['status-code'], 201);
@ -131,12 +150,10 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true);
$this->assertNotEmpty($webhook['data']['$id']);
$this->assertEquals($webhook['data']['name'], 'Demo');
$this->assertIsArray($webhook['data']['$permissions']);
$this->assertIsArray($webhook['data']['$permissions']['read']);
$this->assertIsArray($webhook['data']['$permissions']['write']);
$this->assertCount(1, $webhook['data']['$permissions']['read']);
$this->assertCount(1, $webhook['data']['$permissions']['write']);
$this->assertCount(2, $webhook['data']['rules']);
$this->assertIsArray($webhook['data']['$read']);
$this->assertIsArray($webhook['data']['$write']);
$this->assertCount(1, $webhook['data']['$read']);
$this->assertCount(1, $webhook['data']['$write']);
return [];
}
@ -306,8 +323,8 @@ class WebhooksCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Test',
'env' => 'php-8.0',
'runtime' => ['role:all'],
'execute' => ['role:all'],
'runtime' => 'php-8.0',
'timeout' => 10,
]);