diff --git a/README-CN.md b/README-CN.md
new file mode 100644
index 0000000000..5c4ce54433
--- /dev/null
+++ b/README-CN.md
@@ -0,0 +1,171 @@
+
+
+
+
+
+ 适用于[Flutter/Vue/Angular/React/iOS/Android/* 等等平台 *]的完整后端服务
+
+
+
+
+
+[](https://appwrite.io/discord?r=Github)
+[](https://hub.docker.com/r/appwrite/appwrite)
+[](https://travis-ci.com/appwrite/appwrite)
+[](https://twitter.com/appwrite)
+[](docs/tutorials/add-translations.md)
+[](https://store.appwrite.io)
+
+[English](README.md) | 简体中文
+
+Appwrite是一个基于dcoker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
+
+Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存, 图像处理,云函数计算,[等多种服务](https:/ /appwrite.io/docs)。
+
+
+
+更多信息请到 Appwrite 官网查看: [https://appwrite.io](https://appwrite.io)
+
+内容:
+
+- [安装](#安装)
+ - [Unix](#unix)
+ - [Windows](#windows)
+ - [CMD](#cmd)
+ - [PowerShell](#powershell)
+ - [从旧版本升级](#从旧版本升级)
+- [快速入门](#入门)
+ - [软件服务](#软件服务)
+ - [开发套件](#开发套件)
+ - [客户端](#客户端)
+ - [服务器](#服务器)
+ - [开发者社区](#开发者社区)
+- [软件架构]](#软件架构)
+- [贡献代码](#贡献代码)
+- [安全](#安全)
+- [订阅我们](#订阅我们)
+- [版权说明](#版权说明)
+
+## 安装
+
+Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
+
+开始运行 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
+
+### Unix
+
+```bash
+docker run -it --rm \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
+ --entrypoint="install" \
+ appwrite/appwrite:0.12.1
+```
+
+### Windows
+
+#### CMD
+
+```cmd
+docker run -it --rm ^
+ --volume //var/run/docker.sock:/var/run/docker.sock ^
+ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
+ --entrypoint="install" ^
+ appwrite/appwrite:0.12.1
+```
+
+#### PowerShell
+
+```powershell
+docker run -it --rm ,
+ --volume /var/run/docker.sock:/var/run/docker.sock ,
+ --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
+ --entrypoint="install" ,
+ appwrite/appwrite:0.12.1
+```
+
+运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+
+
+需要自定义容器构架,请查看我们的 Docker [环境变量](https://appwrite.io/docs/environment-variables) 文档。您还可以参考我们的 [docker-compose.yml](https://gist.github.com/eldadfux/977869ff6bdd7312adfd4e629ee15cc5#file-docker-compose-yml) 文件手动设置环境。
+
+### 从旧版本升级
+
+如果您从旧版本升级 Appwrite 服务器,则应在设置完成后使用 Appwrite 迁移工具。有关这方面的更多信息,请查看 [安装文档](https://appwrite.io/docs/installation)。
+
+## 入门
+
+开始使用 Appwrite 只需要在控制台创建一个新项目,选择开发平台,然后抓取我们的开发套件。您可以从以下的教程中找到你喜欢的平台开始使用 Appwrite。
+
+* [开始使用 Web](https://appwrite.io/docs/getting-started-for-web)
+* [开始使用 Flutter](https://appwrite.io/docs/getting-started-for-flutter)
+* [开始使用 Apple](https://appwrite.io/docs/getting-started-for-apple)
+* [开始使用 Android](https://appwrite.io/docs/getting-started-for-android)
+* [开始使用 Server](https://appwrite.io/docs/getting-started-for-server)
+* [开始使用 CLI](https://appwrite.io/docs/command-line)
+
+### 软件服务
+
+* [**帐户**](https://appwrite.io/docs/client/account) -管理当前用户的帐户和登录方式。跟踪和管理用户 Session,登录设备,登录方法和查看相关记录。
+* [**用户**](https://appwrite.io/docs/server/users) - 在以管理员模式登录时管理和列出所有用户。
+* [**团队**](https://appwrite.io/docs/client/teams) - 管理用户分组。邀请成员,管理团队中的用户权限和用户角色。
+* [**数据库**](https://appwrite.io/docs/client/database) - 管理数据库文档和文档集。用检索界面来对文档和文档集进行读取,创建,更新,和删除。
+* [**贮存**](https://appwrite.io/docs/client/storage) - 管理文件的阅读、创建、删除和预览。设置文件的预览来满足程序的个性化需求。所有文件都由 ClamAV 扫描并安全存储和加密。
+* [**云函数**](https://appwrite.io/docs/server/functions) - 在安全,隔离的环境中运行自定义代码。这些代码可以被事件,CRON,或者手动操作触发。
+* [**语言适配**](https://appwrite.io/docs/client/locale) - 根据用户所在的的国家和地区做出合适的语言适配。
+* [**头像**](https://appwrite.io/docs/client/avatars) -管理用户头像、国家旗帜、浏览器图标、信用卡符号,和生成二维码。
+如需完整的 API 界面文档,请访问 [https://appwrite.io/docs](https://appwrite.io/docs)。如需更多教程、新闻和公告,请订阅我们的 [博客](https://medium.com/appwrite-io) 和 加入我们的[Discord 社区](https://discord.gg/GSeTUeA)。
+
+### 开发套件
+
+以下是当前支持的平台和语言列表。如果您想帮助我们为您选择的平台添加支持,您可以访问我们的 [SDK 生成器](https://github.com/appwrite/sdk-generator) 项目并查看我们的 [贡献指南]( https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md)。
+
+#### 客户端
+* ✅ [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
+* ✅ [Flutter](https://github.com/appwrite/sdk-for-flutter) (由 Appwrite 团队维护)
+* ✅ [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
+* ✅ [Android](https://github.com/appwrite/sdk-for-android) (由 Appwrite 团队维护)
+
+#### 服务器
+* ✅ [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
+* ✅ [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwr实验 团队维护)
+* ✅ [Dart](https://github.com/appwrite/sdk-for-dart) - (由 Appwrite 团队维护)
+* ✅ [Deno](https://github.com/appwrite/sdk-for-deno) - **公测** (由 Appwrite 团队维护)
+* ✅ [Ruby](https://github.com/appwrite/sdk-for-ruby) (由 Appwrite 团队维护)
+* ✅ [Python](https://github.com/appwrite/sdk-for-python) (由 Appwrite 团队维护)
+* ✅ [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **公测** (由 Appwrite 团队维护)
+* ✅ [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
+* ✅ [.NET](https://github.com/appwrite/sdk-for-dotnet) - **公测** (由 Appwrite 团队维护)
+
+#### 开发者社区
+* ✅ [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (维护者 [Michael Gangolf](https://github.com/m1ga/))
+* ✅ [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (维护者 [fenix-hub @GodotNuts](https://github.com/fenix-hub))
+
+找不到需要的的 SDK? - 欢迎通过发起PR来帮助我们完善Appwrite的软件生态环境 [SDK 生成器](https://github.com/appwrite/sdk-generator)!
+
+
+## 软件架构
+
+
+
+Appwrite 使用高拓展性的微服务架构。此外,Appwrite 支持多种 API(REST、WebSocket 和 即将推出的 GraphQL),来迎合您的个性化开发习惯。
+
+Appwrite API 界面层利用后台缓存和任务委派来提供极速的响应时间。后台的 Worker 代理还允许您使用消息队列来处理负载,并精确控制硬件合理分配和成本。您可以在 [贡献指南](CONTRIBUTING.md#architecture-1) 中了解有关我们架构的更多信息。
+
+## 贡献代码
+
+所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交PR请求并在合并分支之前得到核心开发人员的批准。这是为了确保正确审查所有代码。
+
+我们欢迎所有人提交PR!如果您愿意提供帮助,可以在 [贡献指南](CONTRIBUTING.md) 中了解有关如何为项目做出贡献的更多信息。
+
+## 安全
+
+为了保护您的隐私,请避免在GitHub 上发布安全问题。发送问题至 security@appwrite.io,我们将为您做更细致的解答。
+
+## 订阅我们
+
+加入我们在世界各地不断发展的社区!请参阅我们的官方 [博客](https://medium.com/appwrite-io)。在 [Twitter](https://twitter.com/appwrite)、[Facebook 页面](https://www.facebook.com/appwrite.io)、[Facebook 群组](https://www.facebook)、[开发者社区](https://dev.to/appwrite) 等平台订阅我们或加入我们的 [Discord 社区](https://discord.gg/GSeTUeA) 以获得更多帮助,想法和讨论。
+
+## 版权说明
+
+版权详情,访问 [BSD 3-Clause License](./LICENSE)。
\ No newline at end of file
diff --git a/README.md b/README.md
index 3670203487..3d1a0db74f 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@
[](docs/tutorials/add-translations.md)
[](https://store.appwrite.io)
+English | [简体中文](README-CN.md)
+
[**Appwrite 0.12 has been released! Learn what's new!**](https://dev.to/appwrite/its-here-announcing-the-release-of-appwrite-012-5c8b)
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
@@ -169,4 +171,4 @@ Join our growing community around the world! See our official [Blog](https://med
## License
-This repository is available under the [BSD 3-Clause License](./LICENSE).
+This repository is available under the [BSD 3-Clause License](./LICENSE).
\ No newline at end of file
diff --git a/app/config/auth.php b/app/config/auth.php
index 086ba9a7b2..aab6833b65 100644
--- a/app/config/auth.php
+++ b/app/config/auth.php
@@ -7,35 +7,35 @@ return [
'name' => 'Email/Password',
'key' => 'emailPassword',
'icon' => '/images/users/email.png',
- 'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateSession',
+ 'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateSession',
'enabled' => true,
],
'magic-url' => [
'name' => 'Magic URL',
'key' => 'usersAuthMagicURL',
'icon' => '/images/users/magic-url.png',
- 'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateMagicURLSession',
+ 'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateMagicURLSession',
'enabled' => true,
],
'anonymous' => [
'name' => 'Anonymous',
'key' => 'anonymous',
'icon' => '/images/users/anonymous.png',
- 'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateAnonymousSession',
+ 'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateAnonymousSession',
'enabled' => true,
],
'invites' => [
'name' => 'Invites',
'key' => 'invites',
'icon' => '/images/users/invites.png',
- 'docs' => 'https://appwrite.io/docs/client/teams?sdk=web#teamsCreateMembership',
+ 'docs' => 'https://appwrite.io/docs/client/teams?sdk=web-default#teamsCreateMembership',
'enabled' => true,
],
'jwt' => [
'name' => 'JWT',
'key' => 'JWT',
'icon' => '/images/users/jwt.png',
- 'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateJWT',
+ 'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateJWT',
'enabled' => true,
],
'phone' => [
diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php
index 9d5ac716b3..da3cfc68b0 100644
--- a/app/controllers/api/database.php
+++ b/app/controllers/api/database.php
@@ -166,8 +166,6 @@ App::post('/v1/database/collections')
$collectionId = $collectionId == 'unique()' ? $dbForProject->getId() : $collectionId;
try {
- $dbForProject->createCollection('collection_' . $collectionId);
-
$collection = $dbForProject->createDocument('collections', new Document([
'$id' => $collectionId,
'$read' => $read ?? [], // Collection permissions for collection documents (based on permission model)
@@ -179,8 +177,12 @@ App::post('/v1/database/collections')
'name' => $name,
'search' => implode(' ', [$collectionId, $name]),
]));
+
+ $dbForProject->createCollection('collection_' . $collectionId);
} catch (DuplicateException $th) {
throw new Exception('Collection already exists', 409);
+ } catch (LimitException $th) {
+ throw new Exception('Collection limit exceeded', 400);
}
$audits
diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php
index a64c47597a..8488b341f0 100644
--- a/app/controllers/api/functions.php
+++ b/app/controllers/api/functions.php
@@ -46,7 +46,7 @@ App::post('/v1/functions')
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
- ->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true)
+ ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true)
->inject('response')
->inject('dbForProject')
->action(function ($functionId, $name, $execute, $runtime, $vars, $events, $schedule, $timeout, $response, $dbForProject) {
@@ -294,7 +294,7 @@ App::put('/v1/functions/:functionId')
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
- ->param('timeout', 15, new Range(1, 900), 'Maximum execution time in seconds.', true)
+ ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true)
->inject('response')
->inject('dbForProject')
->inject('project')
diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php
index d9809bf305..0a5133d6b9 100644
--- a/app/controllers/api/teams.php
+++ b/app/controllers/api/teams.php
@@ -16,6 +16,7 @@ use Utopia\Validator\ArrayList;
use Utopia\Validator\WhiteList;
use Utopia\Database\Database;
use Utopia\Database\Document;
+use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
@@ -761,7 +762,11 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception('Team not found', 404);
}
- if (!$dbForProject->deleteDocument('memberships', $membership->getId())) {
+ try {
+ $dbForProject->deleteDocument('memberships', $membership->getId());
+ } catch (AuthorizationException $exception) {
+ throw new Exception('Unauthorized permissions', 401);
+ } catch (\Exception $exception) {
throw new Exception('Failed to remove membership from DB', 500);
}
@@ -782,7 +787,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
if ($membership->getAttribute('confirm')) { // Count only confirmed members
$team->setAttribute('sum', \max($team->getAttribute('sum', 0) - 1, 0));
- $team = $dbForProject->updateDocument('teams', $team->getId(), $team);
+ Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
}
$audits
diff --git a/app/init.php b/app/init.php
index 439e7cc3b1..d591d1ddbd 100644
--- a/app/init.php
+++ b/app/init.php
@@ -86,6 +86,9 @@ const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
+// Database Reconnect
+const DATABASE_RECONNECT_SLEEP = 2;
+const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
// Database Worker Types
const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
diff --git a/app/realtime.php b/app/realtime.php
index 9acb37570d..b2ddf67634 100644
--- a/app/realtime.php
+++ b/app/realtime.php
@@ -91,13 +91,32 @@ $server->error($logError);
function getDatabase(Registry &$register, string $namespace)
{
- $db = $register->get('dbPool')->get();
- $redis = $register->get('redisPool')->get();
+ $attempts = 0;
- $cache = new Cache(new RedisCache($redis));
- $database = new Database(new MariaDB($db), $cache);
- $database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
- $database->setNamespace($namespace);
+ do {
+ try {
+ $attempts++;
+
+ $db = $register->get('dbPool')->get();
+ $redis = $register->get('redisPool')->get();
+
+ $cache = new Cache(new RedisCache($redis));
+ $database = new Database(new MariaDB($db), $cache);
+ $database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
+ $database->setNamespace($namespace);
+
+ if (!$database->exists($database->getDefaultDatabase(), 'realtime')) {
+ throw new Exception('Collection not ready');
+ }
+ break; // leave loop if successful
+ } catch(\Exception $e) {
+ Console::warning("Database not ready. Retrying connection ({$attempts})...");
+ if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) {
+ throw new \Exception('Failed to connect to database: '. $e->getMessage());
+ }
+ sleep(DATABASE_RECONNECT_SLEEP);
+ }
+ } while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS);
return [
$database,
@@ -106,6 +125,7 @@ function getDatabase(Registry &$register, string $namespace)
$register->get('redisPool')->put($redis);
}
];
+
};
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
diff --git a/composer.json b/composer.json
index d6c0a7ff2d..e7bd6646a9 100644
--- a/composer.json
+++ b/composer.json
@@ -46,7 +46,7 @@
"utopia-php/cache": "0.4.*",
"utopia-php/cli": "0.11.*",
"utopia-php/config": "0.2.*",
- "utopia-php/database": "0.13.*",
+ "utopia-php/database": "0.14.*",
"utopia-php/locale": "0.4.*",
"utopia-php/orchestration": "0.2.*",
"utopia-php/registry": "0.5.*",
diff --git a/composer.lock b/composer.lock
index 62a9f81bd6..91440e4a7a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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": "f022f43cc2d6023c3dad3805b7c4455a",
+ "content-hash": "ab493f0a7f01a1105f8bc5caaf9b928b",
"packages": [
{
"name": "adhocore/jwt",
@@ -355,16 +355,16 @@
},
{
"name": "composer/package-versions-deprecated",
- "version": "1.11.99.4",
+ "version": "1.11.99.5",
"source": {
"type": "git",
"url": "https://github.com/composer/package-versions-deprecated.git",
- "reference": "b174585d1fe49ceed21928a945138948cb394600"
+ "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600",
- "reference": "b174585d1fe49ceed21928a945138948cb394600",
+ "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d",
+ "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d",
"shasum": ""
},
"require": {
@@ -408,7 +408,7 @@
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"support": {
"issues": "https://github.com/composer/package-versions-deprecated/issues",
- "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4"
+ "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5"
},
"funding": [
{
@@ -424,7 +424,7 @@
"type": "tidelift"
}
],
- "time": "2021-09-13T08:41:34+00:00"
+ "time": "2022-01-17T14:14:24+00:00"
},
{
"name": "dragonmantank/cron-expression",
@@ -2141,16 +2141,16 @@
},
{
"name": "utopia-php/database",
- "version": "0.13.2",
+ "version": "0.14.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
- "reference": "bf92279b707b3a10ee5ec5df5c065023b2221357"
+ "reference": "2f2527bb080cf578fba327ea2ec637064561d403"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/database/zipball/bf92279b707b3a10ee5ec5df5c065023b2221357",
- "reference": "bf92279b707b3a10ee5ec5df5c065023b2221357",
+ "url": "https://api.github.com/repos/utopia-php/database/zipball/2f2527bb080cf578fba327ea2ec637064561d403",
+ "reference": "2f2527bb080cf578fba327ea2ec637064561d403",
"shasum": ""
},
"require": {
@@ -2198,9 +2198,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
- "source": "https://github.com/utopia-php/database/tree/0.13.2"
+ "source": "https://github.com/utopia-php/database/tree/0.14.0"
},
- "time": "2022-01-04T10:51:22+00:00"
+ "time": "2022-01-21T16:34:34+00:00"
},
{
"name": "utopia-php/domains",
@@ -3703,9 +3703,6 @@
"require": {
"php": "^7.1 || ^8.0"
},
- "replace": {
- "myclabs/deep-copy": "self.version"
- },
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
@@ -6669,5 +6666,5 @@
"platform-overrides": {
"php": "8.0"
},
- "plugin-api-version": "2.1.0"
+ "plugin-api-version": "2.2.0"
}
diff --git a/src/Appwrite/Resque/Worker.php b/src/Appwrite/Resque/Worker.php
index 1444c90663..b9ed5af275 100644
--- a/src/Appwrite/Resque/Worker.php
+++ b/src/Appwrite/Resque/Worker.php
@@ -70,9 +70,6 @@ abstract class Worker
throw new Exception("Please implement getName method in worker");
}
- const MAX_ATTEMPTS = 10;
- const SLEEP_TIME = 2;
-
const DATABASE_PROJECT = 'project';
const DATABASE_CONSOLE = 'console';
@@ -174,7 +171,7 @@ abstract class Worker
global $register;
$namespace = '';
- $sleep = self::SLEEP_TIME; // overwritten when necessary
+ $sleep = DATABASE_RECONNECT_SLEEP; // overwritten when necessary
switch ($type) {
case self::DATABASE_PROJECT:
@@ -201,18 +198,24 @@ abstract class Worker
$database = new Database(new MariaDB($register->get('db')), $cache);
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace($namespace); // Main DB
+
if (!empty($projectId) && !$database->getDocument('projects', $projectId)->isEmpty()) {
throw new \Exception("Project does not exist: {$projectId}");
}
+
+ if ($type === self::DATABASE_CONSOLE && !$database->exists($database->getDefaultDatabase(), 'realtime')) {
+ throw new \Exception('Console project not ready');
+ }
+
break; // leave loop if successful
} catch(\Exception $e) {
Console::warning("Database not ready. Retrying connection ({$attempts})...");
- if ($attempts >= self::MAX_ATTEMPTS) {
+ if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) {
throw new \Exception('Failed to connect to database: '. $e->getMessage());
}
sleep($sleep);
}
- } while ($attempts < self::MAX_ATTEMPTS);
+ } while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS);
return $database;
}
diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php
index b770b2b6c3..18542961db 100644
--- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php
+++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php
@@ -169,6 +169,21 @@ class DatabaseCustomServerTest extends Scope
]);
$this->assertEquals($response['headers']['status-code'], 400);
+
+ // This collection already exists
+ $response = $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' => 'Test 1',
+ 'collectionId' => 'first',
+ 'read' => ['role:all'],
+ 'write' => ['role:all'],
+ 'permission' => 'document'
+ ]);
+
+ $this->assertEquals($response['headers']['status-code'], 409);
}
public function testDeleteAttribute(): array
diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php
index 89ac02da74..e03ce89a9c 100644
--- a/tests/e2e/Services/Teams/TeamsBaseClient.php
+++ b/tests/e2e/Services/Teams/TeamsBaseClient.php
@@ -391,11 +391,75 @@ trait TeamsBaseClient
{
$teamUid = $data['teamUid'] ?? '';
$membershipUid = $data['membershipUid'] ?? '';
+ $session = $data['session'] ?? '';
+
+ $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals(2, $response['body']['sum']);
+ $ownerMembershipUid = $response['body']['memberships'][0]['$id'];
+
+ /**
+ * Test for FAILURE
+ */
+
+ /**
+ * Test deleting a membership that does not exists
+ */
+ $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/dne', [
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'cookie' => 'a_session_'.$this->getProject()['$id'].'='.$session,
+ ]);
+
+ $this->assertEquals(404, $response['headers']['status-code']);
+
+ /**
+ * Test deleting another user's membership
+ */
+ $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$ownerMembershipUid, [
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'cookie' => 'a_session_'.$this->getProject()['$id'].'='.$session,
+ ]);
+
+ $this->assertEquals(401, $response['headers']['status-code']);
+
/**
* Test for SUCCESS
*/
- $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$membershipUid, array_merge([
+
+ /**
+ * Test for when a user other than the owner tries to delete their membership
+ */
+ $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$membershipUid, [
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'cookie' => 'a_session_'.$this->getProject()['$id'].'='.$session,
+ ]);
+
+ $this->assertEquals(204, $response['headers']['status-code']);
+ $this->assertEmpty($response['body']);
+
+ $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals(1, $response['body']['sum']);
+
+ /**
+ * Test for when the owner tries to delete their membership
+ */
+ $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$ownerMembershipUid, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -404,10 +468,7 @@ trait TeamsBaseClient
$this->assertEquals(204, $response['headers']['status-code']);
$this->assertEmpty($response['body']);
- /**
- * Test for FAILURE
- */
- $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships/'.$membershipUid, array_merge([
+ $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships/'.$ownerMembershipUid, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],