mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'master' into feat-implement-transfers
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
name: "CodeQL"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
name: "Linter"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
name: "Tests"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
tests:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = 2.2.0
|
||||
branch = 2.3.3
|
||||
|
||||
+26
@@ -1,3 +1,29 @@
|
||||
# Version 1.3.1
|
||||
|
||||
## Bugs
|
||||
- Fixed Migration issue regarding 500 error [4906](https://github.com/appwrite/appwrite/pull/5356)
|
||||
|
||||
# Version 1.3.0
|
||||
|
||||
## Features
|
||||
- Password dictionary setting allows to compare user's password against command password database [4906](https://github.com/appwrite/appwrite/pull/4906)
|
||||
- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured. Minimum is 0 which means disabled. [#4866](https://github.com/appwrite/appwrite/pull/4866)
|
||||
- Update APIs to check X-Appwrite-Timestamp header [#5024](https://github.com/appwrite/appwrite/pull/5024)
|
||||
- Database relationships [#5238](https://github.com/appwrite/appwrite/pull/5238)
|
||||
- New query operators [#5238](https://github.com/appwrite/appwrite/pull/5238)
|
||||
- Team preferences [#5196](https://github.com/appwrite/appwrite/pull/5196)
|
||||
- Update attribute metadata [#5164](https://github.com/appwrite/appwrite/pull/5164)
|
||||
|
||||
## Bugs
|
||||
- Fix not storing function's response on response codes 5xx [#4610](https://github.com/appwrite/appwrite/pull/4610)
|
||||
- Fix expire to formatTz in create account session [#4985](https://github.com/appwrite/appwrite/pull/4985)
|
||||
- Fix deleting projects when organization is deleted [#5335](https://github.com/appwrite/appwrite/pull/5335)
|
||||
- Fix deleting collections from a project [#4983](https://github.com/appwrite/appwrite/pull/4983)
|
||||
- Fix cleaning up project databases [#4984](https://github.com/appwrite/appwrite/pull/4984)
|
||||
- Fix creating documents with attributes with special characters [#246](https://github.com/utopia-php/database/pull/246)
|
||||
- Fix deleting attribute not deleting metadata index [#246](https://github.com/utopia-php/database/pull/246)
|
||||
- Fix create attribute event payload [#246](https://github.com/utopia-php/database/pull/246)
|
||||
|
||||
# Version 1.2.1
|
||||
## Changes
|
||||
- Upgrade Console to [2.2.0](https://github.com/appwrite/console/releases/tag/2.2.0)
|
||||
|
||||
+9
-2
@@ -346,8 +346,7 @@ If you are in PHP Storm you don't need any plugin. Below are the settings requir
|
||||
|
||||
1. Create an init file.
|
||||
2. Duplicate **dev/yasd_init.php.stub** file and name it **dev/yasd_init.php**.
|
||||
3. Change the IP address to your development machine's IP. Without the proper IP address, the debugger won't connect.
|
||||
4. Set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file.
|
||||
3. Set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file.
|
||||
|
||||
### VS Code Launch Configuration
|
||||
|
||||
@@ -436,6 +435,14 @@ composer lint
|
||||
composer lint <your file path>
|
||||
```
|
||||
|
||||
## Clearing the Cache
|
||||
|
||||
If you need to clear the cache, you can do so by running the following command:
|
||||
|
||||
```bash
|
||||
docker compose exec redis redis-cli FLUSHALL
|
||||
```
|
||||
|
||||
## Tutorials
|
||||
|
||||
From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials:
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
||||
FROM ionic/base as final
|
||||
FROM appwrite/base:0.2.2 as final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
@@ -183,7 +183,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
# Enable Extensions
|
||||
RUN if [ "$DEBUG" == "true" ]; then printf "zend_extension=yasd \nyasd.debug_mode=remote \nyasd.init_file=/usr/local/dev/yasd_init.php \nyasd.remote_port=9005 \nyasd.log_level=-1" >> /usr/local/etc/php/conf.d/yasd.ini; fi
|
||||
RUN if [ "$DEBUG" == "true" ]; then printf "zend_extension=yasd \nyasd.debug_mode=remote \nyasd.init_file=/usr/src/code/dev/yasd_init.php \nyasd.remote_port=9005 \nyasd.log_level=-1" >> /usr/local/etc/php/conf.d/yasd.ini; fi
|
||||
|
||||
RUN if [ "$DEBUG" == "true" ]; then echo "opcache.enable=0" >> /usr/local/etc/php/conf.d/appwrite.ini; fi
|
||||
RUN echo "opcache.preload_user=www-data" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
|
||||
+11
-8
@@ -1,3 +1,6 @@
|
||||
> 准备好迎接狂风暴'云'了吗? 🌩 ☂️
|
||||
> Appwrite Cloud即将到来!你能够通过https://appwrite.io/cloud了解更多的资讯, 注册即可领取试用额度哦
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img width="260" height="39" src="https://appwrite.io/images/appwrite.svg" alt="Appwrite Logo"></a>
|
||||
@@ -9,7 +12,7 @@
|
||||
</p>
|
||||
|
||||
<!-- [](https://travis-ci.com/appwrite/appwrite) -->
|
||||
|
||||
[](https://appwrite.io/company/careers)
|
||||
[](https://hacktoberfest.appwrite.io)
|
||||
[](https://appwrite.io/discord?r=Github)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
@@ -21,9 +24,9 @@
|
||||
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
[**我们发布了 Appwrite Console 2.0 版本,点击这里了解更多!**](https://medium.com/appwrite-io/announcing-console-2-0-2e0e96891cb0?source=friends_link&sk=7a82b4069778e3adc165dc026e960fe1)
|
||||
[**宣布带有数据库关系的 Appwrite 1.3!了解最新消息!**](https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc)
|
||||
|
||||
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面简化了从零开始编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
|
||||
Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存,图像处理,云函数计算,[等多种服务](https://appwrite.io/docs).
|
||||
|
||||
@@ -55,7 +58,7 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
||||
|
||||
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
|
||||
|
||||
开始运行 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
|
||||
启动 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
|
||||
|
||||
### Unix
|
||||
|
||||
@@ -64,7 +67,7 @@ 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:1.2.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -76,7 +79,7 @@ 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:1.2.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -86,7 +89,7 @@ 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:1.2.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
@@ -159,7 +162,7 @@ Appwrite API 界面层利用后台缓存和任务委派来提供极速的响应
|
||||
|
||||
## 贡献代码
|
||||
|
||||
所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交PR请求并在合并分支之前得到核心开发人员的批准。这是为了确保正确审查所有代码。
|
||||
为了确保正确审查,所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交PR请求并在合并分支之前得到核心开发人员的批准。
|
||||
|
||||
我们欢迎所有人提交PR!如果您愿意提供帮助,可以在 [贡献指南](CONTRIBUTING.md) 中了解有关如何为项目做出贡献的更多信息。
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<!-- [](https://travis-ci.com/appwrite/appwrite) -->
|
||||
|
||||
[](https://appwrite.io/company/careers)
|
||||
[](https://hacktoberfest.appwrite.io)
|
||||
[](https://appwrite.io/discord?r=Github)
|
||||
[](https://github.com/appwrite/appwrite/actions)
|
||||
@@ -25,7 +26,7 @@
|
||||
|
||||
English | [简体中文](README-CN.md)
|
||||
|
||||
[**Announcing Appwrite 1.2 with GraphQL support! Learn what's new!**](https://appwrite.io/graphql)
|
||||
[**Announcing Appwrite 1.3 with Database Relationships! Learn what's new!**](https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc)
|
||||
|
||||
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker<nobr> microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
|
||||
|
||||
@@ -75,7 +76,7 @@ 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:1.2.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -87,7 +88,7 @@ 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:1.2.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -97,7 +98,7 @@ 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:1.2.1
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
|
||||
|
||||
+2
-2
@@ -4,11 +4,11 @@
|
||||
|
||||
| Version | Supported |
|
||||
| --------- | ------------------ |
|
||||
| <= 0.14.x | :x: |
|
||||
| 0.15.x | :white_check_mark: |
|
||||
| <= 0.15.x | :x: |
|
||||
| 1.0.x | :white_check_mark: |
|
||||
| 1.1.x | :white_check_mark: |
|
||||
| 1.2.x | :white_check_mark: |
|
||||
| 1.3.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -346,6 +346,16 @@ $collections = [
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('options'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 16384,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1234,6 +1244,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('passwordHistory'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('password'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1854,6 +1875,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('prefs'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => new \stdClass(),
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
||||
+23
-3
@@ -408,6 +408,16 @@ return [
|
||||
'description' => 'Document with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_UPDATE_CONFLICT => [
|
||||
'name' => Exception::DOCUMENT_UPDATE_CONFLICT,
|
||||
'description' => 'Remote document is newer than local.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_DELETE_RESTRICTED => [
|
||||
'name' => Exception::DOCUMENT_DELETE_RESTRICTED,
|
||||
'description' => 'Document cannot be deleted because it is referenced by another document.',
|
||||
'code' => 403,
|
||||
],
|
||||
|
||||
/** Attributes */
|
||||
Exception::ATTRIBUTE_NOT_FOUND => [
|
||||
@@ -417,12 +427,12 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_UNKNOWN => [
|
||||
'name' => Exception::ATTRIBUTE_UNKNOWN,
|
||||
'description' => 'The attribute required for the index could not be found. Please confirm all your attributes are in the <span class="tag">available</span> state.',
|
||||
'description' => 'The attribute required for the index could not be found. Please confirm all your attributes are in the available state.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_NOT_AVAILABLE => [
|
||||
'name' => Exception::ATTRIBUTE_NOT_AVAILABLE,
|
||||
'description' => 'The requested attribute is not yet <span class="tag">available</span>. Please try again later.',
|
||||
'description' => 'The requested attribute is not yet available. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_FORMAT_UNSUPPORTED => [
|
||||
@@ -432,7 +442,7 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED => [
|
||||
'name' => Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED,
|
||||
'description' => 'Default values cannot be set for <span class="tag">array</span> and <span class="tag">required</span> attributes.',
|
||||
'description' => 'Default values cannot be set for array or required attributes.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_ALREADY_EXISTS => [
|
||||
@@ -450,6 +460,11 @@ return [
|
||||
'description' => 'The attribute value is invalid. Please check the type, range and value of the attribute.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_TYPE_INVALID => [
|
||||
'name' => Exception::ATTRIBUTE_TYPE_INVALID,
|
||||
'description' => 'The attribute type is invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Indexes */
|
||||
Exception::INDEX_NOT_FOUND => [
|
||||
@@ -549,6 +564,11 @@ return [
|
||||
'description' => 'Domain verification for the requested domain has failed.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::DOMAIN_TARGET_INVALID => [
|
||||
'name' => Exception::DOMAIN_TARGET_INVALID,
|
||||
'description' => 'Your Appwrite instance is not publicly accessible. Please check the _APP_DOMAIN_TARGET environment variable of your Appwrite server.',
|
||||
'code' => 501,
|
||||
],
|
||||
Exception::GRAPHQL_NO_QUERY => [
|
||||
'name' => Exception::GRAPHQL_NO_QUERY,
|
||||
'description' => 'Param "query" is not optional.',
|
||||
|
||||
@@ -183,13 +183,16 @@ return [
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a bucket is created.'
|
||||
'$description' => 'This event triggers when a team is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a bucket is deleted.',
|
||||
'$description' => 'This event triggers when a team is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a bucket is updated.',
|
||||
'$description' => 'This event triggers when a team is updated.',
|
||||
'prefs' => [
|
||||
'$description' => 'This event triggers when a team\'s preferences are updated.',
|
||||
],
|
||||
]
|
||||
],
|
||||
'functions' => [
|
||||
|
||||
+23
-23
@@ -11,11 +11,11 @@ return [
|
||||
'description' => 'Client libraries for integrating with Appwrite to build client-based applications and websites. Read the [getting started for web](/docs/getting-started-for-web) or [getting started for Flutter](/docs/getting-started-for-flutter) tutorials to start building your first application.',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'languages' => [ // TODO change key to 'sdks'
|
||||
'sdks' => [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '10.2.0',
|
||||
'version' => '11.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' => '8.2.0',
|
||||
'version' => '9.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' => '1.2.0',
|
||||
'version' => '2.0.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' => '1.2.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
@@ -181,11 +181,11 @@ return [
|
||||
'name' => 'Console',
|
||||
'enabled' => false,
|
||||
'beta' => false,
|
||||
'languages' => [
|
||||
'sdks' => [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '7.2.0',
|
||||
'version' => '0.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
@@ -193,17 +193,17 @@ return [
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'prism' => 'console',
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => '',
|
||||
'gitBranch' => '',
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
|
||||
'gitBranch' => 'main',
|
||||
'gitRepoName' => 'sdk-for-console',
|
||||
'gitUserName' => 'appwrite',
|
||||
],
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '1.1.1',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -227,11 +227,11 @@ return [
|
||||
'description' => 'Libraries for integrating with Appwrite to build server side integrations. Read the [getting started for server](/docs/getting-started-for-server) tutorial to start building your first server integration.',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'languages' => [
|
||||
'sdks' => [
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '8.2.0',
|
||||
'version' => '9.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
@@ -249,7 +249,7 @@ return [
|
||||
[
|
||||
'key' => 'deno',
|
||||
'name' => 'Deno',
|
||||
'version' => '6.2.0',
|
||||
'version' => '7.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-deno',
|
||||
'package' => 'https://deno.land/x/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -267,7 +267,7 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '7.2.0',
|
||||
'version' => '8.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -285,7 +285,7 @@ return [
|
||||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '1.2.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
@@ -303,7 +303,7 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '7.2.0',
|
||||
'version' => '8.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -321,7 +321,7 @@ return [
|
||||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => '1.2.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
@@ -339,7 +339,7 @@ return [
|
||||
[
|
||||
'key' => 'java',
|
||||
'name' => 'Java',
|
||||
'version' => '1.2.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-java',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
@@ -357,7 +357,7 @@ return [
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '1.2.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => false,
|
||||
@@ -375,7 +375,7 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '7.2.0',
|
||||
'version' => '8.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
@@ -393,7 +393,7 @@ return [
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '1.2.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
@@ -415,7 +415,7 @@ return [
|
||||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '1.2.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
|
||||
+18
-5
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'/' => [
|
||||
'key' => 'homepage',
|
||||
'name' => 'Homepage',
|
||||
'web/home' => [
|
||||
'key' => 'web/home',
|
||||
'name' => 'Home',
|
||||
'subtitle' => '',
|
||||
'description' => '',
|
||||
'controller' => 'web/home.php',
|
||||
@@ -14,8 +14,8 @@ return [
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'console' => [
|
||||
'key' => 'console',
|
||||
'web/console' => [
|
||||
'key' => 'web/console',
|
||||
'name' => 'Console',
|
||||
'subtitle' => '',
|
||||
'description' => '',
|
||||
@@ -186,6 +186,19 @@ return [
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/graphql.png',
|
||||
],
|
||||
'console' => [
|
||||
'key' => 'console',
|
||||
'name' => 'Console',
|
||||
'subtitle' => 'The Console service allows you to interact with console relevant informations.',
|
||||
'description' => '',
|
||||
'controller' => 'api/console.php',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'transfers' => [
|
||||
'key' => 'transfers',
|
||||
'name' => 'Transfers',
|
||||
|
||||
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
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
+1
-1
Submodule app/console updated: 2456d8f21c...ac4181aea4
+119
-39
@@ -40,6 +40,8 @@ use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Appwrite\Auth\Validator\PasswordHistory;
|
||||
use Appwrite\Auth\Validator\PasswordDictionary;
|
||||
|
||||
$oauthDefaultSuccess = '/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/auth/oauth2/failure';
|
||||
@@ -60,11 +62,11 @@ App::post('/v1/account')
|
||||
->label('sdk.description', '/docs/references/account/create.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('abuse-limit', 10)
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -72,7 +74,6 @@ App::post('/v1/account')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
if ('console' === $project->getId()) {
|
||||
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
|
||||
@@ -97,6 +98,8 @@ App::post('/v1/account')
|
||||
}
|
||||
}
|
||||
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
$password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
try {
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
@@ -109,10 +112,11 @@ App::post('/v1/account')
|
||||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'password' => $password,
|
||||
'passwordHistory' => $passwordHistory > 0 ? [$password] : [],
|
||||
'passwordUpdate' => DateTime::now(),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => DateTime::now(),
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
@@ -188,7 +192,7 @@ App::post('/v1/account/sessions/email')
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$expire = DateTime::addSeconds(new \DateTime(), $duration);
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
|
||||
$secret = Auth::tokenGenerator();
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
@@ -280,6 +284,12 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
|
||||
$providerEnabled = $project->getAttribute('authProviders', [])[$provider . 'Enabled'] ?? false;
|
||||
|
||||
if (!$providerEnabled) {
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please enable the provider from your ' . APP_NAME . ' console to continue.');
|
||||
}
|
||||
|
||||
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
@@ -371,6 +381,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->label('scope', 'public')
|
||||
->label('audits.event', 'session.create')
|
||||
->label('audits.resource', 'user/{user.$id}')
|
||||
->label('audits.userId', '{user.$id}')
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->label('docs', false)
|
||||
@@ -394,6 +405,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
$validateURL = new URL();
|
||||
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
$providerEnabled = $project->getAttribute('authProviders', [])[$provider . 'Enabled'] ?? false;
|
||||
|
||||
if (!$providerEnabled) {
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please enable the provider from your ' . APP_NAME . ' console to continue.');
|
||||
}
|
||||
|
||||
if (!empty($appSecret) && isset($appSecret['version'])) {
|
||||
$key = App::getEnv('_APP_OPENSSL_KEY_V' . $appSecret['version']);
|
||||
@@ -489,8 +505,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
}
|
||||
}
|
||||
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$password = Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
@@ -501,7 +520,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'email' => $email,
|
||||
'emailVerification' => true,
|
||||
'status' => true, // Email should already be authenticated by OAuth2 provider
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'passwordHistory' => $passwordHistory > 0 ? [$password] : null,
|
||||
'password' => $password,
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => null,
|
||||
@@ -1308,7 +1328,9 @@ App::get('/v1/account')
|
||||
->label('sdk.description', '/docs/references/account/get.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->action(function (Response $response, Document $user) {
|
||||
@@ -1328,11 +1350,13 @@ App::get('/v1/account/prefs')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PREFERENCES)
|
||||
->label('sdk.offline.model', '/account/prefs')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->action(function (Response $response, Document $user) {
|
||||
|
||||
$prefs = $user->getAttribute('prefs', new \stdClass());
|
||||
$prefs = $user->getAttribute('prefs', []);
|
||||
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
@@ -1349,6 +1373,7 @@ App::get('/v1/account/sessions')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_SESSION_LIST)
|
||||
->label('sdk.offline.model', '/account/sessions')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('locale')
|
||||
@@ -1386,7 +1411,7 @@ App::get('/v1/account/logs')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOG_LIST)
|
||||
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true)
|
||||
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('locale')
|
||||
@@ -1447,6 +1472,8 @@ App::get('/v1/account/sessions/:sessionId')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_SESSION)
|
||||
->label('sdk.offline.model', '/account/sessions')
|
||||
->label('sdk.offline.key', '{sessionId}')
|
||||
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to get the current device session.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
@@ -1485,6 +1512,7 @@ App::patch('/v1/account/name')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.update')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('usage.metric', 'users.{scope}.requests.update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -1492,17 +1520,22 @@ App::patch('/v1/account/name')
|
||||
->label('sdk.description', '/docs/references/account/update-name.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $name, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (string $name, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
$user
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email', ''), $user->getAttribute('phone', '')])));
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email', ''), $user->getAttribute('phone', '')]));
|
||||
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
@@ -1524,25 +1557,46 @@ App::patch('/v1/account/password')
|
||||
->label('sdk.description', '/docs/references/account/update-password.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $password, string $oldPassword, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) {
|
||||
|
||||
// Check old password only if its an existing user.
|
||||
if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
|
||||
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', DateTime::now()));
|
||||
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
$history = [];
|
||||
if ($historyLimit > 0) {
|
||||
$history = $user->getAttribute('passwordHistory', []);
|
||||
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
|
||||
if (!$validator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409);
|
||||
}
|
||||
|
||||
$history[] = $newPassword;
|
||||
array_slice($history, (count($history) - $historyLimit), $historyLimit);
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('password', $newPassword)
|
||||
->setAttribute('passwordHistory', $history)
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
@@ -1556,6 +1610,7 @@ App::patch('/v1/account/email')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.update')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('usage.metric', 'users.{scope}.requests.update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -1563,14 +1618,17 @@ App::patch('/v1/account/email')
|
||||
->label('sdk.description', '/docs/references/account/update-email.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $email, string $password, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
$isAnonymousUser = Auth::isAnonymousUser($user); // Check if request is from an anonymous account for converting
|
||||
|
||||
if (
|
||||
@@ -1591,7 +1649,7 @@ App::patch('/v1/account/email')
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $email, $user->getAttribute('phone', '')]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -1608,6 +1666,7 @@ App::patch('/v1/account/phone')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.update')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('usage.metric', 'users.{scope}.requests.update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -1615,14 +1674,17 @@ App::patch('/v1/account/phone')
|
||||
->label('sdk.description', '/docs/references/account/update-phone.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $phone, string $password, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$isAnonymousUser = Auth::isAnonymousUser($user); // Check if request is from an anonymous account for converting
|
||||
|
||||
@@ -1639,7 +1701,7 @@ App::patch('/v1/account/phone')
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $phone]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -1656,6 +1718,7 @@ App::patch('/v1/account/prefs')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.update')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('usage.metric', 'users.{scope}.requests.update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -1663,15 +1726,20 @@ App::patch('/v1/account/prefs')
|
||||
->label('sdk.description', '/docs/references/account/update-prefs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account/prefs')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('prefs', [], new Assoc(), 'Prefs key-value JSON object.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (array $prefs, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (array $prefs, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
|
||||
$user->setAttribute('prefs', $prefs);
|
||||
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
@@ -1685,6 +1753,7 @@ App::patch('/v1/account/status')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.update')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('usage.metric', 'users.{scope}.requests.delete')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -1692,15 +1761,17 @@ App::patch('/v1/account/status')
|
||||
->label('sdk.description', '/docs/references/account/update-status.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->inject('request')
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', false));
|
||||
$user->setAttribute('status', false);
|
||||
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
@@ -1720,6 +1791,7 @@ App::delete('/v1/account/sessions/:sessionId')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
->label('audits.event', 'session.delete')
|
||||
->label('audits.resource', 'user/{user.$id}')
|
||||
->label('audits.userId', '{user.$id}')
|
||||
->label('usage.metric', 'sessions.{scope}.requests.delete')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -1729,6 +1801,7 @@ App::delete('/v1/account/sessions/:sessionId')
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('abuse-limit', 100)
|
||||
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to delete the current device session.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
@@ -1736,7 +1809,7 @@ App::delete('/v1/account/sessions/:sessionId')
|
||||
->inject('locale')
|
||||
->inject('events')
|
||||
->inject('project')
|
||||
->action(function (?string $sessionId, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $events, Document $project) {
|
||||
->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $events, Document $project) {
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
@@ -1748,9 +1821,11 @@ App::delete('/v1/account/sessions/:sessionId')
|
||||
|
||||
foreach ($sessions as $key => $session) {/** @var Document $session */
|
||||
if ($sessionId == $session->getId()) {
|
||||
unset($sessions[$key]);
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $session) {
|
||||
return $dbForProject->deleteDocument('sessions', $session->getId());
|
||||
});
|
||||
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
unset($sessions[$key]);
|
||||
|
||||
$session->setAttribute('current', false);
|
||||
|
||||
@@ -1879,6 +1954,7 @@ App::delete('/v1/account/sessions')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
->label('audits.event', 'session.delete')
|
||||
->label('audits.resource', 'user/{user.$id}')
|
||||
->label('audits.userId', '{user.$id}')
|
||||
->label('usage.metric', 'sessions.{scope}.requests.delete')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -2110,9 +2186,9 @@ App::put('/v1/account/recovery')
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('emailVerification', true));
|
||||
|
||||
$recoveryDocument = $dbForProject->getDocument('tokens', $recovery);
|
||||
@@ -2139,6 +2215,7 @@ App::post('/v1/account/verification')
|
||||
->label('event', 'users.[userId].verification.[tokenId].create')
|
||||
->label('audits.event', 'verification.create')
|
||||
->label('audits.resource', 'user/{response.userId}')
|
||||
->label('audits.userId', '{response.userId}')
|
||||
->label('usage.metric', 'users.{scope}.requests.update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -2250,6 +2327,7 @@ App::put('/v1/account/verification')
|
||||
->label('event', 'users.[userId].verification.[tokenId].update')
|
||||
->label('audits.event', 'verification.update')
|
||||
->label('audits.resource', 'user/{response.userId}')
|
||||
->label('audits.userId', '{response.userId}')
|
||||
->label('usage.metric', 'users.{scope}.requests.update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -2309,6 +2387,7 @@ App::post('/v1/account/verification/phone')
|
||||
->label('event', 'users.[userId].verification.[tokenId].create')
|
||||
->label('audits.event', 'verification.create')
|
||||
->label('audits.resource', 'user/{response.userId}')
|
||||
->label('audits.userId', '{response.userId}')
|
||||
->label('usage.metric', 'users.{scope}.requests.update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
@@ -2394,6 +2473,7 @@ App::put('/v1/account/verification/phone')
|
||||
->label('event', 'users.[userId].verification.[tokenId].update')
|
||||
->label('audits.event', 'verification.update')
|
||||
->label('audits.resource', 'user/{response.userId}')
|
||||
->label('audits.userId', '{response.userId}')
|
||||
->label('usage.metric', 'users.{scope}.requests.update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
App::init()
|
||||
->groups(['console'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
if ($project->getId() !== 'console') {
|
||||
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
App::get('/v1/console/variables')
|
||||
->desc('Get Variables')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'console')
|
||||
->label('sdk.method', 'variables')
|
||||
->label('sdk.description', '/docs/references/console/variables.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_CONSOLE_VARIABLES)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$variables = new Document([
|
||||
'_APP_DOMAIN_TARGET' => App::getEnv('_APP_DOMAIN_TARGET'),
|
||||
'_APP_STORAGE_LIMIT' => +App::getEnv('_APP_STORAGE_LIMIT'),
|
||||
'_APP_FUNCTIONS_SIZE_LIMIT' => +App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT'),
|
||||
'_APP_USAGE_STATS' => App::getEnv('_APP_USAGE_STATS'),
|
||||
]);
|
||||
|
||||
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
|
||||
});
|
||||
+1261
-118
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,6 @@ use Appwrite\Task\Validator\Cron;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Executions;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Functions;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Variables;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -33,7 +32,6 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -63,7 +61,7 @@ App::post('/v1/functions')
|
||||
->label('sdk.response.model', Response::MODEL_FUNCTION)
|
||||
->param('functionId', '', new CustomId(), 'Function ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
|
||||
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
|
||||
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
|
||||
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
||||
@@ -125,7 +123,7 @@ App::get('/v1/functions')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_FUNCTION_LIST)
|
||||
->param('queries', [], new Functions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Functions::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Functions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Functions::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -440,7 +438,7 @@ App::put('/v1/functions/:functionId')
|
||||
->label('sdk.response.model', Response::MODEL_FUNCTION)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
|
||||
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
|
||||
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
||||
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true)
|
||||
@@ -601,8 +599,8 @@ App::post('/v1/functions/:functionId/deployments')
|
||||
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('entrypoint', '', new Text('1028'), 'Entrypoint File.')
|
||||
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', false)
|
||||
->param('activate', false, new Boolean(true), 'Automatically activate the deployment when it is finished building.', false)
|
||||
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', skipValidation: true)
|
||||
->param('activate', false, new Boolean(true), 'Automatically activate the deployment when it is finished building.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -791,7 +789,7 @@ App::get('/v1/functions/:functionId/deployments')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_DEPLOYMENT_LIST)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('queries', [], new Deployments(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Deployments::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Deployments(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Deployments::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -1223,7 +1221,7 @@ App::get('/v1/functions/:functionId/executions')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_EXECUTION_LIST)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('queries', [], new Executions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Executions::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Executions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Executions::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -1348,7 +1346,7 @@ App::post('/v1/functions/:functionId/variables')
|
||||
->label('sdk.response.model', Response::MODEL_VARIABLE)
|
||||
->param('functionId', '', new UID(), 'Function unique ID.', false)
|
||||
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
|
||||
->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject) {
|
||||
@@ -1464,7 +1462,7 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
->param('functionId', '', new UID(), 'Function unique ID.', false)
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
|
||||
->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject) {
|
||||
|
||||
@@ -30,7 +30,7 @@ App::get('/v1/graphql')
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->param('query', '', new Text(0), 'The query to execute.')
|
||||
->param('query', '', new Text(0, 0), 'The query to execute.')
|
||||
->param('operationName', '', new Text(256), 'The name of the operation to execute.', true)
|
||||
->param('variables', '', new Text(0), 'The JSON encoded variables to use in the query.', true)
|
||||
->inject('request')
|
||||
|
||||
@@ -19,6 +19,8 @@ App::get('/v1/locale')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOCALE)
|
||||
->label('sdk.offline.model', '/locale')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
@@ -77,6 +79,8 @@ App::get('/v1/locale/countries')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
|
||||
->label('sdk.offline.model', '/locale/countries')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -108,6 +112,8 @@ App::get('/v1/locale/countries/eu')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
|
||||
->label('sdk.offline.model', '/locale/countries/eu')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -141,6 +147,8 @@ App::get('/v1/locale/countries/phones')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PHONE_LIST)
|
||||
->label('sdk.offline.model', '/locale/countries/phones')
|
||||
->label('sdk.offline.response.key', 'countryCode')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -173,6 +181,8 @@ App::get('/v1/locale/continents')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_CONTINENT_LIST)
|
||||
->label('sdk.offline.model', '/locale/continents')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
@@ -203,6 +213,8 @@ App::get('/v1/locale/currencies')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_CURRENCY_LIST)
|
||||
->label('sdk.offline.model', '/locale/currencies')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$list = Config::getParam('locale-currencies');
|
||||
@@ -224,6 +236,8 @@ App::get('/v1/locale/languages')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LANGUAGE_LIST)
|
||||
->label('sdk.offline.model', '/locale/languages')
|
||||
->label('sdk.offline.response.key', 'code')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$list = Config::getParam('locale-languages');
|
||||
|
||||
@@ -23,7 +23,7 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Registry\Registry;
|
||||
@@ -80,7 +80,7 @@ App::post('/v1/projects')
|
||||
}
|
||||
|
||||
$auth = Config::getParam('auth', []);
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
|
||||
foreach ($auth as $index => $method) {
|
||||
$auths[$method['key'] ?? ''] = true;
|
||||
}
|
||||
@@ -184,7 +184,7 @@ App::get('/v1/projects')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT_LIST)
|
||||
->param('queries', [], new Projects(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Projects::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Projects(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Projects::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
@@ -575,6 +575,68 @@ App::patch('/v1/projects/:projectId/auth/:method')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/password-history')
|
||||
->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateAuthPasswordHistory')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('limit', 0, new Range(0, APP_LIMIT_USER_PASSWORD_HISTORY), 'Set the max number of passwords to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of passwords allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['passwordHistory'] = $limit;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
->desc('Update authentication password disctionary status. Use this endpoint to enable or disable the dicitonary check for user password')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateAuthPasswordDictionary')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['passwordDictionary'] = $enabled;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
->desc('Update Project user sessions limit')
|
||||
->groups(['api', 'projects'])
|
||||
@@ -1340,7 +1402,7 @@ App::post('/v1/projects/:projectId/domains')
|
||||
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
|
||||
if (!$target->isKnown() || $target->isTest()) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
|
||||
throw new Exception(Exception::DOMAIN_TARGET_INVALID, 'Unreachable CNAME target (' . $target->get() . '). Please check the _APP_DOMAIN_TARGET environment variable of your Appwrite server.');
|
||||
}
|
||||
|
||||
$domain = new Domain($domain);
|
||||
|
||||
@@ -63,7 +63,7 @@ App::post('/v1/storage/buckets')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
|
||||
->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
|
||||
->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](/docs/environment-variables#storage)', true)
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
@@ -154,7 +154,7 @@ App::get('/v1/storage/buckets')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUCKET_LIST)
|
||||
->param('queries', [], new Buckets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Buckets::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Buckets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Buckets::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -235,7 +235,7 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled?', true)
|
||||
->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)', true)
|
||||
->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](/docs/environment-variables#storage)', true)
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
@@ -348,7 +348,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->label('sdk.response.model', Response::MODEL_FILE)
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new CustomId(), 'File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('file', [], new File(), 'Binary file.', false)
|
||||
->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](/docs/storage#file-input).', skipValidation: true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -680,7 +680,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_FILE_LIST)
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
|
||||
->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
||||
+174
-62
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Validator\Host;
|
||||
@@ -20,7 +22,6 @@ use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -35,10 +36,9 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::post('/v1/teams')
|
||||
->desc('Create Team')
|
||||
@@ -76,6 +76,7 @@ App::post('/v1/teams')
|
||||
],
|
||||
'name' => $name,
|
||||
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
'prefs' => new \stdClass(),
|
||||
'search' => implode(' ', [$teamId, $name]),
|
||||
])));
|
||||
|
||||
@@ -133,7 +134,8 @@ App::get('/v1/teams')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM_LIST)
|
||||
->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Teams::ALLOWED_ATTRIBUTES), true)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Teams::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -182,6 +184,8 @@ App::get('/v1/teams/:teamId')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->label('sdk.offline.key', '{teamId}')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -196,26 +200,22 @@ App::get('/v1/teams/:teamId')
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
App::put('/v1/teams/:teamId')
|
||||
->desc('Update Team')
|
||||
App::get('/v1/teams/:teamId/prefs')
|
||||
->desc('Get Team Preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.update')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'update')
|
||||
->label('sdk.description', '/docs/references/teams/update-team.md')
|
||||
->label('sdk.method', 'getPrefs')
|
||||
->label('sdk.description', '/docs/references/teams/get-team-prefs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->label('sdk.response.model', Response::MODEL_PREFERENCES)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/prefs')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -223,15 +223,90 @@ App::put('/v1/teams/:teamId')
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = $dbForProject->updateDocument('teams', $team->getId(), $team
|
||||
$prefs = $team->getAttribute('prefs', []);
|
||||
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::put('/v1/teams/:teamId')
|
||||
->desc('Update Name')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.update')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'updateName')
|
||||
->label('sdk.description', '/docs/references/teams/update-team-name.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->label('sdk.offline.key', '{teamId}')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $name, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', implode(' ', [$teamId, $name])));
|
||||
->setAttribute('search', implode(' ', [$teamId, $name]));
|
||||
|
||||
$team = $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $team) {
|
||||
return $dbForProject->updateDocument('teams', $team->getId(), $team);
|
||||
});
|
||||
|
||||
$events->setParam('teamId', $team->getId());
|
||||
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
App::put('/v1/teams/:teamId/prefs')
|
||||
->desc('Update Preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update.prefs')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.update')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'updatePrefs')
|
||||
->label('sdk.description', '/docs/references/teams/update-team-prefs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PREFERENCES)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/prefs')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, array $prefs, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('prefs', $prefs));
|
||||
|
||||
$events->setParam('teamId', $team->getId());
|
||||
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::delete('/v1/teams/:teamId')
|
||||
->desc('Delete Team')
|
||||
->groups(['api', 'teams'])
|
||||
@@ -304,7 +379,9 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->label('abuse-limit', 10)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('email', '', new Email(), 'Email of the new team member.')
|
||||
->param('email', '', new Email(), 'Email of the new team member.', true)
|
||||
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation 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']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
@@ -314,9 +391,13 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('mails')
|
||||
->inject('messaging')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $mails, Event $events) {
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $mails, EventPhone $messaging, Event $events) {
|
||||
|
||||
if (empty($userId) && empty($email) && empty($phone)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required');
|
||||
}
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
@@ -331,8 +412,31 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
|
||||
if (!empty($userId)) {
|
||||
$invitee = $dbForProject->getDocument('users', $userId);
|
||||
if ($invitee->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND, 'User with given userId doesn\'t exist.', 404);
|
||||
}
|
||||
if (!empty($email) && $invitee->getAttribute('email', '') !== $email) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given userId and email doesn\'t match', 409);
|
||||
}
|
||||
if (!empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given userId and phone doesn\'t match', 409);
|
||||
}
|
||||
$email = $invitee->getAttribute('email', '');
|
||||
$phone = $invitee->getAttribute('phone', '');
|
||||
$name = empty($name) ? $invitee->getAttribute('name', '') : $name;
|
||||
} elseif (!empty($email)) {
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
|
||||
if (!empty($invitee) && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given email and phone doesn\'t match', 409);
|
||||
}
|
||||
} elseif (!empty($phone)) {
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]);
|
||||
if (!empty($invitee) && !empty($email) && $invitee->getAttribute('email', '') !== $email) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given phone and email doesn\'t match', 409);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($invitee)) { // Create new user if no user with same email found
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
@@ -355,7 +459,8 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => $email,
|
||||
'email' => empty($email) ? null : $email,
|
||||
'phone' => empty($phone) ? null : $phone,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
@@ -427,46 +532,50 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
$url = Template::parseURL($url);
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
|
||||
$url = Template::unParseURL($url);
|
||||
$url = Template::parseURL($url);
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
|
||||
$url = Template::unParseURL($url);
|
||||
if (!empty($email)) {
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
|
||||
$body->setParam('{{owner}}', $user->getAttribute('name'));
|
||||
$body->setParam('{{team}}', $team->getAttribute('name'));
|
||||
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
|
||||
$body->setParam('{{owner}}', $user->getAttribute('name'));
|
||||
$body->setParam('{{team}}', $team->getAttribute('name'));
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{name}}', $user->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.invitation.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{name}}', $user->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.invitation.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
$body = $body->render();
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
$mails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name'))
|
||||
->trigger()
|
||||
;
|
||||
$mails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name'))
|
||||
->trigger()
|
||||
;
|
||||
} elseif (!empty($phone)) {
|
||||
$messaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($url)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
$events
|
||||
@@ -496,8 +605,9 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/memberships')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -577,6 +687,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/memberships')
|
||||
->label('sdk.offline.key', '{membershipId}')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->inject('response')
|
||||
@@ -890,7 +1002,7 @@ App::get('/v1/teams/:teamId/logs')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOG_LIST)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true)
|
||||
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
|
||||
@@ -34,11 +34,14 @@ use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Boolean;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Validator\Integer;
|
||||
use Appwrite\Auth\Validator\PasswordHistory;
|
||||
use Appwrite\Auth\Validator\PasswordDictionary;
|
||||
|
||||
/** TODO: Remove function when we move to using utopia/platform */
|
||||
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Database $dbForProject, Event $events): Document
|
||||
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $events): Document
|
||||
{
|
||||
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
|
||||
if (!empty($email)) {
|
||||
$email = \strtolower($email);
|
||||
@@ -49,6 +52,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
? ID::unique()
|
||||
: ID::custom($userId);
|
||||
|
||||
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
|
||||
$user = $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
@@ -61,10 +65,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
'phone' => $phone,
|
||||
'phoneVerification' => false,
|
||||
'status' => true,
|
||||
'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null,
|
||||
'password' => $password,
|
||||
'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password],
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
@@ -101,13 +106,15 @@ App::post('/v1/users')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', null, new Email(), 'User email.', true)
|
||||
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true)
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -134,10 +141,11 @@ App::post('/v1/users/bcrypt')
|
||||
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -164,10 +172,11 @@ App::post('/v1/users/md5')
|
||||
->param('password', '', new Password(), 'User password hashed using MD5.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -194,10 +203,11 @@ App::post('/v1/users/argon2')
|
||||
->param('password', '', new Password(), 'User password hashed using Argon2.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -225,16 +235,17 @@ App::post('/v1/users/sha')
|
||||
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$options = '{}';
|
||||
|
||||
if (!empty($passwordVersion)) {
|
||||
$options = '{"version":"' . $passwordVersion . '"}';
|
||||
}
|
||||
|
||||
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -261,10 +272,11 @@ App::post('/v1/users/phpass')
|
||||
->param('password', '', new Password(), 'User password hashed using PHPass.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -296,9 +308,10 @@ App::post('/v1/users/scrypt')
|
||||
->param('passwordLength', 64, new Integer(), 'Optional hash length used to hash password.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$options = [
|
||||
'salt' => $passwordSalt,
|
||||
'costCpu' => $passwordCpu,
|
||||
@@ -307,7 +320,7 @@ App::post('/v1/users/scrypt')
|
||||
'length' => $passwordLength
|
||||
];
|
||||
|
||||
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -337,10 +350,11 @@ App::post('/v1/users/scrypt-modified')
|
||||
->param('passwordSignerKey', '', new Text(128), 'Signer key used to hash password.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -359,7 +373,7 @@ App::get('/v1/users')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER_LIST)
|
||||
->param('queries', [], new Users(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Users::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Users(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Users::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -443,7 +457,7 @@ App::get('/v1/users/:userId/prefs')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$prefs = $user->getAttribute('prefs', new \stdClass());
|
||||
$prefs = $user->getAttribute('prefs', []);
|
||||
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
@@ -543,7 +557,7 @@ App::get('/v1/users/:userId/logs')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOG_LIST)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true)
|
||||
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
@@ -779,11 +793,12 @@ App::patch('/v1/users/:userId/password')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $password, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -791,11 +806,27 @@ App::patch('/v1/users/:userId/password')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
$history = [];
|
||||
if ($historyLimit > 0) {
|
||||
$history = $user->getAttribute('passwordHistory', []);
|
||||
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
|
||||
if (!$validator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409);
|
||||
}
|
||||
|
||||
$history[] = $newPassword;
|
||||
array_slice($history, (count($history) - $historyLimit), $historyLimit);
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('password', $newPassword)
|
||||
->setAttribute('passwordHistory', $history)
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', DateTime::now());
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ App::init()
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $refDomain)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
@@ -384,7 +384,7 @@ App::options()
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
@@ -485,6 +485,10 @@ App::error()
|
||||
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
break;
|
||||
}
|
||||
} elseif ($error instanceof Utopia\Database\Exception\Conflict) {
|
||||
$error = new AppwriteException(AppwriteException::DOCUMENT_UPDATE_CONFLICT, null, null, $error);
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
}
|
||||
|
||||
/** Wrap all exceptions inside Appwrite\Extend\Exception */
|
||||
@@ -590,7 +594,7 @@ App::get('/.well-known/acme-challenge')
|
||||
$uriChunks = \explode('/', $request->getURI());
|
||||
$token = $uriChunks[\count($uriChunks) - 1];
|
||||
|
||||
$validator = new Text(100, [
|
||||
$validator = new Text(100, allowList: [
|
||||
...Text::NUMBERS,
|
||||
...Text::ALPHABET_LOWER,
|
||||
...Text::ALPHABET_UPPER,
|
||||
|
||||
@@ -15,6 +15,7 @@ use Utopia\Validator\Text;
|
||||
use Utopia\Storage\Validator\File;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
App::get('/v1/mock/tests/foo')
|
||||
->desc('Get Foo')
|
||||
@@ -135,6 +136,8 @@ App::post('/v1/mock/tests/bar')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.offline.model', '/mock/tests/bar')
|
||||
->label('sdk.offline.key', '{required}')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
@@ -265,7 +268,7 @@ App::post('/v1/mock/tests/general/upload')
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->param('file', [], new File(), 'Sample file param', false)
|
||||
->param('file', [], new File(), 'Sample file param', skipValidation: true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (string $x, int $y, array $z, mixed $file, Request $request, Response $response) {
|
||||
@@ -426,6 +429,21 @@ App::get('/v1/mock/tests/general/empty')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/nullable')
|
||||
->desc('Nullable Test')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'nullable')
|
||||
->label('sdk.description', 'Mock a nullable parameter.')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('nullable', '', new Nullable(new Text(100)), 'Sample string param')
|
||||
->param('optional', '', new Text(100), 'Sample string param', true)
|
||||
->action(function (string $required, string $nullable, ?string $optional) {
|
||||
});
|
||||
|
||||
/** Endpoint to test if required headers are sent from the SDK */
|
||||
App::get('/v1/mock/tests/general/headers')
|
||||
->desc('Get headers')
|
||||
|
||||
@@ -5,7 +5,6 @@ use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Usage\Stats;
|
||||
@@ -16,7 +15,6 @@ use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
@@ -17,7 +17,7 @@ App::get('/versions')
|
||||
];
|
||||
|
||||
foreach ($platforms as $platform) {
|
||||
$languages = $platform['languages'] ?? [];
|
||||
$languages = $platform['sdks'] ?? [];
|
||||
|
||||
foreach ($languages as $key => $language) {
|
||||
if (isset($language['dev']) && $language['dev']) {
|
||||
|
||||
+7
-3
@@ -165,14 +165,14 @@ App::post('/v1/runtimes')
|
||||
->desc("Create a new runtime server")
|
||||
->param('runtimeId', '', new Text(64), 'Unique runtime ID.')
|
||||
->param('source', '', new Text(0), 'Path to source files.')
|
||||
->param('destination', '', new Text(0), 'Destination folder to store build files into.', true)
|
||||
->param('destination', '', new Text(0, 0), 'Destination folder to store build files into.', true)
|
||||
->param('vars', [], new Assoc(), 'Environment Variables required for the build.')
|
||||
->param('commands', [], new ArrayList(new Text(1024), 100), 'Commands required to build the container. Maximum of 100 commands are allowed, each 1024 characters long.')
|
||||
->param('runtime', '', new Text(128), 'Runtime for the cloud function.')
|
||||
->param('baseImage', '', new Text(128), 'Base image name of the runtime.')
|
||||
->param('entrypoint', '', new Text(256), 'Entrypoint of the code file.', true)
|
||||
->param('remove', false, new Boolean(), 'Remove a runtime after execution.')
|
||||
->param('workdir', '', new Text(256), 'Working directory.', true)
|
||||
->param('workdir', '', new Text(256, 0), 'Working directory.', true)
|
||||
->inject('orchestrationPool')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
@@ -459,7 +459,7 @@ App::post('/v1/execution')
|
||||
->desc('Create an execution')
|
||||
->param('runtimeId', '', new Text(64), 'The runtimeID to execute.')
|
||||
->param('vars', [], new Assoc(), 'Environment variables required for the build.')
|
||||
->param('data', '', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
|
||||
->param('data', '', new Text(8192, 0), 'Data to be forwarded to the function, this is user specified.', true)
|
||||
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
@@ -548,6 +548,10 @@ App::post('/v1/execution')
|
||||
case $statusCode >= 500:
|
||||
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Internal Runtime error.';
|
||||
$stdout = ($executorResponse ?? [])['stdout'] ?? 'Internal Runtime error.';
|
||||
$res = ($executorResponse ?? [])['response'] ?? '';
|
||||
if (is_array($res)) {
|
||||
$res = json_encode($res, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
break;
|
||||
case $statusCode >= 100:
|
||||
$stdout = $executorResponse['stdout'];
|
||||
|
||||
+1
-2
@@ -92,7 +92,6 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
try {
|
||||
$redis->flushAll();
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create();
|
||||
} catch (\Exception $e) {
|
||||
@@ -296,7 +295,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
||||
$log->addExtra('line', $th->getLine());
|
||||
$log->addExtra('trace', $th->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $th->getTrace());
|
||||
$log->addExtra('roles', Authorization::$roles);
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
|
||||
+47
-10
@@ -53,7 +53,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
@@ -86,6 +86,7 @@ const APP_MODE_ADMIN = 'admin';
|
||||
const APP_PAGING_LIMIT = 12;
|
||||
const APP_LIMIT_COUNT = 5000;
|
||||
const APP_LIMIT_USERS = 10000;
|
||||
const APP_LIMIT_USER_PASSWORD_HISTORY = 20;
|
||||
const APP_LIMIT_USER_SESSIONS_MAX = 100;
|
||||
const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
|
||||
const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
|
||||
@@ -99,8 +100,8 @@ const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate pe
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 501;
|
||||
const APP_VERSION_STABLE = '1.2.1';
|
||||
const APP_CACHE_BUSTER = 503;
|
||||
const APP_VERSION_STABLE = '1.3.1';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
@@ -286,12 +287,23 @@ Database::addFilter(
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('attributes', [
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForAttributes()),
|
||||
]);
|
||||
$attributes = $database->find('attributes', [
|
||||
Query::equal('collectionInternalId', [$document->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForAttributes()),
|
||||
]);
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
|
||||
$options = $attribute->getAttribute('options');
|
||||
foreach ($options as $key => $value) {
|
||||
$attribute->setAttribute($key, $value);
|
||||
}
|
||||
$attribute->removeAttribute('options');
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -609,6 +621,12 @@ $register->set('smtp', function () {
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2023-01.mmdb');
|
||||
});
|
||||
$register->set('passwordsDictionary', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
|
||||
$content = explode("\n", $content);
|
||||
$content = array_flip($content);
|
||||
return $content;
|
||||
});
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
$dbHost = App::getEnv('_APP_DB_HOST', '');
|
||||
@@ -1029,6 +1047,11 @@ App::setResource('geodb', function ($register) {
|
||||
return $register->get('geodb');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('passwordsDictionary', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('passwordsDictionary');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('sms', function () {
|
||||
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$user = $dsn->getUser();
|
||||
@@ -1051,7 +1074,7 @@ App::setResource('servers', function () {
|
||||
|
||||
$languages = array_map(function ($language) {
|
||||
return strtolower($language['name']);
|
||||
}, $server['languages']);
|
||||
}, $server['sdks']);
|
||||
|
||||
return $languages;
|
||||
});
|
||||
@@ -1145,3 +1168,17 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
$params,
|
||||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
|
||||
App::setResource('requestTimestamp', function ($request) {
|
||||
//TODO: Move this to the Request class itself
|
||||
$timestampHeader = $request->getHeader('x-appwrite-timestamp');
|
||||
$requestTimestamp = null;
|
||||
if (!empty($timestampHeader)) {
|
||||
try {
|
||||
$requestTimestamp = new \DateTime($timestampHeader);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value');
|
||||
}
|
||||
}
|
||||
return $requestTimestamp;
|
||||
}, ['request']);
|
||||
|
||||
@@ -85,10 +85,11 @@ $cli
|
||||
try {
|
||||
$migration
|
||||
->setProject($project, $projectDB, $consoleDB)
|
||||
->setPDO($register->get('db'))
|
||||
->execute();
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
|
||||
throw $th;
|
||||
}
|
||||
|
||||
clearProjectsCache($redis, $project);
|
||||
|
||||
+16
-6
@@ -26,19 +26,24 @@ $cli
|
||||
->task('sdks')
|
||||
->action(function () {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$selected = \strtolower(Console::confirm('Choose SDK ("*" for all):'));
|
||||
$selectedPlatform = Console::confirm('Choose Platform ("' . APP_PLATFORM_CLIENT . '", "' . APP_PLATFORM_SERVER . '", "' . APP_PLATFORM_CONSOLE . '" or "*" for all):');
|
||||
$selectedSDK = \strtolower(Console::confirm('Choose SDK ("*" for all):'));
|
||||
$version = Console::confirm('Choose an Appwrite version');
|
||||
$git = (Console::confirm('Should we use git push? (yes/no)') == 'yes');
|
||||
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
|
||||
$message = ($git) ? Console::confirm('Please enter your commit message:') : '';
|
||||
|
||||
if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', '0.15.x', '1.0.x', '1.1.x', '1.2.x', 'latest'])) {
|
||||
if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', '0.15.x', '1.0.x', '1.1.x', '1.2.x', '1.3.x', 'latest'])) {
|
||||
throw new Exception('Unknown version given');
|
||||
}
|
||||
|
||||
foreach ($platforms as $key => $platform) {
|
||||
foreach ($platform['languages'] as $language) {
|
||||
if ($selected !== $language['key'] && $selected !== '*') {
|
||||
if ($selectedPlatform !== $key && $selectedPlatform !== '*') {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($platform['sdks'] as $language) {
|
||||
if ($selectedSDK !== $language['key'] && $selectedSDK !== '*') {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -81,8 +86,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
switch ($language['key']) {
|
||||
case 'web':
|
||||
$config = new Web();
|
||||
$config->setNPMPackage('appwrite');
|
||||
$config->setBowerPackage('appwrite');
|
||||
if ($platform['key'] === APP_PLATFORM_CONSOLE) {
|
||||
$config->setNPMPackage('@appwrite.io/console');
|
||||
$config->setBowerPackage('@appwrite.io/console');
|
||||
} else {
|
||||
$config->setNPMPackage('appwrite');
|
||||
$config->setBowerPackage('appwrite');
|
||||
}
|
||||
break;
|
||||
case 'cli':
|
||||
$config = new CLI();
|
||||
|
||||
@@ -383,7 +383,7 @@ class CertificatesV1 extends Worker
|
||||
$locale->setDefault('en');
|
||||
}
|
||||
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$body = Template::fromFile(__DIR__ . '/../config/locale/templates/email-base.tpl');
|
||||
|
||||
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
|
||||
$body->setParam('{{domain}}', $domain);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Resque\Worker;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
@@ -89,16 +92,51 @@ class DatabaseV1 extends Worker
|
||||
$format = $attribute->getAttribute('format', '');
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
$filters = $attribute->getAttribute('filters', []);
|
||||
$options = $attribute->getAttribute('options', []);
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
try {
|
||||
if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
|
||||
throw new Exception('Failed to create Attribute');
|
||||
switch ($type) {
|
||||
case Database::VAR_RELATIONSHIP:
|
||||
$relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']);
|
||||
if ($relatedCollection->isEmpty()) {
|
||||
throw new Exception('Collection not found');
|
||||
}
|
||||
|
||||
if (
|
||||
!$dbForProject->createRelationship(
|
||||
collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(),
|
||||
type: $options['relationType'],
|
||||
twoWay: $options['twoWay'],
|
||||
id: $key,
|
||||
twoWayKey: $options['twoWayKey'],
|
||||
onDelete: $options['onDelete'],
|
||||
)
|
||||
) {
|
||||
throw new Exception('Failed to create Attribute');
|
||||
}
|
||||
|
||||
if ($options['twoWay']) {
|
||||
$relatedAttribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']);
|
||||
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'available'));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
|
||||
throw new Exception('Failed to create Attribute');
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available'));
|
||||
} catch (\Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed'));
|
||||
|
||||
if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) {
|
||||
$relatedAttribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']);
|
||||
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'failed'));
|
||||
}
|
||||
} finally {
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
@@ -121,6 +159,10 @@ class DatabaseV1 extends Worker
|
||||
);
|
||||
}
|
||||
|
||||
if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) {
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId);
|
||||
}
|
||||
|
||||
@@ -129,6 +171,7 @@ class DatabaseV1 extends Worker
|
||||
* @param Document $collection
|
||||
* @param Document $attribute
|
||||
* @param string $projectId
|
||||
* @throws Throwable
|
||||
*/
|
||||
protected function deleteAttribute(Document $database, Document $collection, Document $attribute, string $projectId): void
|
||||
{
|
||||
@@ -143,19 +186,43 @@ class DatabaseV1 extends Worker
|
||||
$collectionId = $collection->getId();
|
||||
$key = $attribute->getAttribute('key', '');
|
||||
$status = $attribute->getAttribute('status', '');
|
||||
$type = $attribute->getAttribute('type', '');
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
$options = $attribute->getAttribute('options', []);
|
||||
$relatedAttribute = new Document();
|
||||
$relatedCollection = new Document();
|
||||
// possible states at this point:
|
||||
// - available: should not land in queue; controller flips these to 'deleting'
|
||||
// - processing: hasn't finished creating
|
||||
// - deleting: was available, in deletion queue for first time
|
||||
// - failed: attribute was never created
|
||||
// - stuck: attribute was available but cannot be removed
|
||||
|
||||
try {
|
||||
if ($status !== 'failed' && !$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
|
||||
throw new Exception('Failed to delete Attribute');
|
||||
if ($status !== 'failed') {
|
||||
if ($type === Database::VAR_RELATIONSHIP) {
|
||||
if ($options['twoWay']) {
|
||||
$relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']);
|
||||
if ($relatedCollection->isEmpty()) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
$relatedAttribute = $dbForProject->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']);
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteRelationship('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
|
||||
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'stuck'));
|
||||
throw new Exception('Failed to delete Relationship');
|
||||
}
|
||||
} elseif (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
|
||||
throw new Exception('Failed to delete Attribute');
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('attributes', $attribute->getId());
|
||||
|
||||
if (!$relatedAttribute->isEmpty()) {
|
||||
$dbForProject->deleteDocument('attributes', $relatedAttribute->getId());
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'stuck'));
|
||||
@@ -199,8 +266,8 @@ class DatabaseV1 extends Worker
|
||||
// array_values wraps array_diff to reindex array keys
|
||||
// when found attribute is removed from array
|
||||
$attributes = \array_values(\array_diff($attributes, [$attributes[$found]]));
|
||||
$lengths = \array_values(\array_diff($lengths, [$lengths[$found]]));
|
||||
$orders = \array_values(\array_diff($orders, [$orders[$found]]));
|
||||
$lengths = \array_values(\array_diff($lengths, isset($lengths[$found]) ? [$lengths[$found]] : []));
|
||||
$orders = \array_values(\array_diff($orders, isset($orders[$found]) ? [$orders[$found]] : []));
|
||||
|
||||
if (empty($attributes)) {
|
||||
$dbForProject->deleteDocument('indexes', $index->getId());
|
||||
@@ -235,6 +302,11 @@ class DatabaseV1 extends Worker
|
||||
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId);
|
||||
$dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||
|
||||
if (!$relatedCollection->isEmpty() && !$relatedAttribute->isEmpty()) {
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
|
||||
$dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,9 @@ class DeletesV1 extends Worker
|
||||
break;
|
||||
case DELETE_TYPE_TEAMS:
|
||||
$this->deleteMemberships($document, $project->getId());
|
||||
if ($project->getId() === 'console') {
|
||||
$this->deleteProjectsByTeam($document);
|
||||
}
|
||||
break;
|
||||
case DELETE_TYPE_BUCKETS:
|
||||
$this->deleteBucket($document, $project->getId());
|
||||
@@ -203,6 +206,21 @@ class DeletesV1 extends Worker
|
||||
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$relationships = \array_filter(
|
||||
$document->getAttribute('attributes'),
|
||||
fn ($attribute) => $attribute['type'] === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
if (!$relationship['twoWay']) {
|
||||
continue;
|
||||
}
|
||||
$relatedCollection = $dbForProject->getDocument('database_' . $databaseInternalId, $relationship['relatedCollection']);
|
||||
$dbForProject->deleteDocument('attributes', $databaseInternalId . '_' . $relatedCollection->getInternalId() . '_' . $relationship['twoWayKey']);
|
||||
$dbForProject->deleteCachedDocument('database_' . $databaseInternalId, $relatedCollection->getId());
|
||||
$dbForProject->deleteCachedCollection('database_' . $databaseInternalId . '_collection_' . $relatedCollection->getInternalId());
|
||||
}
|
||||
|
||||
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $document->getInternalId());
|
||||
|
||||
$this->deleteByGroup('attributes', [
|
||||
@@ -248,6 +266,24 @@ class DeletesV1 extends Worker
|
||||
], $this->getProjectDB($projectId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Utopia\Database\Document $document
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function deleteProjectsByTeam(Document $document): void
|
||||
{
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::equal('teamInternalId', [$document->getInternalId()])
|
||||
]);
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$this->deleteProject($project);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document project document
|
||||
* @throws Exception
|
||||
|
||||
+8
-8
@@ -43,28 +43,28 @@
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-clamav": "1.1.*",
|
||||
"appwrite/php-runtimes": "0.11.*",
|
||||
"utopia-php/abuse": "0.18.*",
|
||||
"utopia-php/abuse": "0.24.*",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
"utopia-php/audit": "0.20.*",
|
||||
"utopia-php/audit": "0.25.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cli": "0.13.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.30.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/database": "0.35.*",
|
||||
"utopia-php/domains": "1.1.*",
|
||||
"utopia-php/framework": "0.26.*",
|
||||
"utopia-php/framework": "0.28.*",
|
||||
"utopia-php/image": "0.5.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/messaging": "0.1.*",
|
||||
"utopia-php/orchestration": "0.6.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.13.*",
|
||||
"utopia-php/storage": "0.14.*",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
"utopia-php/transfer": "dev-feat-database-and-documents",
|
||||
"resque/php-resque": "1.3.6",
|
||||
"matomo/device-detector": "6.0.0",
|
||||
"matomo/device-detector": "6.0.*",
|
||||
"dragonmantank/cron-expression": "3.3.1",
|
||||
"influxdb/influxdb-php": "1.15.2",
|
||||
"phpmailer/phpmailer": "6.6.0",
|
||||
@@ -85,7 +85,7 @@
|
||||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "0.29.*",
|
||||
"appwrite/sdk-generator": "0.32.*",
|
||||
"ext-fileinfo": "*",
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
|
||||
Generated
+192
-102
@@ -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": "f21d616c3ccd0256ce537cba5711749c",
|
||||
"content-hash": "a196257ea0952cad0b534301e4e63966",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -1041,16 +1041,16 @@
|
||||
},
|
||||
{
|
||||
"name": "matomo/device-detector",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matomo-org/device-detector.git",
|
||||
"reference": "7fc2af3af62bd69e6e3404d561e371a83c112be9"
|
||||
"reference": "ce5ef5e6776c16af306d38e20674973f072e05ed"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/7fc2af3af62bd69e6e3404d561e371a83c112be9",
|
||||
"reference": "7fc2af3af62bd69e6e3404d561e371a83c112be9",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/ce5ef5e6776c16af306d38e20674973f072e05ed",
|
||||
"reference": "ce5ef5e6776c16af306d38e20674973f072e05ed",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1106,7 +1106,7 @@
|
||||
"source": "https://github.com/matomo-org/matomo",
|
||||
"wiki": "https://dev.matomo.org/"
|
||||
},
|
||||
"time": "2022-04-11T09:58:17+00:00"
|
||||
"time": "2023-01-16T08:18:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mongodb/mongodb",
|
||||
@@ -1355,21 +1355,21 @@
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-client.git",
|
||||
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
|
||||
"reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
||||
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31",
|
||||
"reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"psr/http-message": "^1.0"
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -1389,7 +1389,7 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP clients",
|
||||
@@ -1401,27 +1401,27 @@
|
||||
"psr-18"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-client/tree/master"
|
||||
"source": "https://github.com/php-fig/http-client/tree/1.0.2"
|
||||
},
|
||||
"time": "2020-06-29T06:28:15+00:00"
|
||||
"time": "2023-04-10T20:12:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-factory.git",
|
||||
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
|
||||
"reference": "e616d01114759c4c489f93b099585439f795fe35"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
||||
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
|
||||
"reference": "e616d01114759c4c489f93b099585439f795fe35",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"psr/http-message": "^1.0"
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -1441,7 +1441,7 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interfaces for PSR-7 HTTP message factories",
|
||||
@@ -1456,31 +1456,31 @@
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-factory/tree/master"
|
||||
"source": "https://github.com/php-fig/http-factory/tree/1.0.2"
|
||||
},
|
||||
"time": "2019-04-30T12:38:16+00:00"
|
||||
"time": "2023-04-10T20:10:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
|
||||
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
|
||||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -1509,9 +1509,9 @@
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-message/tree/master"
|
||||
"source": "https://github.com/php-fig/http-message/tree/1.1"
|
||||
},
|
||||
"time": "2016-08-06T14:39:51+00:00"
|
||||
"time": "2023-04-04T09:50:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
@@ -1898,29 +1898,28 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/abuse",
|
||||
"version": "0.18.0",
|
||||
"version": "0.24.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/abuse.git",
|
||||
"reference": "8496401234f73a49f8c4259d3e89ab4a7c1f9ecf"
|
||||
"reference": "403641f16a53b81ac40b91111a86e5672da49e8c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/8496401234f73a49f8c4259d3e89ab4a7c1f9ecf",
|
||||
"reference": "8496401234f73a49f8c4259d3e89ab4a7c1f9ecf",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/403641f16a53b81ac40b91111a86e5672da49e8c",
|
||||
"reference": "403641f16a53b81ac40b91111a86e5672da49e8c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.30.*"
|
||||
"utopia-php/database": "0.35.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpstan/phpstan": "1.9.x-dev",
|
||||
"phpunit/phpunit": "^9.4",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^9.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1942,9 +1941,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/abuse/issues",
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.18.0"
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.24.0"
|
||||
},
|
||||
"time": "2023-02-14T09:56:04+00:00"
|
||||
"time": "2023-04-11T05:31:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
@@ -2003,28 +2002,26 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "0.20.0",
|
||||
"version": "0.25.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "3fce3f4ad3ea9dfcb39b79668abd76331412a5ed"
|
||||
"reference": "adc209f2e16878e5468f0b9cfd9f7f7ab497db31"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/3fce3f4ad3ea9dfcb39b79668abd76331412a5ed",
|
||||
"reference": "3fce3f4ad3ea9dfcb39b79668abd76331412a5ed",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/adc209f2e16878e5468f0b9cfd9f7f7ab497db31",
|
||||
"reference": "adc209f2e16878e5468f0b9cfd9f7f7ab497db31",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.30.*"
|
||||
"utopia-php/database": "0.35.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -2046,9 +2043,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.20.0"
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.25.0"
|
||||
},
|
||||
"time": "2023-02-14T09:46:54+00:00"
|
||||
"time": "2023-04-11T05:31:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
@@ -2205,33 +2202,37 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "0.30.1",
|
||||
"version": "0.35.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65"
|
||||
"reference": "b5ac84e0c77145bd0a7f38718ad915729c64fa93"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/1cea72c1217357bf0747ae4f28ebef57e9dc0e65",
|
||||
"reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/b5ac84e0c77145bd0a7f38718ad915729c64fa93",
|
||||
"reference": "b5ac84e0c77145bd0a7f38718ad915729c64fa93",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/framework": "0.*.*",
|
||||
"utopia-php/mongo": "0.0.2"
|
||||
"utopia-php/mongo": "0.2.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-mongodb": "*",
|
||||
"ext-redis": "*",
|
||||
"fakerphp/faker": "^1.14",
|
||||
"laravel/pint": "1.4.*",
|
||||
"mongodb/mongodb": "1.8.0",
|
||||
"pcov/clobber": "^2.0",
|
||||
"phpstan/phpstan": "1.10.*",
|
||||
"phpunit/phpunit": "^9.4",
|
||||
"rregeer/phpunit-coverage-check": "^0.3.1",
|
||||
"swoole/ide-helper": "4.8.0",
|
||||
"utopia-php/cli": "^0.14.0",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
"utopia-php/cli": "^0.14.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -2243,7 +2244,7 @@
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A simple library to manage application persistency using multiple database adapters",
|
||||
"description": "A simple library to manage application persistence using multiple database adapters",
|
||||
"keywords": [
|
||||
"database",
|
||||
"framework",
|
||||
@@ -2253,9 +2254,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.30.1"
|
||||
"source": "https://github.com/utopia-php/database/tree/0.35.1"
|
||||
},
|
||||
"time": "2023-02-14T06:25:03+00:00"
|
||||
"time": "2023-04-13T04:30:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
@@ -2313,16 +2314,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "0.26.0",
|
||||
"version": "0.28.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/framework.git",
|
||||
"reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e"
|
||||
"reference": "7f22c556fc5991e54e5811a68fb39809b21bda55"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e",
|
||||
"reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e",
|
||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/7f22c556fc5991e54e5811a68fb39809b21bda55",
|
||||
"reference": "7f22c556fc5991e54e5811a68fb39809b21bda55",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2351,9 +2352,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/framework/issues",
|
||||
"source": "https://github.com/utopia-php/framework/tree/0.26.0"
|
||||
"source": "https://github.com/utopia-php/framework/tree/0.28.1"
|
||||
},
|
||||
"time": "2023-01-13T08:14:43+00:00"
|
||||
"time": "2023-03-02T08:16:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
@@ -2561,16 +2562,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
"version": "0.0.2",
|
||||
"version": "0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/mongo.git",
|
||||
"reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e"
|
||||
"reference": "b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/mongo/zipball/62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e",
|
||||
"reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e",
|
||||
"url": "https://api.github.com/repos/utopia-php/mongo/zipball/b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09",
|
||||
"reference": "b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2615,9 +2616,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/mongo/issues",
|
||||
"source": "https://github.com/utopia-php/mongo/tree/0.0.2"
|
||||
"source": "https://github.com/utopia-php/mongo/tree/0.2.0"
|
||||
},
|
||||
"time": "2022-11-08T11:58:46+00:00"
|
||||
"time": "2023-03-22T10:44:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/orchestration",
|
||||
@@ -2781,26 +2782,30 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/storage",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/storage.git",
|
||||
"reference": "ad1c00f24ca56e73888acc2af3deee4919b1194b"
|
||||
"reference": "eda6651ac16884dc2a79ecb984ea591ba1ed498c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/ad1c00f24ca56e73888acc2af3deee4919b1194b",
|
||||
"reference": "ad1c00f24ca56e73888acc2af3deee4919b1194b",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/eda6651ac16884dc2a79ecb984ea591ba1ed498c",
|
||||
"reference": "eda6651ac16884dc2a79ecb984ea591ba1ed498c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-brotli": "*",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-lz4": "*",
|
||||
"ext-snappy": "*",
|
||||
"ext-zlib": "*",
|
||||
"ext-zstd": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/framework": "0.*.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
},
|
||||
@@ -2814,12 +2819,6 @@
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Eldad Fux",
|
||||
"email": "eldad@appwrite.io"
|
||||
}
|
||||
],
|
||||
"description": "A simple Storage library to manage application storage",
|
||||
"keywords": [
|
||||
"framework",
|
||||
@@ -2830,9 +2829,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/storage/issues",
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.13.2"
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.14.0"
|
||||
},
|
||||
"time": "2022-12-20T11:11:35+00:00"
|
||||
"time": "2023-03-15T00:16:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/swoole",
|
||||
@@ -2948,12 +2947,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/transfer.git",
|
||||
"reference": "fae74e218c5deaa8ec13a7e85921791f8974948f"
|
||||
"reference": "579e02466432d88ba92d70e86553d3e4cc5af867"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/transfer/zipball/fae74e218c5deaa8ec13a7e85921791f8974948f",
|
||||
"reference": "fae74e218c5deaa8ec13a7e85921791f8974948f",
|
||||
"url": "https://api.github.com/repos/utopia-php/transfer/zipball/579e02466432d88ba92d70e86553d3e4cc5af867",
|
||||
"reference": "579e02466432d88ba92d70e86553d3e4cc5af867",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2963,7 +2962,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"squizlabs/php_codesniffer": "3.*",
|
||||
"vimeo/psalm": "^5.6",
|
||||
"vlucas/phpdotenv": "^5.5"
|
||||
},
|
||||
@@ -2998,7 +2997,7 @@
|
||||
"source": "https://github.com/utopia-php/transfer/tree/feat-database-and-documents",
|
||||
"issues": "https://github.com/utopia-php/transfer/issues"
|
||||
},
|
||||
"time": "2023-02-24T16:24:24+00:00"
|
||||
"time": "2023-04-03T09:31:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/websocket",
|
||||
@@ -3186,16 +3185,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "0.29.4",
|
||||
"version": "0.32.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "35ec927d1de1854bebe8894e16b1646c3fdd5567"
|
||||
"reference": "cdec289bcf38c99d0074414d2438e9967d0c9699"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/35ec927d1de1854bebe8894e16b1646c3fdd5567",
|
||||
"reference": "35ec927d1de1854bebe8894e16b1646c3fdd5567",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/cdec289bcf38c99d0074414d2438e9967d0c9699",
|
||||
"reference": "cdec289bcf38c99d0074414d2438e9967d0c9699",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3231,9 +3230,52 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.29.4"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.32.2"
|
||||
},
|
||||
"time": "2023-02-03T05:44:59+00:00"
|
||||
"time": "2023-04-12T21:06:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/deprecations.git",
|
||||
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
|
||||
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^9",
|
||||
"phpunit/phpunit": "^7.5|^8.5|^9.5",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
|
||||
"homepage": "https://www.doctrine-project.org/",
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/deprecations/issues",
|
||||
"source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
|
||||
},
|
||||
"time": "2022-05-02T15:47:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
@@ -3767,24 +3809,27 @@
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/type-resolver",
|
||||
"version": "1.6.2",
|
||||
"version": "1.7.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
||||
"reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d"
|
||||
"reference": "dfc078e8af9c99210337325ff5aa152872c98714"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d",
|
||||
"reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714",
|
||||
"reference": "dfc078e8af9c99210337325ff5aa152872c98714",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/deprecations": "^1.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpdocumentor/reflection-common": "^2.0"
|
||||
"phpdocumentor/reflection-common": "^2.0",
|
||||
"phpstan/phpdoc-parser": "^1.13"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-tokenizer": "*",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpstan/phpstan-phpunit": "^1.1",
|
||||
@@ -3816,9 +3861,9 @@
|
||||
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
|
||||
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.2"
|
||||
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1"
|
||||
},
|
||||
"time": "2022-10-14T12:47:21+00:00"
|
||||
"time": "2023-03-27T19:02:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
@@ -3888,6 +3933,51 @@
|
||||
},
|
||||
"time": "2023-02-02T15:41:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpdoc-parser",
|
||||
"version": "1.18.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||
"reference": "22dcdfd725ddf99583bfe398fc624ad6c5004a0f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/22dcdfd725ddf99583bfe398fc624ad6c5004a0f",
|
||||
"reference": "22dcdfd725ddf99583bfe398fc624ad6c5004a0f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^1.5",
|
||||
"phpstan/phpstan-phpunit": "^1.1",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"symfony/process": "^5.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\PhpDocParser\\": [
|
||||
"src/"
|
||||
]
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.18.1"
|
||||
},
|
||||
"time": "2023-04-07T11:51:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.26",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
|
||||
echo 'execute init_file success' . PHP_EOL;
|
||||
Yasd\Api\setRemoteHost('127.0.0.1'); //Set your development machine's IP
|
||||
Yasd\Api\setRemoteHost('host.docker.internal'); //Set your development machine's IP
|
||||
|
||||
+2
-2
@@ -83,7 +83,7 @@ services:
|
||||
- ./docs:/usr/src/code/docs
|
||||
- ./public:/usr/src/code/public
|
||||
- ./src:/usr/src/code/src
|
||||
- ./vendor:/usr/src/code/vendor
|
||||
- ./dev:/usr/src/code/dev
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
@@ -344,7 +344,6 @@ services:
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
#- ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database
|
||||
depends_on:
|
||||
- redis
|
||||
- mariadb
|
||||
@@ -474,6 +473,7 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- ./dev:/usr/src/code/dev
|
||||
- appwrite-functions:/storage/functions:rw
|
||||
- appwrite-builds:/storage/builds:rw
|
||||
- /tmp:/tmp:rw
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
appwrite console variables
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Account } from "appwrite";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Avatars } from "appwrite";
|
||||
import { Client, Avatars } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Avatars } from "appwrite";
|
||||
import { Client, Avatars } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Avatars } from "appwrite";
|
||||
import { Client, Avatars } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Avatars } from "appwrite";
|
||||
import { Client, Avatars } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Avatars } from "appwrite";
|
||||
import { Client, Avatars } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Avatars } from "appwrite";
|
||||
import { Client, Avatars } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Avatars } from "appwrite";
|
||||
import { Client, Avatars } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Databases } from "appwrite";
|
||||
import { Client, Databases } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Databases } from "appwrite";
|
||||
import { Client, Databases } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Databases } from "appwrite";
|
||||
import { Client, Databases } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user