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 @@ +
+

+ Appwrite Logo +
+
+ 适用于[Flutter/Vue/Angular/React/iOS/Android/* 等等平台 *]的完整后端服务 +
+
+

+ + +[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github) +[![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite) +[![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite) +[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite) +[![翻译](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md) +[![周边商店](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io) + +[English](README.md) | 简体中文 + +Appwrite是一个基于dcoker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。 + +Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存, 图像处理,云函数计算,[等多种服务](https:/ /appwrite.io/docs)。 + +![Appwrite](public/images/github.png) + +更多信息请到 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 软件架构](docs/specs/overview.drawio.svg) + +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 @@ [![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md) [![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](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/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/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'],