Merge remote-tracking branch 'origin/master' into fix-db-attr-number-params
This commit is contained in:
commit
1bcabf8f90
30 changed files with 443 additions and 206 deletions
|
@ -1,10 +1,9 @@
|
||||||
name: "Tests"
|
name: "Tests"
|
||||||
|
|
||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: Unit & E2E
|
name: Unit & E2E
|
||||||
runs-on: self-hosted
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
@ -19,16 +18,29 @@ jobs:
|
||||||
- run: git checkout HEAD^2
|
- run: git checkout HEAD^2
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
- name: Build Appwrite
|
- name: Prepare Docker
|
||||||
# Upstream bug causes buildkit pulls to fail so prefetch base images
|
|
||||||
# https://github.com/moby/moby/issues/41864
|
|
||||||
run: |
|
run: |
|
||||||
|
export COMPOSE_INTERACTIVE_NO_CLI
|
||||||
|
export DOCKER_BUILDKIT=1
|
||||||
|
export COMPOSE_DOCKER_CLI_BUILD=1
|
||||||
echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env
|
echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env
|
||||||
docker pull composer:2.0
|
docker pull composer:2.0
|
||||||
docker pull php:8.0-cli-alpine
|
docker pull php:8.0-cli-alpine
|
||||||
docker compose build --progress=plain
|
docker compose pull
|
||||||
|
|
||||||
|
- name: Prepare Cache
|
||||||
|
uses: satackey/action-docker-layer-caching@v0.0.11
|
||||||
|
# Ignore the failure of a step and avoid terminating the job.
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Build Appwrite
|
||||||
|
run: docker compose build --progress=plain
|
||||||
|
|
||||||
|
- name: Start Appwrite
|
||||||
|
run: |
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
sleep 30
|
sleep 30
|
||||||
|
|
||||||
- name: Doctor
|
- name: Doctor
|
||||||
run: docker compose exec -T appwrite doctor
|
run: docker compose exec -T appwrite doctor
|
||||||
|
|
||||||
|
@ -37,9 +49,3 @@ jobs:
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: docker compose exec -T appwrite test --debug
|
run: docker compose exec -T appwrite test --debug
|
||||||
|
|
||||||
- name: Teardown
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
docker compose down -v
|
|
||||||
docker ps -aq | xargs docker rm --force
|
|
|
@ -1,29 +0,0 @@
|
||||||
#!/bin/bash bash
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
if [ -z "$1" ]
|
|
||||||
then
|
|
||||||
echo "Missing tag number"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$2" ]
|
|
||||||
then
|
|
||||||
echo "Missing version number"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test $(find "./app/db/DBIP/dbip-country-lite-2021-12.mmdb" -mmin +259200)
|
|
||||||
then
|
|
||||||
printf "${RED}GEO country DB has not been updated for more than 6 months. Go to https://db-ip.com/db/download/ip-to-country-lite to download a newer version${NC}\n"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo 'Starting build...'
|
|
||||||
|
|
||||||
docker build --build-arg VERSION="$2" --tag appwrite/appwrite:"$1" .
|
|
||||||
|
|
||||||
echo 'Pushing build to registry...'
|
|
||||||
|
|
||||||
docker push appwrite/appwrite:"$1"
|
|
|
@ -1 +0,0 @@
|
||||||
echo 'Nothing to deploy right now.'
|
|
87
.travis.yml
87
.travis.yml
|
@ -1,87 +0,0 @@
|
||||||
dist: focal
|
|
||||||
|
|
||||||
arch:
|
|
||||||
- amd64
|
|
||||||
|
|
||||||
os: linux
|
|
||||||
|
|
||||||
vm:
|
|
||||||
size: large
|
|
||||||
|
|
||||||
language: shell
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
- team@appwrite.io
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
# Install latest Docker
|
|
||||||
- curl -fsSL https://get.docker.com | sh
|
|
||||||
# Enable Buildkit in Docker config
|
|
||||||
- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json
|
|
||||||
- mkdir -p $HOME/.docker
|
|
||||||
- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json
|
|
||||||
- sudo service docker start
|
|
||||||
# Login to increase Docker Hub ratelimit
|
|
||||||
- >
|
|
||||||
if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then
|
|
||||||
echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin
|
|
||||||
fi
|
|
||||||
- docker --version
|
|
||||||
# Install latest Compose
|
|
||||||
- sudo rm /usr/local/bin/docker-compose
|
|
||||||
- curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > docker-compose
|
|
||||||
- chmod +x docker-compose
|
|
||||||
- sudo mv docker-compose /usr/local/bin
|
|
||||||
- docker-compose --version
|
|
||||||
# Enable Buildkit
|
|
||||||
- docker buildx create --name travis_builder --use
|
|
||||||
- export COMPOSE_INTERACTIVE_NO_CLI
|
|
||||||
- export DOCKER_BUILDKIT=1
|
|
||||||
- export COMPOSE_DOCKER_CLI_BUILD=1
|
|
||||||
- export BUILDKIT_PROGRESS=plain
|
|
||||||
# Only pass a single runtime for CI stability
|
|
||||||
- echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env
|
|
||||||
# Ensure Travis scripts are executable
|
|
||||||
- chmod -R u+x ./.travis-ci
|
|
||||||
|
|
||||||
install:
|
|
||||||
- docker-compose pull
|
|
||||||
# Upstream bug causes buildkit pulls to fail so prefetch base images
|
|
||||||
# https://github.com/moby/moby/issues/41864
|
|
||||||
- docker pull composer:2.0
|
|
||||||
- docker pull php:8.0-cli-alpine
|
|
||||||
- docker-compose build
|
|
||||||
- docker-compose up -d
|
|
||||||
- sleep 60
|
|
||||||
|
|
||||||
script:
|
|
||||||
- docker ps -a
|
|
||||||
# Tests should fail if any container is in exited status
|
|
||||||
# - ALL_UP=`docker ps -aq --filter "status=exited"`
|
|
||||||
# - >
|
|
||||||
# if [[ "$ALL_UP" != "" ]]; then
|
|
||||||
# exit 1
|
|
||||||
# fi
|
|
||||||
- docker-compose logs appwrite
|
|
||||||
- docker-compose logs appwrite-realtime
|
|
||||||
- docker-compose logs mariadb
|
|
||||||
- docker-compose logs appwrite-worker-functions
|
|
||||||
- docker-compose exec appwrite doctor
|
|
||||||
- docker-compose exec appwrite vars
|
|
||||||
- docker-compose exec appwrite test --debug
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
# travis re-uses their build nodes so clean them up
|
|
||||||
- docker buildx rm travis_builder
|
|
||||||
|
|
||||||
after_failure:
|
|
||||||
- docker-compose logs appwrite
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
- provider: script
|
|
||||||
edge: true
|
|
||||||
script: ./.travis-ci/deploy.sh
|
|
||||||
on:
|
|
||||||
repo: appwrite/appwrite
|
|
||||||
branch: deploy
|
|
|
@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
|
||||||
--no-plugins --no-scripts --prefer-dist \
|
--no-plugins --no-scripts --prefer-dist \
|
||||||
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
|
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
|
||||||
|
|
||||||
FROM node:16-alpine as node
|
FROM node:16.13.2-alpine3.15 as node
|
||||||
|
|
||||||
WORKDIR /usr/local/src/
|
WORKDIR /usr/local/src/
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ COPY public /usr/local/src/public
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM php:8.0-cli-alpine as compile
|
FROM php:8.0.14-cli-alpine3.15 as compile
|
||||||
|
|
||||||
ARG DEBUG=false
|
ARG DEBUG=false
|
||||||
ENV DEBUG=$DEBUG
|
ENV DEBUG=$DEBUG
|
||||||
|
@ -123,7 +123,7 @@ RUN \
|
||||||
./configure && \
|
./configure && \
|
||||||
make && make install
|
make && make install
|
||||||
|
|
||||||
FROM php:8.0-cli-alpine as final
|
FROM php:8.0.14-cli-alpine3.15 as final
|
||||||
|
|
||||||
LABEL maintainer="team@appwrite.io"
|
LABEL maintainer="team@appwrite.io"
|
||||||
|
|
||||||
|
|
171
README-CN.md
Normal file
171
README-CN.md
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
<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>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<b>适用于[Flutter/Vue/Angular/React/iOS/Android/* 等等平台 *]的完整后端服务</b>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- [![Hacktoberfest](https://img.shields.io/static/v1?label=hacktoberfest&message=friendly&color=90a88b&style=flat-square)](https://hacktoberfest.appwrite.io) -->
|
||||||
|
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github)
|
||||||
|
[![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite)
|
||||||
|
[![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite)
|
||||||
|
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
|
||||||
|
[![翻译](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md)
|
||||||
|
[![周边商店](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io)
|
||||||
|
|
||||||
|
[English](README.md) | 简体中文
|
||||||
|
|
||||||
|
Appwrite是一个基于dcoker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||||
|
|
||||||
|
Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存, 图像处理,云函数计算,[等多种服务](https:/ /appwrite.io/docs)。
|
||||||
|
|
||||||
|
![Appwrite](public/images/github.png)
|
||||||
|
|
||||||
|
更多信息请到 Appwrite 官网查看: [https://appwrite.io](https://appwrite.io)
|
||||||
|
|
||||||
|
内容:
|
||||||
|
|
||||||
|
- [安装](#安装)
|
||||||
|
- [Unix](#unix)
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [CMD](#cmd)
|
||||||
|
- [PowerShell](#powershell)
|
||||||
|
- [从旧版本升级](#从旧版本升级)
|
||||||
|
- [快速入门](#入门)
|
||||||
|
- [软件服务](#软件服务)
|
||||||
|
- [开发套件](#开发套件)
|
||||||
|
- [客户端](#客户端)
|
||||||
|
- [服务器](#服务器)
|
||||||
|
- [开发者社区](#开发者社区)
|
||||||
|
- [软件架构]](#软件架构)
|
||||||
|
- [贡献代码](#贡献代码)
|
||||||
|
- [安全](#安全)
|
||||||
|
- [订阅我们](#订阅我们)
|
||||||
|
- [版权说明](#版权说明)
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
|
||||||
|
|
||||||
|
开始运行 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
|
||||||
|
|
||||||
|
### Unix
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -it --rm \
|
||||||
|
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||||
|
--entrypoint="install" \
|
||||||
|
appwrite/appwrite:0.12.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
#### CMD
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
docker run -it --rm ^
|
||||||
|
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||||
|
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||||
|
--entrypoint="install" ^
|
||||||
|
appwrite/appwrite:0.12.1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PowerShell
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker run -it --rm ,
|
||||||
|
--volume /var/run/docker.sock:/var/run/docker.sock ,
|
||||||
|
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
|
||||||
|
--entrypoint="install" ,
|
||||||
|
appwrite/appwrite:0.12.1
|
||||||
|
```
|
||||||
|
|
||||||
|
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||||
|
|
||||||
|
|
||||||
|
需要自定义容器构架,请查看我们的 Docker [环境变量](https://appwrite.io/docs/environment-variables) 文档。您还可以参考我们的 [docker-compose.yml](https://gist.github.com/eldadfux/977869ff6bdd7312adfd4e629ee15cc5#file-docker-compose-yml) 文件手动设置环境。
|
||||||
|
|
||||||
|
### 从旧版本升级
|
||||||
|
|
||||||
|
如果您从旧版本升级 Appwrite 服务器,则应在设置完成后使用 Appwrite 迁移工具。有关这方面的更多信息,请查看 [安装文档](https://appwrite.io/docs/installation)。
|
||||||
|
|
||||||
|
## 入门
|
||||||
|
|
||||||
|
开始使用 Appwrite 只需要在控制台创建一个新项目,选择开发平台,然后抓取我们的开发套件。您可以从以下的教程中找到你喜欢的平台开始使用 Appwrite。
|
||||||
|
|
||||||
|
* [开始使用 Web](https://appwrite.io/docs/getting-started-for-web)
|
||||||
|
* [开始使用 Flutter](https://appwrite.io/docs/getting-started-for-flutter)
|
||||||
|
* [开始使用 Apple](https://appwrite.io/docs/getting-started-for-apple)
|
||||||
|
* [开始使用 Android](https://appwrite.io/docs/getting-started-for-android)
|
||||||
|
* [开始使用 Server](https://appwrite.io/docs/getting-started-for-server)
|
||||||
|
* [开始使用 CLI](https://appwrite.io/docs/command-line)
|
||||||
|
|
||||||
|
### 软件服务
|
||||||
|
|
||||||
|
* [**帐户**](https://appwrite.io/docs/client/account) -管理当前用户的帐户和登录方式。跟踪和管理用户 Session,登录设备,登录方法和查看相关记录。
|
||||||
|
* [**用户**](https://appwrite.io/docs/server/users) - 在以管理员模式登录时管理和列出所有用户。
|
||||||
|
* [**团队**](https://appwrite.io/docs/client/teams) - 管理用户分组。邀请成员,管理团队中的用户权限和用户角色。
|
||||||
|
* [**数据库**](https://appwrite.io/docs/client/database) - 管理数据库文档和文档集。用检索界面来对文档和文档集进行读取,创建,更新,和删除。
|
||||||
|
* [**贮存**](https://appwrite.io/docs/client/storage) - 管理文件的阅读、创建、删除和预览。设置文件的预览来满足程序的个性化需求。所有文件都由 ClamAV 扫描并安全存储和加密。
|
||||||
|
* [**云函数**](https://appwrite.io/docs/server/functions) - 在安全,隔离的环境中运行自定义代码。这些代码可以被事件,CRON,或者手动操作触发。
|
||||||
|
* [**语言适配**](https://appwrite.io/docs/client/locale) - 根据用户所在的的国家和地区做出合适的语言适配。
|
||||||
|
* [**头像**](https://appwrite.io/docs/client/avatars) -管理用户头像、国家旗帜、浏览器图标、信用卡符号,和生成二维码。
|
||||||
|
如需完整的 API 界面文档,请访问 [https://appwrite.io/docs](https://appwrite.io/docs)。如需更多教程、新闻和公告,请订阅我们的 [博客](https://medium.com/appwrite-io) 和 加入我们的[Discord 社区](https://discord.gg/GSeTUeA)。
|
||||||
|
|
||||||
|
### 开发套件
|
||||||
|
|
||||||
|
以下是当前支持的平台和语言列表。如果您想帮助我们为您选择的平台添加支持,您可以访问我们的 [SDK 生成器](https://github.com/appwrite/sdk-generator) 项目并查看我们的 [贡献指南]( https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md)。
|
||||||
|
|
||||||
|
#### 客户端
|
||||||
|
* ✅ [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
|
||||||
|
* ✅ [Flutter](https://github.com/appwrite/sdk-for-flutter) (由 Appwrite 团队维护)
|
||||||
|
* ✅ [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||||
|
* ✅ [Android](https://github.com/appwrite/sdk-for-android) (由 Appwrite 团队维护)
|
||||||
|
|
||||||
|
#### 服务器
|
||||||
|
* ✅ [NodeJS](https://github.com/appwrite/sdk-for-node) (由 Appwrite 团队维护)
|
||||||
|
* ✅ [PHP](https://github.com/appwrite/sdk-for-php) (由 Appwr实验 团队维护)
|
||||||
|
* ✅ [Dart](https://github.com/appwrite/sdk-for-dart) - (由 Appwrite 团队维护)
|
||||||
|
* ✅ [Deno](https://github.com/appwrite/sdk-for-deno) - **公测** (由 Appwrite 团队维护)
|
||||||
|
* ✅ [Ruby](https://github.com/appwrite/sdk-for-ruby) (由 Appwrite 团队维护)
|
||||||
|
* ✅ [Python](https://github.com/appwrite/sdk-for-python) (由 Appwrite 团队维护)
|
||||||
|
* ✅ [Kotlin](https://github.com/appwrite/sdk-for-kotlin) - **公测** (由 Appwrite 团队维护)
|
||||||
|
* ✅ [Apple](https://github.com/appwrite/sdk-for-apple) - **公测** (由 Appwrite 团队维护)
|
||||||
|
* ✅ [.NET](https://github.com/appwrite/sdk-for-dotnet) - **公测** (由 Appwrite 团队维护)
|
||||||
|
|
||||||
|
#### 开发者社区
|
||||||
|
* ✅ [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (维护者 [Michael Gangolf](https://github.com/m1ga/))
|
||||||
|
* ✅ [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (维护者 [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||||
|
|
||||||
|
找不到需要的的 SDK? - 欢迎通过发起PR来帮助我们完善Appwrite的软件生态环境 [SDK 生成器](https://github.com/appwrite/sdk-generator)!
|
||||||
|
|
||||||
|
|
||||||
|
## 软件架构
|
||||||
|
|
||||||
|
![Appwrite 软件架构](docs/specs/overview.drawio.svg)
|
||||||
|
|
||||||
|
Appwrite 使用高拓展性的微服务架构。此外,Appwrite 支持多种 API(REST、WebSocket 和 即将推出的 GraphQL),来迎合您的个性化开发习惯。
|
||||||
|
|
||||||
|
Appwrite API 界面层利用后台缓存和任务委派来提供极速的响应时间。后台的 Worker 代理还允许您使用消息队列来处理负载,并精确控制硬件合理分配和成本。您可以在 [贡献指南](CONTRIBUTING.md#architecture-1) 中了解有关我们架构的更多信息。
|
||||||
|
|
||||||
|
## 贡献代码
|
||||||
|
|
||||||
|
所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交PR请求并在合并分支之前得到核心开发人员的批准。这是为了确保正确审查所有代码。
|
||||||
|
|
||||||
|
我们欢迎所有人提交PR!如果您愿意提供帮助,可以在 [贡献指南](CONTRIBUTING.md) 中了解有关如何为项目做出贡献的更多信息。
|
||||||
|
|
||||||
|
## 安全
|
||||||
|
|
||||||
|
为了保护您的隐私,请避免在GitHub 上发布安全问题。发送问题至 security@appwrite.io,我们将为您做更细致的解答。
|
||||||
|
|
||||||
|
## 订阅我们
|
||||||
|
|
||||||
|
加入我们在世界各地不断发展的社区!请参阅我们的官方 [博客](https://medium.com/appwrite-io)。在 [Twitter](https://twitter.com/appwrite)、[Facebook 页面](https://www.facebook.com/appwrite.io)、[Facebook 群组](https://www.facebook)、[开发者社区](https://dev.to/appwrite) 等平台订阅我们或加入我们的 [Discord 社区](https://discord.gg/GSeTUeA) 以获得更多帮助,想法和讨论。
|
||||||
|
|
||||||
|
## 版权说明
|
||||||
|
|
||||||
|
版权详情,访问 [BSD 3-Clause License](./LICENSE)。
|
|
@ -16,6 +16,8 @@
|
||||||
[![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md)
|
[![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md)
|
||||||
[![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io)
|
[![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io)
|
||||||
|
|
||||||
|
English | [简体中文](README-CN.md)
|
||||||
|
|
||||||
[**Appwrite 0.12 has been released! Learn what's new!**](https://dev.to/appwrite/its-here-announcing-the-release-of-appwrite-012-5c8b)
|
[**Appwrite 0.12 has been released! Learn what's new!**](https://dev.to/appwrite/its-here-announcing-the-release-of-appwrite-012-5c8b)
|
||||||
|
|
||||||
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker<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.
|
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.
|
||||||
|
|
|
@ -7,35 +7,35 @@ return [
|
||||||
'name' => 'Email/Password',
|
'name' => 'Email/Password',
|
||||||
'key' => 'emailPassword',
|
'key' => 'emailPassword',
|
||||||
'icon' => '/images/users/email.png',
|
'icon' => '/images/users/email.png',
|
||||||
'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateSession',
|
'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateSession',
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
],
|
],
|
||||||
'magic-url' => [
|
'magic-url' => [
|
||||||
'name' => 'Magic URL',
|
'name' => 'Magic URL',
|
||||||
'key' => 'usersAuthMagicURL',
|
'key' => 'usersAuthMagicURL',
|
||||||
'icon' => '/images/users/magic-url.png',
|
'icon' => '/images/users/magic-url.png',
|
||||||
'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateMagicURLSession',
|
'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateMagicURLSession',
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
],
|
],
|
||||||
'anonymous' => [
|
'anonymous' => [
|
||||||
'name' => 'Anonymous',
|
'name' => 'Anonymous',
|
||||||
'key' => 'anonymous',
|
'key' => 'anonymous',
|
||||||
'icon' => '/images/users/anonymous.png',
|
'icon' => '/images/users/anonymous.png',
|
||||||
'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateAnonymousSession',
|
'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateAnonymousSession',
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
],
|
],
|
||||||
'invites' => [
|
'invites' => [
|
||||||
'name' => 'Invites',
|
'name' => 'Invites',
|
||||||
'key' => 'invites',
|
'key' => 'invites',
|
||||||
'icon' => '/images/users/invites.png',
|
'icon' => '/images/users/invites.png',
|
||||||
'docs' => 'https://appwrite.io/docs/client/teams?sdk=web#teamsCreateMembership',
|
'docs' => 'https://appwrite.io/docs/client/teams?sdk=web-default#teamsCreateMembership',
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
],
|
],
|
||||||
'jwt' => [
|
'jwt' => [
|
||||||
'name' => 'JWT',
|
'name' => 'JWT',
|
||||||
'key' => 'JWT',
|
'key' => 'JWT',
|
||||||
'icon' => '/images/users/jwt.png',
|
'icon' => '/images/users/jwt.png',
|
||||||
'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateJWT',
|
'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateJWT',
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
],
|
],
|
||||||
'phone' => [
|
'phone' => [
|
||||||
|
|
|
@ -166,8 +166,6 @@ App::post('/v1/database/collections')
|
||||||
$collectionId = $collectionId == 'unique()' ? $dbForProject->getId() : $collectionId;
|
$collectionId = $collectionId == 'unique()' ? $dbForProject->getId() : $collectionId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$dbForProject->createCollection('collection_' . $collectionId);
|
|
||||||
|
|
||||||
$collection = $dbForProject->createDocument('collections', new Document([
|
$collection = $dbForProject->createDocument('collections', new Document([
|
||||||
'$id' => $collectionId,
|
'$id' => $collectionId,
|
||||||
'$read' => $read ?? [], // Collection permissions for collection documents (based on permission model)
|
'$read' => $read ?? [], // Collection permissions for collection documents (based on permission model)
|
||||||
|
@ -179,8 +177,12 @@ App::post('/v1/database/collections')
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'search' => implode(' ', [$collectionId, $name]),
|
'search' => implode(' ', [$collectionId, $name]),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
$dbForProject->createCollection('collection_' . $collectionId);
|
||||||
} catch (DuplicateException $th) {
|
} catch (DuplicateException $th) {
|
||||||
throw new Exception('Collection already exists', 409);
|
throw new Exception('Collection already exists', 409);
|
||||||
|
} catch (LimitException $th) {
|
||||||
|
throw new Exception('Collection limit exceeded', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
|
@ -230,7 +232,7 @@ App::get('/v1/database/collections')
|
||||||
$queries = [];
|
$queries = [];
|
||||||
|
|
||||||
if (!empty($search)) {
|
if (!empty($search)) {
|
||||||
$queries[] = new Query('name', Query::TYPE_SEARCH, [$search]);
|
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$usage->setParam('database.collections.read', 1);
|
$usage->setParam('database.collections.read', 1);
|
||||||
|
|
|
@ -46,7 +46,7 @@ App::post('/v1/functions')
|
||||||
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
|
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
|
||||||
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
|
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
|
||||||
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
||||||
->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true)
|
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true)
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->action(function ($functionId, $name, $execute, $runtime, $vars, $events, $schedule, $timeout, $response, $dbForProject) {
|
->action(function ($functionId, $name, $execute, $runtime, $vars, $events, $schedule, $timeout, $response, $dbForProject) {
|
||||||
|
@ -294,7 +294,7 @@ App::put('/v1/functions/:functionId')
|
||||||
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
|
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
|
||||||
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
|
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
|
||||||
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
||||||
->param('timeout', 15, new Range(1, 900), 'Maximum execution time in seconds.', true)
|
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true)
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('project')
|
->inject('project')
|
||||||
|
@ -714,7 +714,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||||
throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404);
|
throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$validator = new Authorization($function, 'execute');
|
$validator = new Authorization('execute');
|
||||||
|
|
||||||
if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function
|
if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function
|
||||||
throw new Exception($validator->getDescription(), 401);
|
throw new Exception($validator->getDescription(), 401);
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Utopia\Validator\ArrayList;
|
||||||
use Utopia\Validator\WhiteList;
|
use Utopia\Validator\WhiteList;
|
||||||
use Utopia\Database\Database;
|
use Utopia\Database\Database;
|
||||||
use Utopia\Database\Document;
|
use Utopia\Database\Document;
|
||||||
|
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||||
use Utopia\Database\Exception\Duplicate;
|
use Utopia\Database\Exception\Duplicate;
|
||||||
use Utopia\Database\Query;
|
use Utopia\Database\Query;
|
||||||
use Utopia\Database\Validator\Authorization;
|
use Utopia\Database\Validator\Authorization;
|
||||||
|
@ -761,7 +762,11 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||||
throw new Exception('Team not found', 404);
|
throw new Exception('Team not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$dbForProject->deleteDocument('memberships', $membership->getId())) {
|
try {
|
||||||
|
$dbForProject->deleteDocument('memberships', $membership->getId());
|
||||||
|
} catch (AuthorizationException $exception) {
|
||||||
|
throw new Exception('Unauthorized permissions', 401);
|
||||||
|
} catch (\Exception $exception) {
|
||||||
throw new Exception('Failed to remove membership from DB', 500);
|
throw new Exception('Failed to remove membership from DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,7 +787,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||||
|
|
||||||
if ($membership->getAttribute('confirm')) { // Count only confirmed members
|
if ($membership->getAttribute('confirm')) { // Count only confirmed members
|
||||||
$team->setAttribute('sum', \max($team->getAttribute('sum', 0) - 1, 0));
|
$team->setAttribute('sum', \max($team->getAttribute('sum', 0) - 1, 0));
|
||||||
$team = $dbForProject->updateDocument('teams', $team->getId(), $team);
|
Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
|
||||||
}
|
}
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
|
|
|
@ -167,6 +167,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
||||||
break;
|
break;
|
||||||
case version_compare ($responseFormat , '0.8.0', '<=') :
|
case version_compare ($responseFormat , '0.8.0', '<=') :
|
||||||
Response::setFilter(new V08());
|
Response::setFilter(new V08());
|
||||||
|
break;
|
||||||
case version_compare ($responseFormat , '0.11.0', '<=') :
|
case version_compare ($responseFormat , '0.11.0', '<=') :
|
||||||
Response::setFilter(new V11());
|
Response::setFilter(new V11());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -86,6 +86,9 @@ const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
|
||||||
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
||||||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||||
|
// Database Reconnect
|
||||||
|
const DATABASE_RECONNECT_SLEEP = 2;
|
||||||
|
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
|
||||||
// Database Worker Types
|
// Database Worker Types
|
||||||
const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
|
const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
|
||||||
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
|
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
|
||||||
|
|
|
@ -91,13 +91,32 @@ $server->error($logError);
|
||||||
|
|
||||||
function getDatabase(Registry &$register, string $namespace)
|
function getDatabase(Registry &$register, string $namespace)
|
||||||
{
|
{
|
||||||
$db = $register->get('dbPool')->get();
|
$attempts = 0;
|
||||||
$redis = $register->get('redisPool')->get();
|
|
||||||
|
|
||||||
$cache = new Cache(new RedisCache($redis));
|
do {
|
||||||
$database = new Database(new MariaDB($db), $cache);
|
try {
|
||||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
$attempts++;
|
||||||
$database->setNamespace($namespace);
|
|
||||||
|
$db = $register->get('dbPool')->get();
|
||||||
|
$redis = $register->get('redisPool')->get();
|
||||||
|
|
||||||
|
$cache = new Cache(new RedisCache($redis));
|
||||||
|
$database = new Database(new MariaDB($db), $cache);
|
||||||
|
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||||
|
$database->setNamespace($namespace);
|
||||||
|
|
||||||
|
if (!$database->exists($database->getDefaultDatabase(), 'realtime')) {
|
||||||
|
throw new Exception('Collection not ready');
|
||||||
|
}
|
||||||
|
break; // leave loop if successful
|
||||||
|
} catch(\Exception $e) {
|
||||||
|
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||||
|
if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) {
|
||||||
|
throw new \Exception('Failed to connect to database: '. $e->getMessage());
|
||||||
|
}
|
||||||
|
sleep(DATABASE_RECONNECT_SLEEP);
|
||||||
|
}
|
||||||
|
} while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
$database,
|
$database,
|
||||||
|
@ -106,6 +125,7 @@ function getDatabase(Registry &$register, string $namespace)
|
||||||
$register->get('redisPool')->put($redis);
|
$register->get('redisPool')->put($redis);
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
|
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
|
||||||
|
|
|
@ -620,6 +620,7 @@ $logs = $this->getParam('logs', null);
|
||||||
data-failure="alert"
|
data-failure="alert"
|
||||||
data-failure-param-alert-text="Failed to create attribute"
|
data-failure-param-alert-text="Failed to create attribute"
|
||||||
data-failure-param-alert-classname="error"
|
data-failure-param-alert-classname="error"
|
||||||
|
@reset="array = required = false"
|
||||||
x-data="{ array: false, required: false }">
|
x-data="{ array: false, required: false }">
|
||||||
|
|
||||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||||
|
@ -675,6 +676,7 @@ $logs = $this->getParam('logs', null);
|
||||||
data-failure="alert"
|
data-failure="alert"
|
||||||
data-failure-param-alert-text="Failed to create attribute"
|
data-failure-param-alert-text="Failed to create attribute"
|
||||||
data-failure-param-alert-classname="error"
|
data-failure-param-alert-classname="error"
|
||||||
|
@reset="array = required = false"
|
||||||
x-data="{ array: false, required: false }">
|
x-data="{ array: false, required: false }">
|
||||||
|
|
||||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||||
|
@ -739,6 +741,7 @@ $logs = $this->getParam('logs', null);
|
||||||
data-failure="alert"
|
data-failure="alert"
|
||||||
data-failure-param-alert-text="Failed to create attribute"
|
data-failure-param-alert-text="Failed to create attribute"
|
||||||
data-failure-param-alert-classname="error"
|
data-failure-param-alert-classname="error"
|
||||||
|
@reset="array = required = false"
|
||||||
x-data="{ array: false, required: false }">
|
x-data="{ array: false, required: false }">
|
||||||
|
|
||||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||||
|
@ -803,6 +806,7 @@ $logs = $this->getParam('logs', null);
|
||||||
data-failure="alert"
|
data-failure="alert"
|
||||||
data-failure-param-alert-text="Failed to create attribute"
|
data-failure-param-alert-text="Failed to create attribute"
|
||||||
data-failure-param-alert-classname="error"
|
data-failure-param-alert-classname="error"
|
||||||
|
@reset="array = required = false"
|
||||||
x-data="{ array: false, required: false }">
|
x-data="{ array: false, required: false }">
|
||||||
|
|
||||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||||
|
@ -855,6 +859,7 @@ $logs = $this->getParam('logs', null);
|
||||||
data-failure="alert"
|
data-failure="alert"
|
||||||
data-failure-param-alert-text="Failed to create attribute"
|
data-failure-param-alert-text="Failed to create attribute"
|
||||||
data-failure-param-alert-classname="error"
|
data-failure-param-alert-classname="error"
|
||||||
|
@reset="array = required = false"
|
||||||
x-data="{ array: false, required: false }">
|
x-data="{ array: false, required: false }">
|
||||||
|
|
||||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||||
|
@ -911,6 +916,7 @@ $logs = $this->getParam('logs', null);
|
||||||
data-failure="alert"
|
data-failure="alert"
|
||||||
data-failure-param-alert-text="Failed to create attribute"
|
data-failure-param-alert-text="Failed to create attribute"
|
||||||
data-failure-param-alert-classname="error"
|
data-failure-param-alert-classname="error"
|
||||||
|
@reset="array = required = false"
|
||||||
x-data="{ array: false, required: false }">
|
x-data="{ array: false, required: false }">
|
||||||
|
|
||||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||||
|
@ -963,6 +969,7 @@ $logs = $this->getParam('logs', null);
|
||||||
data-failure="alert"
|
data-failure="alert"
|
||||||
data-failure-param-alert-text="Failed to create attribute"
|
data-failure-param-alert-text="Failed to create attribute"
|
||||||
data-failure-param-alert-classname="error"
|
data-failure-param-alert-classname="error"
|
||||||
|
@reset="array = required = false"
|
||||||
x-data="{ array: false, required: false }">
|
x-data="{ array: false, required: false }">
|
||||||
|
|
||||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||||
|
@ -1015,6 +1022,7 @@ $logs = $this->getParam('logs', null);
|
||||||
data-failure="alert"
|
data-failure="alert"
|
||||||
data-failure-param-alert-text="Failed to create attribute"
|
data-failure-param-alert-text="Failed to create attribute"
|
||||||
data-failure-param-alert-classname="error"
|
data-failure-param-alert-classname="error"
|
||||||
|
@reset="array = required = false"
|
||||||
x-data="{ array: false, required: false }">
|
x-data="{ array: false, required: false }">
|
||||||
|
|
||||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||||
|
|
|
@ -174,7 +174,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
||||||
<input type="email" class="full-width" id="user-email" name="email" required autocomplete="off" />
|
<input type="email" class="full-width" id="user-email" name="email" required autocomplete="off" />
|
||||||
|
|
||||||
<label for="user-password">Password</label>
|
<label for="user-password">Password</label>
|
||||||
<input type="password" class="full-width" id="user-password" name="password" required pattern=".{6,}" title="Six or more characters" autocomplete="off" />
|
<input type="password" class="full-width" id="user-password" name="password" required minlength="8" title="Eight or more characters" autocomplete="off" />
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,10 @@
|
||||||
<input type="hidden" name="secret" data-ls-bind="{{router.params.secret}}">
|
<input type="hidden" name="secret" data-ls-bind="{{router.params.secret}}">
|
||||||
|
|
||||||
<label>Password</label>
|
<label>Password</label>
|
||||||
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter pattern=".{6,}" title="Six or more characters">
|
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
|
||||||
|
|
||||||
<label>Confirm Password</label>
|
<label>Confirm Password</label>
|
||||||
<input name="passwordAgain" type="password" autocomplete="off" placeholder="" required data-forms-password-meter pattern=".{6,}" title="Six or more characters">
|
<input name="passwordAgain" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary"><i class="fa fa-sign-in"></i> Apply</button>
|
<button type="submit" class="btn btn-primary"><i class="fa fa-sign-in"></i> Apply</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -37,7 +37,7 @@ $root = ($this->getParam('root') !== 'disabled');
|
||||||
|
|
||||||
<input name="email" type="email" class="full-width" autocomplete="email" placeholder="Email" required>
|
<input name="email" type="email" class="full-width" autocomplete="email" placeholder="Email" required>
|
||||||
|
|
||||||
<input name="password" type="password" class="full-width" autocomplete="off" placeholder="Password" required pattern=".{6,}" title="Six or more characters">
|
<input name="password" type="password" class="full-width" autocomplete="off" placeholder="Password" required minlength="8" title="Eight or more characters">
|
||||||
|
|
||||||
<button>Sign In</button>
|
<button>Sign In</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -46,7 +46,7 @@ $root = ($this->getParam('root') !== 'disabled');
|
||||||
<input name="email" type="email" autocomplete="email" placeholder="" required data-ls-bind="{{router.params.email}}">
|
<input name="email" type="email" autocomplete="email" placeholder="" required data-ls-bind="{{router.params.email}}">
|
||||||
|
|
||||||
<label>Password</label>
|
<label>Password</label>
|
||||||
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter pattern=".{8,}" title="Eight or more characters">
|
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
|
||||||
|
|
||||||
<div class="agree margin-top-large margin-bottom-large">
|
<div class="agree margin-top-large margin-bottom-large">
|
||||||
<div class="pull-start margin-end-small margin-bottom">
|
<div class="pull-start margin-end-small margin-bottom">
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
"utopia-php/cache": "0.4.*",
|
"utopia-php/cache": "0.4.*",
|
||||||
"utopia-php/cli": "0.11.*",
|
"utopia-php/cli": "0.11.*",
|
||||||
"utopia-php/config": "0.2.*",
|
"utopia-php/config": "0.2.*",
|
||||||
"utopia-php/database": "0.13.*",
|
"utopia-php/database": "0.14.*",
|
||||||
"utopia-php/locale": "0.4.*",
|
"utopia-php/locale": "0.4.*",
|
||||||
"utopia-php/orchestration": "0.2.*",
|
"utopia-php/orchestration": "0.2.*",
|
||||||
"utopia-php/registry": "0.5.*",
|
"utopia-php/registry": "0.5.*",
|
||||||
|
|
31
composer.lock
generated
31
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f022f43cc2d6023c3dad3805b7c4455a",
|
"content-hash": "ab493f0a7f01a1105f8bc5caaf9b928b",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "adhocore/jwt",
|
"name": "adhocore/jwt",
|
||||||
|
@ -355,16 +355,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/package-versions-deprecated",
|
"name": "composer/package-versions-deprecated",
|
||||||
"version": "1.11.99.4",
|
"version": "1.11.99.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/package-versions-deprecated.git",
|
"url": "https://github.com/composer/package-versions-deprecated.git",
|
||||||
"reference": "b174585d1fe49ceed21928a945138948cb394600"
|
"reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600",
|
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d",
|
||||||
"reference": "b174585d1fe49ceed21928a945138948cb394600",
|
"reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -408,7 +408,7 @@
|
||||||
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
|
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/composer/package-versions-deprecated/issues",
|
"issues": "https://github.com/composer/package-versions-deprecated/issues",
|
||||||
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4"
|
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -424,7 +424,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2021-09-13T08:41:34+00:00"
|
"time": "2022-01-17T14:14:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dragonmantank/cron-expression",
|
"name": "dragonmantank/cron-expression",
|
||||||
|
@ -2141,16 +2141,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/database",
|
"name": "utopia-php/database",
|
||||||
"version": "0.13.2",
|
"version": "0.14.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/utopia-php/database.git",
|
"url": "https://github.com/utopia-php/database.git",
|
||||||
"reference": "bf92279b707b3a10ee5ec5df5c065023b2221357"
|
"reference": "2f2527bb080cf578fba327ea2ec637064561d403"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/bf92279b707b3a10ee5ec5df5c065023b2221357",
|
"url": "https://api.github.com/repos/utopia-php/database/zipball/2f2527bb080cf578fba327ea2ec637064561d403",
|
||||||
"reference": "bf92279b707b3a10ee5ec5df5c065023b2221357",
|
"reference": "2f2527bb080cf578fba327ea2ec637064561d403",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2198,9 +2198,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/utopia-php/database/issues",
|
"issues": "https://github.com/utopia-php/database/issues",
|
||||||
"source": "https://github.com/utopia-php/database/tree/0.13.2"
|
"source": "https://github.com/utopia-php/database/tree/0.14.0"
|
||||||
},
|
},
|
||||||
"time": "2022-01-04T10:51:22+00:00"
|
"time": "2022-01-21T16:34:34+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/domains",
|
"name": "utopia-php/domains",
|
||||||
|
@ -3703,9 +3703,6 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1 || ^8.0"
|
"php": "^7.1 || ^8.0"
|
||||||
},
|
},
|
||||||
"replace": {
|
|
||||||
"myclabs/deep-copy": "self.version"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/collections": "^1.0",
|
"doctrine/collections": "^1.0",
|
||||||
"doctrine/common": "^2.6",
|
"doctrine/common": "^2.6",
|
||||||
|
@ -6669,5 +6666,5 @@
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "8.0"
|
"php": "8.0"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.1.0"
|
"plugin-api-version": "2.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Use this endpoint to log out the currently logged in user from all their account sessions across all of their different devices. When using the option id argument, only the session unique ID provider will be deleted.
|
Use this endpoint to log out the currently logged in user from all their account sessions across all of their different devices. When using the Session ID argument, only the unique session ID provided is deleted.
|
||||||
|
|
28
package-lock.json
generated
28
package-lock.json
generated
|
@ -9,8 +9,8 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^3.6.2",
|
"chart.js": "^3.7.0",
|
||||||
"markdown-it": "^12.3.0",
|
"markdown-it": "^12.3.2",
|
||||||
"pell": "^1.0.6",
|
"pell": "^1.0.6",
|
||||||
"prismjs": "^1.25.0",
|
"prismjs": "^1.25.0",
|
||||||
"turndown": "^7.1.1"
|
"turndown": "^7.1.1"
|
||||||
|
@ -549,9 +549,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chart.js": {
|
"node_modules/chart.js": {
|
||||||
"version": "3.6.2",
|
"version": "3.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.0.tgz",
|
||||||
"integrity": "sha512-Xz7f/fgtVltfQYWq0zL1Xbv7N2inpG+B54p3D5FSvpCdy3sM+oZhbqa42eNuYXltaVvajgX5UpKCU2GeeJIgxg=="
|
"integrity": "sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg=="
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "2.1.8",
|
"version": "2.1.8",
|
||||||
|
@ -2862,9 +2862,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/markdown-it": {
|
"node_modules/markdown-it": {
|
||||||
"version": "12.3.0",
|
"version": "12.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
|
||||||
"integrity": "sha512-T345UZZ6ejQWTjG6PSEHplzNy5m4kF6zvUpHVDv8Snl/pEU0OxIK0jGg8YLVNwJvT8E0YJC7/2UvssJDk/wQCQ==",
|
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1",
|
"argparse": "^2.0.1",
|
||||||
"entities": "~2.1.0",
|
"entities": "~2.1.0",
|
||||||
|
@ -5484,9 +5484,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chart.js": {
|
"chart.js": {
|
||||||
"version": "3.6.2",
|
"version": "3.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.0.tgz",
|
||||||
"integrity": "sha512-Xz7f/fgtVltfQYWq0zL1Xbv7N2inpG+B54p3D5FSvpCdy3sM+oZhbqa42eNuYXltaVvajgX5UpKCU2GeeJIgxg=="
|
"integrity": "sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg=="
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "2.1.8",
|
"version": "2.1.8",
|
||||||
|
@ -7413,9 +7413,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"markdown-it": {
|
"markdown-it": {
|
||||||
"version": "12.3.0",
|
"version": "12.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
|
||||||
"integrity": "sha512-T345UZZ6ejQWTjG6PSEHplzNy5m4kF6zvUpHVDv8Snl/pEU0OxIK0jGg8YLVNwJvT8E0YJC7/2UvssJDk/wQCQ==",
|
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"argparse": "^2.0.1",
|
"argparse": "^2.0.1",
|
||||||
"entities": "~2.1.0",
|
"entities": "~2.1.0",
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
"gulp-less": "^5.0.0"
|
"gulp-less": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^3.6.2",
|
"chart.js": "^3.7.0",
|
||||||
"markdown-it": "^12.3.0",
|
"markdown-it": "^12.3.2",
|
||||||
"pell": "^1.0.6",
|
"pell": "^1.0.6",
|
||||||
"prismjs": "^1.25.0",
|
"prismjs": "^1.25.0",
|
||||||
"turndown": "^7.1.1"
|
"turndown": "^7.1.1"
|
||||||
|
|
|
@ -70,9 +70,6 @@ abstract class Worker
|
||||||
throw new Exception("Please implement getName method in worker");
|
throw new Exception("Please implement getName method in worker");
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_ATTEMPTS = 10;
|
|
||||||
const SLEEP_TIME = 2;
|
|
||||||
|
|
||||||
const DATABASE_PROJECT = 'project';
|
const DATABASE_PROJECT = 'project';
|
||||||
const DATABASE_CONSOLE = 'console';
|
const DATABASE_CONSOLE = 'console';
|
||||||
|
|
||||||
|
@ -174,7 +171,7 @@ abstract class Worker
|
||||||
global $register;
|
global $register;
|
||||||
|
|
||||||
$namespace = '';
|
$namespace = '';
|
||||||
$sleep = self::SLEEP_TIME; // overwritten when necessary
|
$sleep = DATABASE_RECONNECT_SLEEP; // overwritten when necessary
|
||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case self::DATABASE_PROJECT:
|
case self::DATABASE_PROJECT:
|
||||||
|
@ -201,18 +198,24 @@ abstract class Worker
|
||||||
$database = new Database(new MariaDB($register->get('db')), $cache);
|
$database = new Database(new MariaDB($register->get('db')), $cache);
|
||||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||||
$database->setNamespace($namespace); // Main DB
|
$database->setNamespace($namespace); // Main DB
|
||||||
|
|
||||||
if (!empty($projectId) && !$database->getDocument('projects', $projectId)->isEmpty()) {
|
if (!empty($projectId) && !$database->getDocument('projects', $projectId)->isEmpty()) {
|
||||||
throw new \Exception("Project does not exist: {$projectId}");
|
throw new \Exception("Project does not exist: {$projectId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($type === self::DATABASE_CONSOLE && !$database->exists($database->getDefaultDatabase(), 'realtime')) {
|
||||||
|
throw new \Exception('Console project not ready');
|
||||||
|
}
|
||||||
|
|
||||||
break; // leave loop if successful
|
break; // leave loop if successful
|
||||||
} catch(\Exception $e) {
|
} catch(\Exception $e) {
|
||||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||||
if ($attempts >= self::MAX_ATTEMPTS) {
|
if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) {
|
||||||
throw new \Exception('Failed to connect to database: '. $e->getMessage());
|
throw new \Exception('Failed to connect to database: '. $e->getMessage());
|
||||||
}
|
}
|
||||||
sleep($sleep);
|
sleep($sleep);
|
||||||
}
|
}
|
||||||
} while ($attempts < self::MAX_ATTEMPTS);
|
} while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS);
|
||||||
|
|
||||||
return $database;
|
return $database;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ abstract class Scope extends TestCase
|
||||||
|
|
||||||
protected function getLastEmail():array
|
protected function getLastEmail():array
|
||||||
{
|
{
|
||||||
sleep(5);
|
sleep(3);
|
||||||
|
|
||||||
$emails = json_decode(file_get_contents('http://maildev:1080/email'), true);
|
$emails = json_decode(file_get_contents('http://maildev:1080/email'), true);
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ abstract class Scope extends TestCase
|
||||||
|
|
||||||
protected function getLastRequest():array
|
protected function getLastRequest():array
|
||||||
{
|
{
|
||||||
sleep(5);
|
sleep(2);
|
||||||
|
|
||||||
$resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
|
$resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
|
||||||
$resquest['data'] = json_decode($resquest['data'], true);
|
$resquest['data'] = json_decode($resquest['data'], true);
|
||||||
|
|
|
@ -125,6 +125,39 @@ class DatabaseCustomServerTest extends Scope
|
||||||
$this->assertCount(0, $collections['body']['collections']);
|
$this->assertCount(0, $collections['body']['collections']);
|
||||||
$this->assertEmpty($collections['body']['collections']);
|
$this->assertEmpty($collections['body']['collections']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for Search
|
||||||
|
*/
|
||||||
|
$collections = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'search' => 'first'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(1, $collections['body']['sum']);
|
||||||
|
$this->assertEquals('first', $collections['body']['collections'][0]['$id']);
|
||||||
|
|
||||||
|
$collections = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'search' => 'Test'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(2, $collections['body']['sum']);
|
||||||
|
$this->assertEquals('Test 1', $collections['body']['collections'][0]['name']);
|
||||||
|
$this->assertEquals('Test 2', $collections['body']['collections'][1]['name']);
|
||||||
|
|
||||||
|
$collections = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'search' => 'Nonexistent'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(0, $collections['body']['sum']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for FAILURE
|
* Test for FAILURE
|
||||||
*/
|
*/
|
||||||
|
@ -136,6 +169,21 @@ class DatabaseCustomServerTest extends Scope
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertEquals($response['headers']['status-code'], 400);
|
$this->assertEquals($response['headers']['status-code'], 400);
|
||||||
|
|
||||||
|
// This collection already exists
|
||||||
|
$response = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'name' => 'Test 1',
|
||||||
|
'collectionId' => 'first',
|
||||||
|
'read' => ['role:all'],
|
||||||
|
'write' => ['role:all'],
|
||||||
|
'permission' => 'document'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals($response['headers']['status-code'], 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeleteAttribute(): array
|
public function testDeleteAttribute(): array
|
||||||
|
|
|
@ -218,6 +218,32 @@ class FunctionsCustomClientTest extends Scope
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCreateExecutionUnauthorized():array
|
||||||
|
{
|
||||||
|
$function = $this->client->call(Client::METHOD_POST, '/functions', [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||||
|
], [
|
||||||
|
'functionId' => 'unique()',
|
||||||
|
'name' => 'Test',
|
||||||
|
'execute' => [],
|
||||||
|
'runtime' => 'php-8.0',
|
||||||
|
'timeout' => 10,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$execution = $this->client->call(Client::METHOD_POST, '/functions/'.$function['body']['$id'].'/executions', [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], [
|
||||||
|
'async' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(401, $execution['headers']['status-code']);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @depends testCreateCustomExecution
|
* @depends testCreateCustomExecution
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -66,6 +66,7 @@ trait RealtimeBase
|
||||||
$this->assertEquals('error', $payload['type']);
|
$this->assertEquals('error', $payload['type']);
|
||||||
$this->assertEquals(1008, $payload['data']['code']);
|
$this->assertEquals(1008, $payload['data']['code']);
|
||||||
$this->assertEquals('Missing or unknown project ID', $payload['data']['message']);
|
$this->assertEquals('Missing or unknown project ID', $payload['data']['message']);
|
||||||
|
\usleep(250000); // 250ms
|
||||||
$this->expectException(ConnectionException::class); // Check if server disconnnected client
|
$this->expectException(ConnectionException::class); // Check if server disconnnected client
|
||||||
$client->close();
|
$client->close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -391,11 +391,75 @@ trait TeamsBaseClient
|
||||||
{
|
{
|
||||||
$teamUid = $data['teamUid'] ?? '';
|
$teamUid = $data['teamUid'] ?? '';
|
||||||
$membershipUid = $data['membershipUid'] ?? '';
|
$membershipUid = $data['membershipUid'] ?? '';
|
||||||
|
$session = $data['session'] ?? '';
|
||||||
|
|
||||||
|
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()));
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
|
$this->assertEquals(2, $response['body']['sum']);
|
||||||
|
|
||||||
|
$ownerMembershipUid = $response['body']['memberships'][0]['$id'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for FAILURE
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test deleting a membership that does not exists
|
||||||
|
*/
|
||||||
|
$response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/dne', [
|
||||||
|
'origin' => 'http://localhost',
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'cookie' => 'a_session_'.$this->getProject()['$id'].'='.$session,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(404, $response['headers']['status-code']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test deleting another user's membership
|
||||||
|
*/
|
||||||
|
$response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$ownerMembershipUid, [
|
||||||
|
'origin' => 'http://localhost',
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'cookie' => 'a_session_'.$this->getProject()['$id'].'='.$session,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(401, $response['headers']['status-code']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for SUCCESS
|
* Test for SUCCESS
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$membershipUid, array_merge([
|
|
||||||
|
/**
|
||||||
|
* Test for when a user other than the owner tries to delete their membership
|
||||||
|
*/
|
||||||
|
$response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$membershipUid, [
|
||||||
|
'origin' => 'http://localhost',
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'cookie' => 'a_session_'.$this->getProject()['$id'].'='.$session,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(204, $response['headers']['status-code']);
|
||||||
|
$this->assertEmpty($response['body']);
|
||||||
|
|
||||||
|
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()));
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
|
$this->assertEquals(1, $response['body']['sum']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for when the owner tries to delete their membership
|
||||||
|
*/
|
||||||
|
$response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$ownerMembershipUid, array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
@ -404,10 +468,7 @@ trait TeamsBaseClient
|
||||||
$this->assertEquals(204, $response['headers']['status-code']);
|
$this->assertEquals(204, $response['headers']['status-code']);
|
||||||
$this->assertEmpty($response['body']);
|
$this->assertEmpty($response['body']);
|
||||||
|
|
||||||
/**
|
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships/'.$ownerMembershipUid, array_merge([
|
||||||
* Test for FAILURE
|
|
||||||
*/
|
|
||||||
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships/'.$membershipUid, array_merge([
|
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
|
Loading…
Reference in a new issue