mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'origin/master' into feat-graphql-support
# Conflicts: # app/controllers/api/databases.php # app/controllers/general.php # app/init.php # composer.json # composer.lock # docker-compose.yml # phpunit.xml
This commit is contained in:
@@ -56,6 +56,8 @@ _APP_SMTP_PORT=1025
|
||||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_PHONE_PROVIDER=phone://mock
|
||||
_APP_PHONE_FROM=+123456789
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
_APP_STORAGE_PREVIEW_LIMIT=20000000
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
||||
|
||||
@@ -37,6 +37,7 @@ body:
|
||||
label: "🎲 Appwrite version"
|
||||
description: "What version of Appwrite are you running?"
|
||||
options:
|
||||
- Version 0.15.x
|
||||
- Version 0.14.x
|
||||
- Version 0.13.x
|
||||
- Version 0.12.x
|
||||
|
||||
@@ -41,6 +41,6 @@ jobs:
|
||||
- name: Teardown
|
||||
if: always()
|
||||
run: |
|
||||
docker ps -aq | xargs docker rm --force
|
||||
docker volume prune --force
|
||||
docker network prune --force
|
||||
docker ps -aq | xargs docker rm --force || true
|
||||
docker volume prune --force || true
|
||||
docker network prune --force || true
|
||||
+93
-1
@@ -1,4 +1,96 @@
|
||||
- Added support for selfhosted Gitlab (Oauth)
|
||||
# Version 0.15.1
|
||||
## Bugs
|
||||
- Fixed SMS for `createVerification` by @christyjacob4 in https://github.com/appwrite/appwrite/pull/3454
|
||||
- Fixed missing Attributes when creating an Index by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3461
|
||||
- Fixed broken Link for Documents under Collections by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3461
|
||||
- Fixed all `$createdAt` and `$updatedAt` occurences in the UI by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3461
|
||||
- Fixed Delete Document from the UI by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3463
|
||||
- Fixed internal Attribute and Index key on Migration by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3455
|
||||
|
||||
## Docs
|
||||
- Updated Phone Authentication by @christyjacob4 in https://github.com/appwrite/appwrite/pull/3456
|
||||
|
||||
# Version 0.15.0
|
||||
|
||||
## BREAKING CHANGES
|
||||
- Docker Compose V2 is required now
|
||||
- The `POST:/v1/account/sessions` endpoint is now `POST:/v1/account/sessions/email`
|
||||
- All `/v1/database/...` endpoints are now `/v1/databases/...`
|
||||
- `dateCreated` attribute is removed from Teams
|
||||
- `dateCreated` attribute is removed from Executions
|
||||
- `dateCreated` attribute is removed from Files
|
||||
- `dateCreated` and `dateUpdated` attributes are removed from Functions
|
||||
- `dateCreated` and `dateUpdated` attributes are removed from Deployments
|
||||
- `dateCreated` and `dateUpdated` attributes are removed from Buckets
|
||||
- Following Events for Webhooks and Functions are changed:
|
||||
- `collections.[COLLECTION_ID]` is now `databases.[DATABASE_ID].collections.[COLLECTION_ID]`
|
||||
- `collections.[COLLECTION_ID].documents.[DOCUMENT_ID]` is now `databases.[DATABASE_ID].collections.[COLLECTION_ID].documents.[DOCUMENT_ID]`
|
||||
- Following Realtime Channels are changed:
|
||||
- `collections.[COLLECTION_ID]` is now `databases.[DATABASE_ID].ollections.[COLLECTION_ID]`
|
||||
- `collections.[COLLECTION_ID].documents` is now `databases.[DATABASE_ID].ollections.[COLLECTION_ID].documents`
|
||||
- After Migration a Database called `default` is created for all your existing Database Collections
|
||||
|
||||
## Features
|
||||
- Added Phone Authentication by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3357
|
||||
- Added Twilio Support
|
||||
- Added TextMagic Support
|
||||
- Added Telesign Support
|
||||
- Added Endpoint to create Phone Session (`POST:/v1/account/sessions/phone`)
|
||||
- Added Endpoint to confirm Phone Session (`PUT:/v1/account/sessions/phone`)
|
||||
- Added Endpoint to update Account Phone Number (`PATCH:/v1/account/phone`)
|
||||
- Added Endpoint to create Account Phone Verification (`POST:/v1/account/verification/phone`)
|
||||
- Added Endpoint to confirm Account Phone Verification (`PUT:/v1/account/verification/phone`)
|
||||
- Added `_APP_PHONE_PROVIDER` and `_APP_PHONE_FROM` Environment Variable
|
||||
- Added `phone` and `phoneVerification` Attribute to User
|
||||
- Added `$createdAt` and `$updatedAt` Attributes by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3382
|
||||
- Bucket
|
||||
- Collection
|
||||
- Deployment
|
||||
- Document
|
||||
- Domain
|
||||
- Execution
|
||||
- File
|
||||
- Func
|
||||
- Key
|
||||
- Membership
|
||||
- Platform
|
||||
- Project
|
||||
- Team
|
||||
- User
|
||||
- Webhook
|
||||
- Session (only `$createdAt`)
|
||||
- Token (only `$createdAt`)
|
||||
- Added Databases Resource to the Database Service by @lohanidamodar in https://github.com/appwrite/appwrite/pull/3338
|
||||
- Added `databases.read` and `databases.write` Scopes for API Keys
|
||||
- Added New Runtimes
|
||||
- Dart 2.17
|
||||
- Deno 1.21
|
||||
- Java 18
|
||||
- Node 18
|
||||
- Webhooks now have a Signature Key for proof of Origin by @shimonewman in https://github.com/appwrite/appwrite/pull/3351
|
||||
- Start using Docker Compose V2 (from `docker-compose` to `docker compose`) by @Meldiron in https://github.com/appwrite/appwrite/pull/3362
|
||||
- Added support for selfhosted Gitlab (OAuth) by @Meldiron in https://github.com/appwrite/appwrite/pull/3366
|
||||
- Added Dailymotion OAuth Provider by @2002Bishwajeet in https://github.com/appwrite/appwrite/pull/3371
|
||||
- Added Autodesk OAuth Provider by @Haimantika in https://github.com/appwrite/appwrite/pull/3420
|
||||
- Ignore Service Checks when using API Key by @stnguyen90 in https://github.com/appwrite/appwrite/pull/3270
|
||||
- Added WebM as MIME- and Preview Type by @chuongtang in https://github.com/appwrite/appwrite/pull/3327
|
||||
- Expired User Sessions are now deleted by the Maintenance Worker by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3324
|
||||
- Increased JWT rate-limit to 100 per hour by @abnegate in https://github.com/appwrite/appwrite/pull/3345
|
||||
- Internal Database Relations are now resolved using the Internal ID by @fogelito in https://github.com/appwrite/appwrite/pull/3383
|
||||
- Permissions for Documents can be updated without payload now by @gepd in https://github.com/appwrite/appwrite/pull/3346
|
||||
|
||||
## Bugs
|
||||
- Fixed Zoom OAuth scopes
|
||||
- Fixed empty build logs for Functions
|
||||
- Fixed unnecessary SMTP check on Team Invite using an API Key by @stnguyen90 in https://github.com/appwrite/appwrite/pull/3270
|
||||
- Fixed Error Message when adding Team Member to project by @stnguyen90 in https://github.com/appwrite/appwrite/pull/3296
|
||||
- Fixed .NET Runtime Logo by @adityaoberai in https://github.com/appwrite/appwrite/pull/3315
|
||||
- Fixed unnecessary Function execution delays by @Meldiron in https://github.com/appwrite/appwrite/pull/3348
|
||||
- Fixed Runtime race conditions on cold start by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/3361
|
||||
- Fixed Malayalam translation by @varghesejose2020 in https://github.com/appwrite/appwrite/pull/2561
|
||||
- Fixed English translation by @MATsxm in https://github.com/appwrite/appwrite/pull/3337
|
||||
- Fixed spelling in Realtime Worker logs by @gireeshp in https://github.com/appwrite/appwrite/pull/1663
|
||||
- Fixed Docs URL for Yammer OAuth by @everly-gif in https://github.com/appwrite/appwrite/pull/3402
|
||||
|
||||
# Version 0.14.2
|
||||
|
||||
|
||||
+8
-8
@@ -93,7 +93,7 @@ git clone git@github.com:[YOUR_FORK_HERE]/appwrite.git
|
||||
|
||||
cd appwrite
|
||||
|
||||
docker-compose up -d
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Code Autocompletion
|
||||
@@ -108,7 +108,7 @@ docker run --rm --interactive --tty \
|
||||
|
||||
### User Interface
|
||||
|
||||
Appwrite uses an internal micro-framework called Litespeed.js to build simple UI components in vanilla JS and [less](http://lesscss.org/) for compiling CSS code. To apply any of your changes to the UI, use the `gulp build` or `gulp less` commands, and restart the Appwrite main container to load the new static files to memory using `docker-compose restart appwrite`.
|
||||
Appwrite uses an internal micro-framework called Litespeed.js to build simple UI components in vanilla JS and [less](http://lesscss.org/) for compiling CSS code. To apply any of your changes to the UI, use the `gulp build` or `gulp less` commands, and restart the Appwrite main container to load the new static files to memory using `docker compose restart appwrite`.
|
||||
|
||||
### Get Started
|
||||
|
||||
@@ -222,7 +222,7 @@ Currently, all of the Appwrite microservices are intended to communicate using t
|
||||
|
||||
## Ports
|
||||
|
||||
Appwrite dev version uses ports 80 and 443 as an entry point to the Appwrite API and console. We also expose multiple ports in the range of 9500-9504 for debugging some of the Appwrite containers on dev mode. If you have any conflicts with the ports running on your system, you can easily replace them by editing Appwrite's docker-compose.yml file and executing `docker-compose up -d` command.
|
||||
Appwrite dev version uses ports 80 and 443 as an entry point to the Appwrite API and console. We also expose multiple ports in the range of 9500-9504 for debugging some of the Appwrite containers on dev mode. If you have any conflicts with the ports running on your system, you can easily replace them by editing Appwrite's docker-compose.yml file and executing `docker compose up -d` command.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
@@ -363,25 +363,25 @@ In settings, go to **Languages & Frameworks** > **PHP** > **Debug**, there under
|
||||
To run all tests manually, use the Appwrite Docker CLI from your terminal:
|
||||
|
||||
```bash
|
||||
docker-compose exec appwrite test
|
||||
docker compose exec appwrite test
|
||||
```
|
||||
|
||||
To run unit tests use:
|
||||
|
||||
```bash
|
||||
docker-compose exec appwrite test /usr/src/code/tests/unit
|
||||
docker compose exec appwrite test /usr/src/code/tests/unit
|
||||
```
|
||||
|
||||
To run end-2-end tests use:
|
||||
|
||||
```bash
|
||||
docker-compose exec appwrite test /usr/src/code/tests/e2e
|
||||
docker compose exec appwrite test /usr/src/code/tests/e2e
|
||||
```
|
||||
|
||||
To run end-2-end tests for a specific service use:
|
||||
|
||||
```bash
|
||||
docker-compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
|
||||
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
|
||||
```
|
||||
|
||||
## Benchmarking
|
||||
@@ -462,4 +462,4 @@ Submitting documentation updates, enhancements, designs, or bug fixes. Spelling
|
||||
|
||||
### Helping Someone
|
||||
|
||||
Searching for Appwrite on Discord, GitHub, or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Appwrite's repo!
|
||||
Searching for Appwrite on Discord, GitHub, or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Appwrite's repo!
|
||||
+14
-3
@@ -31,7 +31,7 @@ ENV DEBUG=$DEBUG
|
||||
|
||||
ENV PHP_REDIS_VERSION=5.3.7 \
|
||||
PHP_MONGODB_VERSION=1.13.0 \
|
||||
PHP_SWOOLE_VERSION=v4.8.9 \
|
||||
PHP_SWOOLE_VERSION=v4.8.10 \
|
||||
PHP_IMAGICK_VERSION=3.7.0 \
|
||||
PHP_YAML_VERSION=2.2.2 \
|
||||
PHP_MAXMINDDB_VERSION=v1.11.0
|
||||
@@ -131,6 +131,9 @@ ARG VERSION=dev
|
||||
ARG DEBUG=false
|
||||
ENV DEBUG=$DEBUG
|
||||
|
||||
ENV DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
|
||||
ENV DOCKER_COMPOSE_VERSION=v2.5.0
|
||||
|
||||
ENV _APP_SERVER=swoole \
|
||||
_APP_ENV=production \
|
||||
_APP_LOCALE=en \
|
||||
@@ -190,6 +193,8 @@ ENV _APP_SERVER=swoole \
|
||||
_APP_SMTP_SECURE= \
|
||||
_APP_SMTP_USERNAME= \
|
||||
_APP_SMTP_PASSWORD= \
|
||||
_APP_PHONE_PROVIDER= \
|
||||
_APP_PHONE_FROM= \
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000 \
|
||||
_APP_FUNCTIONS_TIMEOUT=900 \
|
||||
_APP_FUNCTIONS_CONTAINERS=10 \
|
||||
@@ -232,12 +237,17 @@ RUN \
|
||||
libmaxminddb-dev \
|
||||
certbot \
|
||||
docker-cli \
|
||||
docker-compose \
|
||||
libgomp \
|
||||
&& docker-php-ext-install sockets opcache pdo_mysql \
|
||||
&& apk del .deps \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN \
|
||||
mkdir -p $DOCKER_CONFIG/cli-plugins \
|
||||
&& ARCH=$(uname -m) && if [ $ARCH == "armv7l" ]; then ARCH="armv7"; fi \
|
||||
&& curl -SL https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-$ARCH -o $DOCKER_CONFIG/cli-plugins/docker-compose \
|
||||
&& chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
|
||||
RUN \
|
||||
if [ "$DEBUG" == "true" ]; then \
|
||||
apk add boost boost-dev; \
|
||||
@@ -292,11 +302,12 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/vars && \
|
||||
chmod +x /usr/local/bin/worker-audits && \
|
||||
chmod +x /usr/local/bin/worker-certificates && \
|
||||
chmod +x /usr/local/bin/worker-database && \
|
||||
chmod +x /usr/local/bin/worker-databases && \
|
||||
chmod +x /usr/local/bin/worker-deletes && \
|
||||
chmod +x /usr/local/bin/worker-functions && \
|
||||
chmod +x /usr/local/bin/worker-builds && \
|
||||
chmod +x /usr/local/bin/worker-mails && \
|
||||
chmod +x /usr/local/bin/worker-messaging && \
|
||||
chmod +x /usr/local/bin/worker-webhooks
|
||||
|
||||
# Letsencrypt Permissions
|
||||
|
||||
+5
-5
@@ -59,7 +59,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:0.14.2
|
||||
appwrite/appwrite:0.15.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -71,7 +71,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:0.14.2
|
||||
appwrite/appwrite:0.15.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -81,13 +81,13 @@ 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.14.2
|
||||
appwrite/appwrite:0.15.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) 文件手动设置环境。
|
||||
需要自定义容器构架,请查看我们的 Docker [环境变量](https://appwrite.io/docs/environment-variables) 文档。您还可以参考我们的 [docker-compose.yml](https://appwrite.io/install/compose) 和 [.env](https://appwrite.io/install/env) 文件手动设置环境。
|
||||
|
||||
### 从旧版本升级
|
||||
|
||||
@@ -109,7 +109,7 @@ docker run -it --rm ,
|
||||
* [**帐户**](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/databases) - 管理数据库文档和文档集。用检索界面来对文档和文档集进行读取,创建,更新,和删除。
|
||||
* [**贮存**](https://appwrite.io/docs/client/storage) - 管理文件的阅读、创建、删除和预览。设置文件的预览来满足程序的个性化需求。所有文件都由 ClamAV 扫描并安全存储和加密。
|
||||
* [**云函数**](https://appwrite.io/docs/server/functions) - 在安全,隔离的环境中运行自定义代码。这些代码可以被事件,CRON,或者手动操作触发。
|
||||
* [**语言适配**](https://appwrite.io/docs/client/locale) - 根据用户所在的的国家和地区做出合适的语言适配。
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
> It's going to get cloudy! 🌩 ☂️
|
||||
> The Appwrite Cloud is coming soon! You can learn more about our upcoming hosted solution and signup for free credits at: 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>
|
||||
@@ -19,7 +22,7 @@
|
||||
|
||||
English | [简体中文](README-CN.md)
|
||||
|
||||
[**Appwrite 0.14 has been released! Learn what's new!**](https://dev.to/appwrite/announcing-appwrite-014-with-11-cloud-function-runtimes-36f5)
|
||||
[**Appwrite 0.15 has been released! Learn what's new!**](https://dev.to/appwrite/announcing-appwrite-015-with-phone-authentication-more-5cjj)
|
||||
|
||||
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.
|
||||
|
||||
@@ -62,7 +65,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:0.14.2
|
||||
appwrite/appwrite:0.15.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -74,7 +77,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:0.14.2
|
||||
appwrite/appwrite:0.15.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -84,13 +87,13 @@ 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.14.2
|
||||
appwrite/appwrite:0.15.1
|
||||
```
|
||||
|
||||
Once the Docker installation completes, 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 installation completes.
|
||||
|
||||
|
||||
For advanced production and custom installation, check out our Docker [environment variables](https://appwrite.io/docs/environment-variables) docs. You can also use our public [docker-compose.yml](https://gist.github.com/eldadfux/977869ff6bdd7312adfd4e629ee15cc5#file-docker-compose-yml) file to manually set up an environment.
|
||||
For advanced production and custom installation, check out our Docker [environment variables](https://appwrite.io/docs/environment-variables) docs. You can also use our public [docker-compose.yml](https://appwrite.io/install/compose) and [.env](https://appwrite.io/install/env) files to manually set up an environment.
|
||||
|
||||
### Upgrade from an Older Version
|
||||
|
||||
@@ -112,7 +115,7 @@ Getting started with Appwrite is as easy as creating a new project, choosing you
|
||||
* [**Account**](https://appwrite.io/docs/client/account) - Manage current user authentication and account. Track and manage the user sessions, devices, sign-in methods, and security logs.
|
||||
* [**Users**](https://appwrite.io/docs/server/users) - Manage and list all project users when in admin mode.
|
||||
* [**Teams**](https://appwrite.io/docs/client/teams) - Manage and group users in teams. Manage memberships, invites, and user roles within a team.
|
||||
* [**Database**](https://appwrite.io/docs/client/database) - Manage database collections and documents. Read, create, update, and delete documents and filter lists of document collections using advanced filters.
|
||||
* [**Databases**](https://appwrite.io/docs/client/databases) - Manage databases, collections and documents. Read, create, update, and delete documents and filter lists of document collections using advanced filters.
|
||||
* [**Storage**](https://appwrite.io/docs/client/storage) - Manage storage files. Read, create, delete, and preview files. Manipulate the preview of your files to fit your app perfectly. All files are scanned by ClamAV and stored in a secure and encrypted way.
|
||||
* [**Functions**](https://appwrite.io/docs/server/functions) - Customize your Appwrite server by executing your custom code in a secure, isolated environment. You can trigger your code on any Appwrite system event, manually or using a CRON schedule.
|
||||
* [**Locale**](https://appwrite.io/docs/client/locale) - Track your user's location, and manage your app locale-based data.
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ return [
|
||||
'name' => 'Email/Password',
|
||||
'key' => 'emailPassword',
|
||||
'icon' => '/images/users/email.png',
|
||||
'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateSession',
|
||||
'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateEmailSession',
|
||||
'enabled' => true,
|
||||
],
|
||||
'magic-url' => [
|
||||
@@ -42,7 +42,7 @@ return [
|
||||
'name' => 'Phone',
|
||||
'key' => 'phone',
|
||||
'icon' => '/images/users/phone.png',
|
||||
'docs' => '',
|
||||
'enabled' => false,
|
||||
'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreatePhoneSession',
|
||||
'enabled' => true,
|
||||
],
|
||||
];
|
||||
|
||||
+276
-147
@@ -16,10 +16,10 @@ $auth = Config::getParam('auth', []);
|
||||
*/
|
||||
|
||||
$collections = [
|
||||
'collections' => [
|
||||
'databases' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'collections',
|
||||
'name' => 'Collections',
|
||||
'$id' => 'databases',
|
||||
'name' => 'Databases',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'name',
|
||||
@@ -31,27 +31,63 @@ $collections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'$id' => 'search',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => 'dateUpdated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'$id' => '_fulltext_search',
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
'collections' => [
|
||||
'$collection' => 'databases',
|
||||
'$id' => 'collections',
|
||||
'name' => 'Collections',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'databaseInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'databaseId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'signed' => true,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
'signed' => true,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'enabled',
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
@@ -120,7 +156,7 @@ $collections = [
|
||||
'name' => 'Attributes',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'collectionId',
|
||||
'$id' => 'databaseInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -130,6 +166,39 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'databaseId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => false,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'collectionInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'collectionId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'key',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -249,11 +318,11 @@ $collections = [
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_collection',
|
||||
'$id' => '_key_db_collection',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['collectionId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
'attributes' => ['databaseInternalId', 'collectionInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -264,7 +333,7 @@ $collections = [
|
||||
'name' => 'Indexes',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'collectionId',
|
||||
'$id' => 'databaseInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -274,6 +343,39 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'databaseId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => false,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'collectionInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'collectionId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'key',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -343,11 +445,11 @@ $collections = [
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_collection',
|
||||
'$id' => '_key_db_collection',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['collectionId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
'attributes' => ['databaseInternalId', 'collectionInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -357,6 +459,17 @@ $collections = [
|
||||
'$id' => 'projects',
|
||||
'name' => 'Projects',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'teamInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'teamId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -594,6 +707,17 @@ $collections = [
|
||||
'$id' => 'platforms',
|
||||
'name' => 'platforms',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'projectInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'projectId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -659,35 +783,13 @@ $collections = [
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'dateUpdated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
]
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_project',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
@@ -699,6 +801,17 @@ $collections = [
|
||||
'$id' => 'domains',
|
||||
'name' => 'domains',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'projectInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'projectId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -781,7 +894,7 @@ $collections = [
|
||||
[
|
||||
'$id' => '_key_project',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
@@ -794,7 +907,7 @@ $collections = [
|
||||
'name' => 'keys',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'projectId',
|
||||
'$id' => 'projectInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -804,6 +917,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'projectId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -837,12 +961,23 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => ['encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => 'expire',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_project',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
@@ -854,6 +989,17 @@ $collections = [
|
||||
'$id' => 'webhooks',
|
||||
'name' => 'webhooks',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'projectInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'projectId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -947,10 +1093,10 @@ $collections = [
|
||||
[
|
||||
'$id' => '_key_project',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
@@ -981,6 +1127,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'phone',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16, // leading '+' and 15 digitts maximum by E.164 format
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'status',
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
@@ -1047,6 +1204,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'phoneVerification',
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'reset',
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
@@ -1111,6 +1279,13 @@ $collections = [
|
||||
'lengths' => [320],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_phone',
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['phone'],
|
||||
'lengths' => [16],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_search',
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
@@ -1126,6 +1301,17 @@ $collections = [
|
||||
'$id' => 'tokens',
|
||||
'name' => 'Tokens',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'userInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'userId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1197,7 +1383,7 @@ $collections = [
|
||||
[
|
||||
'$id' => '_key_user',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userId'],
|
||||
'attributes' => ['userInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
@@ -1209,6 +1395,17 @@ $collections = [
|
||||
'$id' => 'sessions',
|
||||
'name' => 'Sessions',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'userInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'userId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1474,7 +1671,7 @@ $collections = [
|
||||
[
|
||||
'$id' => '_key_user',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userId'],
|
||||
'attributes' => ['userInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
@@ -1497,17 +1694,6 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'total',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
@@ -1548,7 +1734,7 @@ $collections = [
|
||||
'name' => 'Memberships',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'teamId',
|
||||
'$id' => 'userInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -1569,6 +1755,28 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'teamInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'teamId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'roles',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1640,21 +1848,21 @@ $collections = [
|
||||
[
|
||||
'$id' => '_key_unique',
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['teamId', 'userId'],
|
||||
'attributes' => ['teamInternalId', 'userInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_team',
|
||||
'$id' => '_key_user',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['teamId'],
|
||||
'attributes' => ['userInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_user',
|
||||
'$id' => '_key_team',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userId'],
|
||||
'attributes' => ['teamInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
@@ -1695,28 +1903,6 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'dateUpdated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => 'status',
|
||||
@@ -1726,7 +1912,6 @@ $collections = [
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
@@ -1845,17 +2030,6 @@ $collections = [
|
||||
'$id' => 'deployments',
|
||||
'name' => 'Deployments',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1898,7 +2072,6 @@ $collections = [
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
@@ -2147,17 +2320,6 @@ $collections = [
|
||||
'$id' => 'executions',
|
||||
'name' => 'Executions',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'functionId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -2189,7 +2351,6 @@ $collections = [
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
@@ -2368,26 +2529,6 @@ $collections = [
|
||||
'$id' => 'buckets',
|
||||
'name' => 'Buckets',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'signed' => false,
|
||||
'size' => 0,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'dateUpdated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'enabled',
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
@@ -2628,17 +2769,6 @@ $collections = [
|
||||
'$id' => 'files',
|
||||
'$name' => 'Files',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => 'bucketId',
|
||||
@@ -2648,7 +2778,6 @@ $collections = [
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
|
||||
@@ -48,6 +48,11 @@ return [
|
||||
'description' => 'SMTP is disabled on your Appwrite instance. You can <a href="/docs/email-delivery">learn more about setting up SMTP</a> in our docs.',
|
||||
'code' => 503,
|
||||
],
|
||||
Exception::GENERAL_PHONE_DISABLED => [
|
||||
'name' => Exception::GENERAL_PHONE_DISABLED,
|
||||
'description' => 'Phone provider is not configured. Please check the _APP_PHONE_PROVIDER environment variable of your Appwrite server.',
|
||||
'code' => 503,
|
||||
],
|
||||
Exception::GENERAL_ARGUMENT_INVALID => [
|
||||
'name' => Exception::GENERAL_ARGUMENT_INVALID,
|
||||
'description' => 'The request contains one or more invalid arguments. Please refer to the endpoint documentation.',
|
||||
@@ -170,6 +175,16 @@ return [
|
||||
'description' => 'The requested authentication method is either disabled or unsupported. Please check the supported authentication methods in the Appwrite console.',
|
||||
'code' => 501,
|
||||
],
|
||||
Exception::USER_PHONE_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_PHONE_ALREADY_EXISTS,
|
||||
'description' => 'A user with the same phone number already exists in the current project.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::USER_PHONE_NOT_FOUND => [
|
||||
'name' => Exception::USER_PHONE_NOT_FOUND,
|
||||
'description' => 'The current user does not have a phone number associated with their account.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Teams */
|
||||
Exception::TEAM_NOT_FOUND => [
|
||||
|
||||
+48
-34
@@ -69,54 +69,68 @@ return [
|
||||
],
|
||||
]
|
||||
],
|
||||
'collections' => [
|
||||
'$model' => Response::MODEL_COLLECTION,
|
||||
'databases' => [
|
||||
'$model' => Response::MODEL_DATABASE,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any collection event.',
|
||||
'documents' => [
|
||||
'$model' => Response::MODEL_DOCUMENT,
|
||||
'$description' => 'This event triggers on any database event.',
|
||||
'collections' => [
|
||||
'$model' => Response::MODEL_COLLECTION,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any documents event.',
|
||||
'$description' => 'This event triggers on any collection event.',
|
||||
'documents' => [
|
||||
'$model' => Response::MODEL_DOCUMENT,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any documents event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a document is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a document is deleted.'
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a document is updated.'
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'$model' => Response::MODEL_INDEX,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any indexes event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when an index is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when an index is deleted.'
|
||||
]
|
||||
],
|
||||
'attributes' => [
|
||||
'$model' => Response::MODEL_ATTRIBUTE,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any attributes event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when an attribute is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when an attribute is deleted.'
|
||||
]
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a document is created.',
|
||||
'$description' => 'This event triggers when a collection is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a document is deleted.'
|
||||
'$description' => 'This event triggers when a collection is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a document is updated.'
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'$model' => Response::MODEL_INDEX,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any indexes event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when an index is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when an index is deleted.'
|
||||
]
|
||||
],
|
||||
'attributes' => [
|
||||
'$model' => Response::MODEL_ATTRIBUTE,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any attributes event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when an attribute is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when an attribute is deleted.'
|
||||
'$description' => 'This event triggers when a collection is updated.',
|
||||
]
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a collection is created.'
|
||||
'$description' => 'This event triggers when a database is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a collection is deleted.',
|
||||
'$description' => 'This event triggers when a database is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a collection is updated.',
|
||||
'$description' => 'This event triggers when a database is updated.',
|
||||
]
|
||||
],
|
||||
'buckets' => [
|
||||
|
||||
+14
-14
@@ -15,7 +15,7 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '8.0.1',
|
||||
'version' => '9.0.1',
|
||||
'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' => '5.0.0',
|
||||
'version' => '6.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -81,7 +81,7 @@ return [
|
||||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '0.5.0',
|
||||
'version' => '0.6.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
@@ -116,7 +116,7 @@ return [
|
||||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '0.6.1',
|
||||
'version' => '0.7.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
@@ -162,7 +162,7 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '5.0.0',
|
||||
'version' => '6.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
@@ -180,7 +180,7 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '0.17.1',
|
||||
'version' => '0.18.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -208,7 +208,7 @@ return [
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '6.0.0',
|
||||
'version' => '7.0.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
@@ -226,7 +226,7 @@ return [
|
||||
[
|
||||
'key' => 'deno',
|
||||
'name' => 'Deno',
|
||||
'version' => '4.0.0',
|
||||
'version' => '5.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-deno',
|
||||
'package' => 'https://deno.land/x/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -244,7 +244,7 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '5.0.0',
|
||||
'version' => '6.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -262,7 +262,7 @@ return [
|
||||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '0.9.0',
|
||||
'version' => '0.10.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
@@ -280,7 +280,7 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '5.0.0',
|
||||
'version' => '6.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -352,7 +352,7 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '5.0.1',
|
||||
'version' => '6.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
@@ -370,7 +370,7 @@ return [
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '0.5.0',
|
||||
'version' => '0.6.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
@@ -392,7 +392,7 @@ return [
|
||||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '0.5.0',
|
||||
'version' => '0.6.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
|
||||
+31
-10
@@ -31,6 +31,16 @@ return [ // Ordered by ABC.
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'autodesk' => [
|
||||
'name' => 'Autodesk',
|
||||
'developers' => 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/',
|
||||
'icon' => 'icon-autodesk',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'bitbucket' => [
|
||||
'name' => 'BitBucket',
|
||||
'developers' => 'https://developer.atlassian.com/bitbucket',
|
||||
@@ -61,6 +71,16 @@ return [ // Ordered by ABC.
|
||||
'beta' => false,
|
||||
'mock' => false
|
||||
],
|
||||
'dailymotion' => [
|
||||
'name' => 'Dailymotion',
|
||||
'developers' => 'https://developers.dailymotion.com/api/',
|
||||
'icon' => 'icon-dailymotion',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false
|
||||
],
|
||||
'discord' => [
|
||||
'name' => 'Discord',
|
||||
'developers' => 'https://discordapp.com/developers/docs/topics/oauth2',
|
||||
@@ -291,6 +311,16 @@ return [ // Ordered by ABC.
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'zoom' => [
|
||||
'name' => 'Zoom',
|
||||
'developers' => 'https://marketplace.zoom.us/docs/guides/auth/oauth/',
|
||||
'icon' => 'icon-zoom',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
// 'instagram' => [
|
||||
// 'name' => 'Instagram',
|
||||
// 'developers' => 'https://www.instagram.com/developer/',
|
||||
@@ -307,16 +337,7 @@ return [ // Ordered by ABC.
|
||||
// 'beta' => false,
|
||||
// 'mock' => false,
|
||||
// ],
|
||||
'zoom' => [
|
||||
'name' => 'Zoom',
|
||||
'developers' => 'https://marketplace.zoom.us/docs/guides/auth/oauth/',
|
||||
'icon' => 'icon-zoom',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
|
||||
// Keep Last
|
||||
'mock' => [
|
||||
'name' => 'Mock',
|
||||
|
||||
@@ -34,6 +34,8 @@ $admins = [
|
||||
'buckets.write',
|
||||
'users.read',
|
||||
'users.write',
|
||||
'databases.read',
|
||||
'databases.write',
|
||||
'collections.read',
|
||||
'collections.write',
|
||||
'platforms.read',
|
||||
|
||||
@@ -13,6 +13,12 @@ return [ // List of publicly visible scopes
|
||||
'teams.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s teams',
|
||||
],
|
||||
'databases.read' => [
|
||||
'description' => 'Access to read your project\'s databases',
|
||||
],
|
||||
'databases.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s databases',
|
||||
],
|
||||
'collections.read' => [
|
||||
'description' => 'Access to read your project\'s database collections',
|
||||
],
|
||||
|
||||
+11
-8
@@ -53,18 +53,21 @@ return [
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/avatars.png',
|
||||
],
|
||||
'database' => [
|
||||
'key' => 'database',
|
||||
'name' => 'Database',
|
||||
'subtitle' => 'The Database service allows you to create structured collections of documents, query and filter lists of documents',
|
||||
'description' => '/docs/services/database.md',
|
||||
'controller' => 'api/database.php',
|
||||
'databases' => [
|
||||
'key' => 'databases',
|
||||
'name' => 'Databases',
|
||||
'subtitle' => 'The Databases service allows you to create structured collections of documents, query and filter lists of documents',
|
||||
'description' => '/docs/services/databases.md',
|
||||
'controller' => 'api/databases.php',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/client/database',
|
||||
'docsUrl' => 'https://appwrite.io/docs/client/databases',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/database.png',
|
||||
'icon' => '/images/services/databases.png',
|
||||
'globalAttributes' => [
|
||||
'databaseId'
|
||||
]
|
||||
],
|
||||
'locale' => [
|
||||
'key' => 'locale',
|
||||
|
||||
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
@@ -98,7 +98,7 @@ return [
|
||||
// ],
|
||||
[
|
||||
'name' => '_APP_CONSOLE_WHITELIST_IPS',
|
||||
'description' => 'This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\n\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma.',
|
||||
'description' => "This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\n\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma.",
|
||||
'introduction' => '',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
@@ -340,7 +340,7 @@ return [
|
||||
],
|
||||
[
|
||||
'category' => 'SMTP',
|
||||
'description' => 'Appwrite is using an SMTP server for emailing your projects users and server admins. The SMTP env vars are used to allow Appwrite server to connect to the SMTP container.\n\nIf running in production, it might be easier to use a 3rd party SMTP server as it might be a little more difficult to set up a production SMTP server that will not send all your emails into your user\'s SPAM folder.',
|
||||
'description' => "Appwrite is using an SMTP server for emailing your projects users and server admins. The SMTP env vars are used to allow Appwrite server to connect to the SMTP container.\n\nIf running in production, it might be easier to use a 3rd party SMTP server as it might be a little more difficult to set up a production SMTP server that will not send all your emails into your user\'s SPAM folder.",
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_SMTP_HOST',
|
||||
@@ -389,6 +389,30 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'Phone',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_PHONE_PROVIDER',
|
||||
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'phone://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic and telesign.",
|
||||
'introduction' => '0.15.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_PHONE_FROM',
|
||||
'description' => 'Phone number used for sending out messages. Must start with a leading \'+\' and maximum of 15 digits without spaces (+123456789).',
|
||||
'introduction' => '0.15.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'Storage',
|
||||
'description' => '',
|
||||
@@ -677,7 +701,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_RUNTIMES',
|
||||
'description' => "This option allows you to limit the available environments for cloud functions. This option is very useful for low-cost servers to safe disk space.\n\nTo enable/activate this option, pass a list of allowed environments separated by a comma.\n\nCurrently, supported environments are: " . \implode(', ', \array_keys(Config::getParam('runtimes'))),
|
||||
'description' => "This option allows you to enable or disable runtime environments for cloud functions. Disable unused runtimes to save disk space.\n\nTo enable cloud function runtimes, pass a list of enabled environments separated by a comma.\n\nCurrently, supported environments are: " . \implode(', ', \array_keys(Config::getParam('runtimes'))),
|
||||
'introduction' => '0.8.0',
|
||||
'default' => 'node-16.0,php-8.0,python-3.9,ruby-3.0',
|
||||
'required' => false,
|
||||
|
||||
+456
-21
@@ -2,7 +2,9 @@
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Phone;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Auth\Validator\Phone as ValidatorPhone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
@@ -19,6 +21,7 @@ use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Utopia\Audit\Audit as EventAudit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
@@ -129,16 +132,16 @@ App::post('/v1/account')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/account/sessions')
|
||||
->desc('Create Account Session')
|
||||
App::post('/v1/account/sessions/email')
|
||||
->desc('Create Account Session with Email')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('scope', 'public')
|
||||
->label('auth.type', 'emailPassword')
|
||||
->label('sdk.auth', [])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'createSession')
|
||||
->label('sdk.description', '/docs/references/account/create-session.md')
|
||||
->label('sdk.method', 'createEmailSession')
|
||||
->label('sdk.description', '/docs/references/account/create-session-email.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_SESSION)
|
||||
@@ -178,6 +181,7 @@ App::post('/v1/account/sessions')
|
||||
[
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $profile->getId(),
|
||||
'userInternalId' => $profile->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $email,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
@@ -254,7 +258,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('providers'), fn($node) => (!$node['mock'])))) . '.')
|
||||
->param('success', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. 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.', true, ['clients'])
|
||||
->param('failure', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. 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.', true, ['clients'])
|
||||
->param('scopes', [], new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each 128 characters long.', true)
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -507,6 +511,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
$session = new Document(array_merge([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => $provider,
|
||||
'providerUid' => $oauth2ID,
|
||||
'providerAccessToken' => $accessToken,
|
||||
@@ -519,7 +524,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
|
||||
|
||||
$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password'));
|
||||
$isAnonymousUser = Auth::isAnonymousUser($user);
|
||||
|
||||
if ($isAnonymousUser) {
|
||||
$user
|
||||
@@ -661,6 +666,7 @@ App::post('/v1/account/sessions/magic-url')
|
||||
$token = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_MAGIC_URL,
|
||||
'secret' => Auth::hash($loginSecret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
@@ -738,6 +744,8 @@ App::put('/v1/account/sessions/magic-url')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $secret, Request $request, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audits, Event $events) {
|
||||
|
||||
/** @var Utopia\Database\Document $user */
|
||||
|
||||
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
@@ -758,6 +766,7 @@ App::put('/v1/account/sessions/magic-url')
|
||||
[
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_MAGIC_URL,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
@@ -824,6 +833,234 @@ App::put('/v1/account/sessions/magic-url')
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
|
||||
App::post('/v1/account/sessions/phone')
|
||||
->desc('Create Phone session')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'public')
|
||||
->label('auth.type', 'phone')
|
||||
->label('sdk.auth', [])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'createPhoneSession')
|
||||
->label('sdk.description', '/docs/references/account/create-phone-session.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},email:{param-email}')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. 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('number', '', new ValidatorPhone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('events')
|
||||
->inject('messaging')
|
||||
->inject('phone')
|
||||
->action(function (string $userId, string $number, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Event $events, EventPhone $messaging, Phone $phone) {
|
||||
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
|
||||
throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED);
|
||||
}
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$user = $dbForProject->findOne('users', [new Query('phone', Query::TYPE_EQUAL, [$number])]);
|
||||
|
||||
if (!$user) {
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
||||
if ($limit !== 0) {
|
||||
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
|
||||
|
||||
if ($total >= $limit) {
|
||||
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'email' => null,
|
||||
'phone' => $number,
|
||||
'emailVerification' => false,
|
||||
'phoneVerification' => false,
|
||||
'status' => true,
|
||||
'password' => null,
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'reset' => false,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => null,
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $number])
|
||||
])));
|
||||
}
|
||||
|
||||
$secret = $phone->generateSecretDigits();
|
||||
|
||||
$expire = \time() + Auth::TOKEN_EXPIRATION_PHONE;
|
||||
|
||||
$token = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_PHONE,
|
||||
'secret' => $secret,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$messaging
|
||||
->setRecipient($number)
|
||||
->setMessage($secret)
|
||||
->trigger();
|
||||
|
||||
$events->setPayload(
|
||||
$response->output(
|
||||
$token->setAttribute('secret', $secret),
|
||||
Response::MODEL_TOKEN
|
||||
)
|
||||
);
|
||||
|
||||
// Hide secret for clients
|
||||
$token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $secret : '');
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($token, Response::MODEL_TOKEN)
|
||||
;
|
||||
});
|
||||
|
||||
App::put('/v1/account/sessions/phone')
|
||||
->desc('Create Phone session (confirmation)')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'public')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('sdk.auth', [])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'updatePhoneSession')
|
||||
->label('sdk.description', '/docs/references/account/update-phone-session.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_SESSION)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},userId:{param-userId}')
|
||||
->param('userId', '', new CustomId(), 'User ID.')
|
||||
->param('secret', '', new Text(256), 'Valid verification token.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('audits')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $secret, Request $request, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audits, Event $events) {
|
||||
|
||||
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$token = Auth::phoneTokenVerify($user->getAttribute('tokens', []), $secret);
|
||||
|
||||
if (!$token) {
|
||||
throw new Exception('Invalid login token', 401, Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
$secret = Auth::tokenGenerator();
|
||||
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_PHONE,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
],
|
||||
$detector->getOS(),
|
||||
$detector->getClient(),
|
||||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
/**
|
||||
* We act like we're updating and validating
|
||||
* the recovery token but actually we don't need it anymore.
|
||||
*/
|
||||
$dbForProject->deleteDocument('tokens', $token);
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$user->setAttribute('phoneVerification', true);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
if (false === $user) {
|
||||
throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$audits->setResource('user/' . $user->getId());
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $session->getId())
|
||||
;
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
}
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
;
|
||||
|
||||
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
|
||||
|
||||
$session
|
||||
->setAttribute('current', true)
|
||||
->setAttribute('countryName', $countryName)
|
||||
;
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
|
||||
App::post('/v1/account/sessions/anonymous')
|
||||
->desc('Create Anonymous Session')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
@@ -901,6 +1138,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
[
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_ANONYMOUS,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expiry,
|
||||
@@ -965,7 +1203,7 @@ App::post('/v1/account/jwt')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_JWT)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-limit', 100)
|
||||
->label('abuse-key', 'url:{url},userId:{userId}')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
@@ -1285,7 +1523,7 @@ App::patch('/v1/account/email')
|
||||
->inject('events')
|
||||
->action(function (string $email, string $password, Response $response, Document $user, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
|
||||
|
||||
$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting
|
||||
$isAnonymousUser = Auth::isAnonymousUser($user); // Check if request is from an anonymous account for converting
|
||||
|
||||
if (
|
||||
!$isAnonymousUser &&
|
||||
@@ -1295,18 +1533,15 @@ App::patch('/v1/account/email')
|
||||
}
|
||||
|
||||
$email = \strtolower($email);
|
||||
$profile = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
|
||||
|
||||
if ($profile) {
|
||||
throw new Exception('User already registered', 409, Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
$user
|
||||
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')])));
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -1322,6 +1557,59 @@ App::patch('/v1/account/email')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/account/phone')
|
||||
->desc('Update Account Phone')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.phone')
|
||||
->label('scope', 'account')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'updatePhone')
|
||||
->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_USER)
|
||||
->param('number', '', new ValidatorPhone(), '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('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('events')
|
||||
->action(function (string $phone, string $password, Response $response, Document $user, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
|
||||
|
||||
$isAnonymousUser = Auth::isAnonymousUser($user); // Check if request is from an anonymous account for converting
|
||||
|
||||
if (
|
||||
!$isAnonymousUser &&
|
||||
!Auth::passwordVerify($password, $user->getAttribute('password'))
|
||||
) { // Double check user password
|
||||
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('phone', $phone)
|
||||
->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('Phone number already exists', 409, Exception::USER_PHONE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$usage->setParam('users.update', 1);
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/account/prefs')
|
||||
->desc('Update Account Preferences')
|
||||
->groups(['api', 'account'])
|
||||
@@ -1530,8 +1818,7 @@ App::patch('/v1/account/sessions/:sessionId')
|
||||
$session
|
||||
->setAttribute('providerAccessToken', $oauth2->getAccessToken(''))
|
||||
->setAttribute('providerRefreshToken', $oauth2->getRefreshToken(''))
|
||||
->setAttribute('providerAccessTokenExpiry', \time() + (int) $oauth2->getAccessTokenExpiry(''))
|
||||
;
|
||||
->setAttribute('providerAccessTokenExpiry', \time() + (int) $oauth2->getAccessTokenExpiry(''));
|
||||
|
||||
$dbForProject->updateDocument('sessions', $sessionId, $session);
|
||||
|
||||
@@ -1680,6 +1967,7 @@ App::post('/v1/account/recovery')
|
||||
$recovery = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $profile->getId(),
|
||||
'userInternalId' => $profile->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_RECOVERY,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
@@ -1806,7 +2094,7 @@ App::post('/v1/account/verification')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'createVerification')
|
||||
->label('sdk.description', '/docs/references/account/create-verification.md')
|
||||
->label('sdk.description', '/docs/references/account/create-email-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
@@ -1840,6 +2128,7 @@ App::post('/v1/account/verification')
|
||||
$verification = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_VERIFICATION,
|
||||
'secret' => Auth::hash($verificationSecret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
@@ -1895,7 +2184,7 @@ App::put('/v1/account/verification')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'updateVerification')
|
||||
->label('sdk.description', '/docs/references/account/update-verification.md')
|
||||
->label('sdk.description', '/docs/references/account/update-email-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
@@ -1948,3 +2237,149 @@ App::put('/v1/account/verification')
|
||||
|
||||
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
App::post('/v1/account/verification/phone')
|
||||
->desc('Create Phone Verification')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('event', 'users.[userId].verification.[tokenId].create')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'createPhoneVerification')
|
||||
->label('sdk.description', '/docs/references/account/create-phone-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('phone')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('events')
|
||||
->inject('usage')
|
||||
->inject('messaging')
|
||||
->action(function (Request $request, Response $response, Phone $phone, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) {
|
||||
|
||||
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
|
||||
throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED);
|
||||
}
|
||||
|
||||
if (empty($user->getAttribute('phone'))) {
|
||||
throw new Exception('User has no phone number.', 400, Exception::USER_PHONE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$verificationSecret = Auth::tokenGenerator();
|
||||
|
||||
$secret = $phone->generateSecretDigits();
|
||||
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
|
||||
|
||||
$verification = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'type' => Auth::TOKEN_TYPE_PHONE,
|
||||
'secret' => $secret,
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
|
||||
$verification = $dbForProject->createDocument('tokens', $verification
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$messaging
|
||||
->setRecipient($user->getAttribute('phone'))
|
||||
->setMessage($secret)
|
||||
->trigger()
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('tokenId', $verification->getId())
|
||||
->setPayload($response->output(
|
||||
$verification->setAttribute('secret', $verificationSecret),
|
||||
Response::MODEL_TOKEN
|
||||
))
|
||||
;
|
||||
|
||||
// Hide secret for clients
|
||||
$verification->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $verificationSecret : '');
|
||||
|
||||
$audits->setResource('user/' . $user->getId());
|
||||
$usage->setParam('users.update', 1);
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($verification, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
App::put('/v1/account/verification/phone')
|
||||
->desc('Create Phone Verification (confirmation)')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'public')
|
||||
->label('event', 'users.[userId].verification.[tokenId].update')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', 'updatePhoneVerification')
|
||||
->label('sdk.description', '/docs/references/account/update-phone-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{param-userId}')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('secret', '', new Text(256), 'Valid verification token.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Audit $audits, Stats $usage, Event $events) {
|
||||
|
||||
$profile = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($profile->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$verification = Auth::phoneTokenVerify($user->getAttribute('tokens', []), $secret);
|
||||
|
||||
if (!$verification) {
|
||||
throw new Exception('Invalid verification token', 401, Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole('user:' . $profile->getId());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
|
||||
|
||||
$verificationDocument = $dbForProject->getDocument('tokens', $verification);
|
||||
|
||||
/**
|
||||
* We act like we're updating and validating the verification token but actually we don't need it anymore.
|
||||
*/
|
||||
$dbForProject->deleteDocument('tokens', $verification);
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
|
||||
$audits->setResource('user/' . $user->getId());
|
||||
|
||||
$usage->setParam('users.update', 1);
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('tokenId', $verificationDocument->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -66,8 +66,6 @@ App::post('/v1/functions')
|
||||
$function = $dbForProject->createDocument('functions', new Document([
|
||||
'$id' => $functionId,
|
||||
'execute' => $execute,
|
||||
'dateCreated' => time(),
|
||||
'dateUpdated' => time(),
|
||||
'status' => 'disabled',
|
||||
'name' => $name,
|
||||
'runtime' => $runtime,
|
||||
@@ -102,7 +100,7 @@ App::get('/v1/functions')
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of functions to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the function used as the starting point for the query, excluding the function itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -315,7 +313,6 @@ App::put('/v1/functions/:functionId')
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||
'execute' => $execute,
|
||||
'dateUpdated' => time(),
|
||||
'name' => $name,
|
||||
'vars' => $vars,
|
||||
'events' => $events,
|
||||
@@ -575,7 +572,6 @@ App::post('/v1/functions/:functionId/deployments')
|
||||
'$write' => ['role:all'],
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceType' => 'functions',
|
||||
'dateCreated' => time(),
|
||||
'entrypoint' => $entrypoint,
|
||||
'path' => $path,
|
||||
'size' => $fileSize,
|
||||
@@ -605,7 +601,6 @@ App::post('/v1/functions/:functionId/deployments')
|
||||
'$write' => ['role:all'],
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceType' => 'functions',
|
||||
'dateCreated' => time(),
|
||||
'entrypoint' => $entrypoint,
|
||||
'path' => $path,
|
||||
'size' => $fileSize,
|
||||
@@ -646,7 +641,7 @@ App::get('/v1/functions/:functionId/deployments')
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of deployments to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the deployment used as the starting point for the query, excluding the deployment itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -854,11 +849,11 @@ App::post('/v1/functions/:functionId/executions')
|
||||
|
||||
$executionId = $dbForProject->getId();
|
||||
|
||||
/** @var Document $execution */
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', new Document([
|
||||
'$id' => $executionId,
|
||||
'$read' => (!$user->isEmpty()) ? ['user:' . $user->getId()] : [],
|
||||
'$write' => [],
|
||||
'dateCreated' => time(),
|
||||
'functionId' => $function->getId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'trigger' => 'http', // http / schedule / event
|
||||
@@ -894,7 +889,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||
$events
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->setContext($function);
|
||||
->setContext('function', $function);
|
||||
|
||||
if ($async) {
|
||||
$event = new Func();
|
||||
@@ -952,7 +947,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||
$execution->setAttribute('time', $executionResponse['time']);
|
||||
} catch (\Throwable $th) {
|
||||
$endtime = \microtime(true);
|
||||
$time = $endtime - $execution->getAttribute('dateCreated');
|
||||
$time = $endtime - $execution->getCreatedAt();
|
||||
$execution->setAttribute('time', $time);
|
||||
$execution->setAttribute('status', 'failed');
|
||||
$execution->setAttribute('statusCode', $th->getCode());
|
||||
@@ -983,7 +978,7 @@ App::get('/v1/functions/:functionId/executions')
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('cursor', '', new UID(), 'ID of the execution used as the starting point for the query, excluding the execution itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, int $limit, int $offset, string $search, string $cursor, string $cursorDirection, Response $response, Database $dbForProject) {
|
||||
|
||||
@@ -26,6 +26,7 @@ use Appwrite\Extend\Exception;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -77,14 +78,17 @@ App::post('/v1/projects')
|
||||
}
|
||||
|
||||
$projectId = ($projectId == 'unique()') ? $dbForConsole->getId() : $projectId;
|
||||
|
||||
if ($projectId === 'console') {
|
||||
throw new Exception("'console' is a reserved project.", 400, Exception::PROJECT_RESERVED_PROJECT);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->createDocument('projects', new Document([
|
||||
'$id' => $projectId == 'unique()' ? $dbForConsole->getId() : $projectId,
|
||||
'$id' => $projectId,
|
||||
'$read' => ['team:' . $teamId],
|
||||
'$write' => ['team:' . $teamId . '/owner', 'team:' . $teamId . '/developer'],
|
||||
'name' => $name,
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'description' => $description,
|
||||
'logo' => $logo,
|
||||
@@ -108,7 +112,7 @@ App::post('/v1/projects')
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
$dbForProject->setNamespace("_{$project->getId()}");
|
||||
$dbForProject->setNamespace("_{$project->getInternalId()}");
|
||||
$dbForProject->create('appwrite');
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
@@ -169,7 +173,7 @@ App::get('/v1/projects')
|
||||
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Results offset. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the project used as the starting point for the query, excluding the project itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
@@ -267,15 +271,15 @@ App::get('/v1/projects/:projectId/usage')
|
||||
],
|
||||
];
|
||||
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
$dbForProject->setNamespace("_{$project->getInternalId()}");
|
||||
|
||||
$metrics = [
|
||||
'requests',
|
||||
'network',
|
||||
'executions',
|
||||
'users.count',
|
||||
'database.documents.count',
|
||||
'database.collections.count',
|
||||
'databases.documents.count',
|
||||
'databases.collections.count',
|
||||
'storage.total'
|
||||
];
|
||||
|
||||
@@ -322,8 +326,8 @@ App::get('/v1/projects/:projectId/usage')
|
||||
'requests' => $stats['requests'],
|
||||
'network' => $stats['network'],
|
||||
'functions' => $stats['executions'],
|
||||
'documents' => $stats['database.documents.count'],
|
||||
'collections' => $stats['database.collections.count'],
|
||||
'documents' => $stats['databases.documents.count'],
|
||||
'collections' => $stats['databases.collections.count'],
|
||||
'users' => $stats['users.count'],
|
||||
'storage' => $stats['storage.total']
|
||||
]);
|
||||
@@ -582,12 +586,11 @@ App::post('/v1/projects/:projectId/webhooks')
|
||||
|
||||
$security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
|
||||
|
||||
$webhook = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'name' => $name,
|
||||
'events' => $events,
|
||||
@@ -628,7 +631,7 @@ App::get('/v1/projects/:projectId/webhooks')
|
||||
}
|
||||
|
||||
$webhooks = $dbForConsole->find('webhooks', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
], 5000);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
@@ -661,7 +664,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$webhookId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
@@ -689,10 +692,9 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
->param('signatureKey', null, new Text(256), 'Webhook signature key. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $webhookId, string $name, array $events, string $url, bool $security, string $httpUser, string $httpPass, string $signatureKey, Response $response, Database $dbForConsole) {
|
||||
->action(function (string $projectId, string $webhookId, string $name, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
@@ -704,7 +706,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$webhookId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
@@ -720,10 +722,45 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
->setAttribute('httpPass', $httpPass)
|
||||
;
|
||||
|
||||
if (!empty($signatureKey)) {
|
||||
$webhook->setAttribute('signatureKey', $signatureKey);
|
||||
$dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook);
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
->desc('Update Webhook Signature Key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateWebhookSignature')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_WEBHOOK)
|
||||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->param('webhookId', null, new UID(), 'Webhook unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$webhookId]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook->setAttribute('signatureKey', \bin2hex(\random_bytes(64)));
|
||||
|
||||
$dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook);
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
|
||||
@@ -753,7 +790,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$webhookId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
@@ -782,9 +819,10 @@ App::post('/v1/projects/:projectId/keys')
|
||||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', 0, new Integer(), 'Key expiration time in Unix timestamp. Use 0 for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $name, array $scopes, Response $response, Database $dbForConsole) {
|
||||
->action(function (string $projectId, string $name, array $scopes, int $expire, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
@@ -796,9 +834,11 @@ App::post('/v1/projects/:projectId/keys')
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'name' => $name,
|
||||
'scopes' => $scopes,
|
||||
'expire' => $expire,
|
||||
'secret' => \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
@@ -832,7 +872,7 @@ App::get('/v1/projects/:projectId/keys')
|
||||
}
|
||||
|
||||
$keys = $dbForConsole->find('keys', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]),
|
||||
], 5000);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
@@ -865,7 +905,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$keyId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
@@ -889,9 +929,10 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->param('keyId', null, new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', 0, new Integer(), 'Key expiration time in Unix timestamp. Use 0 for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, Response $response, Database $dbForConsole) {
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, int $expire, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
@@ -901,7 +942,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$keyId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
@@ -911,6 +952,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
$key
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('scopes', $scopes)
|
||||
->setAttribute('expire', $expire)
|
||||
;
|
||||
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
@@ -943,7 +985,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$keyId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
@@ -988,14 +1030,13 @@ App::post('/v1/projects/:projectId/platforms')
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'type' => $type,
|
||||
'name' => $name,
|
||||
'key' => $key,
|
||||
'store' => $store,
|
||||
'hostname' => $hostname,
|
||||
'dateCreated' => \time(),
|
||||
'dateUpdated' => \time(),
|
||||
'hostname' => $hostname
|
||||
]);
|
||||
|
||||
$platform = $dbForConsole->createDocument('platforms', $platform);
|
||||
@@ -1061,7 +1102,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$platformId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
@@ -1098,7 +1139,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$platformId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
@@ -1107,7 +1148,6 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
|
||||
$platform
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('dateUpdated', \time())
|
||||
->setAttribute('key', $key)
|
||||
->setAttribute('store', $store)
|
||||
->setAttribute('hostname', $hostname)
|
||||
@@ -1143,7 +1183,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$platformId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
@@ -1183,7 +1223,7 @@ App::post('/v1/projects/:projectId/domains')
|
||||
|
||||
$document = $dbForConsole->findOne('domains', [
|
||||
new Query('domain', Query::TYPE_EQUAL, [$domain]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()]),
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($document && !$document->isEmpty()) {
|
||||
@@ -1202,6 +1242,7 @@ App::post('/v1/projects/:projectId/domains')
|
||||
'$id' => $dbForConsole->getId(),
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'updated' => \time(),
|
||||
'domain' => $domain->get(),
|
||||
@@ -1241,7 +1282,7 @@ App::get('/v1/projects/:projectId/domains')
|
||||
}
|
||||
|
||||
$domains = $dbForConsole->find('domains', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
], 5000);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
@@ -1274,7 +1315,7 @@ App::get('/v1/projects/:projectId/domains/:domainId')
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$domainId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
@@ -1308,7 +1349,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$domainId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
@@ -1368,7 +1409,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$domainId]),
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()])
|
||||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
|
||||
@@ -107,8 +107,6 @@ App::post('/v1/storage/buckets')
|
||||
$bucket = $dbForProject->createDocument('buckets', new Document([
|
||||
'$id' => $bucketId,
|
||||
'$collection' => 'buckets',
|
||||
'dateCreated' => \time(),
|
||||
'dateUpdated' => \time(),
|
||||
'name' => $name,
|
||||
'permission' => $permission,
|
||||
'maximumFileSize' => $maximumFileSize,
|
||||
@@ -158,7 +156,7 @@ App::get('/v1/storage/buckets')
|
||||
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
|
||||
->param('cursor', '', new UID(), 'ID of the bucket used as the starting point for the query, excluding the bucket itself. Should be used for efficient pagination when working with large sets of data.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -546,7 +544,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
'$id' => $fileId,
|
||||
'$read' => $read,
|
||||
'$write' => $write,
|
||||
'dateCreated' => \time(),
|
||||
'bucketId' => $bucket->getId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
@@ -613,7 +610,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
'$id' => $fileId,
|
||||
'$read' => $read,
|
||||
'$write' => $write,
|
||||
'dateCreated' => \time(),
|
||||
'bucketId' => $bucket->getId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
@@ -654,7 +650,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
$events
|
||||
->setParam('bucketId', $bucket->getId())
|
||||
->setParam('fileId', $file->getId())
|
||||
->setContext($bucket)
|
||||
->setContext('bucket', $bucket)
|
||||
;
|
||||
|
||||
$metadata = null; // was causing leaks as it was passed by reference
|
||||
@@ -680,7 +676,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of files to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the file used as the starting point for the query, excluding the file itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -1361,7 +1357,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
$events
|
||||
->setParam('bucketId', $bucket->getId())
|
||||
->setParam('fileId', $file->getId())
|
||||
->setContext($bucket)
|
||||
->setContext('bucket', $bucket)
|
||||
;
|
||||
|
||||
$audits->setResource('file/' . $file->getId());
|
||||
@@ -1463,7 +1459,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
$events
|
||||
->setParam('bucketId', $bucket->getId())
|
||||
->setParam('fileId', $file->getId())
|
||||
->setContext($bucket)
|
||||
->setContext('bucket', $bucket)
|
||||
->setPayload($response->output($file, Response::MODEL_FILE))
|
||||
;
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@ App::post('/v1/teams')
|
||||
'$write' => ['team:' . $teamId . '/owner'],
|
||||
'name' => $name,
|
||||
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
'dateCreated' => \time(),
|
||||
'search' => implode(' ', [$teamId, $name]),
|
||||
])));
|
||||
|
||||
@@ -74,7 +73,9 @@ App::post('/v1/teams')
|
||||
'$read' => ['user:' . $user->getId(), 'team:' . $team->getId()],
|
||||
'$write' => ['user:' . $user->getId(), 'team:' . $team->getId() . '/owner'],
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'roles' => $roles,
|
||||
'invited' => \time(),
|
||||
'joined' => \time(),
|
||||
@@ -118,7 +119,7 @@ App::get('/v1/teams')
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of teams to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the team used as the starting point for the query, excluding the team itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -367,7 +368,9 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:' . $invitee->getId(), 'team:' . $team->getId() . '/owner'],
|
||||
'userId' => $invitee->getId(),
|
||||
'userInternalId' => $invitee->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'roles' => $roles,
|
||||
'invited' => \time(),
|
||||
'joined' => ($isPrivilegedUser || $isAppUser) ? \time() : 0,
|
||||
@@ -446,7 +449,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of memberships to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the membership used as the starting point for the query, excluding the membership itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -702,6 +705,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
$session = new Document(array_merge([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $user->getAttribute('email'),
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
@@ -103,7 +104,7 @@ App::get('/v1/users')
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of users to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursor', '', new UID(), 'ID of the user used as the starting point for the query, excluding the user itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
|
||||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -406,8 +407,8 @@ App::patch('/v1/users/:userId/verification')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'updateVerification')
|
||||
->label('sdk.description', '/docs/references/users/update-user-verification.md')
|
||||
->label('sdk.method', 'updateEmailVerification')
|
||||
->label('sdk.description', '/docs/references/users/update-user-email-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
@@ -438,6 +439,45 @@ App::patch('/v1/users/:userId/verification')
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/verification/phone')
|
||||
->desc('Update Phone Verification')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.verification')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'updatePhoneVerification')
|
||||
->label('sdk.description', '/docs/references/users/update-user-phone-verification.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('phoneVerification', false, new Boolean(), 'User phone verification status.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->inject('events')
|
||||
->action(function (string $userId, bool $phoneVerification, Response $response, Database $dbForProject, Stats $usage, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('phoneVerification', $phoneVerification));
|
||||
|
||||
$usage
|
||||
->setParam('users.update', 1)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/name')
|
||||
->desc('Update Name')
|
||||
->groups(['api', 'users'])
|
||||
@@ -551,15 +591,11 @@ App::patch('/v1/users/:userId/email')
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting
|
||||
if (!$isAnonymousUser) {
|
||||
//TODO: Remove previous unique ID.
|
||||
}
|
||||
|
||||
$email = \strtolower($email);
|
||||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false)
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name')]))
|
||||
;
|
||||
|
||||
@@ -570,6 +606,55 @@ App::patch('/v1/users/:userId/email')
|
||||
}
|
||||
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/phone')
|
||||
->desc('Update Phone')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.phone')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'updatePhone')
|
||||
->label('sdk.description', '/docs/references/users/update-user-phone.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('number', '', new Phone(), 'User phone number.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $number, Response $response, Database $dbForProject, EventAudit $audits, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('phone', $number)
|
||||
->setAttribute('phoneVerification', false)
|
||||
;
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
;
|
||||
|
||||
@@ -19,6 +19,7 @@ use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Response\Filters\V11 as ResponseV11;
|
||||
use Appwrite\Utopia\Response\Filters\V12 as ResponseV12;
|
||||
use Appwrite\Utopia\Response\Filters\V13 as ResponseV13;
|
||||
use Appwrite\Utopia\Response\Filters\V14 as ResponseV14;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -177,6 +178,9 @@ App::init(function (App $utopia, Request $request, Response $response, Document
|
||||
case version_compare($responseFormat, '0.13.4', '<='):
|
||||
Response::setFilter(new ResponseV13());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.14.0', '<='):
|
||||
Response::setFilter(new ResponseV14());
|
||||
break;
|
||||
default:
|
||||
Response::setFilter(null);
|
||||
}
|
||||
@@ -279,6 +283,12 @@ App::init(function (App $utopia, Request $request, Response $response, Document
|
||||
$role = Auth::USER_ROLE_APP;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
|
||||
|
||||
$expire = $key->getAttribute('expire', 0);
|
||||
|
||||
if (!empty($expire) && $expire < \time()) {
|
||||
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
}
|
||||
@@ -292,11 +302,10 @@ App::init(function (App $utopia, Request $request, Response $response, Document
|
||||
|
||||
$service = $route->getLabel('sdk.namespace', '');
|
||||
if (!empty($service)) {
|
||||
$serviceRoles = Authorization::getRoles();
|
||||
if (
|
||||
array_key_exists($service, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$service]
|
||||
&& !(Auth::isPrivilegedUser($serviceRoles) || Auth::isAppUser($serviceRoles))
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
@@ -18,7 +19,7 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
App::init(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, Event $database, Database $dbForProject, string $mode) {
|
||||
App::init(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
@@ -164,7 +165,7 @@ App::init(function (App $utopia, Request $request, Document $project) {
|
||||
}
|
||||
}, ['utopia', 'request', 'project'], 'auth');
|
||||
|
||||
App::shutdown(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, Event $database, string $mode, Database $dbForProject) {
|
||||
App::shutdown(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
|
||||
|
||||
if (!empty($events->getEvent())) {
|
||||
if (empty($events->getPayload())) {
|
||||
@@ -192,16 +193,17 @@ App::shutdown(function (App $utopia, Request $request, Response $response, Docum
|
||||
if ($project->getId() !== 'console') {
|
||||
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
|
||||
$payload = new Document($events->getPayload());
|
||||
$context = $events->getContext() ?? false;
|
||||
|
||||
$collection = ($context && $context->getCollection() === 'collections') ? $context : null;
|
||||
$bucket = ($context && $context->getCollection() === 'buckets') ? $context : null;
|
||||
$db = $events->getContext('database');
|
||||
$collection = $events->getContext('collection');
|
||||
$bucket = $events->getContext('bucket');
|
||||
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $project,
|
||||
database: $db,
|
||||
collection: $collection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
@@ -215,21 +215,56 @@ App::get('/console/keys')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/database')
|
||||
App::get('/console/databases')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/database/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/index.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Database')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/database/collection')
|
||||
App::get('/console/databases/database')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('id', '', new UID(), 'Database unique ID.')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (string $id, Response $response, View $layout) {
|
||||
|
||||
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
->setParam('method', 'database.listLogs')
|
||||
->setParam('params', [
|
||||
'database-id' => '{{router.params.id}}',
|
||||
])
|
||||
;
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/database.phtml');
|
||||
|
||||
$page->setParam('logs', $logs);
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Database')
|
||||
->setParam('body', $page)
|
||||
;
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Expires', 0)
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/console/databases/collection')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
@@ -242,13 +277,14 @@ App::get('/console/database/collection')
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
->setParam('method', 'database.listCollectionLogs')
|
||||
->setParam('method', 'databases.listCollectionLogs')
|
||||
->setParam('params', [
|
||||
'collection-id' => '{{router.params.id}}',
|
||||
'database-id' => '{{router.params.databaseId}}'
|
||||
])
|
||||
;
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/database/collection.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/collection.phtml');
|
||||
|
||||
$page->setParam('logs', $logs);
|
||||
|
||||
@@ -264,29 +300,32 @@ App::get('/console/database/collection')
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/console/database/document')
|
||||
App::get('/console/databases/document')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('databaseId', '', new UID(), 'Database unique ID.')
|
||||
->param('collection', '', new UID(), 'Collection unique ID.')
|
||||
->inject('layout')
|
||||
->action(function (string $collection, View $layout) {
|
||||
->action(function (string $databaseId, string $collection, View $layout) {
|
||||
|
||||
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
->setParam('method', 'database.listDocumentLogs')
|
||||
->setParam('method', 'databases.listDocumentLogs')
|
||||
->setParam('params', [
|
||||
'database-id' => '{{router.params.databaseId}}',
|
||||
'collection-id' => '{{router.params.collection}}',
|
||||
'document-id' => '{{router.params.id}}',
|
||||
])
|
||||
;
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/database/document.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
|
||||
|
||||
$page
|
||||
->setParam('new', false)
|
||||
->setParam('database', $databaseId)
|
||||
->setParam('collection', $collection)
|
||||
->setParam('logs', $logs)
|
||||
;
|
||||
@@ -296,18 +335,20 @@ App::get('/console/database/document')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/database/document/new')
|
||||
App::get('/console/databases/document/new')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('databaseId', '', new UID(), 'Database unique ID.')
|
||||
->param('collection', '', new UID(), 'Collection unique ID.')
|
||||
->inject('layout')
|
||||
->action(function (string $collection, View $layout) {
|
||||
->action(function (string $databaseId, string $collection, View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/database/document.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/databases/document.phtml');
|
||||
|
||||
$page
|
||||
->setParam('new', true)
|
||||
->setParam('database', $databaseId)
|
||||
->setParam('collection', $collection)
|
||||
->setParam('logs', new View())
|
||||
;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+7
-2
@@ -118,6 +118,13 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
if (!$dbForConsole->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Skip to prevent 0.15 migration issues.
|
||||
*/
|
||||
if ($key === 'databases' && $dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), 'collections')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
|
||||
|
||||
$attributes = [];
|
||||
@@ -155,8 +162,6 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
$dbForConsole->createDocument('buckets', new Document([
|
||||
'$id' => 'default',
|
||||
'$collection' => 'buckets',
|
||||
'dateCreated' => \time(),
|
||||
'dateUpdated' => \time(),
|
||||
'name' => 'Default',
|
||||
'permission' => 'file',
|
||||
'maximumFileSize' => (int) App::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
|
||||
|
||||
+49
-16
@@ -23,11 +23,17 @@ use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Phone\Mock;
|
||||
use Appwrite\Auth\Phone\Telesign;
|
||||
use Appwrite\Auth\Phone\TextMagic;
|
||||
use Appwrite\Auth\Phone\Twilio;
|
||||
use Appwrite\DSN\DSN;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\GraphQL\Builder;
|
||||
use Appwrite\GraphQL\CoroutinePromiseAdapter;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
@@ -81,9 +87,10 @@ const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
|
||||
const APP_LIMIT_ENCRYPTION = 20000000; //20MB
|
||||
const APP_LIMIT_COMPRESSION = 20000000; //20MB
|
||||
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
|
||||
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
|
||||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_CACHE_BUSTER = 305;
|
||||
const APP_VERSION_STABLE = '0.14.2';
|
||||
const APP_CACHE_BUSTER = 401;
|
||||
const APP_VERSION_STABLE = '0.15.1';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
@@ -121,6 +128,7 @@ const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
|
||||
const BUILD_TYPE_DEPLOYMENT = 'deployment';
|
||||
const BUILD_TYPE_RETRY = 'retry';
|
||||
// Deletion Types
|
||||
const DELETE_TYPE_DATABASES = 'databases';
|
||||
const DELETE_TYPE_DOCUMENT = 'document';
|
||||
const DELETE_TYPE_COLLECTIONS = 'collections';
|
||||
const DELETE_TYPE_PROJECTS = 'projects';
|
||||
@@ -135,6 +143,7 @@ const DELETE_TYPE_CERTIFICATES = 'certificates';
|
||||
const DELETE_TYPE_USAGE = 'usage';
|
||||
const DELETE_TYPE_REALTIME = 'realtime';
|
||||
const DELETE_TYPE_BUCKETS = 'buckets';
|
||||
const DELETE_TYPE_SESSIONS = 'sessions';
|
||||
// Mail Types
|
||||
const MAIL_TYPE_VERIFICATION = 'verification';
|
||||
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
|
||||
@@ -259,8 +268,9 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('attributes', [
|
||||
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], $database->getAttributeLimit());
|
||||
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
|
||||
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
|
||||
], $database->getAttributeLimit(), 0, []);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -272,7 +282,8 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('indexes', [
|
||||
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
|
||||
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
|
||||
], 64);
|
||||
}
|
||||
);
|
||||
@@ -285,7 +296,7 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('platforms', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
}
|
||||
);
|
||||
@@ -298,7 +309,7 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('domains', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
}
|
||||
);
|
||||
@@ -311,7 +322,7 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('keys', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
}
|
||||
);
|
||||
@@ -324,7 +335,7 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('webhooks', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
}
|
||||
);
|
||||
@@ -336,7 +347,7 @@ Database::addFilter(
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
}
|
||||
);
|
||||
@@ -349,7 +360,7 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('tokens', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
}
|
||||
);
|
||||
@@ -362,7 +373,7 @@ Database::addFilter(
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('memberships', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
}
|
||||
);
|
||||
@@ -463,6 +474,11 @@ $register->set('dbPool', function () {
|
||||
->withPassword($dbPass)
|
||||
->withOptions([
|
||||
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
|
||||
PDO::ATTR_TIMEOUT => 3, // Seconds
|
||||
PDO::ATTR_PERSISTENT => true,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => true,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => true,
|
||||
]),
|
||||
64
|
||||
);
|
||||
@@ -544,7 +560,7 @@ $register->set('smtp', function () {
|
||||
return $mail;
|
||||
});
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-03.mmdb');
|
||||
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-06.mmdb');
|
||||
});
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
@@ -555,11 +571,12 @@ $register->set('db', function () {
|
||||
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
|
||||
|
||||
$pdo = new PDO("mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array(
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
|
||||
PDO::ATTR_TIMEOUT => 3, // Seconds
|
||||
PDO::ATTR_PERSISTENT => true,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_EMULATE_PREPARES => true,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => true,
|
||||
));
|
||||
|
||||
return $pdo;
|
||||
@@ -691,6 +708,7 @@ App::setResource('audits', fn() => new Audit());
|
||||
App::setResource('mails', fn() => new Mail());
|
||||
App::setResource('deletes', fn() => new Delete());
|
||||
App::setResource('database', fn() => new EventDatabase());
|
||||
App::setResource('messaging', fn() => new Phone());
|
||||
App::setResource('usage', function ($register) {
|
||||
return new Stats($register->get('statsd'));
|
||||
}, ['register']);
|
||||
@@ -841,6 +859,7 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
|
||||
App::setResource('console', function () {
|
||||
return new Document([
|
||||
'$id' => 'console',
|
||||
'$internalId' => 'console',
|
||||
'name' => 'Appwrite',
|
||||
'$collection' => 'projects',
|
||||
'description' => 'Appwrite core engine',
|
||||
@@ -870,12 +889,12 @@ App::setResource('console', function () {
|
||||
]);
|
||||
}, []);
|
||||
|
||||
App::setResource('dbForProject', function ($db, $cache, $project) {
|
||||
App::setResource('dbForProject', function ($db, $cache, Document $project) {
|
||||
$cache = new Cache(new RedisCache($cache));
|
||||
|
||||
$database = new Database(new MariaDB($db), $cache);
|
||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$database->setNamespace("_{$project->getId()}");
|
||||
$database->setNamespace("_{$project->getInternalId()}");
|
||||
|
||||
return $database;
|
||||
}, ['db', 'cache', 'project']);
|
||||
@@ -967,6 +986,20 @@ App::setResource('geodb', function ($register) {
|
||||
return $register->get('geodb');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('phone', function () {
|
||||
$dsn = new DSN(App::getEnv('_APP_PHONE_PROVIDER'));
|
||||
$user = $dsn->getUser();
|
||||
$secret = $dsn->getPassword();
|
||||
|
||||
return match ($dsn->getHost()) {
|
||||
'mock' => new Mock('', ''), // used for tests
|
||||
'twilio' => new Twilio($user, $secret),
|
||||
'text-magic' => new TextMagic($user, $secret),
|
||||
'telesign' => new Telesign($user, $secret),
|
||||
default => null
|
||||
};
|
||||
});
|
||||
|
||||
App::setResource('promiseAdapter', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('promiseAdapter');
|
||||
|
||||
+34
-22
@@ -112,8 +112,9 @@ function getDatabase(Registry &$register, string $namespace)
|
||||
if (!$database->exists($database->getDefaultDatabase(), 'realtime')) {
|
||||
throw new Exception('Collection not ready');
|
||||
}
|
||||
|
||||
break; // leave loop if successful
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $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());
|
||||
@@ -138,24 +139,30 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
/**
|
||||
* Create document for this worker to share stats across Containers.
|
||||
*/
|
||||
go(function () use ($register, $containerId, &$statsDocument, $logError) {
|
||||
try {
|
||||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
$document = new Document([
|
||||
'$id' => $database->getId(),
|
||||
'$collection' => 'realtime',
|
||||
'$read' => [],
|
||||
'$write' => [],
|
||||
'container' => $containerId,
|
||||
'timestamp' => time(),
|
||||
'value' => '{}'
|
||||
]);
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
} catch (\Throwable $th) {
|
||||
call_user_func($logError, $th, "createWorkerDocument");
|
||||
} finally {
|
||||
call_user_func($returnDatabase);
|
||||
}
|
||||
go(function () use ($register, $containerId, &$statsDocument) {
|
||||
$attempts = 0;
|
||||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
do {
|
||||
try {
|
||||
$attempts++;
|
||||
$document = new Document([
|
||||
'$id' => $database->getId(),
|
||||
'$collection' => 'realtime',
|
||||
'$read' => [],
|
||||
'$write' => [],
|
||||
'container' => $containerId,
|
||||
'timestamp' => time(),
|
||||
'value' => '{}'
|
||||
]);
|
||||
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
break;
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
|
||||
sleep(DATABASE_RECONNECT_SLEEP);
|
||||
}
|
||||
} while (true);
|
||||
call_user_func($returnDatabase);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -297,7 +304,9 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
|
||||
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
|
||||
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
|
||||
[$database, $returnDatabase] = getDatabase($register, "_{$projectId}");
|
||||
[$consoleDatabase, $returnConsoleDatabase] = getDatabase($register, '_console');
|
||||
$project = Authorization::skip(fn() => $consoleDatabase->getDocument('projects', $projectId));
|
||||
[$database, $returnDatabase] = getDatabase($register, "_{$project->getInternalId()}");
|
||||
|
||||
$user = $database->getDocument('users', $userId);
|
||||
|
||||
@@ -306,6 +315,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
$realtime->subscribe($projectId, $connection, $roles, $realtime->connections[$connection]['channels']);
|
||||
|
||||
call_user_func($returnDatabase);
|
||||
call_user_func($returnConsoleDatabase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +383,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$cache = new Cache(new RedisCache($redis));
|
||||
$database = new Database(new MariaDB($db), $cache);
|
||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$database->setNamespace("_{$project->getId()}");
|
||||
$database->setNamespace("_{$project->getInternalId()}");
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
@@ -480,7 +490,9 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$cache = new Cache(new RedisCache($redis));
|
||||
$database = new Database(new MariaDB($db), $cache);
|
||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$database->setNamespace("_{$realtime->connections[$connection]['projectId']}");
|
||||
$database->setNamespace("_console");
|
||||
$project = Authorization::skip(fn() => $database->getDocument('projects', $realtime->connections[$connection]['projectId']));
|
||||
$database->setNamespace("_{$project->getInternalId()}");
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
|
||||
@@ -33,7 +33,7 @@ $cli
|
||||
* 3. Ask user to backup important volumes, env vars, and SQL tables
|
||||
* In th future we can try and automate this for smaller/medium size setups
|
||||
* 4. Drop new docker-compose.yml setup (located inside the container, no network dependencies with appwrite.io) - DONE
|
||||
* 5. Run docker-compose up -d - DONE
|
||||
* 5. Run docker compose up -d - DONE
|
||||
* 6. Run data migration
|
||||
*/
|
||||
$config = Config::getParam('variables');
|
||||
@@ -214,9 +214,9 @@ $cli
|
||||
}
|
||||
}
|
||||
|
||||
Console::log("Running \"docker-compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes\"");
|
||||
Console::log("Running \"docker compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes\"");
|
||||
|
||||
$exit = Console::execute("${env} docker-compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr);
|
||||
$exit = Console::execute("${env} docker compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr);
|
||||
|
||||
if ($exit !== 0) {
|
||||
$message = 'Failed to install Appwrite dockers';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
global $cli;
|
||||
global $register;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Utopia\App;
|
||||
@@ -93,6 +94,14 @@ $cli
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function notifyDeleteExpiredSessions()
|
||||
{
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_SESSIONS)
|
||||
->setTimestamp(time() - Auth::TOKEN_EXPIRATION_LOGIN_LONG)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function renewCertificates($dbForConsole)
|
||||
{
|
||||
$time = date('d-m-Y H:i:s', time());
|
||||
@@ -136,6 +145,7 @@ $cli
|
||||
notifyDeleteAuditLogs($auditLogRetention);
|
||||
notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d);
|
||||
notifyDeleteConnections();
|
||||
notifyDeleteExpiredSessions();
|
||||
renewCertificates($database);
|
||||
}, $interval);
|
||||
});
|
||||
|
||||
@@ -28,9 +28,9 @@ $cli
|
||||
Console::success('Starting Data Migration to version ' . $version);
|
||||
|
||||
$db = $register->get('db', true);
|
||||
$cache = $register->get('cache', true);
|
||||
|
||||
$cache = new Cache(new RedisCache($cache));
|
||||
$redis = $register->get('cache', true);
|
||||
$redis->flushAll();
|
||||
$cache = new Cache(new RedisCache($redis));
|
||||
|
||||
$projectDB = new Database(new MariaDB($db), $cache);
|
||||
$projectDB->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
@@ -79,5 +79,6 @@ $cli
|
||||
}
|
||||
|
||||
Swoole\Event::wait(); // Wait for Coroutines to finish
|
||||
$redis->flushAll();
|
||||
Console::success('Data Migration Completed');
|
||||
});
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ $cli
|
||||
$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', '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', 'latest'])) {
|
||||
throw new Exception('Unknown version given');
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
->setTwitter(APP_SOCIAL_TWITTER_HANDLE)
|
||||
->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD)
|
||||
->setDefaultHeaders([
|
||||
'X-Appwrite-Response-Format' => '0.14.0',
|
||||
'X-Appwrite-Response-Format' => '0.15.0',
|
||||
]);
|
||||
|
||||
try {
|
||||
|
||||
+74
-84
@@ -123,104 +123,94 @@ $cli
|
||||
],
|
||||
];
|
||||
|
||||
foreach (['swagger2', 'open-api3'] as $format) {
|
||||
foreach ($platforms as $platform) {
|
||||
$routes = [];
|
||||
$models = [];
|
||||
$services = [];
|
||||
foreach ($platforms as $platform) {
|
||||
$routes = [];
|
||||
$models = [];
|
||||
$services = [];
|
||||
|
||||
foreach ($appRoutes as $key => $method) {
|
||||
foreach ($method as $route) {
|
||||
/** @var \Utopia\Route $route */
|
||||
$routeSecurity = $route->getLabel('sdk.auth', []);
|
||||
$sdkPlatofrms = [];
|
||||
foreach ($appRoutes as $key => $method) {
|
||||
foreach ($method as $route) {
|
||||
/** @var \Utopia\Route $route */
|
||||
$routeSecurity = $route->getLabel('sdk.auth', []);
|
||||
$sdkPlaforms = [];
|
||||
|
||||
foreach ($routeSecurity as $value) {
|
||||
switch ($value) {
|
||||
case APP_AUTH_TYPE_SESSION:
|
||||
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
|
||||
break;
|
||||
case APP_AUTH_TYPE_KEY:
|
||||
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case APP_AUTH_TYPE_JWT:
|
||||
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case APP_AUTH_TYPE_ADMIN:
|
||||
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
|
||||
break;
|
||||
}
|
||||
foreach ($routeSecurity as $value) {
|
||||
switch ($value) {
|
||||
case APP_AUTH_TYPE_SESSION:
|
||||
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
|
||||
break;
|
||||
case APP_AUTH_TYPE_KEY:
|
||||
$sdkPlaforms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case APP_AUTH_TYPE_JWT:
|
||||
$sdkPlaforms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case APP_AUTH_TYPE_ADMIN:
|
||||
$sdkPlaforms[] = APP_PLATFORM_CONSOLE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($routeSecurity)) {
|
||||
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
|
||||
}
|
||||
|
||||
if (!$route->getLabel('docs', true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($route->getLabel('sdk.mock', false) && !$mocks) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$route->getLabel('sdk.mock', false) && $mocks) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($route->getLabel('sdk.namespace', null))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$routes[] = $route;
|
||||
$modelLabel = $route->getLabel('sdk.response.model', 'none');
|
||||
\is_array($modelLabel) ? \array_map(function ($m) use ($response) {
|
||||
return $response->getModel($m);
|
||||
}, $modelLabel) : $response->getModel($modelLabel);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Config::getParam('services', []) as $service) {
|
||||
if (
|
||||
!isset($service['docs']) // Skip service if not part of the public API
|
||||
|| !isset($service['sdk'])
|
||||
|| !$service['docs']
|
||||
|| !$service['sdk']
|
||||
) {
|
||||
if (empty($routeSecurity)) {
|
||||
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
|
||||
}
|
||||
|
||||
if (!$route->getLabel('docs', true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$services[] = [
|
||||
'name' => $service['key'] ?? '',
|
||||
'description' => $service['subtitle'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
$models = $response->getModels();
|
||||
|
||||
foreach ($models as $key => $value) {
|
||||
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
|
||||
unset($models[$key]);
|
||||
if ($route->getLabel('sdk.mock', false) && !$mocks) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$route->getLabel('sdk.mock', false) && $mocks) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($route->getLabel('sdk.namespace', null))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlaforms)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$routes[] = $route;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Config::getParam('services', []) as $service) {
|
||||
if (
|
||||
!isset($service['docs']) // Skip service if not part of the public API
|
||||
|| !isset($service['sdk'])
|
||||
|| !$service['docs']
|
||||
|| !$service['sdk']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($format) {
|
||||
case 'swagger2':
|
||||
$formatInstance = new Swagger2(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
|
||||
break;
|
||||
$services[] = [
|
||||
'name' => $service['key'] ?? '',
|
||||
'description' => $service['subtitle'] ?? '',
|
||||
'x-globalAttributes' => $service['globalAttributes'] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
case 'open-api3':
|
||||
$formatInstance = new OpenAPI3(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
|
||||
break;
|
||||
$models = $response->getModels();
|
||||
|
||||
default:
|
||||
throw new Exception('Format not found: ' . $format);
|
||||
break;
|
||||
foreach ($models as $key => $value) {
|
||||
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
|
||||
unset($models[$key]);
|
||||
}
|
||||
}
|
||||
// var_dump($models);
|
||||
$arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
|
||||
foreach (['swagger2', 'open-api3'] as $format) {
|
||||
$formatInstance = match ($format) {
|
||||
'swagger2' => new Swagger2(...$arguments),
|
||||
'open-api3' => new OpenAPI3(...$arguments),
|
||||
default => throw new Exception('Format not found: ' . $format)
|
||||
};
|
||||
|
||||
$specs = new Specification($formatInstance);
|
||||
$endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]');
|
||||
|
||||
+110
-772
@@ -2,271 +2,131 @@
|
||||
|
||||
global $cli, $register;
|
||||
|
||||
use Appwrite\Stats\Usage;
|
||||
use Appwrite\Stats\UsageDB;
|
||||
use InfluxDB\Database as InfluxDatabase;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Redis;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Logger\Log;
|
||||
|
||||
/**
|
||||
* Metrics We collect
|
||||
*
|
||||
* General
|
||||
*
|
||||
* requests
|
||||
* network
|
||||
* executions
|
||||
*
|
||||
* Database
|
||||
*
|
||||
* database.collections.create
|
||||
* database.collections.read
|
||||
* database.collections.update
|
||||
* database.collections.delete
|
||||
* database.documents.create
|
||||
* database.documents.read
|
||||
* database.documents.update
|
||||
* database.documents.delete
|
||||
* database.collections.{collectionId}.documents.create
|
||||
* database.collections.{collectionId}.documents.read
|
||||
* database.collections.{collectionId}.documents.update
|
||||
* database.collections.{collectionId}.documents.delete
|
||||
*
|
||||
* Storage
|
||||
*
|
||||
* storage.buckets.create
|
||||
* storage.buckets.read
|
||||
* storage.buckets.update
|
||||
* storage.buckets.delete
|
||||
* storage.files.create
|
||||
* storage.files.read
|
||||
* storage.files.update
|
||||
* storage.files.delete
|
||||
* storage.buckets.{bucketId}.files.create
|
||||
* storage.buckets.{bucketId}.files.read
|
||||
* storage.buckets.{bucketId}.files.update
|
||||
* storage.buckets.{bucketId}.files.delete
|
||||
*
|
||||
* Users
|
||||
*
|
||||
* users.create
|
||||
* users.read
|
||||
* users.update
|
||||
* users.delete
|
||||
* users.sessions.create
|
||||
* users.sessions.{provider}.create
|
||||
* users.sessions.delete
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* functions.{functionId}.executions
|
||||
* functions.{functionId}.failures
|
||||
* functions.{functionId}.compute
|
||||
*
|
||||
* Counters
|
||||
*
|
||||
* users.count
|
||||
* storage.buckets.count
|
||||
* storage.files.count
|
||||
* storage.buckets.{bucketId}.files.count
|
||||
* database.collections.count
|
||||
* database.documents.count
|
||||
* database.collections.{collectionId}.documents.count
|
||||
*
|
||||
* Totals
|
||||
*
|
||||
* storage.total
|
||||
*
|
||||
*/
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
function getDatabase(Registry &$register, string $namespace): Database
|
||||
{
|
||||
$attempts = 0;
|
||||
|
||||
do {
|
||||
try {
|
||||
$attempts++;
|
||||
|
||||
$db = $register->get('db');
|
||||
$redis = $register->get('cache');
|
||||
|
||||
$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(), 'projects')) {
|
||||
throw new Exception('Projects collection not ready');
|
||||
}
|
||||
break; // leave loop if successful
|
||||
} catch (\Exception$e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) {
|
||||
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
|
||||
}
|
||||
sleep(DATABASE_RECONNECT_SLEEP);
|
||||
}
|
||||
} while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS);
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
function getInfluxDB(Registry &$register): InfluxDatabase
|
||||
{
|
||||
/** @var InfluxDB\Client $client */
|
||||
$client = $register->get('influxdb');
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
|
||||
do { // check if telegraf database is ready
|
||||
try {
|
||||
$attempts++;
|
||||
$database = $client->selectDB('telegraf');
|
||||
if (in_array('telegraf', $client->listDatabases())) {
|
||||
break; // leave the do-while if successful
|
||||
}
|
||||
} catch (\Throwable$th) {
|
||||
Console::warning("InfluxDB not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('InfluxDB database not ready yet');
|
||||
}
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
return $database;
|
||||
}
|
||||
|
||||
$logError = function (Throwable $error, string $action = 'syncUsageStats') use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace("realtime");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Usage stats log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
};
|
||||
|
||||
$cli
|
||||
->task('usage')
|
||||
->desc('Schedules syncing data from influxdb to Appwrite console db')
|
||||
->action(function () use ($register) {
|
||||
->action(function () use ($register, $logError) {
|
||||
Console::title('Usage Aggregation V1');
|
||||
Console::success(APP_NAME . ' usage aggregation process v1 has started');
|
||||
|
||||
$interval = (int) App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '30'); // 30 seconds (by default)
|
||||
$periods = [
|
||||
[
|
||||
'key' => '30m',
|
||||
'startTime' => '-24 hours',
|
||||
],
|
||||
[
|
||||
'key' => '1d',
|
||||
'startTime' => '-90 days',
|
||||
],
|
||||
];
|
||||
|
||||
// all the metrics that we are collecting at the moment
|
||||
$globalMetrics = [
|
||||
'requests' => [
|
||||
'table' => 'appwrite_usage_requests_all',
|
||||
],
|
||||
'network' => [
|
||||
'table' => 'appwrite_usage_network_all',
|
||||
],
|
||||
'executions' => [
|
||||
'table' => 'appwrite_usage_executions_all',
|
||||
],
|
||||
'database.collections.create' => [
|
||||
'table' => 'appwrite_usage_database_collections_create',
|
||||
],
|
||||
'database.collections.read' => [
|
||||
'table' => 'appwrite_usage_database_collections_read',
|
||||
],
|
||||
'database.collections.update' => [
|
||||
'table' => 'appwrite_usage_database_collections_update',
|
||||
],
|
||||
'database.collections.delete' => [
|
||||
'table' => 'appwrite_usage_database_collections_delete',
|
||||
],
|
||||
'database.documents.create' => [
|
||||
'table' => 'appwrite_usage_database_documents_create',
|
||||
],
|
||||
'database.documents.read' => [
|
||||
'table' => 'appwrite_usage_database_documents_read',
|
||||
],
|
||||
'database.documents.update' => [
|
||||
'table' => 'appwrite_usage_database_documents_update',
|
||||
],
|
||||
'database.documents.delete' => [
|
||||
'table' => 'appwrite_usage_database_documents_delete',
|
||||
],
|
||||
'database.collections.collectionId.documents.create' => [
|
||||
'table' => 'appwrite_usage_database_documents_create',
|
||||
'groupBy' => 'collectionId',
|
||||
],
|
||||
'database.collections.collectionId.documents.read' => [
|
||||
'table' => 'appwrite_usage_database_documents_read',
|
||||
'groupBy' => 'collectionId',
|
||||
],
|
||||
'database.collections.collectionId.documents.update' => [
|
||||
'table' => 'appwrite_usage_database_documents_update',
|
||||
'groupBy' => 'collectionId',
|
||||
],
|
||||
'database.collections.collectionId.documents.delete' => [
|
||||
'table' => 'appwrite_usage_database_documents_delete',
|
||||
'groupBy' => 'collectionId',
|
||||
],
|
||||
'storage.buckets.create' => [
|
||||
'table' => 'appwrite_usage_storage_buckets_create',
|
||||
],
|
||||
'storage.buckets.read' => [
|
||||
'table' => 'appwrite_usage_storage_buckets_read',
|
||||
],
|
||||
'storage.buckets.update' => [
|
||||
'table' => 'appwrite_usage_storage_buckets_update',
|
||||
],
|
||||
'storage.buckets.delete' => [
|
||||
'table' => 'appwrite_usage_storage_buckets_delete',
|
||||
],
|
||||
'storage.files.create' => [
|
||||
'table' => 'appwrite_usage_storage_files_create',
|
||||
],
|
||||
'storage.files.read' => [
|
||||
'table' => 'appwrite_usage_storage_files_read',
|
||||
],
|
||||
'storage.files.update' => [
|
||||
'table' => 'appwrite_usage_storage_files_update',
|
||||
],
|
||||
'storage.files.delete' => [
|
||||
'table' => 'appwrite_usage_storage_files_delete',
|
||||
],
|
||||
'storage.buckets.bucketId.files.create' => [
|
||||
'table' => 'appwrite_usage_storage_files_create',
|
||||
'groupBy' => 'bucketId',
|
||||
],
|
||||
'storage.buckets.bucketId.files.read' => [
|
||||
'table' => 'appwrite_usage_storage_files_read',
|
||||
'groupBy' => 'bucketId',
|
||||
],
|
||||
'storage.buckets.bucketId.files.update' => [
|
||||
'table' => 'appwrite_usage_storage_files_update',
|
||||
'groupBy' => 'bucketId',
|
||||
],
|
||||
'storage.buckets.bucketId.files.delete' => [
|
||||
'table' => 'appwrite_usage_storage_files_delete',
|
||||
'groupBy' => 'bucketId',
|
||||
],
|
||||
'users.create' => [
|
||||
'table' => 'appwrite_usage_users_create',
|
||||
],
|
||||
'users.read' => [
|
||||
'table' => 'appwrite_usage_users_read',
|
||||
],
|
||||
'users.update' => [
|
||||
'table' => 'appwrite_usage_users_update',
|
||||
],
|
||||
'users.delete' => [
|
||||
'table' => 'appwrite_usage_users_delete',
|
||||
],
|
||||
'users.sessions.create' => [
|
||||
'table' => 'appwrite_usage_users_sessions_create',
|
||||
],
|
||||
'users.sessions.provider.create' => [
|
||||
'table' => 'appwrite_usage_users_sessions_create',
|
||||
'groupBy' => 'provider',
|
||||
],
|
||||
'users.sessions.delete' => [
|
||||
'table' => 'appwrite_usage_users_sessions_delete',
|
||||
],
|
||||
'functions.functionId.executions' => [
|
||||
'table' => 'appwrite_usage_executions_all',
|
||||
'groupBy' => 'functionId',
|
||||
],
|
||||
'functions.functionId.compute' => [
|
||||
'table' => 'appwrite_usage_executions_time',
|
||||
'groupBy' => 'functionId',
|
||||
],
|
||||
'functions.functionId.failures' => [
|
||||
'table' => 'appwrite_usage_executions_all',
|
||||
'groupBy' => 'functionId',
|
||||
'filters' => [
|
||||
'functionStatus' => 'failed',
|
||||
],
|
||||
],
|
||||
];
|
||||
$database = getDatabase($register, '_console');
|
||||
$influxDB = getInfluxDB($register);
|
||||
|
||||
// TODO Maybe move this to the setResource method, and reuse in the http.php file
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
|
||||
$db = null;
|
||||
$redis = null;
|
||||
do { // connect to db
|
||||
try {
|
||||
$attempts++;
|
||||
$db = $register->get('db');
|
||||
$redis = $register->get('cache');
|
||||
break; // leave the do-while if successful
|
||||
} catch (\Exception $e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
|
||||
}
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
// TODO use inject
|
||||
$cacheAdapter = new Cache(new Redis($redis));
|
||||
$dbForProject = new Database(new MariaDB($db), $cacheAdapter);
|
||||
$dbForConsole = new Database(new MariaDB($db), $cacheAdapter);
|
||||
$dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$dbForConsole->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$dbForConsole->setNamespace('_console');
|
||||
|
||||
$latestTime = [];
|
||||
|
||||
Authorization::disable();
|
||||
$usage = new Usage($database, $influxDB, $logError);
|
||||
$usageDB = new UsageDB($database, $logError);
|
||||
|
||||
$iterations = 0;
|
||||
Console::loop(function () use ($interval, $register, $dbForProject, $dbForConsole, $globalMetrics, $periods, &$latestTime, &$iterations) {
|
||||
Console::loop(function () use ($interval, $usage, $usageDB, &$iterations) {
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregating usage data every {$interval} seconds");
|
||||
|
||||
@@ -274,105 +134,10 @@ $cli
|
||||
|
||||
/**
|
||||
* Aggregate InfluxDB every 30 seconds
|
||||
* @var InfluxDB\Client $client
|
||||
*/
|
||||
$client = $register->get('influxdb');
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
$usage->collect();
|
||||
|
||||
do { // check if telegraf database is ready
|
||||
try {
|
||||
$attempts++;
|
||||
$database = $client->selectDB('telegraf');
|
||||
if (in_array('telegraf', $client->listDatabases())) {
|
||||
break; // leave the do-while if successful
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("InfluxDB not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('InfluxDB database not ready yet');
|
||||
}
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
// sync data
|
||||
foreach ($globalMetrics as $metric => $options) { //for each metrics
|
||||
foreach ($periods as $period) { // aggregate data for each period
|
||||
$start = DateTime::createFromFormat('U', \strtotime($period['startTime']))->format(DateTime::RFC3339);
|
||||
if (!empty($latestTime[$metric][$period['key']])) {
|
||||
$start = DateTime::createFromFormat('U', $latestTime[$metric][$period['key']])->format(DateTime::RFC3339);
|
||||
}
|
||||
$end = DateTime::createFromFormat('U', \strtotime('now'))->format(DateTime::RFC3339);
|
||||
|
||||
$table = $options['table']; //Which influxdb table to query for this metric
|
||||
$groupBy = empty($options['groupBy']) ? '' : ', "' . $options['groupBy'] . '"'; //Some sub level metrics may be grouped by other tags like collectionId, bucketId, etc
|
||||
|
||||
$filters = $options['filters'] ?? []; // Some metrics might have additional filters, like function's status
|
||||
if (!empty($filters)) {
|
||||
$filters = ' AND ' . implode(' AND ', array_map(fn ($filter, $value) => "\"{$filter}\"='{$value}'", array_keys($filters), array_values($filters)));
|
||||
} else {
|
||||
$filters = '';
|
||||
}
|
||||
|
||||
$query = "SELECT sum(value) AS \"value\" FROM \"{$table}\" WHERE \"time\" > '{$start}' AND \"time\" < '{$end}' AND \"metric_type\"='counter' {$filters} GROUP BY time({$period['key']}), \"projectId\" {$groupBy} FILL(null)";
|
||||
try {
|
||||
$result = $database->query($query);
|
||||
|
||||
$points = $result->getPoints();
|
||||
foreach ($points as $point) {
|
||||
$projectId = $point['projectId'];
|
||||
|
||||
if (!empty($projectId) && $projectId !== 'console') {
|
||||
$dbForProject->setNamespace('_' . $projectId);
|
||||
$metricUpdated = $metric;
|
||||
|
||||
if (!empty($groupBy)) {
|
||||
$groupedBy = $point[$options['groupBy']] ?? '';
|
||||
if (empty($groupedBy)) {
|
||||
continue;
|
||||
}
|
||||
$metricUpdated = str_replace($options['groupBy'], $groupedBy, $metric);
|
||||
}
|
||||
|
||||
$time = \strtotime($point['time']);
|
||||
$id = \md5($time . '_' . $period['key'] . '_' . $metricUpdated); //Construct unique id for each metric using time, period and metric
|
||||
$value = (!empty($point['value'])) ? $point['value'] : 0;
|
||||
|
||||
try {
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period['key'],
|
||||
'time' => $time,
|
||||
'metric' => $metricUpdated,
|
||||
'value' => $value,
|
||||
'type' => 0,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $value)
|
||||
);
|
||||
}
|
||||
$latestTime[$metric][$period['key']] = $time;
|
||||
} catch (\Exception $e) { // if projects are deleted this might fail
|
||||
Console::warning("Failed to save data for project {$projectId} and metric {$metricUpdated}: {$e->getMessage()}");
|
||||
Console::warning($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Console::warning("Failed to Query: {$e->getMessage()}");
|
||||
Console::warning($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($iterations % 30 != 0) { // Aggregate aggregate number of objects in database only after 15 minutes
|
||||
if ($iterations % 30 != 0) { // return if 30 iterations has not passed
|
||||
$iterations++;
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
@@ -380,6 +145,7 @@ $cli
|
||||
return;
|
||||
}
|
||||
|
||||
$iterations = 0; // Reset iterations to prevent overflow when running for long time
|
||||
/**
|
||||
* Aggregate MariaDB every 15 minutes
|
||||
* Some of the queries here might contain full-table scans.
|
||||
@@ -387,435 +153,7 @@ $cli
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregating database counters.");
|
||||
|
||||
$latestProject = null;
|
||||
do { // Loop over all the projects
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
|
||||
do { // list projects
|
||||
try {
|
||||
$attempts++;
|
||||
$projects = $dbForConsole->find('projects', [], 100, cursor: $latestProject);
|
||||
break; // leave the do-while if successful
|
||||
} catch (\Exception $e) {
|
||||
Console::warning("Console DB not ready yet. Retrying ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('Failed access console db: ' . $e->getMessage());
|
||||
}
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
if (empty($projects)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$latestProject = $projects[array_key_last($projects)];
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$projectId = $project->getId();
|
||||
|
||||
// Get total storage
|
||||
$dbForProject->setNamespace('_' . $projectId);
|
||||
$deploymentsTotal = $dbForProject->sum('deployments', 'size');
|
||||
|
||||
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
|
||||
$id = \md5($time . '_30m_storage.deployments.total'); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
try {
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => '30m',
|
||||
'time' => $time,
|
||||
'metric' => 'storage.deployments.total',
|
||||
'value' => $deploymentsTotal,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $deploymentsTotal)
|
||||
);
|
||||
}
|
||||
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
|
||||
$id = \md5($time . '_1d_storage.deployments.total'); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'period' => '1d',
|
||||
'time' => $time,
|
||||
'metric' => 'storage.deployments.total',
|
||||
'value' => $deploymentsTotal,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $deploymentsTotal)
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Console::warning("Failed to save data for project {$projectId} and metric storage.deployments.total: {$e->getMessage()}");
|
||||
Console::warning($e->getTraceAsString());
|
||||
}
|
||||
|
||||
|
||||
$collections = [
|
||||
'users' => [
|
||||
'namespace' => '',
|
||||
],
|
||||
'collections' => [
|
||||
'metricPrefix' => 'database',
|
||||
'namespace' => '',
|
||||
'subCollections' => [ // Some collections, like collections and later buckets have child collections that need counting
|
||||
'documents' => [
|
||||
'collectionPrefix' => 'collection_',
|
||||
'namespace' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
'buckets' => [
|
||||
'metricPrefix' => 'storage',
|
||||
'namespace' => '',
|
||||
'subCollections' => [
|
||||
'files' => [
|
||||
'namespace' => '',
|
||||
'collectionPrefix' => 'bucket_',
|
||||
'total' => [
|
||||
'field' => 'sizeOriginal'
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($collections as $collection => $options) {
|
||||
try {
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
$count = $dbForProject->count($collection);
|
||||
$metricPrefix = $options['metricPrefix'] ?? '';
|
||||
$metric = empty($metricPrefix) ? "{$collection}.count" : "{$metricPrefix}.{$collection}.count";
|
||||
|
||||
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
|
||||
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '30m',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
);
|
||||
}
|
||||
|
||||
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
|
||||
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '1d',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
);
|
||||
}
|
||||
|
||||
$subCollections = $options['subCollections'] ?? [];
|
||||
|
||||
if (empty($subCollections)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$latestParent = null;
|
||||
$subCollectionCounts = []; //total project level count of sub collections
|
||||
$subCollectionTotals = []; //total project level sum of sub collections
|
||||
|
||||
do { // Loop over all the parent collection document for each sub collection
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
$parents = $dbForProject->find($collection, [], 100, cursor: $latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections
|
||||
|
||||
if (empty($parents)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$latestParent = $parents[array_key_last($parents)];
|
||||
|
||||
foreach ($parents as $parent) {
|
||||
foreach ($subCollections as $subCollection => $subOptions) { // Sub collection counts, like database.collections.collectionId.documents.count
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
$count = $dbForProject->count(($subOptions['collectionPrefix'] ?? '') . $parent->getInternalId());
|
||||
|
||||
$subCollectionCounts[$subCollection] = ($subCollectionCounts[$subCollection] ?? 0) + $count; // Project level counts for sub collections like database.documents.count
|
||||
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
|
||||
$metric = empty($metricPrefix) ? "{$collection}.{$parent->getId()}.{$subCollection}.count" : "{$metricPrefix}.{$collection}.{$parent->getInternalId()}.{$subCollection}.count";
|
||||
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
|
||||
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '30m',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
);
|
||||
}
|
||||
|
||||
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
|
||||
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '1d',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
);
|
||||
}
|
||||
|
||||
// check if sum calculation is required
|
||||
$total = $subOptions['total'] ?? [];
|
||||
if (empty($total)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
$total = (int) $dbForProject->sum(($subOptions['collectionPrefix'] ?? '') . $parent->getInternalId(), $total['field']);
|
||||
|
||||
$subCollectionTotals[$subCollection] = ($ssubCollectionTotals[$subCollection] ?? 0) + $total; // Project level sum for sub collections like storage.total
|
||||
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
|
||||
$metric = empty($metricPrefix) ? "{$collection}.{$parent->getId()}.{$subCollection}.total" : "{$metricPrefix}.{$collection}.{$parent->getInternalId()}.{$subCollection}.total";
|
||||
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
|
||||
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '30m',
|
||||
'metric' => $metric,
|
||||
'value' => $total,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $total)
|
||||
);
|
||||
}
|
||||
|
||||
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
|
||||
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '1d',
|
||||
'metric' => $metric,
|
||||
'value' => $total,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $total)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!empty($parents));
|
||||
|
||||
/**
|
||||
* Inserting project level counts for sub collections like database.documents.count
|
||||
*/
|
||||
foreach ($subCollectionCounts as $subCollection => $count) {
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
|
||||
$metric = empty($metricPrefix) ? "{$subCollection}.count" : "{$metricPrefix}.{$subCollection}.count";
|
||||
|
||||
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
|
||||
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '30m',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
);
|
||||
}
|
||||
|
||||
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
|
||||
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '1d',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserting project level sums for sub collections like storage.files.total
|
||||
*/
|
||||
foreach ($subCollectionTotals as $subCollection => $count) {
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
|
||||
$metric = empty($metricPrefix) ? "{$subCollection}.total" : "{$metricPrefix}.{$subCollection}.total";
|
||||
|
||||
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
|
||||
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '30m',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
);
|
||||
}
|
||||
|
||||
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
|
||||
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '1d',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
);
|
||||
}
|
||||
|
||||
// aggregate storage.total = storage.files.total + storage.deployments.total
|
||||
if ($metricPrefix === 'storage' && $subCollection === 'files') {
|
||||
$metric = 'storage.total';
|
||||
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
|
||||
$id = \md5($time . '_30m_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '30m',
|
||||
'metric' => $metric,
|
||||
'value' => $count + $deploymentsTotal,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count + $deploymentsTotal)
|
||||
);
|
||||
}
|
||||
|
||||
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
|
||||
$id = \md5($time . '_1d_' . $metric); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
if ($document->isEmpty()) {
|
||||
$dbForProject->createDocument('stats', new Document([
|
||||
'$id' => $id,
|
||||
'time' => $time,
|
||||
'period' => '1d',
|
||||
'metric' => $metric,
|
||||
'value' => $count + $deploymentsTotal,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count + $deploymentsTotal)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception$e) {
|
||||
Console::warning("Failed to save database counters data for project {$collection}: {$e->getMessage()}");
|
||||
Console::warning($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!empty($projects));
|
||||
$usageDB->collect();
|
||||
|
||||
$iterations++;
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
<ul class="links">
|
||||
<li>
|
||||
<a data-ls-attrs="href=/console/database?project={{router.params.project}}"
|
||||
<a data-ls-attrs="href=/console/databases?project={{router.params.project}}"
|
||||
data-analytics
|
||||
data-analytics-event="click"
|
||||
data-analytics-category="console/navigation"
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$type = $this->getParam('type', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$comp = $this->getParam('comp', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
$list = $this->getParam('list', []);
|
||||
$collections = $this->getParam('collections', []);
|
||||
|
||||
$comp->setParam('namespace', 'node');
|
||||
|
||||
if($type === 'document') {
|
||||
$comp->setParam('key', null);
|
||||
}
|
||||
?>
|
||||
|
||||
<input type="hidden" name="<?php echo $this->escape($key); ?>"<?php if($required): ?> required<?php endif; ?> data-cast-to="array-empty">
|
||||
|
||||
<hr />
|
||||
|
||||
<ul data-ls-loop="<?php echo $this->escape($namespace); ?>" data-ls-as="node" class="sortable numbers">
|
||||
<li data-forms-move-up data-forms-move-down>
|
||||
<div class="drop-list bottom end settings" data-ls-ui-open="" data-button-text="" data-button-icon="icon-cog" data-button-aria="Options" data-button-selector="[data-toggler]" data-button-class="round dark small margin-bottom-small margin-top-tiny pull-end" data-blur="1">
|
||||
<ul class="arrow-end margin-top margin-end-negative-small">
|
||||
<li data-move-up>
|
||||
<button type="button" class="link"><i class="icon-up-dir"></i> Move Up</button>
|
||||
</li>
|
||||
<li data-move-down>
|
||||
<button type="button" class="link"><i class="icon-down-dir"></i> Move Down</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" data-ls-ui-trigger="splice-<?php echo $this->escape($namespace); ?>-{{$index}}" class="link"><i class="icon-cancel"></i> Remove</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php echo $comp->render(); ?>
|
||||
|
||||
<hr />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<?php if(!empty($list) && $type === 'document'): ?>
|
||||
<div class="drop-list" data-ls-ui-open="" data-button-text="Add" data-button-aria="Add" data-button-icon="" data-button-selector="[data-toggler]" data-button-class="reverse margin-bottom-small" data-blur="1">
|
||||
<ul>
|
||||
<?php foreach($list as $item):
|
||||
$name = (isset($collections[$item])) ? $collections[$item]->getAttribute('name', '') : '';
|
||||
?>
|
||||
<li>
|
||||
<button type="button" class="link" data-ls-ui-trigger="collection-child-<?php echo $this->escape($namespace); ?>-<?php echo $this->escape($item); ?>"><i class="icon-edit"></i> Add <?php echo $this->escape($name); ?></button>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<button type="button" data-ls-ui-trigger="collection-child-<?php echo $this->escape($namespace); ?>" class="reverse margin-bottom-small">Add</button>
|
||||
<?php endif; ?>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
|
||||
<input name="<?php echo $this->escape($key); ?>" type="hidden" data-forms-switch data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" data-cast-to="boolean"<?php if($required): ?> required<?php endif; ?> class="margin-bottom-no" />
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
$array = $this->getParam('array', false);
|
||||
$list = $this->getParam('list', []);
|
||||
$list = (is_array($list)) ? $list : [];
|
||||
$list = ($array) ? $list : array_shift($list);
|
||||
?>
|
||||
|
||||
<?php if($array): ?>
|
||||
<div data-ls-template="collection-array-{{<?php echo $this->escape($namespace); ?>.$collection}}" data-type="script" class="margin-bottom-negative-small"></div>
|
||||
<?php else: ?>
|
||||
<hr class="margin-top-no" />
|
||||
<div class="clear">
|
||||
<a data-ls-if="(!{{<?php echo $this->escape($namespace); ?>.$id}})" data-ls-attrs="href=/console/database/document?&collection=<?php echo $this->escape($list); ?>&project={{router.params.project}}" class="pull-end text-size-small">New Document <i class="icon-link-ext"></i></a>
|
||||
<a data-ls-if="({{<?php echo $this->escape($namespace); ?>.$id}})" data-ls-attrs="href=/console/database/document?id={{<?php echo $this->escape($namespace); ?>.$id}}&collection=<?php echo $this->escape($list); ?>&project={{router.params.project}}" class="pull-end text-size-small"><span data-ls-bind="Document #{{<?php echo $this->escape($namespace); ?>.$id}}"></span> <i class="icon-link-ext"></i></a>
|
||||
</div>
|
||||
<div data-ls-template="collection-<?php echo $this->escape($list); ?>" data-type="script" class="margin-bottom-negative-small"></div>
|
||||
<hr />
|
||||
<?php endif; ?>
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
$collections = $this->getParam('collections', []);
|
||||
$array = $this->getParam('array', false);
|
||||
$list = $this->getParam('list', []);
|
||||
$list = (is_array($list)) ? $list : [];
|
||||
?>
|
||||
|
||||
<?php foreach($list as $item):
|
||||
$collection = $collections[$item] ?? null;
|
||||
|
||||
if(empty($collection)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules = $collection->getAttribute('rules', []);
|
||||
?>
|
||||
|
||||
|
||||
<div data-ls-if="({{<?php echo $this->escape($namespace); ?>}})"
|
||||
data-service="database.getDocument"
|
||||
data-param-collection-id="<?php echo $this->escape($collection->getId()); ?>"
|
||||
data-param-document-id="{{<?php echo $this->escape($namespace); ?>}}"
|
||||
data-scope="sdk"
|
||||
data-event="load,document-selected-<?php echo $this->escape($collection->getId()); ?>"
|
||||
data-name="project-document-preview"
|
||||
data-success="default">
|
||||
|
||||
<div class="box line margin-bottom-small padding-small fade-bottom">
|
||||
<ul>
|
||||
<?php foreach($rules as $i => $rule):
|
||||
$collectionLabel = $rule['label'] ?? '';
|
||||
$collectionKey = $rule['key'] ?? '';
|
||||
|
||||
if($i === 3) {break;}
|
||||
?>
|
||||
<li class="margin-bottom-small">
|
||||
<p><b><?php echo $this->escape($collectionLabel); ?>: </b> <span data-ls-bind="{{project-document-preview.<?php echo $this->escape($collectionKey); ?>|limit}}" data-unsync="1"></span></p>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<span class="label tag blue">preview</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="text" data-forms-document-preview="<?php echo $this->escape($collection->getId()); ?>"
|
||||
name="<?php echo $this->escape($key); ?>"
|
||||
data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}"
|
||||
class="margin-bottom" disabled<?php if($required): ?> required<?php endif; ?>>
|
||||
|
||||
<button class="reverse margin-end-small" type="button"
|
||||
data-forms-document="open-document-serach-<?php echo $this->escape($collection->getId()); ?>"
|
||||
data-search="<?php echo $this->escape($namespace); ?>"><i class="icon-search"></i> <?php echo $this->escape($collection->getAttribute('name')); ?></button>
|
||||
<?php endforeach; ?>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
<input name="<?php echo $this->escape($key); ?>" type="email" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" placeholder="me@example.com"<?php if($required): ?> required<?php endif; ?> class="margin-bottom-no">
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
<input
|
||||
type="hidden"
|
||||
name="<?php echo $this->escape($key); ?>"
|
||||
data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}"
|
||||
data-read="<?php echo $this->escape(json_encode([])); ?>"
|
||||
data-write="<?php echo $this->escape(json_encode([])); ?>"
|
||||
data-accept=""
|
||||
data-forms-upload=""
|
||||
data-label-button="Upload"
|
||||
data-search="<?php echo $this->escape($namespace); ?>"
|
||||
data-scope="sdk"
|
||||
data-default=""
|
||||
data-project="{{router.params.project}}"
|
||||
<?php if($required): ?> required<?php endif; ?>
|
||||
class="margin-bottom-no">
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
<input name="<?php echo $this->escape($key); ?>" type="text" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" minlength="7" maxlength="15" size="15" pattern="^((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$" title="Please enter a valid IPV4 address" placeholder="255.255.255.255"<?php if($required): ?> required<?php endif; ?> class="margin-bottom-no">
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
|
||||
<textarea type="text" name="<?php echo $this->escape($key); ?>" data-forms-pell data-forms-text-direction data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}"<?php if($required): ?> required<?php endif; ?>></textarea>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
|
||||
<input name="<?php echo $this->escape($key); ?>" type="number" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" data-cast-to="numeric"<?php if($required): ?> required<?php endif; ?> class="margin-bottom-no" step=any />
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
<input name="<?php echo $this->escape($key); ?>" type="text" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" data-forms-text-direction data-forms-text-count<?php if($required): ?> required<?php endif; ?> />
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
<input name="<?php echo $this->escape($key); ?>" type="url" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" placeholder="https://example.com"<?php if($required): ?> required<?php endif; ?> />
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$key = $this->getParam('key', '');
|
||||
$required = $this->getParam('required', '');
|
||||
$namespace = $this->getParam('namespace', '');
|
||||
?>
|
||||
<textarea name="<?php echo $this->escape($key); ?>" type="text" autocomplete="off" data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}" data-forms-text-direction data-forms-text-count<?php if($required): ?> required<?php endif; ?>></textarea>
|
||||
@@ -1,148 +0,0 @@
|
||||
|
||||
<?php
|
||||
$collection = $this->getParam('collection', []);
|
||||
$id = $collection->getId();
|
||||
$name = $collection->getAttribute('name', 'Collection');
|
||||
$rules = $collection->getAttribute('rules', []);
|
||||
?>
|
||||
|
||||
<div data-ui-modal class="modal sticky-footer width-large box close" data-button-hide="on" data-open-event="open-document-serach-<?php echo $this->escape($id); ?>" data-close-event="none">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h2><?php echo $this->escape($name); ?> Search</h2>
|
||||
|
||||
<form class="search margin-bottom"
|
||||
data-service="database.listDocuments"
|
||||
data-event="submit"
|
||||
data-param-collection-id="<?php echo $this->escape($id); ?>"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-offset="0"
|
||||
data-param-order-type="DESC"
|
||||
data-scope="sdk"
|
||||
data-name="project-documents"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<input name="search" id="searchDocuments-<?php echo $this->escape($id); ?>" type="search" autocomplete="off" placeholder="Search" class="margin-bottom-no" data-ls-bind="{{router.params.search}}">
|
||||
</form>
|
||||
|
||||
<div
|
||||
data-service="database.listDocuments"
|
||||
data-event="open-document-serach-<?php echo $this->escape($id); ?>"
|
||||
data-param-collection-id="<?php echo $this->escape($id); ?>"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-offset="0"
|
||||
data-param-order-type="DESC"
|
||||
data-scope="sdk"
|
||||
data-name="project-documents">
|
||||
|
||||
<div data-ls-if="0 == {{project-documents.total}}" class="margin-bottom">
|
||||
<h3 class="margin-bottom-small text-bold">No Documents Found</h3>
|
||||
|
||||
<p class="margin-bottom-no">Try a different search term.</p>
|
||||
</div>
|
||||
|
||||
<div data-ls-if="({{project-documents.total}})">
|
||||
<form class="scroll">
|
||||
<table class="margin-top-no margin-bottom-no">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40"> </th>
|
||||
<?php foreach($rules as $rule):
|
||||
$label = $rule['label'] ?? '';
|
||||
?>
|
||||
<th width="120"><?php echo $this->escape($label); ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-ls-loop="project-documents.documents" data-ls-as="node">
|
||||
<tr>
|
||||
<td data-title="x" class="">
|
||||
<!-- <input type="radio" name="selected" data-ls-attrs="value={{file.$id}}" data-ls-bind="{{search.selected}}" /> -->
|
||||
<input type="radio" name="selected" data-ls-attrs="value={{node.$id}}" data-ls-bind="{{search.selected}}" />
|
||||
</td>
|
||||
<?php foreach($rules as $rule):
|
||||
$label = $rule['label'] ?? '';
|
||||
$key = $rule['key'] ?? '';
|
||||
$type = $rule['type'] ?? '';
|
||||
$array = $rule['array'] ?? '';
|
||||
?>
|
||||
<td data-title="<?php echo $this->escape($label); ?>" class="text-size-small text-height-small">
|
||||
<a data-ls-attrs="href=/console/database/document?id={{node.$id}}&collection=<?php echo $this->escape($id); ?>&project={{router.params.project}}" target="_blank">
|
||||
<?php if(!$array): ?>
|
||||
<?php switch($type):
|
||||
case 'fileId': ?>
|
||||
<img data-ls-if="{{node.<?php echo $this->escape($key); ?>}} != ''" src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{node.<?php echo $this->escape($key); ?>}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="avatar" width="30" height="30" loading="lazy" />
|
||||
<?php break; ?>
|
||||
<?php case 'document': ?>
|
||||
{...}
|
||||
<?php break; ?>
|
||||
<?php default: ?>
|
||||
<span data-ls-bind="{{node.<?php echo $this->escape($key); ?>}}" data-ls-attrs="title={{node.<?php echo $this->escape($key); ?>}}"></span>
|
||||
<?php break; ?>
|
||||
<?php endswitch; ?>
|
||||
<?php else: ?>
|
||||
[...]
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="clear text-align-center paging pull-end">
|
||||
<form
|
||||
data-service="database.listDocuments"
|
||||
data-event="submit"
|
||||
data-param-collection-id="<?php echo $this->escape($id); ?>"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="DESC"
|
||||
data-scope="sdk"
|
||||
data-name="project-documents"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-documents.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
|
||||
</form>
|
||||
|
||||
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-documents.total|pageTotal}}"></span>
|
||||
|
||||
<form
|
||||
data-service="database.listDocuments"
|
||||
data-event="submit"
|
||||
data-param-collection-id="<?php echo $this->escape($id); ?>"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="DESC"
|
||||
data-scope="sdk"
|
||||
data-name="project-documents"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-documents.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form data-service="container.path"
|
||||
data-param-path="search.selected"
|
||||
data-param-type="assign"
|
||||
data-param-value="{{node.$id}}"
|
||||
data-success="trigger"
|
||||
data-success-param-trigger-events="modal-close,document-selected-<?php echo $this->escape($id); ?>"
|
||||
data-event="click"
|
||||
data-scope="window.ls">
|
||||
|
||||
<input type="hidden" name="path" data-ls-bind="{{search.path}}" />
|
||||
<input type="hidden" name="type" value="assign" />
|
||||
<input type="hidden" name="value" data-ls-bind="{{search.selected}}" />
|
||||
|
||||
<button data-ls-if="({{search.selected}})" type="button" class="">Select</button>
|
||||
<button data-ls-if="(!{{search.selected}})" type="button" class="" disabled>Select</button>
|
||||
</form>
|
||||
<button data-ui-modal-close="" type="button" class="reverse desktops-only-inline">Cancel</button>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -1,131 +0,0 @@
|
||||
<div data-ui-modal class="modal sticky-footer width-large box close" data-button-hide="on" data-open-event="open-file-serach" data-close-event="none">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h2>File Search</h2>
|
||||
|
||||
<form class="search margin-bottom"
|
||||
data-service="storage.listFiles"
|
||||
data-event="submit"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-offset=""
|
||||
data-param-order-type="DESC"
|
||||
data-scope="sdk"
|
||||
data-name="project-files"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<input name="search" id="searchFiles" type="search" autocomplete="off" placeholder="Search" class="margin-bottom-no" data-ls-bind="{{router.params.search}}">
|
||||
</form>
|
||||
|
||||
<div
|
||||
data-service="storage.listFiles"
|
||||
data-event="open-file-serach"
|
||||
data-param-search=""
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-offset="0"
|
||||
data-param-order-type="DESC"
|
||||
data-scope="sdk"
|
||||
data-name="project-files"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
|
||||
<div data-ls-if="0 == {{project-files.total}}" class="margin-bottom">
|
||||
<h3 class="margin-bottom-small text-bold">No Files Found</h3>
|
||||
|
||||
<p class="margin-bottom-no">Try a different search term.</p>
|
||||
</div>
|
||||
|
||||
<div data-ls-if="0 != {{project-files.total}}">
|
||||
<div class="scroll">
|
||||
<table class="margin-top-no margin-bottom-no">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40"> </th>
|
||||
<th width="40"> </th>
|
||||
<th width="100">Filename</th>
|
||||
<th width="100">Type</th>
|
||||
<th width="100">Size</th>
|
||||
<th width="80">Created</th>
|
||||
<!-- <th width="30"></th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-ls-loop="project-files.files" data-ls-as="file">
|
||||
<tr>
|
||||
<td data-title="x" class="">
|
||||
<input type="radio" name="selected" data-ls-attrs="value={{file.$id}}" data-ls-bind="{{search.selected}}" />
|
||||
</td>
|
||||
<td data-title="x" class="">
|
||||
<img src="" data-ls-attrs="src={{env.ENDPOINT}}/v1/storage/files/{{file.$id}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="pull-start avatar" width="30" height="30" loading="lazy" />
|
||||
</td>
|
||||
<td data-title="Name: " class="text-one-liner">
|
||||
<span data-ls-bind="{{file.name}}" data-ls-attrs="title={{file.name}}" class="text-fade text-size-small"></span>
|
||||
</td>
|
||||
<td data-title="Type: ">
|
||||
<span data-ls-bind="{{file.mimeType}}" class="text-fade text-size-small"></span>
|
||||
</td>
|
||||
<td data-title="Size: ">
|
||||
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileSize}}"></span>
|
||||
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
|
||||
</td>
|
||||
<td data-title="Created: ">
|
||||
<span class="text-fade text-size-small" data-ls-bind="{{file.dateCreated|dateText}}"></span>
|
||||
</td>
|
||||
<!-- <td class="hide">
|
||||
<a target="_blank" data-ls-attrs="href="><i class="icon-link-ext"></i></a>
|
||||
</td> -->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="clear text-align-center paging pull-end">
|
||||
<form
|
||||
data-service="storage.listFiles"
|
||||
data-event="submit"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="DESC"
|
||||
data-scope="sdk"
|
||||
data-name="project-files"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-files.total}}" class="margin-end-small round small" aria-label="Back"><i class="icon-left-open"></i></button>
|
||||
</form>
|
||||
|
||||
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-files.total|pageTotal}}"></span>
|
||||
|
||||
<form
|
||||
data-service="storage.listFiles"
|
||||
data-event="submit"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="DESC"
|
||||
data-scope="sdk"
|
||||
data-name="project-files"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-files.total}}" class="margin-start-small round small" aria-label="Next"><i class="icon-right-open"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form data-service="container.path"
|
||||
data-param-path="search.selected"
|
||||
data-param-type="assign"
|
||||
data-param-value="{{file.$id}}"
|
||||
data-success="trigger"
|
||||
data-success-param-trigger-events="modal-close"
|
||||
data-event="click"
|
||||
data-scope="window.ls">
|
||||
|
||||
<input type="hidden" name="path" data-ls-bind="{{search.path}}" />
|
||||
<input type="hidden" name="type" value="assign" />
|
||||
<input type="hidden" name="value" data-ls-bind="{{search.selected}}" />
|
||||
|
||||
<button data-ls-if="({{search.selected}})" type="button" class="">Select</button>
|
||||
<button data-ls-if="(!{{search.selected}})" type="button" class="" disabled>Select</button>
|
||||
</form>
|
||||
<button data-ui-modal-close="" type="button" class="reverse desktops-only-inline">Cancel</button>
|
||||
</footer>
|
||||
</div>
|
||||
+93
-59
@@ -4,20 +4,28 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
?>
|
||||
<div
|
||||
data-service="database.getCollection"
|
||||
data-service="databases.getCollection"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-scope="sdk"
|
||||
data-event="load,database.updateCollection,database.createAttribute,database.deleteAttribute,database.createIndex,database.deleteIndex"
|
||||
data-event="load,databases.updateCollection,databases.createAttribute,databases.deleteAttribute,databases.createIndex,databases.deleteIndex"
|
||||
data-name="project-collection">
|
||||
|
||||
<div class="cover">
|
||||
<h1 class="zone xl margin-bottom-large">
|
||||
<a data-ls-attrs="href=/console/database?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Database</a>
|
||||
<div
|
||||
data-service="databases.get"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-scope="sdk"
|
||||
data-event="load,databases.update"
|
||||
data-name="project-database">
|
||||
<div class="cover">
|
||||
<h1 class="zone xl margin-bottom-large">
|
||||
<a data-ls-attrs="href=/console/databases/database?id={{router.params.databaseId}}&project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> <span data-ls-bind="{{project-database.name}}"> </span></a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<span data-ls-bind="{{project-collection.name}}"> </span>
|
||||
</h1>
|
||||
<span data-ls-bind="{{project-collection.name}}"> </span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-ui-modal class="modal width-large box close" data-button-hide="on" data-open-event="open-json">
|
||||
@@ -34,14 +42,15 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<div class="zone xl">
|
||||
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
|
||||
<li data-state="/console/database/collection?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<li data-state="/console/databases/collection?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
|
||||
|
||||
<h2>Documents</h2>
|
||||
|
||||
<div
|
||||
data-service="database.listDocuments"
|
||||
data-event="load,database.createDocument,database.updateDocument,database.deleteDocument"
|
||||
data-service="databases.listDocuments"
|
||||
data-event="load,databases.createDocument,databases.updateDocument,databases.deleteDocument"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-types="DESC"
|
||||
data-param-order-types-cast-to="array"
|
||||
@@ -75,15 +84,15 @@ $logs = $this->getParam('logs', null);
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-ls-attrs="x-init=documents = {{project-documents.documents}}" x-data="{documents: []}">
|
||||
<tbody data-ls-attrs="x-init=documents = {{project-documents.documents}}; databaseId='{{router.params.databaseId}}'" x-data="{documents: []}">
|
||||
<template x-for="doc in documents">
|
||||
<tr>
|
||||
<td data-title="$id: ">
|
||||
<a :href="`/console/database/document?id=${doc.$id}&collection=${doc.$collection}&project=${project}`" x-text="doc.$id"></a>
|
||||
<a :href="`/console/databases/document?id=${doc.$id}&collection=${doc.$collection}&databaseId=${databaseId}&project=${project}`" x-text="doc.$id"></a>
|
||||
</td>
|
||||
<template x-for="attr in attributes">
|
||||
<td x-show="attr.status === 'available'" :data-title="attr.key + ':'">
|
||||
<a :href="`/console/database/document?id=${doc.$id}&collection=${doc.$collection}&project=${project}`">
|
||||
<a :href="`/console/databases/document?id=${doc.$id}&collection=${doc.$collection}&databaseId=${databaseId}&project=${project}`">
|
||||
<span x-text="doc[attr.key] ?? 'n/a'"></span>
|
||||
</a>
|
||||
</td>
|
||||
@@ -98,8 +107,9 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<div class="pull-end text-align-center paging">
|
||||
<form
|
||||
data-service="database.listDocuments"
|
||||
data-service="databases.listDocuments"
|
||||
data-event="submit"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-types="DESC"
|
||||
@@ -114,8 +124,9 @@ $logs = $this->getParam('logs', null);
|
||||
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-documents.total|pageTotal}}"></span>
|
||||
|
||||
<form
|
||||
data-service="database.listDocuments"
|
||||
data-service="databases.listDocuments"
|
||||
data-event="submit"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-types="DESC"
|
||||
@@ -128,15 +139,15 @@ $logs = $this->getParam('logs', null);
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<a data-ls-if="0 < {{project-collection.attributes.length}}" data-ls-attrs="href=/console/database/document/new?collection={{router.params.id}}&project={{router.params.project}}" class="button">
|
||||
<a data-ls-if="0 < {{project-collection.attributes.length}}" data-ls-attrs="href=/console/databases/document/new?collection={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}" class="button">
|
||||
Add Document
|
||||
</a>
|
||||
<a data-ls-if="!{{project-collection.attributes.length}}" data-ls-attrs="href=/console/database/collection/attributes?id={{router.params.id}}&project={{router.params.project}}" class="button">
|
||||
<a data-ls-if="!{{project-collection.attributes.length}}" data-ls-attrs="href=/console/databases/collection/attributes?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}" class="button">
|
||||
Create Attribute
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li data-state="/console/database/collection/attributes?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<li data-state="/console/databases/collection/attributes?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
|
||||
<h2>Attributes</h2>
|
||||
|
||||
<div class="clear box margin-bottom">
|
||||
@@ -266,13 +277,14 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Delete Collection Attribute"
|
||||
data-service="database.deleteAttribute"
|
||||
data-service="databases.deleteAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-confirm="Are you sure you want to delete this attribute?"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-success="alert,trigger"
|
||||
data-success-param-alert-text="Deleted attribute successfully"
|
||||
data-success-param-trigger-events="database.deleteAttribute"
|
||||
data-success-param-trigger-events="databases.deleteAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to delete attribute"
|
||||
data-failure-param-alert-classname="error">
|
||||
@@ -320,7 +332,7 @@ $logs = $this->getParam('logs', null);
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li data-state="/console/database/collection/indexes?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<li data-state="/console/databases/collection/indexes?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
|
||||
<h2>Indexes</h2>
|
||||
|
||||
<div class="clear box margin-bottom">
|
||||
@@ -382,13 +394,14 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Delete Collection Index"
|
||||
data-service="database.deleteIndex"
|
||||
data-service="databases.deleteIndex"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-confirm="Are you sure you want to delete this index?"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-success="alert,trigger"
|
||||
data-success-param-alert-text="Deleted index successfully"
|
||||
data-success-param-trigger-events="database.deleteIndex"
|
||||
data-success-param-trigger-events="databases.deleteIndex"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to delete index"
|
||||
data-failure-param-alert-classname="error">
|
||||
@@ -407,18 +420,19 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<button class="new-index">Add Index</button>
|
||||
</li>
|
||||
<li data-state="/console/database/collection/activity?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<li data-state="/console/databases/collection/activity?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
|
||||
<h2>Activity</h2>
|
||||
|
||||
<?php echo $logs->render(); ?>
|
||||
</li>
|
||||
<li data-state="/console/database/collection/usage?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<li data-state="/console/databases/collection/usage?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
|
||||
<form
|
||||
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
|
||||
data-service="database.getCollectionUsage"
|
||||
data-service="databases.getCollectionUsage"
|
||||
data-event="submit"
|
||||
data-name="usage"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-range="90d">
|
||||
<button class="tick">90d</button>
|
||||
</form>
|
||||
@@ -427,9 +441,10 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<form
|
||||
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
|
||||
data-service="database.getCollectionUsage"
|
||||
data-service="databases.getCollectionUsage"
|
||||
data-event="submit"
|
||||
data-name="usage"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-collection-id="{{router.params.id}}">
|
||||
<button class="tick">30d</button>
|
||||
</form>
|
||||
@@ -438,9 +453,10 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<form
|
||||
class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
|
||||
data-service="database.getCollectionUsage"
|
||||
data-service="databases.getCollectionUsage"
|
||||
data-event="submit"
|
||||
data-name="usage"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-range="24h">
|
||||
<button class="tick">24h</button>
|
||||
@@ -450,7 +466,7 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<h2>Usage</h2>
|
||||
|
||||
<div data-service="database.getCollectionUsage" data-event="load" data-name="usage" data-param-collection-id="{{router.params.id}}">
|
||||
<div data-service="databases.getCollectionUsage" data-event="load" data-name="usage" data-param-collection-id="{{router.params.id}}" data-param-database-id="{{router.params.databaseId}}">
|
||||
<h3 class="margin-bottom-tiny">Documents</h3>
|
||||
<p class="text-fade">Count of documents over time</p>
|
||||
<div class="box margin-bottom-small">
|
||||
@@ -489,7 +505,7 @@ $logs = $this->getParam('logs', null);
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li data-state="/console/database/collection/settings?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<li data-state="/console/databases/collection/settings?id={{router.params.id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
|
||||
<h2>Settings</h2>
|
||||
|
||||
<div class="row responsive margin-top-negative">
|
||||
@@ -500,13 +516,14 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Update Database Collection"
|
||||
data-service="database.updateCollection"
|
||||
data-service="databases.updateCollection"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-success="alert,trigger"
|
||||
data-success-param-alert-text="Updated collection successfully"
|
||||
data-success-param-trigger-events="database.updateCollection"
|
||||
data-success-param-trigger-events="databases.updateCollection"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to update collection"
|
||||
data-failure-param-alert-classname="error">
|
||||
@@ -577,24 +594,25 @@ $logs = $this->getParam('logs', null);
|
||||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-collection.dateUpdated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-collection.dateCreated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-collection.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-collection.$createdAt|dateText}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form
|
||||
name="database.deleteCollection" class="margin-bottom"
|
||||
name="databases.deleteCollection" class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Delete Database Collection"
|
||||
data-service="database.deleteCollection"
|
||||
data-service="databases.deleteCollection"
|
||||
data-event="submit"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-confirm="Are you sure you want to delete this collection?"
|
||||
data-success="alert,trigger,redirect"
|
||||
data-success-param-alert-text="Collection deleted successfully"
|
||||
data-success-param-trigger-events="database.deleteCollection"
|
||||
data-success-param-trigger-events="databases.deleteCollection"
|
||||
data-success-param-redirect-url="/console/database?project={{router.params.project}}"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to delete collection"
|
||||
@@ -620,12 +638,12 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (string)"
|
||||
data-service="database.createStringAttribute"
|
||||
data-service="databases.createStringAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="database.createAttribute"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create attribute"
|
||||
data-failure-param-alert-classname="error"
|
||||
@@ -634,6 +652,7 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
|
||||
<label for="string-key">Attribute ID</label>
|
||||
<input id="string-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$" />
|
||||
@@ -676,12 +695,12 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (integer)"
|
||||
data-service="database.createIntegerAttribute"
|
||||
data-service="databases.createIntegerAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="database.createAttribute"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create attribute"
|
||||
data-failure-param-alert-classname="error"
|
||||
@@ -689,6 +708,7 @@ $logs = $this->getParam('logs', null);
|
||||
x-data="{ array: false, required: false, min: null, max: null }">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="integer-key">Attribute ID</label>
|
||||
@@ -741,12 +761,12 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (float)"
|
||||
data-service="database.createFloatAttribute"
|
||||
data-service="databases.createFloatAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="database.createAttribute"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create attribute"
|
||||
data-failure-param-alert-classname="error"
|
||||
@@ -754,6 +774,7 @@ $logs = $this->getParam('logs', null);
|
||||
x-data="{ array: false, required: false, min: null, max: null }">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="float-key">Attribute ID</label>
|
||||
@@ -806,12 +827,12 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (email)"
|
||||
data-service="database.createEmailAttribute"
|
||||
data-service="databases.createEmailAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="database.createAttribute"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create attribute"
|
||||
data-failure-param-alert-classname="error"
|
||||
@@ -819,6 +840,7 @@ $logs = $this->getParam('logs', null);
|
||||
x-data="{ array: false, required: false }">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="email-key">Attribute ID</label>
|
||||
@@ -859,12 +881,12 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (boolean)"
|
||||
data-service="database.createBooleanAttribute"
|
||||
data-service="databases.createBooleanAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="database.createAttribute"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create attribute"
|
||||
data-failure-param-alert-classname="error"
|
||||
@@ -872,6 +894,7 @@ $logs = $this->getParam('logs', null);
|
||||
x-data="{ array: false, required: false }">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="boolean-key">Attribute ID</label>
|
||||
@@ -916,12 +939,12 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (IP)"
|
||||
data-service="database.createIpAttribute"
|
||||
data-service="databases.createIpAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="database.createAttribute"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create attribute"
|
||||
data-failure-param-alert-classname="error"
|
||||
@@ -929,6 +952,7 @@ $logs = $this->getParam('logs', null);
|
||||
x-data="{ array: false, required: false }">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="ip-key">Attribute ID</label>
|
||||
@@ -969,12 +993,12 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (url)"
|
||||
data-service="database.createUrlAttribute"
|
||||
data-service="databases.createUrlAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="database.createAttribute"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create attribute"
|
||||
data-failure-param-alert-classname="error"
|
||||
@@ -982,6 +1006,7 @@ $logs = $this->getParam('logs', null);
|
||||
x-data="{ array: false, required: false }">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="url-key">Attribute ID</label>
|
||||
@@ -1022,12 +1047,12 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Attribute (enum)"
|
||||
data-service="database.createEnumAttribute"
|
||||
data-service="databases.createEnumAttribute"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new attribute successfully"
|
||||
data-success-param-trigger-events="database.createAttribute"
|
||||
data-success-param-trigger-events="databases.createAttribute"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create attribute"
|
||||
data-failure-param-alert-classname="error"
|
||||
@@ -1035,6 +1060,7 @@ $logs = $this->getParam('logs', null);
|
||||
x-data="{ array: false, required: false }">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="enum-key">Attribute ID</label>
|
||||
@@ -1102,17 +1128,18 @@ $logs = $this->getParam('logs', null);
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Collection Index"
|
||||
data-service="database.createIndex"
|
||||
data-service="databases.createIndex"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="alert,trigger,reset"
|
||||
data-success-param-alert-text="Created new index successfully"
|
||||
data-success-param-trigger-events="database.createIndex"
|
||||
data-success-param-trigger-events="databases.createIndex"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create index"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="index-key">Index Key</label>
|
||||
@@ -1134,8 +1161,15 @@ $logs = $this->getParam('logs', null);
|
||||
<div data-forms-clone="" data-label="Add Attribute" data-target="attributes-section" data-first="1">
|
||||
<div class="row responsive thin margin-bottom-tiny">
|
||||
<div class="col span-7 margin-bottom-small">
|
||||
<select data-duplications data-ls-attrs="name=attributes" data-ls-loop="project-collection.attributes" data-ls-as="option" data-cast-to="array" class="margin-bottom-no">
|
||||
<option data-ls-attrs="value={{option.key}}" data-ls-bind="{{option.key}}"></option>
|
||||
<select data-duplications data-ls-attrs="name=attributes" data-cast-to="array" class="margin-bottom-no">
|
||||
<optgroup label="Internal">
|
||||
<option value="$id">$id</option>
|
||||
<option value="$createdAt">$createdAt</option>
|
||||
<option value="$updatedAt">$updatedAt</option>
|
||||
</optgroup>
|
||||
<optgroup label="Attributes" data-ls-loop="project-collection.attributes" data-ls-as="option">
|
||||
<option data-ls-attrs="value={{option.key}}" data-ls-bind="{{option.key}}"></option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col span-4 margin-bottom-small">
|
||||
@@ -1157,7 +1191,7 @@ $logs = $this->getParam('logs', null);
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="margin-top" data-service="database.listCollections" data-event="load,database.createCollection,database.updateCollection,database.deleteCollection" data-scope="sdk" data-param-limit="100" data-name="project-collections">
|
||||
<div class="margin-top" data-service="databases.listCollections" data-param-database-id="{{router.params.databaseId}}" data-event="load,databases.createCollection,databases.updateCollection,databases.deleteCollection" data-scope="sdk" data-param-limit="100" data-name="project-collections">
|
||||
</div>
|
||||
|
||||
<script type="text/html" id="template-attribute-title-first">
|
||||
@@ -0,0 +1,326 @@
|
||||
<div
|
||||
data-service="databases.get"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-scope="sdk"
|
||||
data-event="load,databases.update"
|
||||
data-name="project-database">
|
||||
<div class="cover">
|
||||
<h1 class="zone xl margin-bottom-large">
|
||||
<a data-ls-attrs="href=/console/databases?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Database</a>
|
||||
<br />
|
||||
|
||||
<span data-ls-bind="{{project-database.name}}"> </span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div data-ui-modal class="modal width-large box close" data-button-hide="on" data-open-event="open-json">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h2>JSON View</h2>
|
||||
|
||||
<div class="margin-bottom">
|
||||
<input type="hidden" data-ls-bind="{{project-database}}" data-forms-code />
|
||||
</div>
|
||||
|
||||
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</div>
|
||||
|
||||
<div class="zone xl">
|
||||
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
|
||||
<li data-state="/console/databases/database?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<h2>Collections</h2>
|
||||
|
||||
<div class="margin-top"
|
||||
data-service="databases.listCollections"
|
||||
data-event="load,databases.createCollection,databases.updateCollection,databases.deleteCollection"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-offset="{{router.params.offset}}"
|
||||
data-param-order-type="ASC"
|
||||
data-scope="sdk"
|
||||
data-name="project-collections">
|
||||
|
||||
<div data-ls-if="(!{{project-collections.total}})" class="box dashboard margin-bottom">
|
||||
<div class="margin-bottom-small margin-top-small margin-end margin-start">
|
||||
<h3 class="margin-bottom-small text-bold">No Collections Found</h3>
|
||||
|
||||
<p class="margin-bottom-no">You haven't created any collections for your project yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-ls-if="0 != {{project-collections.total}}">
|
||||
<ul data-ls-loop="project-collections.collections" data-ls-as="collection" data-ls-append="" class="tiles cell-3 margin-bottom-small">
|
||||
<li class="margin-bottom">
|
||||
<a data-ls-attrs="href=/console/databases/collection?id={{collection.$id}}&databaseId={{router.params.id}}&project={{router.params.project}}" class="box">
|
||||
<div data-ls-bind="{{collection.name}}" class="text-one-liner margin-bottom text-bold"> </div>
|
||||
|
||||
<i class="icon-right-open"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="pull-end text-align-center paging">
|
||||
<form
|
||||
data-service="databases.listCollections"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-event="submit"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="ASC"
|
||||
data-scope="sdk"
|
||||
data-name="project-collections"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
|
||||
</form>
|
||||
|
||||
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-collections.total|pageTotal}}"></span>
|
||||
|
||||
<form
|
||||
data-service="databases.listCollections"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-event="submit"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="ASC"
|
||||
data-scope="sdk"
|
||||
data-name="project-collections"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Collection">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h1>New Collection</h1>
|
||||
|
||||
<form
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Database Collection"
|
||||
data-service="databases.createCollection"
|
||||
data-event="submit"
|
||||
data-scope="sdk"
|
||||
data-success="alert,reset,redirect,trigger"
|
||||
data-success-param-alert-text="Collection created successfully"
|
||||
data-success-param-redirect-url="/console/databases/collection/settings?id={{serviceData.$id}}&databaseId={{router.params.id}}&project={{router.params.project}}"
|
||||
data-success-param-trigger-events="databases.createCollection"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create collection"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.id}}" />
|
||||
|
||||
<label for="collection-id">Collection ID</label>
|
||||
<input
|
||||
type="hidden"
|
||||
data-custom-id
|
||||
data-id-type="auto"
|
||||
data-validator="databases.getCollection"
|
||||
required
|
||||
maxlength="36"
|
||||
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
|
||||
name="collectionId" />
|
||||
|
||||
<label for="collection-name">Name</label>
|
||||
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
|
||||
|
||||
<input type="hidden" id="collection-permission" name="permission" required value="collection" />
|
||||
<input type="hidden" id="collection-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
<input type="hidden" id="collection-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
|
||||
<hr />
|
||||
|
||||
<button type="submit">Create</button> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<li data-state="/console/databases/database/usage?project={{router.params.project}}&id={{router.params.id}}">
|
||||
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
|
||||
data-service="databases.getDatabaseUsage"
|
||||
data-event="submit"
|
||||
data-name="usage"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-param-range="90d">
|
||||
<button class="tick">90d</button>
|
||||
</form>
|
||||
|
||||
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
|
||||
|
||||
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
|
||||
data-service="databases.getDatabaseUsage"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-event="submit"
|
||||
data-name="usage">
|
||||
<button class="tick">30d</button>
|
||||
</form>
|
||||
|
||||
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
|
||||
|
||||
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
|
||||
data-service="databases.getDatabaseUsage"
|
||||
data-event="submit"
|
||||
data-name="usage"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-param-range="24h">
|
||||
<button class="tick">24h</button>
|
||||
</form>
|
||||
|
||||
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '24h'" disabled>24h</button>
|
||||
|
||||
<h2>Usage</h2>
|
||||
|
||||
<div
|
||||
data-service="databases.getDatabaseUsage"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-event="load"
|
||||
data-name="usage">
|
||||
<h3 class="margin-bottom-tiny">Objects</h3>
|
||||
<p class="text-fade">Count of collections and documents over time</p>
|
||||
<div class="box margin-bottom-small">
|
||||
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
|
||||
<div class="chart background-image-no border-no margin-bottom-no">
|
||||
<input
|
||||
type="hidden"
|
||||
data-ls-bind="{{usage}}"
|
||||
data-forms-chart="Collections=collectionsCount,Documents=documentsCount"
|
||||
data-show-y-axis="true"
|
||||
data-height="140" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="chart-notes margin-bottom-large">
|
||||
<li>Total Collections <span data-ls-bind="({{usage.collectionsCount|statsGetLast|statsTotal}})"></span></li>
|
||||
<li>Total Documents <span data-ls-bind="({{usage.documentsCount|statsGetLast|statsTotal}})"></span></li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3 class="margin-bottom-tiny">Collections</h3>
|
||||
<p class="text-fade">Count of collections create, read, update and delete operations over time</p>
|
||||
<div class="box margin-bottom-small">
|
||||
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
|
||||
<div class="chart background-image-no border-no margin-bottom-no">
|
||||
<input
|
||||
type="hidden"
|
||||
data-ls-bind="{{usage}}"
|
||||
data-forms-chart="Created=collectionsCreate,Read=collectionsRead,Updated=collectionsUpdate,Deleted=collectionsDelete"
|
||||
data-show-y-axis="true"
|
||||
data-colors="create,read,update,delete"
|
||||
data-height="140" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="chart-notes margin-bottom-large crud">
|
||||
<li>Created</li>
|
||||
<li>Read</li>
|
||||
<li>Updated</li>
|
||||
<li>Deleted</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="margin-bottom-tiny">Documents</h3>
|
||||
<p class="text-fade">Count of documents create, read, update and delete operations over time</p>
|
||||
<div class="box margin-bottom-small">
|
||||
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
|
||||
<div class="chart background-image-no border-no margin-bottom-no">
|
||||
<input
|
||||
type="hidden"
|
||||
data-ls-bind="{{usage}}"
|
||||
data-forms-chart="Created=documentsCreate,Read=documentsRead,Updated=documentsUpdate,Deleted=documentsDelete"
|
||||
data-show-y-axis="true"
|
||||
data-colors="create,read,update,delete"
|
||||
data-height="140" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="chart-notes margin-bottom-large crud">
|
||||
<li>Created</li>
|
||||
<li>Read</li>
|
||||
<li>Updated</li>
|
||||
<li>Deleted</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li data-state="/console/databases/database/settings?id={{router.params.id}}&project={{router.params.project}}">
|
||||
<h2>Settings</h2>
|
||||
|
||||
<div class="row responsive margin-top-negative">
|
||||
<div class="col span-8 margin-bottom">
|
||||
<form
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Update Database"
|
||||
data-service="databases.update"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-success="alert,trigger"
|
||||
data-success-param-alert-text="Updated database successfully"
|
||||
data-success-param-trigger-events="databases.update"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to update database"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<label> </label>
|
||||
|
||||
<div class="box">
|
||||
<label for="database-name">Name</label>
|
||||
<input name="name" id="database-name" type="text" autocomplete="off" data-ls-bind="{{project-database.name}}" data-forms-text-direction required placeholder="Database Name" maxlength="128" />
|
||||
|
||||
<hr class="margin-top-no" />
|
||||
|
||||
<button>Update</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col span-4 sticky-top">
|
||||
<label>Database ID</label>
|
||||
<div class="input-copy margin-bottom">
|
||||
<input id="id" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-database.$id}}" disabled data-forms-copy class="margin-bottom-no" />
|
||||
</div>
|
||||
|
||||
<ul class="margin-bottom-large text-fade text-size-small">
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-database.dateUpdated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-database.dateCreated|dateText}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form
|
||||
name="databases.delete" class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Delete Database"
|
||||
data-service="databases.delete"
|
||||
data-event="submit"
|
||||
data-param-database-id="{{router.params.id}}"
|
||||
data-confirm="Are you sure you want to delete this Database?"
|
||||
data-success="alert,trigger,redirect"
|
||||
data-success-param-alert-text="Database deleted successfully"
|
||||
data-success-param-trigger-events="databases.delete"
|
||||
data-success-param-redirect-url="/console/databases?project={{router.params.project}}"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to delete database"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<button type="submit" class="danger fill">Delete Database</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+20
-14
@@ -5,15 +5,17 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
?>
|
||||
<div
|
||||
data-service="database.getCollection"
|
||||
data-service="databases.getCollection"
|
||||
data-param-collection-id="{{router.params.collection}}"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-scope="sdk"
|
||||
data-event="load,database.updateDocument"
|
||||
data-event="load,databases.updateDocument"
|
||||
data-name="project-collection">
|
||||
|
||||
<div
|
||||
data-service="database.getDocument"
|
||||
data-service="databases.getDocument"
|
||||
data-param-collection-id="{{router.params.collection}}"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-document-id="{{router.params.id}}"
|
||||
data-scope="sdk"
|
||||
data-event="load"
|
||||
@@ -22,7 +24,7 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<div class="cover">
|
||||
<h1 class="zone xl margin-bottom-large">
|
||||
<a data-ls-attrs="href=/console/database/collection?id={{router.params.collection}}&project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> <span data-ls-bind="{{project-collection.name}}"></span></a>
|
||||
<a data-ls-attrs="href=/console/databases/collection?id={{router.params.collection}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> <span data-ls-bind="{{project-collection.name}}"></span></a>
|
||||
|
||||
<br />
|
||||
|
||||
@@ -45,7 +47,7 @@ $logs = $this->getParam('logs', null);
|
||||
|
||||
<div class="zone xl margin-bottom-no">
|
||||
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
|
||||
<li data-state="/console/database/document?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}">
|
||||
<li data-state="/console/databases/document?id={{router.params.id}}&collection={{router.params.collection}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}">
|
||||
<h2 class="margin-bottom">Overview</h2>
|
||||
|
||||
<div class="row responsive">
|
||||
@@ -64,19 +66,20 @@ $logs = $this->getParam('logs', null);
|
||||
<?php if($new): ?>
|
||||
data-analytics-label="Create Database Document"
|
||||
data-success="trigger,redirect"
|
||||
data-success-param-trigger-events="database.createDocument"
|
||||
data-success-param-redirect-url="/console/database/collection?id={{project-collection.$id}}&project={{router.params.project}}"
|
||||
data-success-param-trigger-events="databases.createDocument"
|
||||
data-success-param-redirect-url="/console/databases/collection?id={{project-collection.$id}}&databaseId={{router.params.databaseId}}&project={{router.params.project}}"
|
||||
data-failure-param-alert-text="Failed to create document"
|
||||
<?php else: ?>
|
||||
data-analytics-label="Update Database Document"
|
||||
data-success="trigger,alert"
|
||||
data-success-param-trigger-events="database.updateDocument"
|
||||
data-success-param-trigger-events="databases.updateDocument"
|
||||
data-success-param-alert-text="Your document was updated"
|
||||
data-failure-param-alert-text="Failed to update document"
|
||||
<?php endif; ?>
|
||||
>
|
||||
|
||||
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
|
||||
<input type="hidden" name="databaseId" data-ls-bind="{{router.params.databaseId}}" />
|
||||
<?php if(!$new): ?><input type="hidden" name="documentId" data-ls-bind="{{project-document.$id}}" /><?php endif; ?>
|
||||
|
||||
<div class="box">
|
||||
@@ -86,7 +89,7 @@ $logs = $this->getParam('logs', null);
|
||||
type="hidden"
|
||||
data-custom-id
|
||||
data-id-type="auto"
|
||||
data-validator="database.getDocument"
|
||||
data-validator="databases.getDocument"
|
||||
required
|
||||
maxlength="36"
|
||||
name="documentId"
|
||||
@@ -356,24 +359,27 @@ $logs = $this->getParam('logs', null);
|
||||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-document.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-document.$createdAt|dateText}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<div data-ls-if="({{project-document.$id}})">
|
||||
<form name="database.deleteDocument" class="margin-bottom"
|
||||
<form name="databases.deleteDocument" class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Delete Collection Document"
|
||||
data-service="database.deleteDocument"
|
||||
data-service="databases.deleteDocument"
|
||||
data-event="submit"
|
||||
data-param-database-id="{{router.params.databaseId}}"
|
||||
data-param-collection-id="{{router.params.collection}}"
|
||||
data-param-document-id="{{project-document.$id}}"
|
||||
data-confirm="Are you sure you want to delete this document?"
|
||||
data-success="alert,trigger,redirect"
|
||||
data-success-param-alert-text="Document deleted successfully"
|
||||
data-success-param-trigger-events="database.deleteDocument"
|
||||
data-success-param-redirect-url="/console/database/collection?id={{router.params.collection}}&project={{router.params.project}}"
|
||||
data-success-param-trigger-events="databases.deleteDocument"
|
||||
data-success-param-redirect-url="/console/databases/collection?id={{router.params.collection}}&project={{router.params.project}}&databaseId={{router.params.databaseId}}"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to delete collection"
|
||||
data-failure-param-alert-classname="error">
|
||||
@@ -385,7 +391,7 @@ $logs = $this->getParam('logs', null);
|
||||
</div>
|
||||
</li>
|
||||
<?php if(!$new): ?>
|
||||
<li data-state="/console/database/document/activity?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}">
|
||||
<li data-state="/console/databases/document/activity?id={{router.params.id}}&collection={{router.params.collection}}&project={{router.params.project}}">
|
||||
<h2>Activity</h2>
|
||||
|
||||
<?php echo $logs->render(); ?>
|
||||
@@ -9,32 +9,32 @@
|
||||
|
||||
<div class="zone xl">
|
||||
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
|
||||
<li data-state="/console/database?project={{router.params.project}}">
|
||||
<h2>Collections</h2>
|
||||
<li data-state="/console/databases?project={{router.params.project}}">
|
||||
<h2>Databases</h2>
|
||||
|
||||
<div class="margin-top"
|
||||
data-service="database.listCollections"
|
||||
data-event="load,database.createCollection,database.updateCollection,database.deleteCollection"
|
||||
data-service="databases.list"
|
||||
data-event="load,databases.create,databases.update,databases.delete"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-offset="{{router.params.offset}}"
|
||||
data-param-order-type="ASC"
|
||||
data-scope="sdk"
|
||||
data-name="project-collections">
|
||||
data-name="project-databases">
|
||||
|
||||
<div data-ls-if="(!{{project-collections.total}})" class="box dashboard margin-bottom">
|
||||
<div data-ls-if="(!{{project-databases.total}})" class="box dashboard margin-bottom">
|
||||
<div class="margin-bottom-small margin-top-small margin-end margin-start">
|
||||
<h3 class="margin-bottom-small text-bold">No Collections Found</h3>
|
||||
<h3 class="margin-bottom-small text-bold">No Databases Found</h3>
|
||||
|
||||
<p class="margin-bottom-no">You haven't created any collections for your project yet.</p>
|
||||
<p class="margin-bottom-no">You haven't created any databases for your project yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-ls-if="0 != {{project-collections.total}}">
|
||||
<ul data-ls-loop="project-collections.collections" data-ls-as="collection" data-ls-append="" class="tiles cell-3 margin-bottom-small">
|
||||
<div data-ls-if="0 != {{project-databases.total}}">
|
||||
<ul data-ls-loop="project-databases.databases" data-ls-as="database" data-ls-append="" class="tiles cell-3 margin-bottom-small">
|
||||
<li class="margin-bottom">
|
||||
<a data-ls-attrs="href=/console/database/collection?id={{collection.$id}}&project={{router.params.project}}" class="box">
|
||||
<div data-ls-bind="{{collection.name}}" class="text-one-liner margin-bottom text-bold"> </div>
|
||||
<a data-ls-attrs="href=/console/databases/database?id={{database.$id}}&project={{router.params.project}}" class="box">
|
||||
<div data-ls-bind="{{database.name}}" class="text-one-liner margin-bottom text-bold"> </div>
|
||||
|
||||
<i class="icon-right-open"></i>
|
||||
</a>
|
||||
@@ -44,84 +44,79 @@
|
||||
|
||||
<div class="pull-end text-align-center paging">
|
||||
<form
|
||||
data-service="database.listCollections"
|
||||
data-service="databases.list"
|
||||
data-event="submit"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="ASC"
|
||||
data-scope="sdk"
|
||||
data-name="project-collections"
|
||||
data-name="project-databases"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
|
||||
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-total="{{project-databases.total}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
|
||||
</form>
|
||||
|
||||
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-collections.total|pageTotal}}"></span>
|
||||
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-databases.total|pageTotal}}"></span>
|
||||
|
||||
<form
|
||||
data-service="database.listCollections"
|
||||
data-service="databases.list"
|
||||
data-event="submit"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="ASC"
|
||||
data-scope="sdk"
|
||||
data-name="project-collections"
|
||||
data-name="project-databases"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-collections.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
|
||||
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-total="{{project-databases.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Collection">
|
||||
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Database">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h1>New Collection</h1>
|
||||
<h1>New Database</h1>
|
||||
|
||||
<form
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Create Database Collection"
|
||||
data-service="database.createCollection"
|
||||
data-analytics-label="Create Database"
|
||||
data-service="databases.create"
|
||||
data-event="submit"
|
||||
data-scope="sdk"
|
||||
data-success="alert,reset,redirect,trigger"
|
||||
data-success-param-alert-text="Collection created successfully"
|
||||
data-success-param-redirect-url="/console/database/collection/settings?id={{serviceData.$id}}&project={{router.params.project}}"
|
||||
data-success-param-trigger-events="database.createCollection"
|
||||
data-success-param-alert-text="Database created successfully"
|
||||
data-success-param-redirect-url="/console/databases/database?id={{serviceData.$id}}&project={{router.params.project}}"
|
||||
data-success-param-trigger-events="databases.create"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to create collection"
|
||||
data-failure-param-alert-text="Failed to create database"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<label for="collection-id">Collection ID</label>
|
||||
<label for="database-id">Database ID</label>
|
||||
<input
|
||||
type="hidden"
|
||||
data-custom-id
|
||||
data-id-type="auto"
|
||||
data-validator="database.getCollection"
|
||||
data-validator="databases.get"
|
||||
required
|
||||
maxlength="36"
|
||||
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
|
||||
name="collectionId" />
|
||||
|
||||
<label for="collection-name">Name</label>
|
||||
<input type="text" class="full-width" id="collection-name" name="name" required autocomplete="off" maxlength="128" />
|
||||
|
||||
<input type="hidden" id="collection-permission" name="permission" required value="collection" />
|
||||
<input type="hidden" id="collection-read" name="read" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
<input type="hidden" id="collection-write" name="write" required data-cast-to="json" value="<?php echo htmlentities(json_encode([])); ?>" />
|
||||
name="databaseId" />
|
||||
|
||||
<label for="database-name">Name</label>
|
||||
<input type="text" class="full-width" id="database-name" name="name" required autocomplete="off" maxlength="128" />
|
||||
<hr />
|
||||
|
||||
<button type="submit">Create</button> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<li data-state="/console/database/usage?project={{router.params.project}}">
|
||||
</li><!-- TODO need to work on usage -->
|
||||
<li data-state="/console/databases/usage?project={{router.params.project}}">
|
||||
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
|
||||
data-service="database.getUsage"
|
||||
data-service="databases.getUsage"
|
||||
data-event="submit"
|
||||
data-name="usage"
|
||||
data-param-range="90d">
|
||||
@@ -131,7 +126,7 @@
|
||||
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
|
||||
|
||||
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
|
||||
data-service="database.getUsage"
|
||||
data-service="databases.getUsage"
|
||||
data-event="submit"
|
||||
data-name="usage">
|
||||
<button class="tick">30d</button>
|
||||
@@ -140,7 +135,7 @@
|
||||
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
|
||||
|
||||
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
|
||||
data-service="database.getUsage"
|
||||
data-service="databases.getUsage"
|
||||
data-event="submit"
|
||||
data-name="usage"
|
||||
data-param-range="24h">
|
||||
@@ -152,7 +147,7 @@
|
||||
<h2>Usage</h2>
|
||||
|
||||
<div
|
||||
data-service="database.getUsage"
|
||||
data-service="databases.getUsage"
|
||||
data-event="load"
|
||||
data-name="usage">
|
||||
<h3 class="margin-bottom-tiny">Objects</h3>
|
||||
@@ -163,7 +158,7 @@
|
||||
<input
|
||||
type="hidden"
|
||||
data-ls-bind="{{usage}}"
|
||||
data-forms-chart="Collections=collectionsCount,Documents=documentsCount"
|
||||
data-forms-chart="Databases=databasesCount,Collections=collectionsCount,Documents=documentsCount"
|
||||
data-show-y-axis="true"
|
||||
data-height="140" />
|
||||
</div>
|
||||
@@ -171,10 +166,33 @@
|
||||
</div>
|
||||
|
||||
<ul class="chart-notes margin-bottom-large">
|
||||
<li>Total Databases <span data-ls-bind="({{usage.databasesCount|statsGetLast|statsTotal}})"></span></li>
|
||||
<li>Total Collections <span data-ls-bind="({{usage.collectionsCount|statsGetLast|statsTotal}})"></span></li>
|
||||
<li>Total Documents <span data-ls-bind="({{usage.documentsCount|statsGetLast|statsTotal}})"></span></li>
|
||||
</ul>
|
||||
|
||||
<h3 class="margin-bottom-tiny">Databases</h3>
|
||||
<p class="text-fade">Count of databases create, read, update and delete operations over time</p>
|
||||
<div class="box margin-bottom-small">
|
||||
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
|
||||
<div class="chart background-image-no border-no margin-bottom-no">
|
||||
<input
|
||||
type="hidden"
|
||||
data-ls-bind="{{usage}}"
|
||||
data-forms-chart="Created=databasesCreate,Read=databasesRead,Updated=databasesUpdate,Deleted=databasesDelete"
|
||||
data-show-y-axis="true"
|
||||
data-colors="create,read,update,delete"
|
||||
data-height="140" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="chart-notes margin-bottom-large crud">
|
||||
<li>Created</li>
|
||||
<li>Read</li>
|
||||
<li>Updated</li>
|
||||
<li>Deleted</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="margin-bottom-tiny">Collections</h3>
|
||||
<p class="text-fade">Count of collections create, read, update and delete operations over time</p>
|
||||
@@ -4,7 +4,12 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
||||
$events = $this->getParam('events', []);
|
||||
$timeout = $this->getParam('timeout', 900);
|
||||
$usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
|
||||
$patterns = [];
|
||||
$patterns = [
|
||||
'documents',
|
||||
'documents.create',
|
||||
'documents.update',
|
||||
'documents.delete',
|
||||
];
|
||||
|
||||
foreach ($events as $name => $event) {
|
||||
$patterns[] = $name;
|
||||
@@ -170,7 +175,7 @@ sort($patterns);
|
||||
<span data-ls-if="{{deployment.status}} == 'failed'" style="color: var(--config-color-danger)" class="pull-start" data-ls-bind="{{deployment.status}}"></span>
|
||||
<span data-ls-if="{{deployment.status}} == 'ready'" style="color: var(--config-color-success)" class="pull-start" data-ls-bind="{{deployment.status}}"></span>
|
||||
<span data-ls-if="{{deployment.status}} == 'processing' || {{deployment.status}} == 'building'" style="color: var(--config-color-info)" class="pull-start" data-ls-bind="{{deployment.status}}"></span>
|
||||
<span class="pull-start" data-ls-bind=" | Created {{deployment.dateCreated|timeSince}} | {{deployment.size|humanFileSize}}{{deployment.size|humanFileUnit}}"></span>
|
||||
<span class="pull-start" data-ls-bind=" | Created {{deployment.$createdAt|timeSince}} | {{deployment.size|humanFileSize}}{{deployment.size|humanFileUnit}}"></span>
|
||||
|
||||
<span data-ls-if="{{deployment.status}} == 'failed'"> | <button type="button" class="link text-size-small" data-ls-ui-trigger="open-stderr-{{deployment.$id}}">Logs</button></span>
|
||||
<span data-ls-if="{{deployment.status}} == 'ready'"> | <button type="button" class="link text-size-small" data-ls-ui-trigger="open-stdout-{{deployment.$id}}">Logs</button></span>
|
||||
@@ -245,7 +250,7 @@ sort($patterns);
|
||||
</div>
|
||||
|
||||
<ul class="margin-bottom-large text-fade text-size-small">
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<button data-ls-ui-trigger="open-json"
|
||||
class="link text-size-small"
|
||||
data-analytics
|
||||
@@ -255,8 +260,8 @@ sort($patterns);
|
||||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.dateUpdated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.dateCreated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.$createdAt|dateText}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form name="functions.delete" class="margin-bottom"
|
||||
@@ -402,7 +407,7 @@ sort($patterns);
|
||||
<i class="dot success" data-ls-if="{{execution.status}} === 'completed'"></i>
|
||||
</td>
|
||||
<td data-title="Date: ">
|
||||
<span data-ls-bind="{{execution.dateCreated|dateTime}}"></span>
|
||||
<span data-ls-bind="{{execution.$createdAt|dateTime}}"></span>
|
||||
</td>
|
||||
<td data-title="Status: ">
|
||||
<span data-ls-bind="{{execution.status}}"></span>
|
||||
@@ -604,7 +609,7 @@ sort($patterns);
|
||||
</div>
|
||||
|
||||
<ul class="margin-bottom-large text-fade text-size-small">
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<button data-ls-ui-trigger="open-json"
|
||||
class="link text-size-small"
|
||||
data-analytics
|
||||
@@ -614,8 +619,8 @@ sort($patterns);
|
||||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.dateUpdated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.dateCreated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.$createdAt|dateText}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form name="functions.delete" class="margin-bottom"
|
||||
@@ -662,6 +667,11 @@ sort($patterns);
|
||||
<input id="subResource" type="text" :placeholder="subResourceName" x-model="subResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
|
||||
</div>
|
||||
<div x-show="hasSubSubResource">
|
||||
<label x-text="subSubResourceName + ' (optional)'" for="subSubResource"></label>
|
||||
<input id="subSubResource" type="text" :placeholder="subSubResourceName" x-model="subSubResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
|
||||
</div>
|
||||
<div x-show="hasAttribute">
|
||||
<label for="attribute">Add Attribute (optional)</label>
|
||||
<select id="attribute" x-model="attribute">
|
||||
|
||||
@@ -190,7 +190,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
||||
<span data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
|
||||
</div>
|
||||
<div class="margin-bottom">
|
||||
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Created at: <span data-ls-bind="{{file.dateCreated|dateText}}"></span>
|
||||
<i class="icon-angle-circled-right margin-start-negative-tiny margin-end-tiny"></i> Created at: <span data-ls-bind="{{file.$createdAt|dateText}}"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -211,7 +211,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
||||
<span class="text-fade text-size-small" data-ls-bind="{{file.sizeOriginal|humanFileUnit}}"></span>
|
||||
</td>
|
||||
<td data-title="Created: ">
|
||||
<span class="text-fade text-size-small" data-ls-bind="{{file.dateCreated|dateText}}"></span>
|
||||
<span class="text-fade text-size-small" data-ls-bind="{{file.$createdAt|dateText}}"></span>
|
||||
</td>
|
||||
<td data-title="" class="cell-options-more" style="overflow: visible">
|
||||
<div class="drop-list end" data-ls-ui-open="" data-button-aria="File Options" data-button-class="icon-dot-3 reset-inner-button" data-blur="1">
|
||||
@@ -471,7 +471,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
||||
</div>
|
||||
|
||||
<ul class="margin-bottom-large text-fade text-size-small">
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<button data-ls-ui-trigger="open-json"
|
||||
class="link text-size-small"
|
||||
data-analytics
|
||||
@@ -481,8 +481,8 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
|
||||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-bucket.dateUpdated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-bucket.dateCreated|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-bucket.$updatedAt|dateText}}"></span></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-bucket.$createdAt|dateText}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form name="storage.deleteBucket" class="margin-bottom"
|
||||
|
||||
@@ -80,19 +80,19 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
||||
<td data-title="Name: ">
|
||||
<a data-ls-attrs="href=/console/users/user?id={{user.$id}}&project={{router.params.project}}">
|
||||
<span data-ls-bind="{{user.name}}" data-ls-attrs="title={{user.name}}"></span>
|
||||
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} !== ''">Unknown</span>
|
||||
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} === ''">Anonymous User</span>
|
||||
<span data-ls-if="{{user.name|escape}} === '' && ({{user.email}} !== '' || {{user.phone}} !== '')">Unknown</span>
|
||||
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} === '' && {{user.phone}} === ''">Anonymous User</span>
|
||||
</a>
|
||||
</td>
|
||||
<td data-title="Email: ">
|
||||
<small data-ls-bind="{{user.email}}" data-ls-attrs="title={{user.email}}"></span>
|
||||
<span data-ls-bind="{{user.email}}" data-ls-attrs="title={{user.email}}"></span>
|
||||
</td>
|
||||
<td data-title="Status: ">
|
||||
<span data-ls-if="{{user.emailVerification}} === true && {{user.status}} === true">
|
||||
<span data-ls-if="({{user.emailVerification}} || {{user.phoneVerification}}) && {{user.status}} === true">
|
||||
<span class="tag green">Verified</span>
|
||||
</span>
|
||||
|
||||
<span data-ls-if="{{user.emailVerification}} !== true && {{user.status}} === true">
|
||||
<span data-ls-if="!({{user.emailVerification}} || {{user.phoneVerification}}) && {{user.status}} === true">
|
||||
<span class="tag">Unverified</span>
|
||||
</span>
|
||||
|
||||
@@ -248,7 +248,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
||||
<a data-ls-attrs="href=/console/users/teams/team?id={{team.$id}}&project={{router.params.project}}" data-ls-bind="{{team.name}}" data-ls-attrs="title={{team.name}}"></a>
|
||||
</td>
|
||||
<td data-title="Members: "><span data-ls-bind="{{team.total}} members"></span></td>
|
||||
<td data-title="Date Created: "><small data-ls-bind="{{team.dateCreated|dateText}}"></small></td>
|
||||
<td data-title="Date Created: "><small data-ls-bind="{{team.$createdAt|dateText}}"></small></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -214,9 +214,9 @@
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="View as JSON (Team)">
|
||||
View as JSON
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{team.dateCreated|dateText}}"></span></li>
|
||||
</button>
|
||||
</li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{team.$createdAt|dateText}}"></span></li>
|
||||
</ul>
|
||||
|
||||
<form name="teams.delete" class="margin-bottom"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-name">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h2>Update name</h2>
|
||||
<h2>Update Name</h2>
|
||||
|
||||
<form name="users.updateName"
|
||||
data-analytics
|
||||
@@ -85,6 +85,41 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-phone">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h2>Update Phone</h2>
|
||||
|
||||
<form name="users.updatePhone"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Update User Phone"
|
||||
data-service="users.updatePhone"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="trigger,alert"
|
||||
data-success-param-alert-text="User phone was updated successfully"
|
||||
data-success-param-trigger-events="users.update"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to update user email"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
|
||||
|
||||
<label for="number">Phone number</label>
|
||||
<input name="number" id="number" type="text" autocomplete="off" data-ls-bind="{{user.phone}}" pattern="^\+?[1-9]\d{1,14}$" class="full-width" title="Phone number with a leading '+' and maximum of 15 digits (+123456789).">
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Phone number with a leading '+' and maximum of 15 digits (+123456789)</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<footer>
|
||||
<button type="submit" class="">Update</button> <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ui-modal class="modal width-medium box close" data-button-hide="on" data-open-event="open-update-password">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
@@ -144,20 +179,31 @@
|
||||
<div class="col span-8">
|
||||
<label> </label>
|
||||
|
||||
<div class="box margin-bottom-large">
|
||||
<div class="box margin-bottom-large" style="padding-bottom: 5px">
|
||||
<div class="text-align-center">
|
||||
<img src="" data-ls-attrs="src={{user|avatar}}" data-size="200" alt="User Avatar" class="avatar huge margin-top-negative-xxl" />
|
||||
|
||||
<div class="margin-top-small" data-ls-bind="Member since {{user.registration|dateText}}"></div>
|
||||
<div class="margin-top-small">
|
||||
<span data-ls-if="{{user.emailVerification}} === true">
|
||||
<div class="margin-top-small margin-bottom-small" data-ls-bind="Member since {{user.registration|dateText}}"></div>
|
||||
<hr class="margin-top-tiny margin-bottom-tiny" data-ls-if="{{user.email}}">
|
||||
<div class="margin-top-small margin-bottom-small clear" data-ls-if="{{user.email}}">
|
||||
<span data-ls-bind="{{user.email}}" class="pull-start"></span>
|
||||
<span data-ls-if="{{user.emailVerification}} === true" class="pull-end">
|
||||
<span class="tag green">Verified</span>
|
||||
</span>
|
||||
<span data-ls-if="{{user.emailVerification}} !== true">
|
||||
<span data-ls-if="{{user.emailVerification}} !== true" class="pull-end">
|
||||
<span class="tag">Unverified</span>
|
||||
</span>
|
||||
</div>
|
||||
<hr class="margin-top-tiny margin-bottom-tiny" data-ls-if="{{user.phone}}">
|
||||
<div class="margin-top-small margin-bottom-small clear" data-ls-if="{{user.phone}}">
|
||||
<span data-ls-bind="{{user.phone}}" class="pull-start"></span>
|
||||
<span data-ls-if="{{user.phoneVerification}} === true" class="pull-end">
|
||||
<span class="tag green">Verified</span>
|
||||
</span>
|
||||
<span data-ls-if="{{user.phoneVerification}} !== true" class="pull-end">
|
||||
<span class="tag">Unverified</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="margin-top-small" data-ls-bind="{{user.email}}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -236,8 +282,9 @@
|
||||
</div>
|
||||
|
||||
<ul class="margin-bottom-large text-fade text-size-small">
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-name" class="link text-size-small">Update name</button></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-name" class="link text-size-small">Update Name</button></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-email" class="link text-size-small">Update Email</button></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-phone" class="link text-size-small">Update Phone</button></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-update-password" class="link text-size-small">Update Password</button></li>
|
||||
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
|
||||
<button data-ls-ui-trigger="open-json"
|
||||
@@ -251,14 +298,14 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div data-ls-if="{{user.emailVerification}} === false" style="display: none">
|
||||
<div data-ls-if="{{user.email}} && {{user.emailVerification}} === false" style="display: none">
|
||||
<form name="users.updateVerification" class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Update Verification Status"
|
||||
data-service="users.updateVerification"
|
||||
data-analytics-label="Update Email Verification Status"
|
||||
data-service="users.updateEmailVerification"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="trigger,alert"
|
||||
@@ -270,18 +317,18 @@
|
||||
>
|
||||
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
|
||||
<input type="hidden" disabled name="emailVerification" value="true" data-cast-to="boolean">
|
||||
<button type="submit" class="dark fill">Verify Account</button>
|
||||
<button type="submit" class="dark fill">Verify Email</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ls-if="{{user.emailVerification}} === true" style="display: none">
|
||||
<div data-ls-if="{{user.email}} && {{user.emailVerification}} === true" style="display: none">
|
||||
<form name="users.updateVerification" class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Update Verification Status"
|
||||
data-service="users.updateVerification"
|
||||
data-analytics-label="Update Email Verification Status"
|
||||
data-service="users.updateEmailVerification"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="trigger,alert"
|
||||
@@ -293,7 +340,53 @@
|
||||
>
|
||||
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
|
||||
<input type="hidden" disabled name="emailVerification" value="false" data-cast-to="boolean">
|
||||
<button type="submit" class="dark fill">Unverify Account</button>
|
||||
<button type="submit" class="dark fill">Unverify Email</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ls-if="{{user.phone}} && {{user.phoneVerification}} === false" style="display: none">
|
||||
<form name="users.updateVerification" class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Update Phone Verification Status"
|
||||
data-service="users.updatePhoneVerification"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="trigger,alert"
|
||||
data-success-param-alert-text="User verification status was updated successfully"
|
||||
data-success-param-trigger-events="users.update"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to update user verification status"
|
||||
data-failure-param-alert-classname="error"
|
||||
>
|
||||
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
|
||||
<input type="hidden" disabled name="phoneVerification" value="true" data-cast-to="boolean">
|
||||
<button type="submit" class="dark fill">Verify Phone</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div data-ls-if="{{user.phone}} && {{user.phoneVerification}} === true" style="display: none">
|
||||
<form name="users.updateVerification" class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Update Phone Verification Status"
|
||||
data-service="users.updatePhoneVerification"
|
||||
data-scope="sdk"
|
||||
data-event="submit"
|
||||
data-success="trigger,alert"
|
||||
data-success-param-alert-text="User verification status was updated successfully"
|
||||
data-success-param-trigger-events="users.update"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to update user verification status"
|
||||
data-failure-param-alert-classname="error"
|
||||
>
|
||||
<input type="hidden" disabled name="userId" data-ls-bind="{{user.$id}}">
|
||||
<input type="hidden" disabled name="phoneVerification" value="false" data-cast-to="boolean">
|
||||
<button type="submit" class="dark fill">Unverify Phone</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<?php
|
||||
$new = $this->getParam('new', false);
|
||||
$events = $this->getParam('events', []);
|
||||
$patterns = [];
|
||||
$patterns = [
|
||||
'documents',
|
||||
'documents.create',
|
||||
'documents.update',
|
||||
'documents.delete',
|
||||
];
|
||||
|
||||
foreach ($events as $name => $event) {
|
||||
$patterns[] = $name;
|
||||
@@ -30,7 +35,7 @@ sort($patterns);
|
||||
data-service="projects.getWebhook"
|
||||
data-name="project-webhook"
|
||||
data-scope="console"
|
||||
data-event="load,projects.createWebhook, projects.deleteWebhook, projects.updateWebhook"
|
||||
data-event="load,projects.createWebhook,projects.deleteWebhook,projects.updateWebhook,projects.updateWebhookSignature"
|
||||
data-param-project-id="{{router.params.project}}"
|
||||
data-param-webhook-id="{{router.params.id}}"
|
||||
data-success="trigger"
|
||||
@@ -156,7 +161,53 @@ sort($patterns);
|
||||
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-webhook.$id}}" disabled data-forms-copy>
|
||||
</div>
|
||||
|
||||
<form class="margin-bottom" data-analytics data-analytics-activity data-analytics-event="submit" data-analytics-category="console" data-analytics-label="Delete Project Webhook" data-service="projects.deleteWebhook" data-scope="console" data-event="submit" data-confirm="Are you sure you want to delete this webhook?" data-success="alert,redirect" data-success-param-redirect-url="/console/webhooks?project={{router.params.project}}" data-success-param-alert-text="Deleted webhook successfully" data-success-param-trigger-events="projects.deleteWebhook" data-failure="alert" data-failure-param-alert-text="Failed to delete webhook" data-failure-param-alert-classname="error">
|
||||
<label>Signature Key <span class="tooltip small" data-tooltip="Can be used to validate your Webhooks."><i class="icon-info-circled"></i></span></label>
|
||||
<div class="input-copy margin-bottom">
|
||||
<input id="signatureKey" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-webhook.signatureKey}}" disabled data-forms-copy>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Update Project Webhook Signature"
|
||||
data-service="projects.updateWebhookSignature"
|
||||
data-scope="console"
|
||||
data-event="submit"
|
||||
data-confirm="Are you sure you want to generate a new Signature Key?"
|
||||
data-success="trigger"
|
||||
data-success-param-alert-text="Updated webhook signature key successfully"
|
||||
data-success-param-trigger-events="projects.updateWebhookSignature"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to update webhook signature key"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" />
|
||||
|
||||
<button class="fill">Update Signature Key</button>
|
||||
</form>
|
||||
|
||||
<form
|
||||
class="margin-bottom"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="console"
|
||||
data-analytics-label="Delete Project Webhook"
|
||||
data-service="projects.deleteWebhook"
|
||||
data-scope="console"
|
||||
data-event="submit"
|
||||
data-confirm="Are you sure you want to delete this webhook?"
|
||||
data-success="alert,redirect"
|
||||
data-success-param-redirect-url="/console/webhooks?project={{router.params.project}}"
|
||||
data-success-param-alert-text="Deleted webhook successfully"
|
||||
data-success-param-trigger-events="projects.deleteWebhook"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Failed to delete webhook"
|
||||
data-failure-param-alert-classname="error">
|
||||
|
||||
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
|
||||
<input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" />
|
||||
@@ -187,6 +238,11 @@ sort($patterns);
|
||||
<input id="subResource" type="text" :placeholder="subResourceName" x-model="subResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
|
||||
</div>
|
||||
<div x-show="hasSubSubResource">
|
||||
<label x-text="subSubResourceName + ' (optional)'" for="subSubResource"></label>
|
||||
<input id="subSubResource" type="text" :placeholder="subSubResourceName" x-model="subSubResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
|
||||
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
|
||||
</div>
|
||||
<div x-show="hasAttribute">
|
||||
<label for="attribute">Add Attribute (optional)</label>
|
||||
<select id="attribute" x-model="attribute">
|
||||
|
||||
@@ -19,17 +19,17 @@ $root = ($this->getParam('root') !== 'disabled');
|
||||
|
||||
<p>Login using email and password</p>
|
||||
|
||||
<form name="account.createSession"
|
||||
<form name="account.createEmailSession"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
data-analytics-event="submit"
|
||||
data-analytics-category="home"
|
||||
data-analytics-label="Create Account Session"
|
||||
data-service="account.createSession"
|
||||
data-service="account.createEmailSession"
|
||||
data-scope="console"
|
||||
data-event="submit"
|
||||
data-success="trigger,hide,redirect"
|
||||
data-success-param-trigger-events="account.createSession"
|
||||
data-success-param-trigger-events="account.createEmailSession"
|
||||
data-success-param-redirect-url="/console"
|
||||
data-failure="alert"
|
||||
data-failure-param-alert-text="Login failed. Please check your credentials."
|
||||
|
||||
@@ -149,6 +149,8 @@ services:
|
||||
- _APP_MAINTENANCE_RETENTION_EXECUTION
|
||||
- _APP_MAINTENANCE_RETENTION_ABUSE
|
||||
- _APP_MAINTENANCE_RETENTION_AUDIT
|
||||
- _APP_PHONE_PROVIDER
|
||||
- _APP_PHONE_SECRET
|
||||
|
||||
appwrite-realtime:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
@@ -295,11 +297,11 @@ services:
|
||||
- _APP_EXECUTOR_SECRET
|
||||
- _APP_EXECUTOR_HOST
|
||||
|
||||
appwrite-worker-database:
|
||||
appwrite-worker-databases:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
entrypoint: worker-database
|
||||
entrypoint: worker-databases
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-database
|
||||
container_name: appwrite-worker-databases
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -497,6 +499,26 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-messaging:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
entrypoint: worker-messaging
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-messaging
|
||||
networks:
|
||||
- appwrite
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_PHONE_PROVIDER
|
||||
- _APP_PHONE_FROM
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-maintenance:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
entrypoint: maintenance
|
||||
@@ -552,6 +574,8 @@ services:
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-schedule:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
|
||||
@@ -15,9 +15,6 @@ use Utopia\Config\Config;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
// Disable Auth since we already validate it in the API
|
||||
Authorization::disable();
|
||||
|
||||
Console::title('Builds V1 Worker');
|
||||
Console::success(APP_NAME . ' build worker v1 has started');
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@ class CertificatesV1 extends Worker
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
/**
|
||||
* 1. Read arguments and validate domain
|
||||
* 2. Get main domain
|
||||
|
||||
@@ -5,7 +5,6 @@ use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Resque\Worker;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
@@ -20,12 +19,11 @@ class DatabaseV1 extends Worker
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
Authorization::disable();
|
||||
|
||||
$type = $this->args['type'];
|
||||
$project = new Document($this->args['project']);
|
||||
$collection = new Document($this->args['collection'] ?? []);
|
||||
$document = new Document($this->args['document'] ?? []);
|
||||
$database = new Document($this->args['database'] ?? []);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception('Missing collection');
|
||||
@@ -37,24 +35,22 @@ class DatabaseV1 extends Worker
|
||||
|
||||
switch (strval($type)) {
|
||||
case DATABASE_TYPE_CREATE_ATTRIBUTE:
|
||||
$this->createAttribute($collection, $document, $project->getId());
|
||||
$this->createAttribute($database, $collection, $document, $project->getId());
|
||||
break;
|
||||
case DATABASE_TYPE_DELETE_ATTRIBUTE:
|
||||
$this->deleteAttribute($collection, $document, $project->getId());
|
||||
$this->deleteAttribute($database, $collection, $document, $project->getId());
|
||||
break;
|
||||
case DATABASE_TYPE_CREATE_INDEX:
|
||||
$this->createIndex($collection, $document, $project->getId());
|
||||
$this->createIndex($database, $collection, $document, $project->getId());
|
||||
break;
|
||||
case DATABASE_TYPE_DELETE_INDEX:
|
||||
$this->deleteIndex($collection, $document, $project->getId());
|
||||
$this->deleteIndex($database, $collection, $document, $project->getId());
|
||||
break;
|
||||
|
||||
default:
|
||||
Console::error('No database operation for type: ' . $type);
|
||||
break;
|
||||
}
|
||||
|
||||
Authorization::reset();
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
@@ -62,16 +58,18 @@ class DatabaseV1 extends Worker
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $database
|
||||
* @param Document $collection
|
||||
* @param Document $attribute
|
||||
* @param string $projectId
|
||||
*/
|
||||
protected function createAttribute(Document $collection, Document $attribute, string $projectId): void
|
||||
protected function createAttribute(Document $database, Document $collection, Document $attribute, string $projectId): void
|
||||
{
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$events = Event::generateEvents('collections.[collectionId].attributes.[attributeId].update', [
|
||||
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].update', [
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionId' => $collection->getId(),
|
||||
'attributeId' => $attribute->getId()
|
||||
]);
|
||||
@@ -94,7 +92,7 @@ class DatabaseV1 extends Worker
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
try {
|
||||
if (!$dbForProject->createAttribute('collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
|
||||
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'));
|
||||
@@ -106,7 +104,7 @@ class DatabaseV1 extends Worker
|
||||
// Pass first, most verbose event pattern
|
||||
event: $events[0],
|
||||
payload: $attribute,
|
||||
project: $project
|
||||
project: $project,
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
@@ -117,25 +115,28 @@ class DatabaseV1 extends Worker
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'projectId' => $projectId,
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionId' => $collection->getId()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('collections', $collectionId);
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $database
|
||||
* @param Document $collection
|
||||
* @param Document $attribute
|
||||
* @param string $projectId
|
||||
*/
|
||||
protected function deleteAttribute(Document $collection, Document $attribute, string $projectId): void
|
||||
protected function deleteAttribute(Document $database, Document $collection, Document $attribute, string $projectId): void
|
||||
{
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$events = Event::generateEvents('collections.[collectionId].attributes.[attributeId].delete', [
|
||||
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete', [
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionId' => $collection->getId(),
|
||||
'attributeId' => $attribute->getId()
|
||||
]);
|
||||
@@ -151,7 +152,7 @@ class DatabaseV1 extends Worker
|
||||
// - failed: attribute was never created
|
||||
// - stuck: attribute was available but cannot be removed
|
||||
try {
|
||||
if ($status !== 'failed' && !$dbForProject->deleteAttribute('collection_' . $collection->getInternalId(), $key)) {
|
||||
if ($status !== 'failed' && !$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
|
||||
throw new Exception('Failed to delete Attribute');
|
||||
}
|
||||
$dbForProject->deleteDocument('attributes', $attribute->getId());
|
||||
@@ -174,6 +175,7 @@ class DatabaseV1 extends Worker
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'projectId' => $projectId,
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionId' => $collection->getId()
|
||||
]
|
||||
);
|
||||
@@ -223,7 +225,7 @@ class DatabaseV1 extends Worker
|
||||
}
|
||||
|
||||
if ($exists) { // Delete the duplicate if created, else update in db
|
||||
$this->deleteIndex($collection, $index, $projectId);
|
||||
$this->deleteIndex($database, $collection, $index, $projectId);
|
||||
} else {
|
||||
$dbForProject->updateDocument('indexes', $index->getId(), $index);
|
||||
}
|
||||
@@ -231,21 +233,23 @@ class DatabaseV1 extends Worker
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('collections', $collectionId);
|
||||
$dbForProject->deleteCachedCollection('collection_' . $collection->getInternalId());
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId);
|
||||
$dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $database
|
||||
* @param Document $collection
|
||||
* @param Document $index
|
||||
* @param string $projectId
|
||||
*/
|
||||
protected function createIndex(Document $collection, Document $index, string $projectId): void
|
||||
protected function createIndex(Document $database, Document $collection, Document $index, string $projectId): void
|
||||
{
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$events = Event::generateEvents('collections.[collectionId].indexes.[indexId].update', [
|
||||
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].update', [
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionId' => $collection->getId(),
|
||||
'indexId' => $index->getId()
|
||||
]);
|
||||
@@ -258,7 +262,7 @@ class DatabaseV1 extends Worker
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
try {
|
||||
if (!$dbForProject->createIndex('collection_' . $collection->getInternalId(), $key, $type, $attributes, $lengths, $orders)) {
|
||||
if (!$dbForProject->createIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $attributes, $lengths, $orders)) {
|
||||
throw new Exception('Failed to create Index');
|
||||
}
|
||||
$dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available'));
|
||||
@@ -281,25 +285,28 @@ class DatabaseV1 extends Worker
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'projectId' => $projectId,
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionId' => $collection->getId()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('collections', $collectionId);
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $database
|
||||
* @param Document $collection
|
||||
* @param Document $index
|
||||
* @param string $projectId
|
||||
*/
|
||||
protected function deleteIndex(Document $collection, Document $index, string $projectId): void
|
||||
protected function deleteIndex(Document $database, Document $collection, Document $index, string $projectId): void
|
||||
{
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$events = Event::generateEvents('collections.[collectionId].indexes.[indexId].delete', [
|
||||
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].delete', [
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionId' => $collection->getId(),
|
||||
'indexId' => $index->getId()
|
||||
]);
|
||||
@@ -308,7 +315,7 @@ class DatabaseV1 extends Worker
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
try {
|
||||
if ($status !== 'failed' && !$dbForProject->deleteIndex('collection_' . $collection->getInternalId(), $key)) {
|
||||
if ($status !== 'failed' && !$dbForProject->deleteIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) {
|
||||
throw new Exception('Failed to delete index');
|
||||
}
|
||||
$dbForProject->deleteDocument('indexes', $index->getId());
|
||||
@@ -331,11 +338,12 @@ class DatabaseV1 extends Worker
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'projectId' => $projectId,
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionId' => $collection->getId()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('collections', $collection->getId());
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId(), $collection->getId());
|
||||
}
|
||||
}
|
||||
+45
-7
@@ -15,9 +15,6 @@ use Utopia\Audit\Audit;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
Console::title('Deletes V1 Worker');
|
||||
Console::success(APP_NAME . ' deletes worker v1 has started' . "\n");
|
||||
|
||||
@@ -47,6 +44,9 @@ class DeletesV1 extends Worker
|
||||
$document = new Document($this->args['document'] ?? []);
|
||||
|
||||
switch ($document->getCollection()) {
|
||||
case DELETE_TYPE_DATABASES:
|
||||
$this->deleteDatabase($document, $project->getId());
|
||||
break;
|
||||
case DELETE_TYPE_COLLECTIONS:
|
||||
$this->deleteCollection($document, $project->getId());
|
||||
break;
|
||||
@@ -79,8 +79,8 @@ class DeletesV1 extends Worker
|
||||
break;
|
||||
|
||||
case DELETE_TYPE_AUDIT:
|
||||
$timestamp = $payload['timestamp'] ?? 0;
|
||||
$document = new Document($payload['document'] ?? []);
|
||||
$timestamp = $this->args['timestamp'] ?? 0;
|
||||
$document = new Document($this->args['document'] ?? []);
|
||||
|
||||
if (!empty($timestamp)) {
|
||||
$this->deleteAuditLogs($this->args['timestamp']);
|
||||
@@ -100,6 +100,10 @@ class DeletesV1 extends Worker
|
||||
$this->deleteRealtimeUsage($this->args['timestamp']);
|
||||
break;
|
||||
|
||||
case DELETE_TYPE_SESSIONS:
|
||||
$this->deleteExpiredSessions($this->args['timestamp']);
|
||||
break;
|
||||
|
||||
case DELETE_TYPE_CERTIFICATES:
|
||||
$document = new Document($this->args['document']);
|
||||
$this->deleteCertificates($document);
|
||||
@@ -118,6 +122,25 @@ class DeletesV1 extends Worker
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document database document
|
||||
* @param string $projectId
|
||||
*/
|
||||
protected function deleteDatabase(Document $document, string $projectId): void
|
||||
{
|
||||
$databaseId = $document->getId();
|
||||
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$this->deleteByGroup('database_' . $document->getInternalId(), [], $dbForProject, function ($document) use ($projectId) {
|
||||
$this->deleteCollection($document, $projectId);
|
||||
});
|
||||
|
||||
$dbForProject->deleteCollection('database_' . $document->getInternalId());
|
||||
|
||||
$this->deleteAuditLogsByResource('database/' . $databaseId, $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document teams document
|
||||
* @param string $projectId
|
||||
@@ -125,10 +148,11 @@ class DeletesV1 extends Worker
|
||||
protected function deleteCollection(Document $document, string $projectId): void
|
||||
{
|
||||
$collectionId = $document->getId();
|
||||
$databaseId = str_replace('database_', '', $document->getCollection());
|
||||
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$dbForProject->deleteCollection('collection_' . $document->getInternalId());
|
||||
$dbForProject->deleteCollection('database_' . $databaseId . '_collection_' . $document->getInternalId());
|
||||
|
||||
$this->deleteByGroup('attributes', [
|
||||
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
|
||||
@@ -245,7 +269,21 @@ class DeletesV1 extends Worker
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
// Delete Executions
|
||||
$this->deleteByGroup('executions', [
|
||||
new Query('dateCreated', Query::TYPE_LESSER, [$timestamp])
|
||||
new Query('$createdAt', Query::TYPE_LESSER, [$timestamp])
|
||||
], $dbForProject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp
|
||||
*/
|
||||
protected function deleteExpiredSessions(int $timestamp): void
|
||||
{
|
||||
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
// Delete Sessions
|
||||
$this->deleteByGroup('sessions', [
|
||||
new Query('expire', Query::TYPE_LESSER, [$timestamp])
|
||||
], $dbForProject);
|
||||
});
|
||||
}
|
||||
|
||||
+35
-37
@@ -44,6 +44,10 @@ class FunctionsV1 extends Worker
|
||||
$user = new Document($this->args['user'] ?? []);
|
||||
$payload = json_encode($this->args['payload'] ?? []);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
$database = $this->getProjectDB($project->getId());
|
||||
|
||||
/**
|
||||
@@ -57,7 +61,7 @@ class FunctionsV1 extends Worker
|
||||
/** @var Document[] $functions */
|
||||
|
||||
while ($sum >= $limit) {
|
||||
$functions = Authorization::skip(fn () => $database->find('functions', [], $limit, $offset, ['name'], [Database::ORDER_ASC]));
|
||||
$functions = $database->find('functions', [], $limit, $offset, ['name'], [Database::ORDER_ASC]);
|
||||
$sum = \count($functions);
|
||||
$offset = $offset + $limit;
|
||||
|
||||
@@ -101,7 +105,7 @@ class FunctionsV1 extends Worker
|
||||
$jwt = $this->args['jwt'] ?? '';
|
||||
$data = $this->args['data'] ?? '';
|
||||
|
||||
$function = Authorization::skip(fn () => $database->getDocument('functions', $execution->getAttribute('functionId')));
|
||||
$function = $database->getDocument('functions', $execution->getAttribute('functionId'));
|
||||
|
||||
$this->execute(
|
||||
project: $project,
|
||||
@@ -132,7 +136,7 @@ class FunctionsV1 extends Worker
|
||||
*/
|
||||
|
||||
// Reschedule
|
||||
$function = Authorization::skip(fn () => $database->getDocument('functions', $function->getId()));
|
||||
$function = $database->getDocument('functions', $function->getId());
|
||||
|
||||
if (empty($function->getId())) {
|
||||
throw new Exception('Function not found (' . $function->getId() . ')');
|
||||
@@ -149,11 +153,11 @@ class FunctionsV1 extends Worker
|
||||
->setAttribute('scheduleNext', $next)
|
||||
->setAttribute('schedulePrevious', \time());
|
||||
|
||||
$function = Authorization::skip(fn () => $database->updateDocument(
|
||||
$function = $database->updateDocument(
|
||||
'functions',
|
||||
$function->getId(),
|
||||
$function->setAttribute('scheduleNext', (int) $next)
|
||||
));
|
||||
);
|
||||
|
||||
if ($function === false) {
|
||||
throw new Exception('Function update failed.');
|
||||
@@ -198,7 +202,7 @@ class FunctionsV1 extends Worker
|
||||
$deploymentId = $function->getAttribute('deployment', '');
|
||||
|
||||
/** Check if deployment exists */
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $deploymentId));
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $functionId) {
|
||||
throw new Exception('Deployment not found. Create deployment before trying to execute a function', 404);
|
||||
@@ -209,7 +213,7 @@ class FunctionsV1 extends Worker
|
||||
}
|
||||
|
||||
/** Check if build has exists */
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception('Build not found', 404);
|
||||
}
|
||||
@@ -228,35 +232,30 @@ class FunctionsV1 extends Worker
|
||||
$runtime = $runtimes[$function->getAttribute('runtime')];
|
||||
|
||||
/** Create execution or update execution status */
|
||||
$execution = Authorization::skip(function () use ($dbForProject, &$executionId, $functionId, $deploymentId, $trigger, $user) {
|
||||
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
|
||||
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
|
||||
if ($execution->isEmpty()) {
|
||||
$executionId = $dbForProject->getId();
|
||||
$execution = $dbForProject->createDocument('executions', new Document([
|
||||
'$id' => $executionId,
|
||||
'$read' => $user->isEmpty() ? [] : ['user:' . $user->getId()],
|
||||
'$write' => [],
|
||||
'functionId' => $functionId,
|
||||
'deploymentId' => $deploymentId,
|
||||
'trigger' => $trigger,
|
||||
'status' => 'waiting',
|
||||
'statusCode' => 0,
|
||||
'response' => '',
|
||||
'stderr' => '',
|
||||
'time' => 0.0,
|
||||
'search' => implode(' ', [$functionId, $executionId]),
|
||||
]));
|
||||
|
||||
if ($execution->isEmpty()) {
|
||||
$executionId = $dbForProject->getId();
|
||||
$execution = $dbForProject->createDocument('executions', new Document([
|
||||
'$id' => $executionId,
|
||||
'$read' => $user->isEmpty() ? [] : ['user:' . $user->getId()],
|
||||
'$write' => [],
|
||||
'dateCreated' => time(),
|
||||
'functionId' => $functionId,
|
||||
'deploymentId' => $deploymentId,
|
||||
'trigger' => $trigger,
|
||||
'status' => 'waiting',
|
||||
'statusCode' => 0,
|
||||
'response' => '',
|
||||
'stderr' => '',
|
||||
'time' => 0.0,
|
||||
'search' => implode(' ', [$functionId, $executionId]),
|
||||
]));
|
||||
|
||||
if ($execution->isEmpty()) {
|
||||
throw new Exception('Failed to create or read execution');
|
||||
}
|
||||
throw new Exception('Failed to create or read execution');
|
||||
}
|
||||
$execution->setAttribute('status', 'processing');
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
|
||||
return $execution;
|
||||
});
|
||||
}
|
||||
$execution->setAttribute('status', 'processing');
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
|
||||
/** Collect environment variables */
|
||||
$vars = [
|
||||
@@ -298,7 +297,7 @@ class FunctionsV1 extends Worker
|
||||
->setAttribute('time', $executionResponse['time']);
|
||||
} catch (\Throwable $th) {
|
||||
$endtime = \microtime(true);
|
||||
$time = $endtime - $execution->getAttribute('dateCreated');
|
||||
$time = $endtime - $execution->getCreatedAt();
|
||||
$execution
|
||||
->setAttribute('time', $time)
|
||||
->setAttribute('status', 'failed')
|
||||
@@ -307,8 +306,7 @@ class FunctionsV1 extends Worker
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
|
||||
$execution = Authorization::skip(fn () => $dbForProject->updateDocument('executions', $executionId, $execution));
|
||||
/** @var Document $execution */
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
|
||||
/** Trigger Webhook */
|
||||
$executionModel = new Execution();
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Phone;
|
||||
use Appwrite\Auth\Phone\Mock;
|
||||
use Appwrite\Auth\Phone\Telesign;
|
||||
use Appwrite\Auth\Phone\TextMagic;
|
||||
use Appwrite\Auth\Phone\Twilio;
|
||||
use Appwrite\DSN\DSN;
|
||||
use Appwrite\Resque\Worker;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
Console::title('Messaging V1 Worker');
|
||||
Console::success(APP_NAME . ' messaging worker v1 has started' . "\n");
|
||||
|
||||
class MessagingV1 extends Worker
|
||||
{
|
||||
protected ?Phone $phone = null;
|
||||
protected ?string $from = null;
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return "mails";
|
||||
}
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$dsn = new DSN(App::getEnv('_APP_PHONE_PROVIDER'));
|
||||
$user = $dsn->getUser();
|
||||
$secret = $dsn->getPassword();
|
||||
|
||||
$this->phone = match ($dsn->getHost()) {
|
||||
'mock' => new Mock('', ''), // used for tests
|
||||
'twilio' => new Twilio($user, $secret),
|
||||
'text-magic' => new TextMagic($user, $secret),
|
||||
'telesign' => new Telesign($user, $secret),
|
||||
default => null
|
||||
};
|
||||
|
||||
$this->from = App::getEnv('_APP_PHONE_FROM');
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) {
|
||||
Console::info('Skipped sms processing. No Phone provider has been set.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($this->from)) {
|
||||
Console::info('Skipped sms processing. No phone number has been set.');
|
||||
return;
|
||||
}
|
||||
|
||||
$recipient = $this->args['recipient'];
|
||||
$message = $this->args['message'];
|
||||
|
||||
try {
|
||||
$this->phone->send($this->from, $recipient, $message);
|
||||
} catch (\Exception $error) {
|
||||
throw new Exception('Error sending message: ' . $error->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,4 @@ else
|
||||
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
fi
|
||||
|
||||
INTERVAL=0.1 QUEUE='v1-database' APP_INCLUDE='/usr/src/code/app/workers/database.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
||||
INTERVAL=0.1 QUEUE='v1-database' APP_INCLUDE='/usr/src/code/app/workers/databases.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
|
||||
then
|
||||
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
else
|
||||
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
fi
|
||||
|
||||
INTERVAL=1 QUEUE='v1-messaging' APP_INCLUDE='/usr/src/code/app/workers/messaging.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
||||
+4
-4
@@ -36,7 +36,7 @@
|
||||
"ext-zlib": "*",
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-clamav": "1.1.*",
|
||||
"appwrite/php-runtimes": "0.9.*",
|
||||
"appwrite/php-runtimes": "0.10.*",
|
||||
"utopia-php/framework": "0.19.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/abuse": "0.7.*",
|
||||
@@ -45,7 +45,7 @@
|
||||
"utopia-php/cache": "0.6.*",
|
||||
"utopia-php/cli": "0.12.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.17.*",
|
||||
"utopia-php/database": "0.18.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/registry": "dev-feat-allow-querying",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
@@ -63,7 +63,6 @@
|
||||
"chillerlan/php-qrcode": "4.3.3",
|
||||
"adhocore/jwt": "1.1.2",
|
||||
"slickdeals/statsd": "3.1.0",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"webonyx/graphql-php": "14.1.1"
|
||||
},
|
||||
"repositories": [
|
||||
@@ -73,8 +72,9 @@
|
||||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "0.18.8",
|
||||
"appwrite/sdk-generator": "0.19.4",
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"swoole/ide-helper": "4.8.9",
|
||||
"textalk/websocket": "1.5.7"
|
||||
},
|
||||
|
||||
Generated
+75
-74
@@ -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": "66447142ffcb7c061df7a86729c8ecac",
|
||||
"content-hash": "266a47c93a9de6f1c36211aa5cf52799",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -115,11 +115,11 @@
|
||||
},
|
||||
{
|
||||
"name": "appwrite/php-runtimes",
|
||||
"version": "0.9.1",
|
||||
"version": "0.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/runtimes.git",
|
||||
"reference": "01acf8741f539f64248d54a9f0f6c4d39195d16c"
|
||||
"reference": "09874846c6bdb7be58c97b12323d2b35ec995409"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
@@ -154,7 +154,7 @@
|
||||
"php",
|
||||
"runtimes"
|
||||
],
|
||||
"time": "2022-05-19T11:48:16+00:00"
|
||||
"time": "2022-06-28T05:26:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-qrcode",
|
||||
@@ -1581,65 +1581,9 @@
|
||||
},
|
||||
"time": "2021-06-04T20:33:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.7.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
||||
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
|
||||
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-simplexml": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/phpcs",
|
||||
"bin/phpcbf"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Greg Sherwood",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
|
||||
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"keywords": [
|
||||
"phpcs",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
|
||||
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
|
||||
},
|
||||
"time": "2022-06-18T07:21:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.1.0",
|
||||
"version": "v3.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
@@ -1686,7 +1630,7 @@
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0"
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2107,16 +2051,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "0.17.1",
|
||||
"version": "0.18.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "a4d001452b78b85335ffbd34176cd45d2b13c1ca"
|
||||
"reference": "e9e163642546343267c2fe0ee90016a4a0230b4a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/a4d001452b78b85335ffbd34176cd45d2b13c1ca",
|
||||
"reference": "a4d001452b78b85335ffbd34176cd45d2b13c1ca",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/e9e163642546343267c2fe0ee90016a4a0230b4a",
|
||||
"reference": "e9e163642546343267c2fe0ee90016a4a0230b4a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2131,6 +2075,7 @@
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.14",
|
||||
"phpunit/phpunit": "^9.4",
|
||||
"swoole/ide-helper": "4.8.0",
|
||||
"utopia-php/cli": "^0.11.0",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
},
|
||||
@@ -2164,9 +2109,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.17.1"
|
||||
"source": "https://github.com/utopia-php/database/tree/0.18.6"
|
||||
},
|
||||
"time": "2022-05-16T09:11:44+00:00"
|
||||
"time": "2022-06-27T17:28:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
@@ -2949,16 +2894,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "0.18.8",
|
||||
"version": "0.19.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "8ba45dfb74ff6062f96c0e4d10d7c4fae94768b1"
|
||||
"reference": "51a8e4205cd4809deeff8121a24e717642d68564"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/8ba45dfb74ff6062f96c0e4d10d7c4fae94768b1",
|
||||
"reference": "8ba45dfb74ff6062f96c0e4d10d7c4fae94768b1",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/51a8e4205cd4809deeff8121a24e717642d68564",
|
||||
"reference": "51a8e4205cd4809deeff8121a24e717642d68564",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2993,9 +2938,9 @@
|
||||
"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.18.8"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.19.4"
|
||||
},
|
||||
"time": "2022-05-19T10:34:06+00:00"
|
||||
"time": "2022-06-29T15:19:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
@@ -5028,6 +4973,62 @@
|
||||
],
|
||||
"time": "2020-09-28T06:39:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.7.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
|
||||
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
|
||||
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-simplexml": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/phpcs",
|
||||
"bin/phpcbf"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Greg Sherwood",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
|
||||
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"keywords": [
|
||||
"phpcs",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
|
||||
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
|
||||
},
|
||||
"time": "2022-06-18T07:21:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "swoole/ide-helper",
|
||||
"version": "4.8.9",
|
||||
|
||||
+31
-3
@@ -175,6 +175,8 @@ services:
|
||||
- _APP_MAINTENANCE_RETENTION_EXECUTION
|
||||
- _APP_MAINTENANCE_RETENTION_ABUSE
|
||||
- _APP_MAINTENANCE_RETENTION_AUDIT
|
||||
- _APP_PHONE_PROVIDER
|
||||
- _APP_PHONE_SECRET
|
||||
- _APP_GRAPHQL_MAX_COMPLEXITY
|
||||
- _APP_GRAPHQL_MAX_DEPTH
|
||||
- _APP_GRAPHQL_REQUEST_TIMEOUT
|
||||
@@ -321,10 +323,10 @@ services:
|
||||
- _APP_EXECUTOR_SECRET
|
||||
- _APP_EXECUTOR_HOST
|
||||
|
||||
appwrite-worker-database:
|
||||
entrypoint: worker-database
|
||||
appwrite-worker-databases:
|
||||
entrypoint: worker-databases
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-database
|
||||
container_name: appwrite-worker-databases
|
||||
build:
|
||||
context: .
|
||||
networks:
|
||||
@@ -528,6 +530,30 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-messaging:
|
||||
entrypoint: worker-messaging
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-messaging
|
||||
build:
|
||||
context: .
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_PHONE_PROVIDER
|
||||
- _APP_PHONE_FROM
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-maintenance:
|
||||
entrypoint: maintenance
|
||||
<<: *x-logging
|
||||
@@ -592,6 +618,8 @@ services:
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-schedule:
|
||||
entrypoint: schedule
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createAnonymousSession(new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createEmailSession(
|
||||
"email@example.com",
|
||||
"password"
|
||||
new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createJWT(new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createMagicURLSession(
|
||||
"[USER_ID]",
|
||||
"email@example.com",
|
||||
new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createOAuth2Session(
|
||||
this,
|
||||
"amazon",
|
||||
new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createPhoneSession(
|
||||
"[USER_ID]",
|
||||
""
|
||||
new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createPhoneVerification(new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createRecovery(
|
||||
"email@example.com",
|
||||
"https://example.com"
|
||||
new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createVerification(
|
||||
"https://example.com"
|
||||
new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.services.Account
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.create(
|
||||
"[USER_ID]",
|
||||
"email@example.com",
|
||||
"password",
|
||||
new Continuation<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(@NotNull Object o) {
|
||||
String json = "";
|
||||
try {
|
||||
if (o instanceof Result.Failure) {
|
||||
Result.Failure failure = (Result.Failure) o;
|
||||
throw failure.exception;
|
||||
} else {
|
||||
Response response = (Response) o;
|
||||
json = response.body().string();
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
Log.e("ERROR", th.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user