1
0
Fork 0
mirror of synced 2024-06-13 16:24:47 +12:00

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

This commit is contained in:
Matej Baco 2021-12-31 16:48:52 +01:00
commit b703943b26
37 changed files with 683 additions and 116 deletions

2
.env
View file

@ -43,3 +43,5 @@ _APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
_APP_USAGE_STATS=enabled
_APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=

View file

@ -65,6 +65,7 @@ script:
exit 1
fi
- docker-compose logs appwrite
- docker-compose logs appwrite-realtime
- docker-compose logs mariadb
- docker-compose logs appwrite-worker-functions
- docker-compose exec appwrite doctor

View file

@ -24,6 +24,8 @@
- Deno 1.12
- Deno 1.13
- Deno 1.14
- PHP 8.1
- Node 17
- Added translations:
- German `de` by @SoftCreatR in https://github.com/appwrite/appwrite/pull/1790
- Hebrew `he` by @Kokoden in https://github.com/appwrite/appwrite/pull/1846

View file

@ -181,7 +181,9 @@ ENV _APP_SERVER=swoole \
_APP_MAINTENANCE_RETENTION_AUDIT=1209600 \
# 1 Day = 86400 s
_APP_MAINTENANCE_RETENTION_ABUSE=86400 \
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_INTERVAL=86400 \
_APP_LOGGING_PROVIDER= \
_APP_LOGGING_CONFIG=
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

View file

@ -150,6 +150,24 @@ return [
'question' => '',
'filter' => ''
],
[
'name' => '_APP_LOGGING_PROVIDER',
'description' => 'This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, to enable the logger set the value to one of \'sentry\', \'raygun\', \'appsignal\'',
'introduction' => '0.12.0',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_LOGGING_CONFIG',
'description' => 'This variable configures authentication to 3rd party error logging providers. If using Sentry, this should be \'SENTRY_API_KEY;SENTRY_APP_ID\'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key.',
'introduction' => '0.12.0',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_USAGE_AGGREGATION_INTERVAL',
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to mariadb from InfluxDB. The default value is 30 seconds.',

View file

@ -973,10 +973,12 @@ App::post('/v1/database/collections/:collectionId/attributes/integer')
throw new Exception($validator->getDescription(), 400);
}
$size = $max > 2147483647 ? 8 : 4; // Automatically create BigInt depending on max value
$attribute = createAttribute($collectionId, new Document([
'key' => $key,
'type' => Database::VAR_INTEGER,
'size' => 0,
'size' => $size,
'required' => $required,
'default' => $default,
'array' => $array,
@ -1679,7 +1681,7 @@ App::get('/v1/database/collections/:collectionId/documents')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('queries', [], new ArrayList(new Text(0)), 'Array of query strings.', true) // TODO: research limitations - temporarily unlimited length
->param('queries', [], new ArrayList(new Text(0), 100), 'Array of query strings.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
@ -1717,7 +1719,15 @@ App::get('/v1/database/collections/:collectionId/documents')
}
}
$queries = \array_map(fn ($query) => Query::parse($query), $queries);
$queries = \array_map(function ($query) {
$query = Query::parse($query);
if (\count($query->getValues()) > 100) {
throw new Exception("You cannot use more than 100 query values on attribute '{$query->getAttribute()}'", 400);
}
return $query;
}, $queries);
if (!empty($queries)) {
$validator = new QueriesValidator(new QueryValidator($collection->getAttribute('attributes', [])), $collection->getAttribute('indexes', []), true);

View file

@ -3,6 +3,8 @@
require_once __DIR__.'/../init.php';
use Utopia\App;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\View;
@ -297,19 +299,72 @@ App::options(function ($request, $response) {
->noContent();
}, ['request', 'response']);
App::error(function ($error, $utopia, $request, $response, $layout, $project) {
App::error(function ($error, $utopia, $request, $response, $layout, $project, $logger, $loggerBreadcrumbs) {
/** @var Exception $error */
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Utopia\View $layout */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Logger\Logger $logger */
/** @var Utopia\Logger\Log\Breadcrumb[] $loggerBreadcrumbs */
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->match($request);
if($logger) {
if($error->getCode() >= 500 || $error->getCode() === 0) {
try {
$user = $utopia->getResource('user');
/** @var Appwrite\Database\Document $user */
} catch(\Throwable $th) {
// All good, user is optional information for logger
}
$log = new Utopia\Logger\Log();
if(isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', Authorization::$roles);
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: '.$responseCode);
}
}
if ($error instanceof PDOException) {
throw $error;
}
$route = $utopia->match($request);
$template = ($route) ? $route->getLabel('error', null) : null;
if (php_sapi_name() === 'cli') {
@ -326,8 +381,6 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
Console::error('[Error] Line: '.$error->getLine());
}
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
switch ($error->getCode()) { // Don't show 500 errors!
case 400: // Error allowed publicly
case 401: // Error allowed publicly
@ -394,7 +447,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
$response->dynamic(new Document($output),
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR);
}, ['error', 'utopia', 'request', 'response', 'layout', 'project']);
}, ['error', 'utopia', 'request', 'response', 'layout', 'project', 'logger', 'loggerBreadcrumbs']);
App::get('/manifest.json')
->desc('Progressive app manifest file')

View file

@ -17,6 +17,8 @@ use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\Database\Document;
use Utopia\Swoole\Files;
use Appwrite\Utopia\Request;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
$http = new Server("0.0.0.0", App::getEnv('PORT', 80));
@ -207,6 +209,59 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$app->run($request, $response);
} catch (\Throwable $th) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$logger = $app->getResource("logger");
if($logger) {
try {
$user = $app->getResource('user');
/** @var Appwrite\Database\Document $user */
} catch(\Throwable $_th) {
// All good, user is optional information for logger
}
$loggerBreadcrumbs = $app->getResource("loggerBreadcrumbs");
$route = $app->match($request);
$log = new Utopia\Logger\Log();
if(isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($th->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($th));
$log->addTag('code', $th->getCode());
// $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $th->getFile());
$log->addExtra('line', $th->getLine());
$log->addExtra('trace', $th->getTraceAsString());
$log->addExtra('roles', Authorization::$roles);
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: '.$responseCode);
}
Console::error('[Error] Type: '.get_class($th));
Console::error('[Error] Message: '.$th->getMessage());
Console::error('[Error] File: '.$th->getFile());
@ -221,12 +276,20 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$swooleResponse->setStatusCode(500);
if(App::isDevelopment()) {
$swooleResponse->end('error: '.$th->getMessage());
}
else {
$swooleResponse->end('500: Server Error');
}
$output = ((App::isDevelopment())) ? [
'message' => 'Error: '. $th->getMessage(),
'code' => 500,
'file' => $th->getFile(),
'line' => $th->getLine(),
'trace' => $th->getTrace(),
'version' => $version,
] : [
'message' => 'Error: Server Error',
'code' => 500,
'version' => $version,
];
$swooleResponse->end(\json_encode($output));
} finally {
/** @var PDOPool $dbPool */
$dbPool = $register->get('dbPool');

View file

@ -30,6 +30,7 @@ use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Stats\Stats;
use Appwrite\Utopia\View;
use Utopia\App;
use Utopia\Logger\Logger;
use Utopia\Config\Config;
use Utopia\Locale\Locale;
use Utopia\Registry\Registry;
@ -144,7 +145,7 @@ Config::load('locale-continents', __DIR__.'/config/locale/continents.php');
Config::load('storage-logos', __DIR__.'/config/storage/logos.php');
Config::load('storage-mimes', __DIR__.'/config/storage/mimes.php');
Config::load('storage-inputs', __DIR__.'/config/storage/inputs.php');
Config::load('storage-outputs', __DIR__.'/config/storage/outputs.php');
Config::load('storage-outputs', __DIR__.'/config/storage/outputs.php');
$user = App::getEnv('_APP_REDIS_USER','');
$pass = App::getEnv('_APP_REDIS_PASS','');
@ -375,6 +376,22 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function($attribute) {
/*
* Registry
*/
$register->set('logger', function () { // Register error logger
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
if(empty($providerName) || empty($providerConfig)) {
return null;
}
if(!Logger::hasProvider($providerName)) {
throw new Exception("Logging provider not supported. Logging disabled.");
}
$classname = '\\Utopia\\Logger\\Adapter\\'.\ucfirst($providerName);
$adapter = new $classname($providerConfig);
return new Logger($adapter);
});
$register->set('dbPool', function () { // Register DB connection
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbPort = App::getEnv('_APP_DB_PORT', '');
@ -581,6 +598,14 @@ Locale::setLanguageFromJSON('zh-tw', __DIR__.'/config/locale/translations/zh-tw.
]);
// Runtime Execution
App::setResource('logger', function($register) {
return $register->get('logger');
}, ['register']);
App::setResource('loggerBreadcrumbs', function() {
return [];
});
App::setResource('register', fn() => $register);
App::setResource('layout', function($locale) {

View file

@ -14,6 +14,8 @@ use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Logger\Log;
use Utopia\Database\Database;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Cache;
@ -51,6 +53,43 @@ $adapter->setPackageMaxLength(64000); // Default maximum Package Size (64kb)
$server = new Server($adapter);
$logError = function(Throwable $error, string $action) use ($register) {
$logger = $register->get('logger');
if($logger) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace("realtime");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', get_class($error));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
$responseCode = $logger->addLog($log);
Console::info('Realtime log pushed with status code: '.$responseCode);
}
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $error->getMessage());
Console::error('[Error] File: ' . $error->getFile());
Console::error('[Error] Line: ' . $error->getLine());
};
$server->error($logError);
function getDatabase(Registry &$register, string $namespace)
{
$db = $register->get('dbPool')->get();
@ -70,13 +109,13 @@ function getDatabase(Registry &$register, string $namespace)
];
};
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument) {
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
Console::success('Server started succefully');
/**
* Create document for this worker to share stats across Containers.
*/
go(function () use ($register, $containerId, &$statsDocument) {
go(function () use ($register, $containerId, &$statsDocument, $logError) {
try {
[$database, $returnDatabase] = getDatabase($register, '_project_console');
$document = new Document([
@ -90,10 +129,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
]);
$statsDocument = Authorization::skip(fn() => $database->createDocument('realtime', $document));
} catch (\Throwable $th) {
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
call_user_func($logError, $th, "createWorkerDocument");
} finally {
call_user_func($returnDatabase);
}
@ -102,7 +138,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
/**
* Save current connections to the Database every 5 seconds.
*/
Timer::tick(5000, function () use ($register, $stats, $containerId, &$statsDocument) {
Timer::tick(5000, function () use ($register, $stats, $containerId, &$statsDocument, $logError) {
/** @var Document $statsDocument */
foreach ($stats as $projectId => $value) {
$connections = $stats->get($projectId, 'connections') ?? 0;
@ -142,23 +178,20 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
Authorization::skip(fn() => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (\Throwable $th) {
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
call_user_func($logError, $th, "updateWorkerDocument");
} finally {
call_user_func($returnDatabase);
}
});
});
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime) {
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) {
Console::success('Worker ' . $workerId . ' started succefully');
$attempts = 0;
$start = time();
Timer::tick(5000, function () use ($server, $register, $realtime, $stats) {
Timer::tick(5000, function () use ($server, $register, $realtime, $stats, $logError) {
/**
* Sending current connections to project channels on the console project every 5 seconds.
*/
@ -300,6 +333,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
}
});
} catch (\Throwable $th) {
call_user_func($logError, $th, "pubSubConnection");
Console::error('Pub/sub error: ' . $th->getMessage());
$register->get('redisPool')->put($redis);
$attempts++;
@ -312,7 +347,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
Console::error('Failed to restart pub/sub...');
});
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime) {
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) {
$app = new App('UTC');
$request = new Request($request);
$response = new Response(new SwooleResponse());
@ -409,6 +444,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$stats->incr($project->getId(), 'connections');
$stats->incr($project->getId(), 'connectionsTotal');
} catch (\Throwable $th) {
call_user_func($logError, $th, "initServer");
$response = [
'type' => 'error',
'data' => [

View file

@ -3,6 +3,7 @@
global $cli;
use Appwrite\ClamAV\Network;
use Utopia\Logger\Logger;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\App;
@ -82,6 +83,16 @@ $cli
Console::log('🟢 HTTPS force option is enabled');
}
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
if(empty($providerName) || empty($providerConfig) || !Logger::hasProvider($providerName)) {
Console::log('🔴 Logging adapter is disabled');
} else {
Console::log('🟢 Logging adapter is enabled (' . $providerName . ')');
}
\sleep(0.2);
try {

View file

@ -211,7 +211,7 @@
required
maxlength="36"
class=""
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_.-]{1,36}$"
name="projectId" />
<label>Name</label>

View file

@ -564,8 +564,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="string-key">Attribute ID</label>
<input id="string-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="string-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<label for="string-length">Size</label>
<input id="string-length" name="size" type="number" class="margin-bottom" autocomplete="off" required value="255" data-cast-to="integer" />
@ -641,8 +641,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="integer-key">Attribute ID</label>
<input id="integer-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="integer-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input type="hidden" name="required" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
@ -723,8 +723,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="float-key">Attribute ID</label>
<input id="float-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="float-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input type="hidden" name="required" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
@ -804,8 +804,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="email-key">Attribute ID</label>
<input id="email-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="128" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="email-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="128" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
@ -874,8 +874,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="boolean-key">Attribute ID</label>
<input id="boolean-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="boolean-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
@ -947,8 +947,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="ip-key">Attribute ID</label>
<input id="ip-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="ip-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
@ -1017,8 +1017,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="url-key">Attribute ID</label>
<input id="url-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="url-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
@ -1087,8 +1087,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="enum-key">Attribute ID</label>
<input id="enum-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="enum-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<label>Elements</label>
@ -1184,8 +1184,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="index-key">Index Key</label>
<input id="index-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="index-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<label for="index-type">Type</label>
<select id="index-type" name="type">

View file

@ -83,7 +83,7 @@ $logs = $this->getParam('logs', null);
name="documentId"
id="documentId"
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<?php endif; ?>
<fieldset name="data" data-cast-to="object" data-ls-attrs="x-init=doc = {{project-document}}" x-data="{doc: {}}">

View file

@ -102,7 +102,7 @@
data-validator="database.getCollection"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
name="collectionId" />
<label for="collection-name">Name</label>

View file

@ -113,7 +113,7 @@ $runtimes = $this->getParam('runtimes', []);
data-validator="functions.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
name="functionId" />
<label for="name">Name</label>

View file

@ -245,7 +245,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
data-validator="storage.getFile"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
name="fileId"
id="fileId" />

View file

@ -163,7 +163,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
data-validator="users.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
id="userId"
name="userId" />
@ -311,7 +311,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
data-validator="teams.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
id="teamId"
name="teamId" />

View file

@ -106,6 +106,8 @@ services:
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_RUNTIMES
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STATSD_HOST
- _APP_STATSD_PORT
- _APP_MAINTENANCE_INTERVAL

View file

@ -11,6 +11,10 @@ Console::success(APP_NAME . ' audits worker v1 has started');
class AuditsV1 extends Worker
{
public function getName(): string {
return "audits";
}
public function init(): void
{
}

View file

@ -16,6 +16,10 @@ Console::success(APP_NAME . ' certificates worker v1 has started');
class CertificatesV1 extends Worker
{
public function getName(): string {
return "certificates";
}
public function init(): void
{
}

View file

@ -23,6 +23,10 @@ class DeletesV1 extends Worker
*/
protected $consoleDB = null;
public function getName(): string {
return "deletes";
}
public function init(): void
{
}
@ -102,7 +106,7 @@ class DeletesV1 extends Worker
$dbForProject = $this->getProjectDB($projectId);
$dbForProject->deleteCollection($collectionId);
$dbForProject->deleteCollection('collection_' . $collectionId);
$this->deleteByGroup('attributes', [
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])

View file

@ -100,6 +100,10 @@ class FunctionsV1 extends Worker
public array $allowed = [];
public function getName(): string {
return "functions";
}
public function init(): void
{
}

View file

@ -13,6 +13,10 @@ Console::success(APP_NAME . ' mails worker v1 has started' . "\n");
class MailsV1 extends Worker
{
public function getName(): string {
return "mails";
}
public function init(): void
{
}

View file

@ -11,6 +11,10 @@ Console::success(APP_NAME . ' webhooks worker v1 has started');
class WebhooksV1 extends Worker
{
public function getName(): string {
return "webhooks";
}
public function init(): void
{
}

View file

@ -39,6 +39,7 @@
"appwrite/php-runtimes": "0.6.*",
"utopia-php/framework": "0.19.*",
"utopia-php/logger": "0.1.*",
"utopia-php/abuse": "0.7.*",
"utopia-php/analytics": "0.2.*",
"utopia-php/audit": "0.8.*",
@ -53,7 +54,7 @@
"utopia-php/domains": "1.1.*",
"utopia-php/swoole": "0.3.*",
"utopia-php/storage": "0.5.*",
"utopia-php/websocket": "0.0.*",
"utopia-php/websocket": "0.1.0",
"utopia-php/image": "0.5.*",
"resque/php-resque": "1.3.6",

92
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": "2b1ed15e618832ee86b69cff2dcdd6ac",
"content-hash": "e0243d2a276d074c4af4ac21f521c953",
"packages": [
{
"name": "adhocore/jwt",
@ -115,16 +115,16 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.6.1",
"version": "0.6.2",
"source": {
"type": "git",
"url": "https://github.com/appwrite/php-runtimes.git",
"reference": "a42434de2fbd60818244c1a9b2ac0429ad0ef9ee"
"reference": "33e087933f4353b1b3aad8d00e32ef82af18a292"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/php-runtimes/zipball/a42434de2fbd60818244c1a9b2ac0429ad0ef9ee",
"reference": "a42434de2fbd60818244c1a9b2ac0429ad0ef9ee",
"url": "https://api.github.com/repos/appwrite/php-runtimes/zipball/33e087933f4353b1b3aad8d00e32ef82af18a292",
"reference": "33e087933f4353b1b3aad8d00e32ef82af18a292",
"shasum": ""
},
"require": {
@ -164,9 +164,9 @@
],
"support": {
"issues": "https://github.com/appwrite/php-runtimes/issues",
"source": "https://github.com/appwrite/php-runtimes/tree/0.6.1"
"source": "https://github.com/appwrite/php-runtimes/tree/0.6.2"
},
"time": "2021-10-21T11:32:25+00:00"
"time": "2021-12-31T07:16:48+00:00"
},
{
"name": "chillerlan/php-qrcode",
@ -2405,6 +2405,72 @@
},
"time": "2021-07-24T11:35:55+00:00"
},
{
"name": "utopia-php/logger",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/logger.git",
"reference": "a7d626e349e8736e46d4d75f5ba686b40e73c097"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/logger/zipball/a7d626e349e8736e46d4d75f5ba686b40e73c097",
"reference": "a7d626e349e8736e46d4d75f5ba686b40e73c097",
"shasum": ""
},
"require": {
"php": ">=8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"vimeo/psalm": "4.0.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\Logger\\": "src/Logger"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Eldad Fux",
"email": "eldad@appwrite.io"
},
{
"name": "Matej Bačo",
"email": "matej@appwrite.io"
},
{
"name": "Christy Jacob",
"email": "christy@appwrite.io"
}
],
"description": "Utopia Logger library is simple and lite library for logging information, such as errors or warnings. This library is aiming to be as simple and easy to learn and use.",
"keywords": [
"appsignal",
"errors",
"framework",
"logger",
"logging",
"logs",
"php",
"raygun",
"sentry",
"upf",
"utopia",
"warnings"
],
"support": {
"issues": "https://github.com/utopia-php/logger/issues",
"source": "https://github.com/utopia-php/logger/tree/0.1.0"
},
"time": "2021-12-20T06:57:26+00:00"
},
{
"name": "utopia-php/orchestration",
"version": "0.2.1",
@ -2730,16 +2796,16 @@
},
{
"name": "utopia-php/websocket",
"version": "0.0.1",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/websocket.git",
"reference": "808317ef4ea0683c2c82dee5d543b1c8378e2e1b"
"reference": "51fcb86171400d8aa40d76c54593481fd273dab5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/websocket/zipball/808317ef4ea0683c2c82dee5d543b1c8378e2e1b",
"reference": "808317ef4ea0683c2c82dee5d543b1c8378e2e1b",
"url": "https://api.github.com/repos/utopia-php/websocket/zipball/51fcb86171400d8aa40d76c54593481fd273dab5",
"reference": "51fcb86171400d8aa40d76c54593481fd273dab5",
"shasum": ""
},
"require": {
@ -2782,9 +2848,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/websocket/issues",
"source": "https://github.com/utopia-php/websocket/tree/0.0.1"
"source": "https://github.com/utopia-php/websocket/tree/0.1.0"
},
"time": "2021-07-11T13:09:44+00:00"
"time": "2021-12-20T10:50:09+00:00"
},
{
"name": "webmozart/assert",

View file

@ -125,6 +125,8 @@ services:
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_RUNTIMES
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-realtime:
entrypoint: realtime
@ -154,6 +156,7 @@ services:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
# - ./vendor:/usr/src/code/vendor
depends_on:
- redis
environment:
@ -168,6 +171,8 @@ services:
- _APP_DB_USER
- _APP_DB_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-audits:
entrypoint: worker-audits
@ -193,6 +198,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-webhooks:
entrypoint: worker-webhooks
@ -215,6 +222,8 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-deletes:
entrypoint: worker-deletes
@ -244,6 +253,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-database:
entrypoint: worker-database
@ -270,6 +281,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-certificates:
entrypoint: worker-certificates
@ -299,6 +312,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-functions:
entrypoint: worker-functions
@ -339,6 +354,8 @@ services:
- _APP_STATSD_PORT
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-mails:
entrypoint: worker-mails
@ -367,6 +384,8 @@ services:
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-maintenance:
entrypoint: maintenance

14
package-lock.json generated
View file

@ -10,7 +10,7 @@
"license": "BSD-3-Clause",
"dependencies": {
"chart.js": "^3.6.2",
"markdown-it": "^12.2.0",
"markdown-it": "^12.3.0",
"pell": "^1.0.6",
"prismjs": "^1.25.0",
"turndown": "^7.1.1"
@ -2862,9 +2862,9 @@
}
},
"node_modules/markdown-it": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz",
"integrity": "sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==",
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.0.tgz",
"integrity": "sha512-T345UZZ6ejQWTjG6PSEHplzNy5m4kF6zvUpHVDv8Snl/pEU0OxIK0jGg8YLVNwJvT8E0YJC7/2UvssJDk/wQCQ==",
"dependencies": {
"argparse": "^2.0.1",
"entities": "~2.1.0",
@ -7413,9 +7413,9 @@
}
},
"markdown-it": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz",
"integrity": "sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg==",
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.0.tgz",
"integrity": "sha512-T345UZZ6ejQWTjG6PSEHplzNy5m4kF6zvUpHVDv8Snl/pEU0OxIK0jGg8YLVNwJvT8E0YJC7/2UvssJDk/wQCQ==",
"requires": {
"argparse": "^2.0.1",
"entities": "~2.1.0",

View file

@ -18,7 +18,7 @@
},
"dependencies": {
"chart.js": "^3.6.2",
"markdown-it": "^12.2.0",
"markdown-it": "^12.3.0",
"pell": "^1.0.6",
"prismjs": "^1.25.0",
"turndown": "^7.1.1"

View file

@ -72,7 +72,7 @@
const setIdType = function (idType) {
if (idType == "custom") {
element.setAttribute("data-id-type", idType);
info.innerHTML = "Allowed Characters A-Z, a-z, 0-9, and non-leading underscore";
info.innerHTML = "Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot";
if (prevData === 'auto-generated') {
prevData = ""
}

View file

@ -42,6 +42,7 @@ class V11 extends Migration
$this->options = array_map(fn ($option) => $option === 'yes' ? true : false, $this->options);
if (!is_null($cache)) {
$this->cache->flushAll();
$cacheAdapter = new Cache(new RedisCache($this->cache));
$this->dbProject = new Database(new MariaDB($this->db), $cacheAdapter); // namespace is set on execution
$this->dbConsole = new Database(new MariaDB($this->db), $cacheAdapter);
@ -63,6 +64,7 @@ class V11 extends Migration
$oldProject = $this->project;
$this->dbProject->setNamespace('_project_' . $oldProject->getId());
$this->dbConsole->setNamespace('_project_console');
Console::info('');
Console::info('------------------------------------');
@ -73,7 +75,11 @@ class V11 extends Migration
* Create internal/external structure for projects and skip the console project.
*/
if ($oldProject->getId() !== 'console') {
$project = $this->dbConsole->getDocument('projects', $oldProject->getId());
try {
$project = $this->dbConsole->getDocument(collection: 'projects', id: $oldProject->getId());
} catch (\Throwable $th) {
Console::error($th->getTraceAsString());
}
/**
* Migrate Project Document.
@ -88,10 +94,10 @@ class V11 extends Migration
/**
* Create internal tables
*/
if (!$this->dbProject->exists('appwrite')) {
$this->dbProject->create('appwrite');
try {
Console::log('Created internal tables for : ' . $project->getAttribute('name') . ' (' . $project->getId() . ')');
}
$this->dbProject->createMetadata();
} catch (\Throwable $th) { }
/**
* Create Audit tables
@ -195,12 +201,8 @@ class V11 extends Migration
$this->dbProject->createDocument($new->getCollection(), $new);
}
} catch (\Throwable $th) {
Console::error('Failed to update document: ' . $th->getMessage());
Console::error("Failed to migrate document ({$new->getId()}) from collection ({$new->getCollection()}): " . $th->getMessage());
continue;
if ($document && $new->getId() !== $document->getId()) {
throw new Exception('Duplication Error');
}
}
}
@ -270,6 +272,7 @@ class V11 extends Migration
'dateCreated' => time(),
'dateUpdated' => time(),
'name' => $name,
'enabled' => true,
'search' => implode(' ', [$id, $name]),
]));
} else {
@ -284,7 +287,7 @@ class V11 extends Migration
foreach ($attributes as $attribute) {
try {
$this->dbProject->createAttribute(
collection: $attribute['$collection'],
collection: 'collection_' . $attribute['$collection'],
id: $attribute['$id'],
type: $attribute['type'],
size: $attribute['size'],
@ -296,7 +299,6 @@ class V11 extends Migration
formatOptions: $attribute['formatOptions'] ?? [],
filters: $attribute['filters']
);
$this->dbProject->createDocument('attributes', new Document([
'$id' => $attribute['$collection'] . '_' . $attribute['$id'],
'key' => $attribute['$id'],
@ -391,7 +393,13 @@ class V11 extends Migration
}, $document);
$document = new Document($document->getArrayCopy());
$document = $this->migratePermissions($document);
$this->dbProject->createDocument('collection_' . $collection, $document);
try {
$this->dbProject->createDocument('collection_' . $collection, $document);
} catch (\Throwable $th) {
Console::error("Failed to migrate document ({$document->getId()}): " . $th->getMessage());
continue;
}
}
$offset += $this->limit;
}
@ -426,7 +434,30 @@ class V11 extends Migration
}
switch ($document->getAttribute('$collection')) {
case OldDatabase::SYSTEM_COLLECTION_PLATFORMS:
case OldDatabase::SYSTEM_COLLECTION_PROJECTS:
$newProviders = [];
$providers = Config::getParam('providers', []);
/*
* Add enabled OAuth2 providers to default data rules
*/
foreach ($providers as $index => $provider) {
$appId = $document->getAttribute('usersOauth2'.\ucfirst($index).'Appid');
$appSecret = $document->getAttribute('usersOauth2'.\ucfirst($index).'Secret');
if (!is_null($appId) || !is_null($appId)) {
$newProviders[$appId] = $appSecret;
}
$document
->removeAttribute('usersOauth2'.\ucfirst($index).'Appid')
->removeAttribute('usersOauth2'.\ucfirst($index).'Secret');
}
$document->setAttribute('providers', $newProviders);
break;
case OldDatabase::SYSTEM_COLLECTION_PLATFORMS:
$projectId = $this->getProjectIdFromReadPermissions($document);
/**
@ -478,6 +509,29 @@ class V11 extends Migration
break;
case OldDatabase::SYSTEM_COLLECTION_KEYS:
$projectId = $this->getProjectIdFromReadPermissions($document);
/**
* Set Project ID
*/
if ($document->getAttribute('projectId') === null) {
$document->setAttribute('projectId', $projectId);
}
/**
* Set scopes if empty
*/
if (empty($document->getAttribute('scopes', []))) {
$document->setAttribute('scopes', []);
}
/**
* Reset Permissions
*/
$document->setAttribute('$read', ['role:all']);
$document->setAttribute('$write', ['role:all']);
break;
case OldDatabase::SYSTEM_COLLECTION_WEBHOOKS:
$projectId = $this->getProjectIdFromReadPermissions($document);
@ -533,6 +587,19 @@ class V11 extends Migration
$write = $document->getWrite();
$document->setAttribute('$write', str_replace('user:{self}', "user:{$document->getId()}", $write));
break;
case OldDatabase::SYSTEM_COLLECTION_TEAMS:
/**
* Replace team:{self} with team:TEAM_ID
*/
$read = $document->getWrite();
$write = $document->getWrite();
$document->setAttribute('$read', str_replace('team:{self}', "team:{$document->getId()}", $read));
$document->setAttribute('$write', str_replace('team:{self}', "team:{$document->getId()}", $write));
break;
case OldDatabase::SYSTEM_COLLECTION_FILES:
/**
@ -634,6 +701,10 @@ class V11 extends Migration
$size = $type === Database::VAR_STRING ? 65_535 : 0; // Max size of text in MariaDB
if ($required) {
$default = null;
}
$attributes[$key] = [
'$collection' => $collectionId,
'$id' => $id,

View file

@ -9,15 +9,66 @@ use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Adapter\MariaDB;
use Exception;
abstract class Worker
{
/**
* Callbacks that will be executed when an error occurs
*
* @var array
*/
static protected array $errorCallbacks = [];
/**
* Associative array holding all information passed into the worker
*
* @return array
*/
public array $args = [];
abstract public function init(): void;
/**
* Function for identifying the worker needs to be set to unique name
*
* @return string
* @throws Exception
*/
public function getName(): string
{
throw new Exception("Please implement getName method in worker");
}
abstract public function run(): void;
/**
* Function executed before running first task.
* Can include any preparations, such as connecting to external services or loading files
*
* @return void
* @throws \Exception|\Throwable
*/
public function init() {
throw new Exception("Please implement getName method in worker");
}
abstract public function shutdown(): void;
/**
* Function executed when new task requests is received.
* You can access $args here, it will contain event information
*
* @return void
* @throws \Exception|\Throwable
*/
public function run() {
throw new Exception("Please implement getName method in worker");
}
/**
* Function executed just before shutting down the worker.
* You can do cleanup here, such as disconnecting from services or removing temp files
*
* @return void
* @throws \Exception|\Throwable
*/
public function shutdown() {
throw new Exception("Please implement getName method in worker");
}
const MAX_ATTEMPTS = 10;
const SLEEP_TIME = 2;
@ -25,19 +76,73 @@ abstract class Worker
const DATABASE_PROJECT = 'project';
const DATABASE_CONSOLE = 'console';
/**
* A wrapper around 'init' function with non-worker-specific code
*
* @return void
* @throws \Exception|\Throwable
*/
public function setUp(): void
{
$this->init();
try {
$this->init();
} catch(\Throwable $error) {
foreach (self::$errorCallbacks as $errorCallback) {
$errorCallback($error, "init", $this->getName());
}
throw $error;
}
}
/**
* A wrapper around 'run' function with non-worker-specific code
*
* @return void
* @throws \Exception|\Throwable
*/
public function perform(): void
{
$this->run();
try {
$this->run();
} catch(\Throwable $error) {
foreach (self::$errorCallbacks as $errorCallback) {
$errorCallback($error, "run", $this->getName(), $this->args);
}
throw $error;
}
}
/**
* A wrapper around 'shutdown' function with non-worker-specific code
*
* @return void
* @throws \Exception|\Throwable
*/
public function tearDown(): void
{
$this->shutdown();
try {
$this->shutdown();
} catch(\Throwable $error) {
foreach (self::$errorCallbacks as $errorCallback) {
$errorCallback($error, "shutdown", $this->getName());
}
throw $error;
}
}
/**
* Register callback. Will be executed when error occurs.
* @param callable $callback
* @param Throwable $error
* @return self
*/
public static function error(callable $callback): void
{
\array_push(self::$errorCallbacks, $callback);
}
/**
* Get internal project database

View file

@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use Utopia\Database\Document;
class User extends Model
{
@ -61,6 +62,24 @@ class User extends Model
;
}
/**
* Get Collection
*
* @return string
*/
public function filter(Document $document): Document
{
$prefs = $document->getAttribute('prefs');
if($prefs instanceof Document) {
$prefs = $prefs->getArrayCopy();
}
if(is_array($prefs) && empty($prefs)) {
$document->setAttribute('prefs', new \stdClass);
}
return $document;
}
/**
* Get Name
*

View file

@ -151,10 +151,11 @@ class Client
* @param string $path
* @param array $params
* @param array $headers
* @param bool $decode
* @return array|string
* @throws Exception
*/
public function call(string $method, string $path = '', array $headers = [], array $params = [])
public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true)
{
$headers = array_merge($this->headers, $headers);
$ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
@ -216,17 +217,19 @@ class Client
$responseType = $responseHeaders['content-type'] ?? '';
$responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
switch (substr($responseType, 0, strpos($responseType, ';'))) {
case 'application/json':
$json = json_decode($responseBody, true);
if ($json === null) {
throw new Exception('Failed to parse response: '.$responseBody);
}
$responseBody = $json;
$json = null;
break;
if($decode) {
switch (substr($responseType, 0, strpos($responseType, ';'))) {
case 'application/json':
$json = json_decode($responseBody, true);
if ($json === null) {
throw new Exception('Failed to parse response: '.$responseBody);
}
$responseBody = $json;
$json = null;
break;
}
}
if ((curl_errno($ch)/* || 200 != $responseStatus*/)) {

View file

@ -1145,6 +1145,17 @@ trait DatabaseBase
$this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
$this->assertCount(1, $documents['body']['documents']);
$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()), [
'queries' => ['$id.equal("' . $documents['body']['documents'][0]['$id'] . '")'],
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']);
$this->assertCount(1, $documents['body']['documents']);
$documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@ -1201,6 +1212,21 @@ trait DatabaseBase
$this->assertEquals(400, $documents['headers']['status-code']);
$this->assertEquals('Index not found: actors', $documents['body']['message']);
$conditions = [];
for ($i=0; $i < 101; $i++) {
$conditions[] = $i;
}
$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()), [
'queries' => ['releaseYear.equal(' . implode(',', $conditions) . ')'],
]);
$this->assertEquals(400, $documents['headers']['status-code']);
return [];
}

View file

@ -20,13 +20,20 @@ trait UsersBase
'email' => 'cristiano.ronaldo@manchester-united.co.uk',
'password' => 'password',
'name' => 'Cristiano Ronaldo',
]);
], false);
// Test empty prefs is object not array
$bodyString = $user['body'];
$prefs = substr($bodyString, strpos($bodyString, '"prefs":')+8,2);
$this->assertEquals('{}', $prefs);
$body = json_decode($bodyString, true);
$this->assertEquals($user['headers']['status-code'], 201);
$this->assertEquals($user['body']['name'], 'Cristiano Ronaldo');
$this->assertEquals($user['body']['email'], 'cristiano.ronaldo@manchester-united.co.uk');
$this->assertEquals($user['body']['status'], true);
$this->assertGreaterThan(0, $user['body']['registration']);
$this->assertEquals($body['name'], 'Cristiano Ronaldo');
$this->assertEquals($body['email'], 'cristiano.ronaldo@manchester-united.co.uk');
$this->assertEquals($body['status'], true);
$this->assertGreaterThan(0, $body['registration']);
/**
* Test Create with Custom ID for SUCCESS
@ -48,7 +55,7 @@ trait UsersBase
$this->assertEquals(true, $res['body']['status']);
$this->assertGreaterThan(0, $res['body']['registration']);
return ['userId' => $user['body']['$id']];
return ['userId' => $body['$id']];
}
/**