1
0
Fork 0
mirror of synced 2024-05-15 10:12:40 +12:00

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

This commit is contained in:
Matej Baco 2022-01-04 15:29:42 +01:00
commit e2aebdd2bc
280 changed files with 813 additions and 391 deletions

View file

@ -28,7 +28,7 @@ jobs:
docker pull php:8.0-cli-alpine
docker compose build --progress=plain
docker compose up -d
sleep 10
sleep 30
- name: Doctor
run: docker compose exec -T appwrite doctor

View file

@ -2,11 +2,12 @@
## Features
- Completely rewritten Database service:
- Completely rewritten Database service: **Breaking Change**
- Collection rules are now attributes
- Filters for have been replaced with a new, more powerful syntax
- Custom indexes for more performant queries
- Enum Attributes
- Maximum `sum` returned does not exceed 5000 documents anymore **Breaking Change**
- **DEPRECATED** Nested documents has been removed
- **DEPRECATED** Wildcard rule has been removed
- You can now set custom IDs when creating following resources:
@ -17,10 +18,14 @@
- File
- Collection
- Document
- Wildcard permissions `*` are now `role:all`
- All resources with custom ID support required you to set an ID now
- Passing `unique()` will generate a unique ID
- Auto-generated ID's are now 20 characters long
- Wildcard permissions `*` are now `role:all` **Breaking Change**
- Collections can be enabled and disabled
- Permissions are now found as top-level keys `$read` and `$write` instead of nested under `$permissions`
- Accessing collections with insufficient permissions now return a `401` isntead of `404` status code
- Offset cannot be higher than 5000 now and cursor pagination is required
- Added Cursor pagination to all endpoints that provide pagination by offset
- Added new Usage worker to aggregate usage statistics
- Added new Database worker to handle heavy database tasks in the background
@ -38,22 +43,24 @@
- Teams
- Users
- Functions
- Added Flutter Desktop Support
- Fixed several memory leaks in the Console
- Added pagination to account activities in the Console
- Added following events from User service to Webhooks and Functions:
- `users.update.email`
- `users.update.name`
- `users.update.password`
- Added new environment variables to enable error logging:
- The `_APP_LOGGING_PROVIDER` variable allows you to enable the logger set the value to one of `sentry`, `raygun`, `appsignal`.
- The `_APP_LOGGING_CONFIG` 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.
- Added new environment variable `_APP_USAGE_AGGREGATION_INTERVAL` to configure the usage worker interval
- Added negative rotation values to file preview endpoint
- Multiple HealthAPI response models were changed to new (better) schema
- Multiple responses from the Health service were changed to new (better) schema **Breaking Change**
- Method `health.getAntiVirus()` has been renamed to `health.getAntivirus()`
- Added following langauges to the Locale service:
- Latin
- Sindhi
- Telugu
- **DEPRECATED** Tasks service
- **DEPRECATED** Tasks service **Breaking Change**
## Bugs
- Fixed `/v1/avatars/initials` when no space in the name, will try to split by `_`

View file

@ -15,7 +15,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '5.0.0',
'version' => '6.0.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@ -63,7 +63,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '2.1.0',
'version' => '3.0.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@ -81,7 +81,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '0.1.1',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@ -116,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '0.2.1',
'version' => '0.3.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@ -190,7 +190,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '3.0.0',
'version' => '4.0.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@ -208,7 +208,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '1.0.0',
'version' => '2.0.0',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@ -226,7 +226,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '2.3.2',
'version' => '3.0.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@ -244,7 +244,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '0.5.1',
'version' => '0.6.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@ -262,7 +262,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '2.4.1',
'version' => '3.0.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@ -316,7 +316,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.3.0',
'version' => '0.4.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => false,
@ -334,7 +334,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '2.0.0',
'version' => '3.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@ -352,7 +352,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '0.12.1',
'version' => '0.13.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://github.com/appwrite/sdk-for-cli',
'enabled' => true,
@ -370,7 +370,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '0.1.1',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@ -392,7 +392,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '0.1.0',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1740,7 +1740,7 @@ App::post('/v1/account/recovery')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->label('abuse-key', ['url:{url},email:{param-email}', 'ip:{ip}'])
->param('email', '', new Email(), 'User email.')
->param('url', '', function ($clients) {return new Host($clients);}, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients'])
->inject('request')

View file

@ -111,7 +111,7 @@ function createAttribute(string $collectionId, Document $attribute, Response $re
}
$dbForProject->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedCollection($collectionId);
$dbForProject->deleteCachedCollection('collection_' . $collectionId);
// Pass clone of $attribute object to workers
// so we can later modify Document to fit response model
@ -1257,7 +1257,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:key')
}
$dbForProject->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedCollection($collectionId);
$dbForProject->deleteCachedCollection('collection_' . $collectionId);
$database
->setParam('type', DATABASE_TYPE_DELETE_ATTRIBUTE)
@ -2076,13 +2076,15 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
->inject('dbForProject')
->inject('events')
->inject('audits')
->inject('deletes')
->inject('usage')
->inject('mode')
->action(function ($collectionId, $documentId, $response, $dbForProject, $events, $audits, $usage, $mode) {
->action(function ($collectionId, $documentId, $response, $dbForProject, $events, $audits, $deletes, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $deletes */
/** @var Appwrite\Stats\Stats $usage */
/** @var string $mode */
@ -2129,6 +2131,11 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
*/
$document->setAttribute('$collection', $collectionId);
$deletes
->setParam('type', DELETE_TYPE_AUDIT)
->setParam('document', $document)
;
$usage
->setParam('database.documents.delete', 1)
->setParam('collectionId', $collectionId)

View file

@ -341,8 +341,7 @@ App::get('/v1/health/anti-virus')
$output['version'] = @$antivirus->version();
$output['status'] = (@$antivirus->ping()) ? 'pass' : 'fail';
} catch( \Exception $e) {
$output['status'] = 'offline';
$output['version'] = '';
throw new Exception('Antivirus is not available', 500);
}
}

View file

@ -38,41 +38,51 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
/*
* Abuse Check
*/
$timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname().$route->getPath())
;
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
// TODO make sure we get array here
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if(!empty($value)) {
$timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value);
}
foreach ($abuseKeyLabel as $abuseKey) {
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname().$route->getPath());
$timeLimitArray[] = $timeLimit;
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit()) {
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
;
}
$closestLimit = null;
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (($abuse->check() // Route is rate-limited
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if(!empty($value)) {
$timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
$closestLimit = $timeLimit->remaining();
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
;
}
if (($abuse->check() // Route is rate-limited
&& App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not disabled
&& (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key
{
throw new Exception('Too many requests', 429);
throw new Exception('Too many requests', 429);
}
}
/*

View file

@ -14,7 +14,6 @@ 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;
@ -110,6 +109,7 @@ function getDatabase(Registry &$register, string $namespace)
};
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
sleep(5); // wait for the initial database schema to be ready
Console::success('Server started succefully');
/**
@ -138,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, $logError) {
Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
/** @var Document $statsDocument */
foreach ($stats as $projectId => $value) {
$connections = $stats->get($projectId, 'connections') ?? 0;
@ -398,7 +398,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$abuse = new Abuse($timeLimit);
if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
throw new Exception('Too many requests', 1013);
}

View file

@ -217,7 +217,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
git fetch && \
git pull ' . $gitUrl . ' && \
rm -rf ' . $target . '/* && \
cp -r ' . $result . '/ ' . $target . '/ && \
cp -r ' . $result . '/* ' . $target . '/ && \
git add . && \
git commit -m "' . $message . '" && \
git push -u origin ' . $gitBranch . '
@ -231,7 +231,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$docDirectories = $language['docDirectories'] ?? [''];
if($version === 'latest') {
if ($version === 'latest') {
continue;
}

View file

@ -24,12 +24,11 @@ $cli
$appRoutes = App::getRoutes();
$response = new Response(new HttpResponse());
$mocks = ($mode === 'mocks');
App::setResource('request', fn() => new Request);
App::setResource('response', fn() => $response);
App::setResource('db', fn() => $db);
App::setResource('cache', fn() => $redis);
App::setResource('request', fn () => new Request);
App::setResource('response', fn () => $response);
App::setResource('db', fn () => $db);
App::setResource('cache', fn () => $redis);
$platforms = [
'client' => APP_PLATFORM_CLIENT,
@ -132,10 +131,11 @@ $cli
$services = [];
foreach ($appRoutes as $key => $method) {
foreach ($method as $route) { /** @var \Utopia\Route $route */
foreach ($method as $route) {
/** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
@ -152,115 +152,116 @@ $cli
break;
}
}
if(empty($routeSecurity)) {
if (empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
if ($route->getLabel('sdk.mock', false) && !$mocks) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $mocks) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
continue;
}
$routes[] = $route;
$modelLabel = $route->getLabel('sdk.response.model', 'none');
\is_array($modelLabel) ? \array_map(function($m) use($response) {
\is_array($modelLabel) ? \array_map(function ($m) use ($response) {
return $response->getModel($m);
}, $modelLabel) : $response->getModel($modelLabel);
}
}
foreach (Config::getParam('services', []) as $service) {
if(!isset($service['docs']) // Skip service if not part of the public API
if (
!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']) {
|| !$service['sdk']
) {
continue;
}
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
];
}
$models = $response->getModels();
foreach ($models as $key => $value) {
if($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
}
}
switch ($format) {
case 'swagger2':
$formatInstance = new Swagger2(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
case 'open-api3':
$formatInstance = new OpenAPI3(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
default:
throw new Exception('Format not found: '.$format);
throw new Exception('Format not found: ' . $format);
break;
}
$specs = new Specification($formatInstance);
$endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]');
$email = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$formatInstance
->setParam('name', APP_NAME)
->setParam('description', 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)')
->setParam('endpoint', 'https://HOSTNAME/v1')
->setParam('version', APP_VERSION_STABLE)
->setParam('terms', $endpoint.'/policy/terms')
->setParam('terms', $endpoint . '/policy/terms')
->setParam('support.email', $email)
->setParam('support.url', $endpoint.'/support')
->setParam('contact.name', APP_NAME.' Team')
->setParam('support.url', $endpoint . '/support')
->setParam('contact.name', APP_NAME . ' Team')
->setParam('contact.email', $email)
->setParam('contact.url', $endpoint.'/support')
->setParam('contact.url', $endpoint . '/support')
->setParam('license.name', 'BSD-3-Clause')
->setParam('license.url', 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE')
->setParam('docs.description', 'Full API docs, specs and tutorials')
->setParam('docs.url', $endpoint.'/docs')
;
->setParam('docs.url', $endpoint . '/docs');
if($mocks) {
$path = __DIR__.'/../config/specs/'.$format.'-mocks-'.$platform.'.json';
if ($mocks) {
$path = __DIR__ . '/../config/specs/' . $format . '-mocks-' . $platform . '.json';
if(!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save mocks spec file: '.$path);
if (!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save mocks spec file: ' . $path);
}
Console::success('Saved mocks spec file: ' . realpath($path));
continue;
}
$path = __DIR__.'/../config/specs/'.$format.'-'.$version.'-'.$platform.'.json';
$path = __DIR__ . '/../config/specs/' . $format . '-' . $version . '-' . $platform . '.json';
if(!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save spec file: '.$path);
if (!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save spec file: ' . $path);
}
Console::success('Saved spec file: ' . realpath($path));
}
}
});
});

View file

@ -47,7 +47,7 @@ $logs = $this->getParam('logs', null);
data-param-order-types-cast-to="array"
data-scope="sdk"
data-name="project-documents"
x-data="{project: new URLSearchParams(location.search).get('project')}">
x-data="{ project: new URLSearchParams(location.search).get('project') }">
<div data-ls-if="0 == {{project-documents.sum}}" class="box margin-bottom">
<h3 class="margin-bottom-small text-bold">No Documents Found</h3>
@ -101,7 +101,6 @@ $logs = $this->getParam('logs', null);
data-service="database.listDocuments"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-types="DESC"
data-param-order-types-cast-to="array"
@ -118,7 +117,6 @@ $logs = $this->getParam('logs', null);
data-service="database.listDocuments"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-types="DESC"
data-param-order-types-cast-to="array"
@ -355,6 +353,7 @@ $logs = $this->getParam('logs', null);
<span data-ls-if="{{index.status}} == 'processing'" class="text-size-small text-info">processing&nbsp;</span>
<span data-ls-if="{{index.status}} == 'failed'" class="text-size-small text-danger">failed&nbsp;</span>
<span data-ls-if="{{index.status}} == 'deleting'" class="text-size-small text-danger">deleting&nbsp;</span>
<span data-ls-if="{{index.status}} == 'stuck'" class="text-size-small text-danger">stuck&nbsp;</span>
</td>
<td data-title="Index Key: ">

View file

@ -210,6 +210,7 @@ class DatabaseV1 extends Worker
}
$dbForProject->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedCollection('collection_' . $collectionId);
}
/**

View file

@ -67,7 +67,17 @@ class DeletesV1 extends Worker
break;
case DELETE_TYPE_AUDIT:
$this->deleteAuditLogs($this->args['timestamp']);
$timestamp = $this->args['timestamp'] ?? 0;
$document = new Document($this->args['document'] ?? []);
if (!empty($timestamp)) {
$this->deleteAuditLogs($this->args['timestamp']);
}
if (!$document->isEmpty()) {
$this->deleteAuditLogsByResource('document/' . $document->getId(), $projectId);
}
break;
case DELETE_TYPE_ABUSE:
@ -115,6 +125,8 @@ class DeletesV1 extends Worker
$this->deleteByGroup('indexes', [
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
], $dbForProject);
$this->deleteAuditLogsByResource('collection/' . $collectionId, $projectId);
}
/**
@ -263,6 +275,18 @@ class DeletesV1 extends Worker
});
}
/**
* @param int $timestamp
*/
protected function deleteAuditLogsByResource(string $resource, string $projectId): void
{
$dbForProject = $this->getProjectDB($projectId);
$this->deleteByGroup(Audit::COLLECTION, [
new Query('resource', Query::TYPE_EQUAL, [$resource])
], $dbForProject);
}
/**
* @param Document $document function document
* @param string $projectId

View file

@ -73,7 +73,7 @@
}
],
"require-dev": {
"appwrite/sdk-generator": "0.16.3",
"appwrite/sdk-generator": "0.17.0",
"phpunit/phpunit": "9.5.10",
"swoole/ide-helper": "4.8.3",
"textalk/websocket": "1.5.5",

View file

@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Account account = new Account(client);
account.createMagicURLSession(
"",
"[USER_ID]",
"email@example.com",
new Continuation<Object>() {
@NotNull

View file

@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Account account = new Account(client);
account.create(
"",
"[USER_ID]",
"email@example.com",
"password",
new Continuation<Object>() {

View file

@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Account account = new Account(client);
account.updateMagicURLSession(
"",
"[USER_ID]",
"[SECRET]"
new Continuation<Object>() {
@NotNull

View file

@ -20,7 +20,7 @@ public class MainActivity extends AppCompatActivity {
database.createDocument(
"[COLLECTION_ID]",
"",
"[DOCUMENT_ID]",
mapOf( "a" to "b" ),
new Continuation<Object>() {
@NotNull

View file

@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Storage storage = new Storage(client);
storage.createFile(
"",
"[FILE_ID]",
File("./path-to-files/image.jpg"),
new Continuation<Object>() {
@NotNull

View file

@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Teams teams = new Teams(client);
teams.create(
"",
"[TEAM_ID]",
"[NAME]",
new Continuation<Object>() {
@NotNull

View file

@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = account.createMagicURLSession(
userId = "",
userId = "[USER_ID]",
email = "email@example.com",
)
val json = response.body?.string()

View file

@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = account.create(
userId = "",
userId = "[USER_ID]",
email = "email@example.com",
password = "password",
)

View file

@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = account.updateMagicURLSession(
userId = "",
userId = "[USER_ID]",
secret = "[SECRET]"
)
val json = response.body?.string()

View file

@ -19,7 +19,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = database.createDocument(
collectionId = "[COLLECTION_ID]",
documentId = "",
documentId = "[DOCUMENT_ID]",
data = mapOf( "a" to "b" ),
)
val json = response.body?.string()

View file

@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = storage.createFile(
fileId = "",
fileId = "[FILE_ID]",
file = File("./path-to-files/image.jpg"),
)
val json = response.body?.string()

View file

@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = teams.create(
teamId = "",
teamId = "[TEAM_ID]",
name = "[NAME]",
)
val json = response.body?.string()

View file

@ -7,7 +7,7 @@ func main() {
let account = Account(client)
account.createMagicURLSession(
userId: "",
userId: "[USER_ID]",
email: "email@example.com"
) { result in
switch result {

View file

@ -7,7 +7,7 @@ func main() {
let account = Account(client)
account.create(
userId: "",
userId: "[USER_ID]",
email: "email@example.com",
password: "password"
) { result in

View file

@ -7,7 +7,7 @@ func main() {
let account = Account(client)
account.updateMagicURLSession(
userId: "",
userId: "[USER_ID]",
secret: "[SECRET]"
) { result in
switch result {

View file

@ -8,7 +8,7 @@ func main() {
let database = Database(client)
database.createDocument(
collectionId: "[COLLECTION_ID]",
documentId: "",
documentId: "[DOCUMENT_ID]",
data:
) { result in
switch result {

View file

@ -7,7 +7,7 @@ func main() {
let storage = Storage(client)
storage.createFile(
fileId: "",
fileId: "[FILE_ID]",
file: File(name: "image.jpg", buffer: yourByteBuffer)
) { result in
switch result {

View file

@ -7,7 +7,7 @@ func main() {
let teams = Teams(client)
teams.create(
teamId: "",
teamId: "[TEAM_ID]",
name: "[NAME]"
) { result in
switch result {

View file

@ -9,7 +9,7 @@ void main() { // Init SDK
.setProject('5df5acd0d48c2') // Your project ID
;
Future result = account.createMagicURLSession(
userId: '',
userId: '[USER_ID]',
email: 'email@example.com',
);

View file

@ -9,7 +9,7 @@ void main() { // Init SDK
.setProject('5df5acd0d48c2') // Your project ID
;
Future result = account.create(
userId: '',
userId: '[USER_ID]',
email: 'email@example.com',
password: 'password',
);

View file

@ -9,7 +9,7 @@ void main() { // Init SDK
.setProject('5df5acd0d48c2') // Your project ID
;
Future result = account.updateMagicURLSession(
userId: '',
userId: '[USER_ID]',
secret: '[SECRET]',
);

View file

@ -10,7 +10,7 @@ void main() { // Init SDK
;
Future result = database.createDocument(
collectionId: '[COLLECTION_ID]',
documentId: '',
documentId: '[DOCUMENT_ID]',
data: {},
);

View file

@ -10,7 +10,7 @@ void main() { // Init SDK
.setProject('5df5acd0d48c2') // Your project ID
;
Future result = storage.createFile(
fileId: '',
fileId: '[FILE_ID]',
file: await MultipartFile.fromPath('file', './path-to-files/image.jpg', 'image.jpg'),
);

View file

@ -9,7 +9,7 @@ void main() { // Init SDK
.setProject('5df5acd0d48c2') // Your project ID
;
Future result = teams.create(
teamId: '',
teamId: '[TEAM_ID]',
name: '[NAME]',
);

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.account.createMagicURLSession('', 'email@example.com');
let promise = sdk.account.createMagicURLSession('[USER_ID]', 'email@example.com');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.account.create('', 'email@example.com', 'password');
let promise = sdk.account.create('[USER_ID]', 'email@example.com', 'password');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.account.updateMagicURLSession('', '[SECRET]');
let promise = sdk.account.updateMagicURLSession('[USER_ID]', '[SECRET]');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.createDocument('[COLLECTION_ID]', '', {});
let promise = sdk.database.createDocument('[COLLECTION_ID]', '[DOCUMENT_ID]', {});
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.storage.createFile('', document.getElementById('uploader').files[0]);
let promise = sdk.storage.createFile('[FILE_ID]', document.getElementById('uploader').files[0]);
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.teams.create('', '[NAME]');
let promise = sdk.teams.create('[TEAM_ID]', '[NAME]');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.account.createMagicURLSession('', 'email@example.com');
let promise = sdk.account.createMagicURLSession('[USER_ID]', 'email@example.com');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.account.create('', 'email@example.com', 'password');
let promise = sdk.account.create('[USER_ID]', 'email@example.com', 'password');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.account.updateMagicURLSession('', '[SECRET]');
let promise = sdk.account.updateMagicURLSession('[USER_ID]', '[SECRET]');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.createCollection('', '[NAME]', 'document', ["role:all"], ["role:all"]);
let promise = sdk.database.createCollection('[COLLECTION_ID]', '[NAME]', 'document', ["role:all"], ["role:all"]);
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.createDocument('[COLLECTION_ID]', '', {});
let promise = sdk.database.createDocument('[COLLECTION_ID]', '[DOCUMENT_ID]', {});
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.functions.create('', '[NAME]', [], 'dotnet-5.0');
let promise = sdk.functions.create('[FUNCTION_ID]', '[NAME]', [], 'node-14.5');
promise.then(function (response) {
console.log(response); // Success

View file

@ -0,0 +1,14 @@
const sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.health.getAntivirus();
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.projects.create('', '[NAME]', '[TEAM_ID]');
let promise = sdk.projects.create('[PROJECT_ID]', '[NAME]', '[TEAM_ID]');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.storage.createFile('', document.getElementById('uploader').files[0]);
let promise = sdk.storage.createFile('[FILE_ID]', document.getElementById('uploader').files[0]);
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.teams.create('', '[NAME]');
let promise = sdk.teams.create('[TEAM_ID]', '[NAME]');
promise.then(function (response) {
console.log(response); // Success

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.users.create('', 'email@example.com', 'password');
let promise = sdk.users.create('[USER_ID]', 'email@example.com', 'password');
promise.then(function (response) {
console.log(response); // Success

View file

@ -1,6 +1,6 @@
appwrite database createBooleanAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId="" \
--key="" \
--required="" \
--default="" \
--array=""

View file

@ -1,5 +1,5 @@
appwrite database createCollection \
--collectionId="" \
--collectionId="[COLLECTION_ID]" \
--name="[NAME]" \
--permission="document" \
--read="[&quot;role:all&quot;]" \

View file

@ -1,6 +1,6 @@
appwrite database createDocument \
--collectionId="[COLLECTION_ID]" \
--documentId="" \
--documentId="[DOCUMENT_ID]" \
--data="{}" \
--read="[&quot;role:all&quot;]" \
--write="[&quot;role:all&quot;]"

View file

@ -1,6 +1,6 @@
appwrite database createEmailAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId="" \
--key="" \
--required="" \
--default="email@example.com" \
--array=""

View file

@ -1,6 +1,6 @@
appwrite database createEnumAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId="" \
--key="" \
--elements="" \
--required="" \
--default="[DEFAULT]" \

View file

@ -1,6 +1,6 @@
appwrite database createFloatAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId="" \
--key="" \
--required="" \
--min="" \
--max="" \

View file

@ -1,6 +1,6 @@
appwrite database createIndex \
--collectionId="[COLLECTION_ID]" \
--indexId="" \
--key="" \
--type="key" \
--attributes="" \
--orders=""

View file

@ -1,6 +1,6 @@
appwrite database createIntegerAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId="" \
--key="" \
--required="" \
--min="" \
--max="" \

View file

@ -1,6 +1,6 @@
appwrite database createIpAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId="" \
--key="" \
--required="" \
--default="" \
--array=""

View file

@ -1,6 +1,6 @@
appwrite database createStringAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId="" \
--key="" \
--size="1" \
--required="" \
--default="[DEFAULT]" \

View file

@ -1,6 +1,6 @@
appwrite database createUrlAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId="" \
--key="" \
--required="" \
--default="https://example.com" \
--array=""

View file

@ -1,3 +1,3 @@
appwrite database deleteAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId=""
--key=""

View file

@ -1,3 +1,3 @@
appwrite database deleteIndex \
--collectionId="[COLLECTION_ID]" \
--indexId=""
--key=""

View file

@ -1,3 +1,3 @@
appwrite database getAttribute \
--collectionId="[COLLECTION_ID]" \
--attributeId=""
--key=""

View file

@ -1,3 +1,3 @@
appwrite database getIndex \
--collectionId="[COLLECTION_ID]" \
--indexId=""
--key=""

Some files were not shown because too many files have changed in this diff Show more