Merge branch 'master' into feat-empty-payload
45
.env
|
|
@ -1,6 +1,6 @@
|
|||
_APP_ENV=production
|
||||
_APP_ENV=development
|
||||
_APP_LOCALE=en
|
||||
_APP_WORKER_PER_CORE=6
|
||||
_APP_CONSOLE_WHITELIST_ROOT=disabled
|
||||
_APP_CONSOLE_WHITELIST_EMAILS=
|
||||
_APP_CONSOLE_WHITELIST_IPS=
|
||||
|
|
@ -15,11 +15,35 @@ _APP_DOMAIN=demo.appwrite.io
|
|||
_APP_DOMAIN_TARGET=demo.appwrite.io
|
||||
_APP_REDIS_HOST=redis
|
||||
_APP_REDIS_PORT=6379
|
||||
_APP_REDIS_PASS=
|
||||
_APP_REDIS_USER=
|
||||
_APP_DB_HOST=mariadb
|
||||
_APP_DB_PORT=3306
|
||||
_APP_DB_SCHEMA=appwrite
|
||||
_APP_DB_USER=user
|
||||
_APP_DB_PASS=password
|
||||
_APP_DB_ROOT_PASS=rootsecretpassword
|
||||
_APP_STORAGE_DEVICE=Local
|
||||
_APP_STORAGE_S3_ACCESS_KEY=
|
||||
_APP_STORAGE_S3_SECRET=
|
||||
_APP_STORAGE_S3_REGION=us-east-1
|
||||
_APP_STORAGE_S3_BUCKET=
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY=
|
||||
_APP_STORAGE_DO_SPACES_SECRET=
|
||||
_APP_STORAGE_DO_SPACES_REGION=us-east-1
|
||||
_APP_STORAGE_DO_SPACES_BUCKET=
|
||||
_APP_STORAGE_BACKBLAZE_ACCESS_KEY=
|
||||
_APP_STORAGE_BACKBLAZE_SECRET=
|
||||
_APP_STORAGE_BACKBLAZE_REGION=us-west-004
|
||||
_APP_STORAGE_BACKBLAZE_BUCKET=
|
||||
_APP_STORAGE_LINODE_ACCESS_KEY=
|
||||
_APP_STORAGE_LINODE_SECRET=
|
||||
_APP_STORAGE_LINODE_REGION=eu-central-1
|
||||
_APP_STORAGE_LINODE_BUCKET=
|
||||
_APP_STORAGE_WASABI_ACCESS_KEY=
|
||||
_APP_STORAGE_WASABI_SECRET=
|
||||
_APP_STORAGE_WASABI_REGION=eu-central-1
|
||||
_APP_STORAGE_WASABI_BUCKET=
|
||||
_APP_STORAGE_ANTIVIRUS=disabled
|
||||
_APP_STORAGE_ANTIVIRUS_HOST=clamav
|
||||
_APP_STORAGE_ANTIVIRUS_PORT=3310
|
||||
|
|
@ -32,16 +56,27 @@ _APP_SMTP_PORT=1025
|
|||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_STORAGE_LIMIT=10000000
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
_APP_STORAGE_PREVIEW_LIMIT=20000000
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
||||
_APP_FUNCTIONS_TIMEOUT=900
|
||||
_APP_FUNCTIONS_BUILD_TIMEOUT=900
|
||||
_APP_FUNCTIONS_CONTAINERS=10
|
||||
_APP_FUNCTIONS_CPUS=1
|
||||
_APP_FUNCTIONS_MEMORY=256
|
||||
_APP_FUNCTIONS_MEMORY_SWAP=256
|
||||
_APP_FUNCTIONS_CPUS=0
|
||||
_APP_FUNCTIONS_MEMORY=0
|
||||
_APP_FUNCTIONS_MEMORY_SWAP=0
|
||||
_APP_FUNCTIONS_INACTIVE_THRESHOLD=60
|
||||
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
|
||||
_APP_EXECUTOR_SECRET=your-secret-key
|
||||
_APP_EXECUTOR_HOST=http://appwrite-executor/v1
|
||||
_APP_MAINTENANCE_INTERVAL=86400
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE=86400
|
||||
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=30
|
||||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
DOCKERHUB_PULL_USERNAME=
|
||||
DOCKERHUB_PULL_PASSWORD=
|
||||
DOCKERHUB_PULL_EMAIL=
|
||||
2
.github/ISSUE_TEMPLATE/bug.yaml
vendored
|
|
@ -37,6 +37,8 @@ body:
|
|||
label: "🎲 Appwrite version"
|
||||
description: "What version of Appwrite are you running?"
|
||||
options:
|
||||
- Version 0.14.x
|
||||
- Version 0.13.x
|
||||
- Version 0.12.x
|
||||
- Version 0.11.x
|
||||
- Version 0.10.x
|
||||
|
|
|
|||
34
.github/workflows/linter.yml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
name: "Linter"
|
||||
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
tests:
|
||||
name: Linter
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.0'
|
||||
|
||||
- name: Install dependencies
|
||||
uses: php-actions/composer@v6
|
||||
with:
|
||||
php_version: '8.0'
|
||||
args: --profile --ignore-platform-reqs
|
||||
|
||||
- name: Run Linter
|
||||
run: ./vendor/bin/phpcs -p
|
||||
3
.github/workflows/tests.yml
vendored
|
|
@ -41,5 +41,6 @@ jobs:
|
|||
- name: Teardown
|
||||
if: always()
|
||||
run: |
|
||||
docker compose down -v
|
||||
docker ps -aq | xargs docker rm --force
|
||||
docker volume prune --force
|
||||
docker network prune --force
|
||||
1
.gitignore
vendored
|
|
@ -2,6 +2,7 @@
|
|||
/vendor/
|
||||
/node_modules/
|
||||
/tests/resources/storage/
|
||||
/tests/resources/functions/**/code.tar.gz
|
||||
/app/sdks/*
|
||||
/.idea/
|
||||
.DS_Store
|
||||
|
|
|
|||
13
.gitpod.Dockerfile
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
FROM gitpod/workspace-full
|
||||
|
||||
RUN sudo apt update
|
||||
RUN sudo add-apt-repository ppa:ondrej/php -y
|
||||
# Disable current PHP installation
|
||||
RUN sudo a2dismod php7.4 mpm_prefork
|
||||
|
||||
# Install apache2 (PHP install requires to do this first) and php8.0
|
||||
RUN sudo install-packages \
|
||||
-o Dpkg::Options::="--force-confdef" \
|
||||
-o Dpkg::Options::="--force-confold" \
|
||||
apache2 php8.0
|
||||
|
||||
34
.gitpod.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
tasks:
|
||||
- name: Run Appwrite Docker Stack
|
||||
init: |
|
||||
docker-compose pull
|
||||
docker-compose build
|
||||
command: |
|
||||
docker run --rm --interactive --tty \
|
||||
--volume $PWD:/app \
|
||||
composer update \
|
||||
--ignore-platform-reqs \
|
||||
--optimize-autoloader \
|
||||
--no-plugins \
|
||||
--no-scripts \
|
||||
--prefer-dist
|
||||
|
||||
ports:
|
||||
- port: 8080
|
||||
onOpen: open-preview
|
||||
visibility: public
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- ms-azuretools.vscode-docker
|
||||
|
||||
github:
|
||||
# https://www.gitpod.io/docs/prebuilds#github-specific-configuration
|
||||
prebuilds:
|
||||
# enable for pull requests coming from forks (defaults to false)
|
||||
pullRequestsFromForks: true
|
||||
# add a check to pull requests (defaults to true)
|
||||
addCheck: false
|
||||
191
CHANGES.md
|
|
@ -1,3 +1,191 @@
|
|||
# Version 0.14.2
|
||||
|
||||
## Features
|
||||
|
||||
- Support for Backblaze adapter in Storage
|
||||
- Support for Linode adapter in Storage
|
||||
- Support for Wasabi adapter in Storage
|
||||
- New Cloud Function Runtimes:
|
||||
- Dart 2.17
|
||||
- Deno 1.21
|
||||
- Java 18
|
||||
- Node 18
|
||||
- Improved overall Migration speed
|
||||
|
||||
|
||||
# Version 0.14.1
|
||||
|
||||
## Bugs
|
||||
* Fixed scheduled Cloud Functions execution with cron-job by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3245
|
||||
* Fixed missing runtime icons by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3234
|
||||
* Fixed Google OAuth by @Meldiron in https://github.com/appwrite/appwrite/pull/3236
|
||||
* Fixed certificate generation when hostname was set to 'localhost' by @Meldiron in https://github.com/appwrite/appwrite/pull/3237
|
||||
* Fixed Installation overriding default env variables by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3241
|
||||
|
||||
# Version 0.14.0
|
||||
|
||||
## Features
|
||||
- **BREAKING CHANGE** New Event Model
|
||||
- The new Event Model allows you to define events for Webhooks or Functions more granular
|
||||
- Account and Users events have been merged to just Users
|
||||
- Examples:
|
||||
- `database.documents.create` is now `collections.[COLLECTION_ID].documents.[DOCUMENT_ID].create`
|
||||
- Both placeholders needs to be replaced with either `*` for wildcard or an ID of the respective collection or document
|
||||
- So you can listen to every document that is created in the `posts` collection with `collections.posts.*.documents.*.create`
|
||||
- `event` in the Realtime payload has been renamed to `events` and contains all possible events
|
||||
- `X-Appwrite-Webhook-Event` Webhook header has been renamed to `X-Appwrite-Webhook-Events` and contains all possible events
|
||||
- **BREAKING CHANGE** Renamed `providers` to `authProviders` in Projects
|
||||
- **BREAKING CHANGE** Renamed `stdout` to `response` in Execution
|
||||
- **BREAKING CHANGE** Removed delete endpoint from the Accounts API
|
||||
- **BREAKING CHANGE** Renamed `name` to `userName` on Membership response model
|
||||
- **BREAKING CHANGE** Renamed `email` to `userEmail` on Membership response model
|
||||
- **BREAKING CHANGE** Renamed `event` to `events` on Realtime Response and now is an array of strings
|
||||
- Added `teamName` to Membership response model
|
||||
- Added new endpoint to update user's status from the Accounts API
|
||||
- Deleted users will now free their ID and not reserve it anymore
|
||||
- Added new endpoint to list all memberships on the Users API
|
||||
- Increased Execution `response` to 1MB
|
||||
- Increased Build `stdout` to 1MB
|
||||
- Added Wildcard support to Platforms
|
||||
- Added Activity page to Teams console
|
||||
- Added button to verify/unverify user's e-mail address in the console
|
||||
- Added Docker log limits to `docker-compose.yaml`
|
||||
- Renamed `_APP_EXECUTOR_RUNTIME_NETWORK` environment variable to `OPEN_RUNTIMES_NETWORK`
|
||||
- Added Auth0 OAuth2 provider
|
||||
- Added Okta Oauth2 provider @tanay1337 in https://github.com/appwrite/appwrite/pull/3139
|
||||
|
||||
## Bugs
|
||||
- Fixed issues with `min`, `max` and `default` values for float attributes
|
||||
- Fixed account created with Magic URL to set a new password
|
||||
- Fixed Database to respect `null` values
|
||||
- Fixed missing realtime events from the Users API
|
||||
- Fixed missing events when all sessions are deleted from the Users and Account API
|
||||
- Fixed dots in database attributes
|
||||
- Fixed renewal of SSL certificates
|
||||
- Fixed errors in the certificates workers
|
||||
- Fixed HTTPS redirect bug for non GET requests
|
||||
- Fixed search when a User is updated
|
||||
- Fixed aspect ratio bug in Avatars API
|
||||
- Fixed wrong `Fail to Warmup ...` error message in Executor
|
||||
- Fixed UI when file uploader is covered by jumpt to top button
|
||||
- Fixed bug that allowed Queries on failed indexes
|
||||
- Fixed UI when an alert with a lot text disappears too fast by increasing duration
|
||||
- Fixed issues with cache and case-sensivity on ID's
|
||||
- Fixed storage stats by upgrading to `BIGINT`
|
||||
- Fixed `storage.total` stats which now is a sum of `storage.files.total` and `storage.deployments.total`
|
||||
- Fixed Project logo preview
|
||||
- Fixed UI for missing icons in Collection attributes
|
||||
- Fixed UI to allow single-character custom ID's
|
||||
- Fixed array size validation in the Database Service
|
||||
- Fixed file preview when file extension is missing
|
||||
- Fixed `Open an Issue` link in the console
|
||||
- Fixed missing environment variables on Executor service
|
||||
- Fixed all endpoints that expect an Array in their params to have not more than 100 items
|
||||
- Added Executor host variables as a part of infrastructure configuration by @sjke in https://github.com/appwrite/appwrite/pull/3084
|
||||
- Added new tab/window for new release link by @Akshay-Rana-Gujjar in https://github.com/appwrite/appwrite/pull/3202
|
||||
|
||||
# Version 0.13.4
|
||||
|
||||
## Features
|
||||
- Added `detailedTrace` to Logger events
|
||||
- Added new `_APP_STORAGE_PREVIEW_LIMIT` environment variable to configure maximum preview file size
|
||||
|
||||
## Bugs
|
||||
- Fixed missing volume mount in Docker Compose
|
||||
- Fixed upload with Bucket File permission
|
||||
- Fixed custom ID validation in Console
|
||||
- Fixed file preview with no `output` passed
|
||||
- Fixed GitHub issue URL in Console
|
||||
- Fixed double PDOException logging
|
||||
- Fixed functions cleanup when container is already initialized
|
||||
- Fixed float input precision in Console
|
||||
|
||||
# Version 0.13.3
|
||||
## Bugs
|
||||
- Fixed search for terms that inlcude `@` characters
|
||||
- Fixed Bucket permissions
|
||||
- Fixed file upload error in UI
|
||||
- Fixed input field for float attributes in UI
|
||||
- Fixed `appwrite-executor` restart behavior in docker-compose.yml
|
||||
|
||||
# Version 0.13.2
|
||||
## Bugs
|
||||
- Fixed global issue with write permissions
|
||||
- Added missing `_APP_EXECUTOR_SECRET` environment variable for deletes worker
|
||||
- Increased execution `stdout` and `stderr` from 8000 to 16384 character limit
|
||||
- Increased maximum file size for image preview to 20mb
|
||||
- Fixed iOS platforms for origin validation by @stnguyen90 in https://github.com/appwrite/appwrite/pull/2907
|
||||
|
||||
# Version 0.13.1
|
||||
## Bugs
|
||||
- Fixed the Console UI redirect breaking the header and navigation
|
||||
- Fixed timeout in Functions API to respect the environment variable `_APP_FUNCTIONS_TIMEOUT`
|
||||
- Fixed team invite to be invalid after successful use by @Malte2036 in https://github.com/appwrite/appwrite/issues/2593
|
||||
|
||||
# Version 0.13.0
|
||||
## Features
|
||||
### Functions
|
||||
- Synchronous function execution
|
||||
- Improved functions execution times by alot
|
||||
- Added a new worker to build deployments
|
||||
- Functions are now executed differently and your functions need to be adapted **Breaking Change**
|
||||
- Tags are now called Deployments **Breaking Change**
|
||||
- Renamed `tagId` to `deplyomentId` in collections **Breaking Change**
|
||||
- Updated event names from `function.tags.*` to `function.deployments.*` **Breaking Change**
|
||||
- Java runtimes are currently not supported **Breaking Change**
|
||||
### Storage
|
||||
- Added Buckets
|
||||
- Buckets allow you to configure following settings:
|
||||
- Maximum File Size
|
||||
- Enabled/Disabled
|
||||
- Encryption
|
||||
- Anti Virus
|
||||
- Allowed file extensions
|
||||
- Permissions
|
||||
- Bucket Level
|
||||
- File Level
|
||||
- Support for S3 and Digitalocean Spaces
|
||||
- Efficiently process large files by loading only chunks
|
||||
- Files larger then 5MB needs to be uploaded in chunks using Content-Range header. SDKs handle this internally **Breaking Change**
|
||||
- Encryption, Compression is now limited to files smaller or equal to 20MB
|
||||
- New UI in the console for uploading files with progress indication
|
||||
- Concurrent file uploads
|
||||
- Added `buckets.read` and `buckets.write` scope to API keys
|
||||
|
||||
### Account
|
||||
- Renamed `providerToken` to `providerAccessToken` in sessions **Breaking Change**
|
||||
- New endpoint to refresh the OAuth Access Token
|
||||
- OAuth sessions now include `providerAccessTokenExpiry` and `providerRefreshToken`
|
||||
- Notion and Stripe have been added to the OAuth Providers
|
||||
- Microsoft OAuth provider now supports custom domains
|
||||
|
||||
### Others
|
||||
- Renamed `sum` to `total` on multiple endpoints returning a list of resource **Breaking Change**
|
||||
- Added new `_APP_WORKER_PER_CORE` environment variable to configure the amount of internal workers per core for performance optimization
|
||||
|
||||
## Bugs
|
||||
- Fixed issue with 36 character long custom IDs
|
||||
- Fixed permission issues and is now more consistent and returns all resources
|
||||
- Fixed total amount of documents not being updated
|
||||
- Fixed issue with searching though memberships
|
||||
- Fixed image preview rotation
|
||||
- Fixed Database index names that contain SQL keywords
|
||||
- Fixed UI to reveal long e-mail addresses on User list
|
||||
- Fixed UI for Attribute default value field to reset after submit
|
||||
- Fixed UI to check for new available version of Appwrite
|
||||
- Fixed UI default values when creating Integer or Float attributes
|
||||
- Removed `_project` prepend from internal Database Schema
|
||||
- Added dedicated internal permissions table for each Collection
|
||||
|
||||
## Security
|
||||
- Remove `appwrite.io` and `appwrite.test` from authorized domains for session verification
|
||||
|
||||
## Upgrades
|
||||
|
||||
- Upgraded `redis` extenstion to version 5.3.7
|
||||
- Upgraded `swoole` extenstion to version 4.8.7
|
||||
- Upgraded GEO IP database to version March 2022
|
||||
|
||||
# Version 0.12.3
|
||||
|
||||
## Bugs
|
||||
|
|
@ -200,7 +388,6 @@
|
|||
## Bugs
|
||||
- Fixed memory leak in realtime service (#1606)
|
||||
- Fixed function execution output now being UTF-8 encoded before saved (#1607)
|
||||
|
||||
# Version 0.10.2
|
||||
|
||||
## Bugs
|
||||
|
|
@ -232,9 +419,7 @@
|
|||
- Fixed MariaDB timeout after 24 hours (#1510)
|
||||
- Fixed upgrading installation with customized `docker-compose.yml` file (#1513)
|
||||
- Fixed usage stats on the dashboard displaying invalid total users count (#1514)
|
||||
|
||||
# Version 0.9.4
|
||||
|
||||
## Security
|
||||
|
||||
- Fixed security vulnerability that exposes project ID's from other admin users (#1453)
|
||||
|
|
|
|||
179
CONTRIBUTING.md
|
|
@ -12,11 +12,12 @@ Help us keep Appwrite open and inclusive. Please read and follow our [Code of Co
|
|||
|
||||
## Submit a Pull Request 🚀
|
||||
|
||||
Branch naming convention is as following
|
||||
Branch naming convention is as following
|
||||
|
||||
`TYPE-ISSUE_ID-DESCRIPTION`
|
||||
|
||||
example:
|
||||
|
||||
```
|
||||
doc-548-submit-a-pull-request-section-to-contribution-guide
|
||||
```
|
||||
|
|
@ -29,32 +30,55 @@ When `TYPE` can be:
|
|||
- **fix** - a bug fix
|
||||
- **refactor** - code change that neither fixes a bug nor adds a feature
|
||||
|
||||
**All PRs must include a commit message with the changes description!**
|
||||
**All PRs must include a commit message with the changes description!**
|
||||
|
||||
For the initial start, fork the project and use git clone command to download the repository to your computer. A standard procedure for working on an issue would be to:
|
||||
|
||||
1. `git pull`, before creating a new branch, pull the changes from upstream. Your master needs to be up to date.
|
||||
|
||||
```
|
||||
$ git pull
|
||||
```
|
||||
|
||||
2. Create new branch from `master` like: `doc-548-submit-a-pull-request-section-to-contribution-guide`<br/>
|
||||
|
||||
```
|
||||
$ git checkout -b [name_of_your_new_branch]
|
||||
```
|
||||
|
||||
3. Work - commit - repeat ( be sure to be in your branch )
|
||||
|
||||
4. Push changes to GitHub
|
||||
4. Before you push your changes, make sure your code follows the `PSR12` coding standards , which is the standard Appwrite follows currently. You can easily do this by running the formatter.
|
||||
|
||||
```bash
|
||||
./vendor/bin/phpcbf <your file path>
|
||||
```
|
||||
|
||||
Now, go a step further by running the linter by the following command to manually fix the issues the formatter wasn't able to fix.
|
||||
|
||||
```bash
|
||||
./vendor/bin/phpcs <your file path>
|
||||
```
|
||||
|
||||
This will give you a list of errors for you to rectify , if there is an instance you need more information on the errors being displayed you can pass in additional command line arguments. More list of available arguments can be found [here](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage). A very useful command line argument is `--report=diff`. This will give you the expected changes by the linter for easy fixing of formatting issues.
|
||||
|
||||
```bash
|
||||
./vendor/bin/phpcs --report=diff <your file path>
|
||||
```
|
||||
|
||||
5. Push changes to GitHub
|
||||
|
||||
```
|
||||
$ git push origin [name_of_your_new_branch]
|
||||
```
|
||||
|
||||
5. Submit your changes for review
|
||||
If you go to your repository on GitHub, you'll see a `Compare & pull request` button. Click on that button.
|
||||
6. Start a Pull Request
|
||||
Now submit the pull request and click on `Create pull request`.
|
||||
7. Get a code review approval/reject
|
||||
8. After approval, merge your PR
|
||||
9. GitHub will automatically delete the branch after the merge is done. (they can still be restored).
|
||||
6. Submit your changes for review
|
||||
If you go to your repository on GitHub, you'll see a `Compare & pull request` button. Click on that button.
|
||||
7. Start a Pull Request
|
||||
Now submit the pull request and click on `Create pull request`.
|
||||
8. Get a code review approval/reject
|
||||
9. After approval, merge your PR
|
||||
10. GitHub will automatically delete the branch after the merge is done. (they can still be restored).
|
||||
|
||||
## Setup From Source
|
||||
|
||||
|
|
@ -90,18 +114,20 @@ Appwrite uses an internal micro-framework called Litespeed.js to build simple UI
|
|||
|
||||
After finishing the installation process, you can start writing and editing code.
|
||||
|
||||
|
||||
#### Advanced Topics
|
||||
|
||||
We love to create issues that are good for beginners and label them as `good first issue` or `hacktoberfest`, but some more advanced topics might require extra knowledge. Below is a list of links you can use to learn more about some of the more advance topics that will help you master the Appwrite codebase.
|
||||
|
||||
##### Tools and Libs
|
||||
|
||||
- [Docker](https://www.docker.com/get-started)
|
||||
- [PHP FIG](https://www.php-fig.org/) - [PSR-1](https://www.php-fig.org/psr/psr-1/) and [PSR-4](https://www.php-fig.org/psr/psr-4/)
|
||||
- [PHP FIG](https://www.php-fig.org/) - [PSR-12](https://www.php-fig.org/psr/psr-12/)
|
||||
- [PHP Swoole](https://www.swoole.co.uk/)
|
||||
|
||||
Learn more at our [Technology Stack](#technology-stack) section.
|
||||
|
||||
##### Network and Protocols
|
||||
|
||||
- [OSI Model](https://en.wikipedia.org/wiki/OSI_model)
|
||||
- [TCP vs UDP](https://www.guru99.com/tcp-vs-udp-understanding-the-difference.html#:~:text=TCP%20is%20a%20connection%2Doriented,speed%20of%20UDP%20is%20faster&text=TCP%20does%20error%20checking%20and,but%20it%20discards%20erroneous%20packets.)
|
||||
- [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol)
|
||||
|
|
@ -110,10 +136,12 @@ Learn more at our [Technology Stack](#technology-stack) section.
|
|||
- [gRPC](https://en.wikipedia.org/wiki/GRPC)
|
||||
|
||||
##### Architecture
|
||||
|
||||
- [Microservices vs Monolithic](https://www.mulesoft.com/resources/api/microservices-vs-monolithic#:~:text=Microservices%20architecture%20vs%20monolithic%20architecture&text=A%20monolithic%20application%20is%20built%20as%20a%20single%20unit.&text=To%20make%20any%20alterations%20to,formally%20with%20business%2Doriented%20APIs.)
|
||||
- [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) - Appwrite console architecture
|
||||
|
||||
##### Security
|
||||
|
||||
- [Appwrite Auth and ACL](https://github.com/appwrite/appwrite/blob/0.7.x/docs/specs/authentication.drawio.svg)
|
||||
- [OAuth](https://en.wikipedia.org/wiki/OAuth)
|
||||
- [Encryption](https://medium.com/searchencrypt/what-is-encryption-how-does-it-work-e8f20e340537#:~:text=Encryption%20is%20a%20process%20that,%2C%20or%20decrypt%2C%20the%20information.)
|
||||
|
|
@ -124,8 +152,8 @@ Learn more at our [Technology Stack](#technology-stack) section.
|
|||
Appwrite's current structure is a combination of both [Monolithic](https://en.wikipedia.org/wiki/Monolithic_application) and [Microservice](https://en.wikipedia.org/wiki/Microservices) architectures, but our final goal, as we grow, is to be using only microservices.
|
||||
|
||||
---
|
||||

|
||||
---
|
||||
|
||||
## 
|
||||
|
||||
### File Structure
|
||||
|
||||
|
|
@ -204,15 +232,15 @@ Appwrite stack is combined from a variety of open-source technologies and tools.
|
|||
|
||||
### Other Technologies
|
||||
|
||||
* Redis - for managing cache and in-memory data (currently, we do not use Redis for persistent data)
|
||||
* MariaDB - for database storage and queries
|
||||
* InfluxDB - for managing stats and time-series based data
|
||||
* Statsd - for sending data over UDP protocol (using Telegraf)
|
||||
* ClamAV - for validating and scanning storage files
|
||||
* Imagemagick - for manipulating and managing image media files.
|
||||
* Webp - for better compression of images on supporting clients
|
||||
* SMTP - for sending email messages and alerts
|
||||
* Resque - for managing data queues and scheduled tasks over a Redis server
|
||||
- Redis - for managing cache and in-memory data (currently, we do not use Redis for persistent data)
|
||||
- MariaDB - for database storage and queries
|
||||
- InfluxDB - for managing stats and time-series based data
|
||||
- Statsd - for sending data over UDP protocol (using Telegraf)
|
||||
- ClamAV - for validating and scanning storage files
|
||||
- Imagemagick - for manipulating and managing image media files.
|
||||
- Webp - for better compression of images on supporting clients
|
||||
- SMTP - for sending email messages and alerts
|
||||
- Resque - for managing data queues and scheduled tasks over a Redis server
|
||||
|
||||
## Package Managers
|
||||
|
||||
|
|
@ -224,7 +252,7 @@ Appwrite uses [PHP's Composer](https://getcomposer.org/) for managing dependenci
|
|||
|
||||
## Coding Standards
|
||||
|
||||
Appwrite is following the [PHP-FIG standards](https://www.php-fig.org/). Currently, we are using both PSR-0 and PSR-4 for coding standards and autoloading standards. Soon we will also review the project for support with PSR-12 (Extended Coding Style).
|
||||
Appwrite is following the [PHP-FIG standards](https://www.php-fig.org/). Currently, we are using both PSR-0 and PSR-12 for coding standards and autoloading standards.
|
||||
|
||||
We use prettier for our JS coding standards and auto-formatting our code.
|
||||
|
||||
|
|
@ -236,14 +264,14 @@ We wish Appwrite will be as easy to set up and in a single, localhost, and easy
|
|||
|
||||
When contributing code, please take into account the following considerations:
|
||||
|
||||
* Response Time
|
||||
* Throughput
|
||||
* Requests per Seconds
|
||||
* Network Usage
|
||||
* Memory Usage
|
||||
* Browser Rendering
|
||||
* Background Jobs
|
||||
* Task Execution Time
|
||||
- Response Time
|
||||
- Throughput
|
||||
- Requests per Seconds
|
||||
- Network Usage
|
||||
- Memory Usage
|
||||
- Browser Rendering
|
||||
- Background Jobs
|
||||
- Task Execution Time
|
||||
|
||||
## Security & Privacy
|
||||
|
||||
|
|
@ -280,9 +308,10 @@ Before running the command, make sure you have proper write permissions to the A
|
|||
```bash
|
||||
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x -t appwrite/appwrite:dev --push .
|
||||
```
|
||||
|
||||
**Build Functions Runtimes**
|
||||
|
||||
The Runtimes for all supported cloud functions (multicore builds) can be found at the [appwrite/php-runtimes](https://github.com/appwrite/php-runtimes) repository.
|
||||
The Runtimes for all supported cloud functions (multicore builds) can be found at the [open-runtimes/open-runtimes](https://github.com/open-runtimes/open-runtimes) repository.
|
||||
|
||||
## Generate SDK
|
||||
|
||||
|
|
@ -299,11 +328,11 @@ For generating a new console SDK follow the next steps:
|
|||
|
||||
Things to remember when releasing SDKs
|
||||
|
||||
* Update the Changelogs in **docs/sdks** (right now only Dart and Flutter are using these)
|
||||
* Update **GETTING_STARTED.md** in **docs/sdks** for each SDKs if any changes in the related APIs in there
|
||||
* Update SDK versions as required on **app/config/platforms.php**
|
||||
* Generate SDKs using the command `php app/cli.php sdks` and follow the instructions
|
||||
* Release new tags on GitHub repository for each SDKs
|
||||
- Update the Changelogs in **docs/sdks** (right now only Dart and Flutter are using these)
|
||||
- Update **GETTING_STARTED.md** in **docs/sdks** for each SDKs if any changes in the related APIs in there
|
||||
- Update SDK versions as required on **app/config/platforms.php**
|
||||
- Generate SDKs using the command `php app/cli.php sdks` and follow the instructions
|
||||
- Release new tags on GitHub repository for each SDKs
|
||||
|
||||
## Debug
|
||||
|
||||
|
|
@ -315,13 +344,13 @@ First, you need to create an init file. Duplicate **dev/yasd_init.php.stub** fil
|
|||
|
||||
```json
|
||||
{
|
||||
"name": "Listen for Xdebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9005,
|
||||
"pathMappings": {
|
||||
"/usr/src/code": "${workspaceRoot}"
|
||||
},
|
||||
"name": "Listen for Xdebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9005,
|
||||
"pathMappings": {
|
||||
"/usr/src/code": "${workspaceRoot}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -354,65 +383,62 @@ To run end-2-end tests for a spcific service use:
|
|||
```bash
|
||||
docker-compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
|
||||
```
|
||||
|
||||
## Benchmarking
|
||||
|
||||
You can use WRK Docker image to benchmark the server performance. Benchmarking is extremely useful when you want to compare how the server behaves before and after a change has been applied. Replace [APPWRITE_HOSTNAME_OR_IP] with your Appwrite server hostname or IP. Note that localhost is not accessible from inside the WRK container.
|
||||
|
||||
```
|
||||
Options:
|
||||
-c, --connections <N> Connections to keep open
|
||||
-d, --duration <T> Duration of test
|
||||
-t, --threads <N> Number of threads to use
|
||||
|
||||
-s, --script <S> Load Lua script file
|
||||
-H, --header <H> Add header to request
|
||||
--latency Print latency statistics
|
||||
--timeout <T> Socket/request timeout
|
||||
-v, --version Print version details
|
||||
```
|
||||
Options:
|
||||
-c, --connections <N> Connections to keep open
|
||||
-d, --duration <T> Duration of test
|
||||
-t, --threads <N> Number of threads to use
|
||||
|
||||
-s, --script <S> Load Lua script file
|
||||
-H, --header <H> Add header to request
|
||||
--latency Print latency statistics
|
||||
--timeout <T> Socket/request timeout
|
||||
-v, --version Print version details
|
||||
```
|
||||
|
||||
```bash
|
||||
docker run --rm skandyla/wrk -t3 -c100 -d30 https://[APPWRITE_HOSTNAME_OR_IP]
|
||||
```
|
||||
|
||||
## Code Maintenance
|
||||
## Code Maintenance
|
||||
|
||||
We use some automation tools to help us keep a healthy codebase.
|
||||
|
||||
Improve PHP execution time by using [fully-qualified function calls](https://veewee.github.io/blog/optimizing-php-performance-by-fq-function-calls/):
|
||||
**Run Formatter:**
|
||||
|
||||
```bash
|
||||
php-cs-fixer fix src/ --rules=native_function_invocation --allow-risky=yes
|
||||
# Run on all files
|
||||
./vendor/bin/phpcbf
|
||||
# Run on single file or folder
|
||||
./vendor/bin/phpcbf <your file path>
|
||||
```
|
||||
|
||||
Coding Standards:
|
||||
**Run Linter:**
|
||||
|
||||
```bash
|
||||
php-cs-fixer fix app/controllers --rules='{"braces": {"allow_single_line_closure": true}}'
|
||||
```
|
||||
|
||||
```bash
|
||||
php-cs-fixer fix src --rules='{"braces": {"allow_single_line_closure": true}}'
|
||||
```
|
||||
|
||||
Static Code Analysis:
|
||||
|
||||
```bash
|
||||
docker-compose exec appwrite /usr/src/code/vendor/bin/psalm
|
||||
# Run on all files
|
||||
./vendor/bin/phpcs
|
||||
# Run on single file or folder
|
||||
./vendor/bin/phpcs <your file path>
|
||||
```
|
||||
|
||||
## Tutorials
|
||||
|
||||
From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials:
|
||||
|
||||
* [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
|
||||
* [Appwrite Environment Variables](./docs/tutorials/environment-variables.md)
|
||||
* [Running in Production](./docs/tutorials/running-in-production.md)
|
||||
* [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
|
||||
- [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
|
||||
- [Appwrite Environment Variables](./docs/tutorials/environment-variables.md)
|
||||
- [Running in Production](./docs/tutorials/running-in-production.md)
|
||||
- [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
|
||||
|
||||
## Other Ways to Help
|
||||
|
||||
Pull requests are great, but there are many other areas where you can help Appwrite.
|
||||
Pull requests are great, but there are many other areas where you can help Appwrite.
|
||||
|
||||
### Blogging & Speaking
|
||||
|
||||
|
|
@ -437,4 +463,3 @@ 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!
|
||||
|
||||
|
|
|
|||
40
Dockerfile
|
|
@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
|
|||
--no-plugins --no-scripts --prefer-dist \
|
||||
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
|
||||
|
||||
FROM node:16.13.2-alpine3.15 as node
|
||||
FROM node:16.14.2-alpine3.15 as node
|
||||
|
||||
WORKDIR /usr/local/src/
|
||||
|
||||
|
|
@ -24,14 +24,14 @@ COPY public /usr/local/src/public
|
|||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
||||
FROM php:8.0.14-cli-alpine3.15 as compile
|
||||
FROM php:8.0.18-cli-alpine3.15 as compile
|
||||
|
||||
ARG DEBUG=false
|
||||
ENV DEBUG=$DEBUG
|
||||
|
||||
ENV PHP_REDIS_VERSION=5.3.5 \
|
||||
PHP_MONGODB_VERSION=1.9.1 \
|
||||
PHP_SWOOLE_VERSION=v4.8.5 \
|
||||
ENV PHP_REDIS_VERSION=5.3.7 \
|
||||
PHP_MONGODB_VERSION=1.13.0 \
|
||||
PHP_SWOOLE_VERSION=v4.8.9 \
|
||||
PHP_IMAGICK_VERSION=3.7.0 \
|
||||
PHP_YAML_VERSION=2.2.2 \
|
||||
PHP_MAXMINDDB_VERSION=v1.11.0
|
||||
|
|
@ -123,7 +123,7 @@ RUN \
|
|||
./configure && \
|
||||
make && make install
|
||||
|
||||
FROM php:8.0.14-cli-alpine3.15 as final
|
||||
FROM php:8.0.18-cli-alpine3.15 as final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
|
|
@ -134,6 +134,7 @@ ENV DEBUG=$DEBUG
|
|||
ENV _APP_SERVER=swoole \
|
||||
_APP_ENV=production \
|
||||
_APP_LOCALE=en \
|
||||
_APP_WORKER_PER_CORE= \
|
||||
_APP_DOMAIN=localhost \
|
||||
_APP_DOMAIN_TARGET=localhost \
|
||||
_APP_HOME=https://appwrite.io \
|
||||
|
|
@ -152,6 +153,27 @@ ENV _APP_SERVER=swoole \
|
|||
_APP_STORAGE_ANTIVIRUS=enabled \
|
||||
_APP_STORAGE_ANTIVIRUS_HOST=clamav \
|
||||
_APP_STORAGE_ANTIVIRUS_PORT=3310 \
|
||||
_APP_STORAGE_DEVICE=Local \
|
||||
_APP_STORAGE_S3_ACCESS_KEY= \
|
||||
_APP_STORAGE_S3_SECRET= \
|
||||
_APP_STORAGE_S3_REGION= \
|
||||
_APP_STORAGE_S3_BUCKET= \
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY= \
|
||||
_APP_STORAGE_DO_SPACES_SECRET= \
|
||||
_APP_STORAGE_DO_SPACES_REGION= \
|
||||
_APP_STORAGE_DO_SPACES_BUCKET= \
|
||||
_APP_STORAGE_BACKBLAZE_ACCESS_KEY= \
|
||||
_APP_STORAGE_BACKBLAZE_SECRET= \
|
||||
_APP_STORAGE_BACKBLAZE_REGION= \
|
||||
_APP_STORAGE_BACKBLAZE_BUCKET= \
|
||||
_APP_STORAGE_LINODE_ACCESS_KEY= \
|
||||
_APP_STORAGE_LINODE_SECRET= \
|
||||
_APP_STORAGE_LINODE_REGION= \
|
||||
_APP_STORAGE_LINODE_BUCKET= \
|
||||
_APP_STORAGE_WASABI_ACCESS_KEY= \
|
||||
_APP_STORAGE_WASABI_SECRET= \
|
||||
_APP_STORAGE_WASABI_REGION= \
|
||||
_APP_STORAGE_WASABI_BUCKET= \
|
||||
_APP_REDIS_HOST=redis \
|
||||
_APP_REDIS_PORT=6379 \
|
||||
_APP_DB_HOST=mariadb \
|
||||
|
|
@ -168,11 +190,15 @@ ENV _APP_SERVER=swoole \
|
|||
_APP_SMTP_SECURE= \
|
||||
_APP_SMTP_USERNAME= \
|
||||
_APP_SMTP_PASSWORD= \
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000 \
|
||||
_APP_FUNCTIONS_TIMEOUT=900 \
|
||||
_APP_FUNCTIONS_CONTAINERS=10 \
|
||||
_APP_FUNCTIONS_CPUS=1 \
|
||||
_APP_FUNCTIONS_MEMORY=128 \
|
||||
_APP_FUNCTIONS_MEMORY_SWAP=128 \
|
||||
_APP_EXECUTOR_SECRET=a-random-secret \
|
||||
_APP_EXECUTOR_HOST=http://appwrite-executor/v1 \
|
||||
_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes \
|
||||
_APP_SETUP=self-hosted \
|
||||
_APP_VERSION=$VERSION \
|
||||
_APP_USAGE_STATS=enabled \
|
||||
|
|
@ -257,6 +283,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
|||
chmod +x /usr/local/bin/install && \
|
||||
chmod +x /usr/local/bin/migrate && \
|
||||
chmod +x /usr/local/bin/realtime && \
|
||||
chmod +x /usr/local/bin/executor && \
|
||||
chmod +x /usr/local/bin/schedule && \
|
||||
chmod +x /usr/local/bin/sdks && \
|
||||
chmod +x /usr/local/bin/specs && \
|
||||
|
|
@ -268,6 +295,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
|||
chmod +x /usr/local/bin/worker-database && \
|
||||
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-webhooks
|
||||
|
||||
|
|
|
|||
18
README-CN.md
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
|
||||
Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存, 图像处理,云函数计算,[等多种服务](https:/ /appwrite.io/docs)。
|
||||
Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存,图像处理,云函数计算,[等多种服务](https://appwrite.io/docs).
|
||||
|
||||

|
||||
|
||||
|
|
@ -34,18 +34,18 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
|
|||
- [CMD](#cmd)
|
||||
- [PowerShell](#powershell)
|
||||
- [从旧版本升级](#从旧版本升级)
|
||||
- [快速入门](#入门)
|
||||
- [入门](#入门)
|
||||
- [软件服务](#软件服务)
|
||||
- [开发套件](#开发套件)
|
||||
- [客户端](#客户端)
|
||||
- [服务器](#服务器)
|
||||
- [开发者社区](#开发者社区)
|
||||
- [软件架构]](#软件架构)
|
||||
- [软件架构](#软件架构)
|
||||
- [贡献代码](#贡献代码)
|
||||
- [安全](#安全)
|
||||
- [订阅我们](#订阅我们)
|
||||
- [版权说明](#版权说明)
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
|
||||
|
|
@ -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.12.3
|
||||
appwrite/appwrite:0.14.2
|
||||
```
|
||||
|
||||
### 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.12.3
|
||||
appwrite/appwrite:0.14.2
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
|
@ -81,7 +81,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.12.3
|
||||
appwrite/appwrite:0.14.2
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
|
@ -118,7 +118,7 @@ docker run -it --rm ,
|
|||
|
||||
### 开发套件
|
||||
|
||||
以下是当前支持的平台和语言列表。如果您想帮助我们为您选择的平台添加支持,您可以访问我们的 [SDK 生成器](https://github.com/appwrite/sdk-generator) 项目并查看我们的 [贡献指南]( https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md)。
|
||||
以下是当前支持的平台和语言列表。如果您想帮助我们为您选择的平台添加支持,您可以访问我们的 [SDK 生成器](https://github.com/appwrite/sdk-generator) 项目并查看我们的 [贡献指南](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md)。
|
||||
|
||||
#### 客户端
|
||||
* ✅ [Web](https://github.com/appwrite/sdk-for-web) (由 Appwrite 团队维护)
|
||||
|
|
@ -168,4 +168,4 @@ Appwrite API 界面层利用后台缓存和任务委派来提供极速的响应
|
|||
|
||||
## 版权说明
|
||||
|
||||
版权详情,访问 [BSD 3-Clause License](./LICENSE)。
|
||||
版权详情,访问 [BSD 3-Clause License](./LICENSE)。
|
||||
|
|
|
|||
16
README.md
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
English | [简体中文](README-CN.md)
|
||||
|
||||
[**Appwrite 0.12 has been released! Learn what's new!**](https://dev.to/appwrite/its-here-announcing-the-release-of-appwrite-012-5c8b)
|
||||
[**Appwrite 0.14 has been released! Learn what's new!**](https://dev.to/appwrite/announcing-appwrite-014-with-11-cloud-function-runtimes-36f5)
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -48,12 +48,12 @@ Table of Contents:
|
|||
- [Security](#security)
|
||||
- [Follow Us](#follow-us)
|
||||
- [License](#license)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Appwrite backend server is designed to run in a container environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool like Kubernetes, Docker Swarm, or Rancher.
|
||||
|
||||
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
|
||||
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
|
||||
|
||||
### Unix
|
||||
|
||||
|
|
@ -62,7 +62,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.12.3
|
||||
appwrite/appwrite:0.14.2
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
|
@ -74,7 +74,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.12.3
|
||||
appwrite/appwrite:0.14.2
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
|
@ -84,7 +84,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.12.3
|
||||
appwrite/appwrite:0.14.2
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -142,8 +142,8 @@ Below is a list of currently supported platforms and languages. If you wish to h
|
|||
* ✅ [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Experimental** (Maintained by the Appwrite Team)
|
||||
|
||||
#### Community
|
||||
* ✅ [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (Maintained by [Michael Gangolf](https://github.com/m1ga/))
|
||||
* ✅ [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (Maintained by [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
* ✅ [Appcelerator Titanium](https://github.com/m1ga/ti.appwrite) (Maintained by [Michael Gangolf](https://github.com/m1ga/))
|
||||
* ✅ [Godot Engine](https://github.com/GodotNuts/appwrite-sdk) (Maintained by [fenix-hub @GodotNuts](https://github.com/fenix-hub))
|
||||
|
||||
Looking for more SDKs? - Help us by contributing a pull request to our [SDK Generator](https://github.com/appwrite/sdk-generator)!
|
||||
|
||||
|
|
|
|||
10
app/cli.php
|
|
@ -1,10 +1,14 @@
|
|||
<?php
|
||||
require_once __DIR__.'/init.php';
|
||||
require_once __DIR__.'/controllers/general.php';
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\CLI;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
Authorization::disable();
|
||||
|
||||
$cli = new CLI();
|
||||
|
||||
|
|
@ -25,4 +29,4 @@ $cli
|
|||
Console::log(App::getEnv('_APP_VERSION', 'UNKNOWN'));
|
||||
});
|
||||
|
||||
$cli->run();
|
||||
$cli->run();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php
|
||||
|
||||
// Auth methods
|
||||
|
||||
|
|
@ -45,4 +45,4 @@ return [
|
|||
'docs' => '',
|
||||
'enabled' => false,
|
||||
],
|
||||
];
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,20 +2,20 @@
|
|||
|
||||
return [
|
||||
// Codes based on: https://github.com/matomo-org/device-detector/blob/master/Parser/Client/Browser.php
|
||||
'aa' => __DIR__.'/browsers/avant.png',
|
||||
'an' => __DIR__.'/browsers/android-webview-beta.png',
|
||||
'ch' => __DIR__.'/browsers/chrome.png',
|
||||
'ci' => __DIR__.'/browsers/chrome.png', //Chrome Mobile iOS
|
||||
'cm' => __DIR__.'/browsers/chrome.png', //Chrome Mobile
|
||||
'cr' => __DIR__.'/browsers/chromium.png',
|
||||
'ff' => __DIR__.'/browsers/firefox.png',
|
||||
'sf' => __DIR__.'/browsers/safari.png',
|
||||
'mf' => __DIR__.'/browsers/safari.png',
|
||||
'ps' => __DIR__.'/browsers/edge.png',
|
||||
'oi' => __DIR__.'/browsers/edge.png',
|
||||
'om' => __DIR__.'/browsers/opera-mini.png',
|
||||
'op' => __DIR__.'/browsers/opera.png',
|
||||
'on' => __DIR__.'/browsers/opera.png',
|
||||
'aa' => __DIR__ . '/browsers/avant.png',
|
||||
'an' => __DIR__ . '/browsers/android-webview-beta.png',
|
||||
'ch' => __DIR__ . '/browsers/chrome.png',
|
||||
'ci' => __DIR__ . '/browsers/chrome.png', //Chrome Mobile iOS
|
||||
'cm' => __DIR__ . '/browsers/chrome.png', //Chrome Mobile
|
||||
'cr' => __DIR__ . '/browsers/chromium.png',
|
||||
'ff' => __DIR__ . '/browsers/firefox.png',
|
||||
'sf' => __DIR__ . '/browsers/safari.png',
|
||||
'mf' => __DIR__ . '/browsers/safari.png',
|
||||
'ps' => __DIR__ . '/browsers/edge.png',
|
||||
'oi' => __DIR__ . '/browsers/edge.png',
|
||||
'om' => __DIR__ . '/browsers/opera-mini.png',
|
||||
'op' => __DIR__ . '/browsers/opera.png',
|
||||
'on' => __DIR__ . '/browsers/opera.png',
|
||||
|
||||
/*
|
||||
'36' => '360 Phone Browser',
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'amex' => __DIR__.'/credit-cards/amex.png',
|
||||
'argencard' => __DIR__.'/credit-cards/argencard.png',
|
||||
'cabal' => __DIR__.'/credit-cards/cabal.png',
|
||||
'censosud' => __DIR__.'/credit-cards/consosud.png',
|
||||
'diners' => __DIR__.'/credit-cards/diners.png',
|
||||
'discover' => __DIR__.'/credit-cards/discover.png',
|
||||
'elo' => __DIR__.'/credit-cards/elo.png',
|
||||
'hipercard' => __DIR__.'/credit-cards/hipercard.png',
|
||||
'jcb' => __DIR__.'/credit-cards/jcb.png',
|
||||
'mastercard' => __DIR__.'/credit-cards/mastercard.png',
|
||||
'naranja' => __DIR__.'/credit-cards/naranja.png',
|
||||
'targeta-shopping' => __DIR__.'/credit-cards/tarjeta-shopping.png',
|
||||
'union-china-pay' => __DIR__.'/credit-cards/union-china-pay.png',
|
||||
'visa' => __DIR__.'/credit-cards/visa.png',
|
||||
'mir' => __DIR__.'/credit-cards/mir.png',
|
||||
'maestro' => __DIR__.'/credit-cards/maestro.png',
|
||||
'amex' => __DIR__ . '/credit-cards/amex.png',
|
||||
'argencard' => __DIR__ . '/credit-cards/argencard.png',
|
||||
'cabal' => __DIR__ . '/credit-cards/cabal.png',
|
||||
'censosud' => __DIR__ . '/credit-cards/consosud.png',
|
||||
'diners' => __DIR__ . '/credit-cards/diners.png',
|
||||
'discover' => __DIR__ . '/credit-cards/discover.png',
|
||||
'elo' => __DIR__ . '/credit-cards/elo.png',
|
||||
'hipercard' => __DIR__ . '/credit-cards/hipercard.png',
|
||||
'jcb' => __DIR__ . '/credit-cards/jcb.png',
|
||||
'mastercard' => __DIR__ . '/credit-cards/mastercard.png',
|
||||
'naranja' => __DIR__ . '/credit-cards/naranja.png',
|
||||
'targeta-shopping' => __DIR__ . '/credit-cards/tarjeta-shopping.png',
|
||||
'union-china-pay' => __DIR__ . '/credit-cards/union-china-pay.png',
|
||||
'visa' => __DIR__ . '/credit-cards/visa.png',
|
||||
'mir' => __DIR__ . '/credit-cards/mir.png',
|
||||
'maestro' => __DIR__ . '/credit-cards/maestro.png',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,198 +1,198 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'af' => __DIR__.'/flags/af.png',
|
||||
'ao' => __DIR__.'/flags/ao.png',
|
||||
'al' => __DIR__.'/flags/al.png',
|
||||
'ad' => __DIR__.'/flags/ad.png',
|
||||
'ae' => __DIR__.'/flags/ae.png',
|
||||
'ar' => __DIR__.'/flags/ar.png',
|
||||
'am' => __DIR__.'/flags/am.png',
|
||||
'ag' => __DIR__.'/flags/ag.png',
|
||||
'au' => __DIR__.'/flags/au.png',
|
||||
'at' => __DIR__.'/flags/at.png',
|
||||
'az' => __DIR__.'/flags/az.png',
|
||||
'bi' => __DIR__.'/flags/bi.png',
|
||||
'be' => __DIR__.'/flags/be.png',
|
||||
'bj' => __DIR__.'/flags/bj.png',
|
||||
'bf' => __DIR__.'/flags/bf.png',
|
||||
'bd' => __DIR__.'/flags/bd.png',
|
||||
'bg' => __DIR__.'/flags/bg.png',
|
||||
'bh' => __DIR__.'/flags/bh.png',
|
||||
'bs' => __DIR__.'/flags/bs.png',
|
||||
'ba' => __DIR__.'/flags/ba.png',
|
||||
'by' => __DIR__.'/flags/by.png',
|
||||
'bz' => __DIR__.'/flags/bz.png',
|
||||
'bo' => __DIR__.'/flags/bo.png',
|
||||
'br' => __DIR__.'/flags/br.png',
|
||||
'bb' => __DIR__.'/flags/bb.png',
|
||||
'bn' => __DIR__.'/flags/bn.png',
|
||||
'bt' => __DIR__.'/flags/bt.png',
|
||||
'bw' => __DIR__.'/flags/bw.png',
|
||||
'cf' => __DIR__.'/flags/cf.png',
|
||||
'ca' => __DIR__.'/flags/ca.png',
|
||||
'ch' => __DIR__.'/flags/ch.png',
|
||||
'cl' => __DIR__.'/flags/cl.png',
|
||||
'cn' => __DIR__.'/flags/cn.png',
|
||||
'ci' => __DIR__.'/flags/ci.png',
|
||||
'cm' => __DIR__.'/flags/cm.png',
|
||||
'cd' => __DIR__.'/flags/cd.png',
|
||||
'cg' => __DIR__.'/flags/cg.png',
|
||||
'co' => __DIR__.'/flags/co.png',
|
||||
'km' => __DIR__.'/flags/km.png',
|
||||
'cv' => __DIR__.'/flags/cv.png',
|
||||
'cr' => __DIR__.'/flags/cr.png',
|
||||
'cu' => __DIR__.'/flags/cu.png',
|
||||
'cy' => __DIR__.'/flags/cy.png',
|
||||
'cz' => __DIR__.'/flags/cz.png',
|
||||
'de' => __DIR__.'/flags/de.png',
|
||||
'dj' => __DIR__.'/flags/dj.png',
|
||||
'dm' => __DIR__.'/flags/dm.png',
|
||||
'dk' => __DIR__.'/flags/dk.png',
|
||||
'do' => __DIR__.'/flags/do.png',
|
||||
'dz' => __DIR__.'/flags/dz.png',
|
||||
'ec' => __DIR__.'/flags/ec.png',
|
||||
'eg' => __DIR__.'/flags/eg.png',
|
||||
'er' => __DIR__.'/flags/er.png',
|
||||
'es' => __DIR__.'/flags/es.png',
|
||||
'ee' => __DIR__.'/flags/ee.png',
|
||||
'et' => __DIR__.'/flags/et.png',
|
||||
'fi' => __DIR__.'/flags/fi.png',
|
||||
'fj' => __DIR__.'/flags/fj.png',
|
||||
'fr' => __DIR__.'/flags/fr.png',
|
||||
'fm' => __DIR__.'/flags/fm.png',
|
||||
'ga' => __DIR__.'/flags/ga.png',
|
||||
'gb' => __DIR__.'/flags/gb.png',
|
||||
'ge' => __DIR__.'/flags/ge.png',
|
||||
'gh' => __DIR__.'/flags/gh.png',
|
||||
'gn' => __DIR__.'/flags/gn.png',
|
||||
'gm' => __DIR__.'/flags/gm.png',
|
||||
'gw' => __DIR__.'/flags/gw.png',
|
||||
'gq' => __DIR__.'/flags/gq.png',
|
||||
'gr' => __DIR__.'/flags/gr.png',
|
||||
'gd' => __DIR__.'/flags/gd.png',
|
||||
'gt' => __DIR__.'/flags/gt.png',
|
||||
'gy' => __DIR__.'/flags/gy.png',
|
||||
'hn' => __DIR__.'/flags/hn.png',
|
||||
'hr' => __DIR__.'/flags/hr.png',
|
||||
'ht' => __DIR__.'/flags/ht.png',
|
||||
'hu' => __DIR__.'/flags/hu.png',
|
||||
'id' => __DIR__.'/flags/id.png',
|
||||
'in' => __DIR__.'/flags/in.png',
|
||||
'ie' => __DIR__.'/flags/ie.png',
|
||||
'ir' => __DIR__.'/flags/ir.png',
|
||||
'iq' => __DIR__.'/flags/iq.png',
|
||||
'is' => __DIR__.'/flags/is.png',
|
||||
'il' => __DIR__.'/flags/il.png',
|
||||
'it' => __DIR__.'/flags/it.png',
|
||||
'jm' => __DIR__.'/flags/jm.png',
|
||||
'jo' => __DIR__.'/flags/jo.png',
|
||||
'jp' => __DIR__.'/flags/jp.png',
|
||||
'kz' => __DIR__.'/flags/kz.png',
|
||||
'ke' => __DIR__.'/flags/ke.png',
|
||||
'kg' => __DIR__.'/flags/kg.png',
|
||||
'kh' => __DIR__.'/flags/kh.png',
|
||||
'ki' => __DIR__.'/flags/ki.png',
|
||||
'kn' => __DIR__.'/flags/kn.png',
|
||||
'kr' => __DIR__.'/flags/kr.png',
|
||||
'kw' => __DIR__.'/flags/kw.png',
|
||||
'la' => __DIR__.'/flags/la.png',
|
||||
'lb' => __DIR__.'/flags/lb.png',
|
||||
'lr' => __DIR__.'/flags/lr.png',
|
||||
'ly' => __DIR__.'/flags/ly.png',
|
||||
'lc' => __DIR__.'/flags/lc.png',
|
||||
'li' => __DIR__.'/flags/li.png',
|
||||
'lk' => __DIR__.'/flags/lk.png',
|
||||
'ls' => __DIR__.'/flags/ls.png',
|
||||
'lt' => __DIR__.'/flags/ls.png',
|
||||
'lu' => __DIR__.'/flags/lu.png',
|
||||
'lv' => __DIR__.'/flags/lv.png',
|
||||
'ma' => __DIR__.'/flags/ma.png',
|
||||
'mc' => __DIR__.'/flags/mc.png',
|
||||
'md' => __DIR__.'/flags/md.png',
|
||||
'mg' => __DIR__.'/flags/mg.png',
|
||||
'mv' => __DIR__.'/flags/mv.png',
|
||||
'mx' => __DIR__.'/flags/mx.png',
|
||||
'mh' => __DIR__.'/flags/mh.png',
|
||||
'mk' => __DIR__.'/flags/mk.png',
|
||||
'ml' => __DIR__.'/flags/ml.png',
|
||||
'mt' => __DIR__.'/flags/mt.png',
|
||||
'mm' => __DIR__.'/flags/mm.png',
|
||||
'me' => __DIR__.'/flags/me.png',
|
||||
'mn' => __DIR__.'/flags/mn.png',
|
||||
'mz' => __DIR__.'/flags/mz.png',
|
||||
'mr' => __DIR__.'/flags/mr.png',
|
||||
'mu' => __DIR__.'/flags/mu.png',
|
||||
'mw' => __DIR__.'/flags/mw.png',
|
||||
'my' => __DIR__.'/flags/my.png',
|
||||
'na' => __DIR__.'/flags/na.png',
|
||||
'ne' => __DIR__.'/flags/ne.png',
|
||||
'ng' => __DIR__.'/flags/ng.png',
|
||||
'ni' => __DIR__.'/flags/ni.png',
|
||||
'nl' => __DIR__.'/flags/nl.png',
|
||||
'no' => __DIR__.'/flags/no.png',
|
||||
'np' => __DIR__.'/flags/np.png',
|
||||
'nr' => __DIR__.'/flags/nr.png',
|
||||
'nz' => __DIR__.'/flags/nz.png',
|
||||
'om' => __DIR__.'/flags/om.png',
|
||||
'pk' => __DIR__.'/flags/pk.png',
|
||||
'pa' => __DIR__.'/flags/pa.png',
|
||||
'pe' => __DIR__.'/flags/pe.png',
|
||||
'ph' => __DIR__.'/flags/ph.png',
|
||||
'pw' => __DIR__.'/flags/pw.png',
|
||||
'pg' => __DIR__.'/flags/pg.png',
|
||||
'pl' => __DIR__.'/flags/pl.png',
|
||||
'kp' => __DIR__.'/flags/kp.png',
|
||||
'pt' => __DIR__.'/flags/pt.png',
|
||||
'py' => __DIR__.'/flags/py.png',
|
||||
'qa' => __DIR__.'/flags/qa.png',
|
||||
'ro' => __DIR__.'/flags/ro.png',
|
||||
'ru' => __DIR__.'/flags/ru.png',
|
||||
'rw' => __DIR__.'/flags/rw.png',
|
||||
'sa' => __DIR__.'/flags/sa.png',
|
||||
'sd' => __DIR__.'/flags/sd.png',
|
||||
'sn' => __DIR__.'/flags/sn.png',
|
||||
'sg' => __DIR__.'/flags/sg.png',
|
||||
'sb' => __DIR__.'/flags/sb.png',
|
||||
'sl' => __DIR__.'/flags/sl.png',
|
||||
'sv' => __DIR__.'/flags/sv.png',
|
||||
'sm' => __DIR__.'/flags/sm.png',
|
||||
'so' => __DIR__.'/flags/so.png',
|
||||
'rs' => __DIR__.'/flags/rs.png',
|
||||
'ss' => __DIR__.'/flags/ss.png',
|
||||
'st' => __DIR__.'/flags/st.png',
|
||||
'sr' => __DIR__.'/flags/sr.png',
|
||||
'sk' => __DIR__.'/flags/sk.png',
|
||||
'si' => __DIR__.'/flags/si.png',
|
||||
'se' => __DIR__.'/flags/se.png',
|
||||
'sz' => __DIR__.'/flags/sz.png',
|
||||
'sc' => __DIR__.'/flags/sc.png',
|
||||
'sy' => __DIR__.'/flags/sy.png',
|
||||
'td' => __DIR__.'/flags/td.png',
|
||||
'tg' => __DIR__.'/flags/tg.png',
|
||||
'th' => __DIR__.'/flags/th.png',
|
||||
'tj' => __DIR__.'/flags/tj.png',
|
||||
'tm' => __DIR__.'/flags/tm.png',
|
||||
'tl' => __DIR__.'/flags/tl.png',
|
||||
'to' => __DIR__.'/flags/to.png',
|
||||
'tt' => __DIR__.'/flags/tt.png',
|
||||
'tn' => __DIR__.'/flags/tn.png',
|
||||
'tr' => __DIR__.'/flags/tr.png',
|
||||
'tv' => __DIR__.'/flags/tv.png',
|
||||
'tz' => __DIR__.'/flags/tz.png',
|
||||
'ug' => __DIR__.'/flags/ug.png',
|
||||
'ua' => __DIR__.'/flags/ua.png',
|
||||
'uy' => __DIR__.'/flags/uy.png',
|
||||
'us' => __DIR__.'/flags/us.png',
|
||||
'uz' => __DIR__.'/flags/uz.png',
|
||||
'va' => __DIR__.'/flags/va.png',
|
||||
'vc' => __DIR__.'/flags/vc.png',
|
||||
've' => __DIR__.'/flags/ve.png',
|
||||
'vn' => __DIR__.'/flags/vn.png',
|
||||
'vu' => __DIR__.'/flags/vu.png',
|
||||
'ws' => __DIR__.'/flags/ws.png',
|
||||
'ye' => __DIR__.'/flags/ye.png',
|
||||
'za' => __DIR__.'/flags/za.png',
|
||||
'zm' => __DIR__.'/flags/zm.png',
|
||||
'zw' => __DIR__.'/flags/zw.png',
|
||||
'af' => __DIR__ . '/flags/af.png',
|
||||
'ao' => __DIR__ . '/flags/ao.png',
|
||||
'al' => __DIR__ . '/flags/al.png',
|
||||
'ad' => __DIR__ . '/flags/ad.png',
|
||||
'ae' => __DIR__ . '/flags/ae.png',
|
||||
'ar' => __DIR__ . '/flags/ar.png',
|
||||
'am' => __DIR__ . '/flags/am.png',
|
||||
'ag' => __DIR__ . '/flags/ag.png',
|
||||
'au' => __DIR__ . '/flags/au.png',
|
||||
'at' => __DIR__ . '/flags/at.png',
|
||||
'az' => __DIR__ . '/flags/az.png',
|
||||
'bi' => __DIR__ . '/flags/bi.png',
|
||||
'be' => __DIR__ . '/flags/be.png',
|
||||
'bj' => __DIR__ . '/flags/bj.png',
|
||||
'bf' => __DIR__ . '/flags/bf.png',
|
||||
'bd' => __DIR__ . '/flags/bd.png',
|
||||
'bg' => __DIR__ . '/flags/bg.png',
|
||||
'bh' => __DIR__ . '/flags/bh.png',
|
||||
'bs' => __DIR__ . '/flags/bs.png',
|
||||
'ba' => __DIR__ . '/flags/ba.png',
|
||||
'by' => __DIR__ . '/flags/by.png',
|
||||
'bz' => __DIR__ . '/flags/bz.png',
|
||||
'bo' => __DIR__ . '/flags/bo.png',
|
||||
'br' => __DIR__ . '/flags/br.png',
|
||||
'bb' => __DIR__ . '/flags/bb.png',
|
||||
'bn' => __DIR__ . '/flags/bn.png',
|
||||
'bt' => __DIR__ . '/flags/bt.png',
|
||||
'bw' => __DIR__ . '/flags/bw.png',
|
||||
'cf' => __DIR__ . '/flags/cf.png',
|
||||
'ca' => __DIR__ . '/flags/ca.png',
|
||||
'ch' => __DIR__ . '/flags/ch.png',
|
||||
'cl' => __DIR__ . '/flags/cl.png',
|
||||
'cn' => __DIR__ . '/flags/cn.png',
|
||||
'ci' => __DIR__ . '/flags/ci.png',
|
||||
'cm' => __DIR__ . '/flags/cm.png',
|
||||
'cd' => __DIR__ . '/flags/cd.png',
|
||||
'cg' => __DIR__ . '/flags/cg.png',
|
||||
'co' => __DIR__ . '/flags/co.png',
|
||||
'km' => __DIR__ . '/flags/km.png',
|
||||
'cv' => __DIR__ . '/flags/cv.png',
|
||||
'cr' => __DIR__ . '/flags/cr.png',
|
||||
'cu' => __DIR__ . '/flags/cu.png',
|
||||
'cy' => __DIR__ . '/flags/cy.png',
|
||||
'cz' => __DIR__ . '/flags/cz.png',
|
||||
'de' => __DIR__ . '/flags/de.png',
|
||||
'dj' => __DIR__ . '/flags/dj.png',
|
||||
'dm' => __DIR__ . '/flags/dm.png',
|
||||
'dk' => __DIR__ . '/flags/dk.png',
|
||||
'do' => __DIR__ . '/flags/do.png',
|
||||
'dz' => __DIR__ . '/flags/dz.png',
|
||||
'ec' => __DIR__ . '/flags/ec.png',
|
||||
'eg' => __DIR__ . '/flags/eg.png',
|
||||
'er' => __DIR__ . '/flags/er.png',
|
||||
'es' => __DIR__ . '/flags/es.png',
|
||||
'ee' => __DIR__ . '/flags/ee.png',
|
||||
'et' => __DIR__ . '/flags/et.png',
|
||||
'fi' => __DIR__ . '/flags/fi.png',
|
||||
'fj' => __DIR__ . '/flags/fj.png',
|
||||
'fr' => __DIR__ . '/flags/fr.png',
|
||||
'fm' => __DIR__ . '/flags/fm.png',
|
||||
'ga' => __DIR__ . '/flags/ga.png',
|
||||
'gb' => __DIR__ . '/flags/gb.png',
|
||||
'ge' => __DIR__ . '/flags/ge.png',
|
||||
'gh' => __DIR__ . '/flags/gh.png',
|
||||
'gn' => __DIR__ . '/flags/gn.png',
|
||||
'gm' => __DIR__ . '/flags/gm.png',
|
||||
'gw' => __DIR__ . '/flags/gw.png',
|
||||
'gq' => __DIR__ . '/flags/gq.png',
|
||||
'gr' => __DIR__ . '/flags/gr.png',
|
||||
'gd' => __DIR__ . '/flags/gd.png',
|
||||
'gt' => __DIR__ . '/flags/gt.png',
|
||||
'gy' => __DIR__ . '/flags/gy.png',
|
||||
'hn' => __DIR__ . '/flags/hn.png',
|
||||
'hr' => __DIR__ . '/flags/hr.png',
|
||||
'ht' => __DIR__ . '/flags/ht.png',
|
||||
'hu' => __DIR__ . '/flags/hu.png',
|
||||
'id' => __DIR__ . '/flags/id.png',
|
||||
'in' => __DIR__ . '/flags/in.png',
|
||||
'ie' => __DIR__ . '/flags/ie.png',
|
||||
'ir' => __DIR__ . '/flags/ir.png',
|
||||
'iq' => __DIR__ . '/flags/iq.png',
|
||||
'is' => __DIR__ . '/flags/is.png',
|
||||
'il' => __DIR__ . '/flags/il.png',
|
||||
'it' => __DIR__ . '/flags/it.png',
|
||||
'jm' => __DIR__ . '/flags/jm.png',
|
||||
'jo' => __DIR__ . '/flags/jo.png',
|
||||
'jp' => __DIR__ . '/flags/jp.png',
|
||||
'kz' => __DIR__ . '/flags/kz.png',
|
||||
'ke' => __DIR__ . '/flags/ke.png',
|
||||
'kg' => __DIR__ . '/flags/kg.png',
|
||||
'kh' => __DIR__ . '/flags/kh.png',
|
||||
'ki' => __DIR__ . '/flags/ki.png',
|
||||
'kn' => __DIR__ . '/flags/kn.png',
|
||||
'kr' => __DIR__ . '/flags/kr.png',
|
||||
'kw' => __DIR__ . '/flags/kw.png',
|
||||
'la' => __DIR__ . '/flags/la.png',
|
||||
'lb' => __DIR__ . '/flags/lb.png',
|
||||
'lr' => __DIR__ . '/flags/lr.png',
|
||||
'ly' => __DIR__ . '/flags/ly.png',
|
||||
'lc' => __DIR__ . '/flags/lc.png',
|
||||
'li' => __DIR__ . '/flags/li.png',
|
||||
'lk' => __DIR__ . '/flags/lk.png',
|
||||
'ls' => __DIR__ . '/flags/ls.png',
|
||||
'lt' => __DIR__ . '/flags/ls.png',
|
||||
'lu' => __DIR__ . '/flags/lu.png',
|
||||
'lv' => __DIR__ . '/flags/lv.png',
|
||||
'ma' => __DIR__ . '/flags/ma.png',
|
||||
'mc' => __DIR__ . '/flags/mc.png',
|
||||
'md' => __DIR__ . '/flags/md.png',
|
||||
'mg' => __DIR__ . '/flags/mg.png',
|
||||
'mv' => __DIR__ . '/flags/mv.png',
|
||||
'mx' => __DIR__ . '/flags/mx.png',
|
||||
'mh' => __DIR__ . '/flags/mh.png',
|
||||
'mk' => __DIR__ . '/flags/mk.png',
|
||||
'ml' => __DIR__ . '/flags/ml.png',
|
||||
'mt' => __DIR__ . '/flags/mt.png',
|
||||
'mm' => __DIR__ . '/flags/mm.png',
|
||||
'me' => __DIR__ . '/flags/me.png',
|
||||
'mn' => __DIR__ . '/flags/mn.png',
|
||||
'mz' => __DIR__ . '/flags/mz.png',
|
||||
'mr' => __DIR__ . '/flags/mr.png',
|
||||
'mu' => __DIR__ . '/flags/mu.png',
|
||||
'mw' => __DIR__ . '/flags/mw.png',
|
||||
'my' => __DIR__ . '/flags/my.png',
|
||||
'na' => __DIR__ . '/flags/na.png',
|
||||
'ne' => __DIR__ . '/flags/ne.png',
|
||||
'ng' => __DIR__ . '/flags/ng.png',
|
||||
'ni' => __DIR__ . '/flags/ni.png',
|
||||
'nl' => __DIR__ . '/flags/nl.png',
|
||||
'no' => __DIR__ . '/flags/no.png',
|
||||
'np' => __DIR__ . '/flags/np.png',
|
||||
'nr' => __DIR__ . '/flags/nr.png',
|
||||
'nz' => __DIR__ . '/flags/nz.png',
|
||||
'om' => __DIR__ . '/flags/om.png',
|
||||
'pk' => __DIR__ . '/flags/pk.png',
|
||||
'pa' => __DIR__ . '/flags/pa.png',
|
||||
'pe' => __DIR__ . '/flags/pe.png',
|
||||
'ph' => __DIR__ . '/flags/ph.png',
|
||||
'pw' => __DIR__ . '/flags/pw.png',
|
||||
'pg' => __DIR__ . '/flags/pg.png',
|
||||
'pl' => __DIR__ . '/flags/pl.png',
|
||||
'kp' => __DIR__ . '/flags/kp.png',
|
||||
'pt' => __DIR__ . '/flags/pt.png',
|
||||
'py' => __DIR__ . '/flags/py.png',
|
||||
'qa' => __DIR__ . '/flags/qa.png',
|
||||
'ro' => __DIR__ . '/flags/ro.png',
|
||||
'ru' => __DIR__ . '/flags/ru.png',
|
||||
'rw' => __DIR__ . '/flags/rw.png',
|
||||
'sa' => __DIR__ . '/flags/sa.png',
|
||||
'sd' => __DIR__ . '/flags/sd.png',
|
||||
'sn' => __DIR__ . '/flags/sn.png',
|
||||
'sg' => __DIR__ . '/flags/sg.png',
|
||||
'sb' => __DIR__ . '/flags/sb.png',
|
||||
'sl' => __DIR__ . '/flags/sl.png',
|
||||
'sv' => __DIR__ . '/flags/sv.png',
|
||||
'sm' => __DIR__ . '/flags/sm.png',
|
||||
'so' => __DIR__ . '/flags/so.png',
|
||||
'rs' => __DIR__ . '/flags/rs.png',
|
||||
'ss' => __DIR__ . '/flags/ss.png',
|
||||
'st' => __DIR__ . '/flags/st.png',
|
||||
'sr' => __DIR__ . '/flags/sr.png',
|
||||
'sk' => __DIR__ . '/flags/sk.png',
|
||||
'si' => __DIR__ . '/flags/si.png',
|
||||
'se' => __DIR__ . '/flags/se.png',
|
||||
'sz' => __DIR__ . '/flags/sz.png',
|
||||
'sc' => __DIR__ . '/flags/sc.png',
|
||||
'sy' => __DIR__ . '/flags/sy.png',
|
||||
'td' => __DIR__ . '/flags/td.png',
|
||||
'tg' => __DIR__ . '/flags/tg.png',
|
||||
'th' => __DIR__ . '/flags/th.png',
|
||||
'tj' => __DIR__ . '/flags/tj.png',
|
||||
'tm' => __DIR__ . '/flags/tm.png',
|
||||
'tl' => __DIR__ . '/flags/tl.png',
|
||||
'to' => __DIR__ . '/flags/to.png',
|
||||
'tt' => __DIR__ . '/flags/tt.png',
|
||||
'tn' => __DIR__ . '/flags/tn.png',
|
||||
'tr' => __DIR__ . '/flags/tr.png',
|
||||
'tv' => __DIR__ . '/flags/tv.png',
|
||||
'tz' => __DIR__ . '/flags/tz.png',
|
||||
'ug' => __DIR__ . '/flags/ug.png',
|
||||
'ua' => __DIR__ . '/flags/ua.png',
|
||||
'uy' => __DIR__ . '/flags/uy.png',
|
||||
'us' => __DIR__ . '/flags/us.png',
|
||||
'uz' => __DIR__ . '/flags/uz.png',
|
||||
'va' => __DIR__ . '/flags/va.png',
|
||||
'vc' => __DIR__ . '/flags/vc.png',
|
||||
've' => __DIR__ . '/flags/ve.png',
|
||||
'vn' => __DIR__ . '/flags/vn.png',
|
||||
'vu' => __DIR__ . '/flags/vu.png',
|
||||
'ws' => __DIR__ . '/flags/ws.png',
|
||||
'ye' => __DIR__ . '/flags/ye.png',
|
||||
'za' => __DIR__ . '/flags/za.png',
|
||||
'zm' => __DIR__ . '/flags/zm.png',
|
||||
'zw' => __DIR__ . '/flags/zw.png',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
return [
|
||||
// Codes based on: https://github.com/matomo-org/device-detector/blob/master/Parser/Client/Browser.php
|
||||
'AND' => __DIR__.'/os/android.png',
|
||||
'ATV' => __DIR__.'/os/apple-tv.png',
|
||||
'COS' => __DIR__.'/os/chrome-os.png',
|
||||
'AND' => __DIR__ . '/os/android.png',
|
||||
'ATV' => __DIR__ . '/os/apple-tv.png',
|
||||
'COS' => __DIR__ . '/os/chrome-os.png',
|
||||
|
||||
/*
|
||||
'AIX' => 'AIX',
|
||||
|
|
|
|||
500
app/config/errors.php
Normal file
|
|
@ -0,0 +1,500 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* List of server wide error codes and their respective messages.
|
||||
*/
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
|
||||
return [
|
||||
/** General Errors */
|
||||
Exception::GENERAL_UNKNOWN => [
|
||||
'name' => Exception::GENERAL_UNKNOWN,
|
||||
'description' => 'An unknown error has occured. Please check the logs for more information.',
|
||||
'code' => 500,
|
||||
],
|
||||
Exception::GENERAL_MOCK => [
|
||||
'name' => Exception::GENERAL_MOCK,
|
||||
'description' => 'General errors thrown by the mock controller used for testing.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GENERAL_ACCESS_FORBIDDEN => [
|
||||
'name' => Exception::GENERAL_ACCESS_FORBIDDEN,
|
||||
'description' => 'Access to this API is forbidden.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::GENERAL_UNKNOWN_ORIGIN => [
|
||||
'name' => Exception::GENERAL_UNKNOWN_ORIGIN,
|
||||
'description' => 'The request originated from an unknown origin. If you trust this domain, please list it as a trusted platform in the Appwrite console.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::GENERAL_SERVICE_DISABLED => [
|
||||
'name' => Exception::GENERAL_SERVICE_DISABLED,
|
||||
'description' => 'The requested service is disabled. You can enable the service from the Appwrite console.',
|
||||
'code' => 503,
|
||||
],
|
||||
Exception::GENERAL_UNAUTHORIZED_SCOPE => [
|
||||
'name' => Exception::GENERAL_UNAUTHORIZED_SCOPE,
|
||||
'description' => 'The current user or API key does not have the required scopes to access the requested resource.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::GENERAL_RATE_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::GENERAL_RATE_LIMIT_EXCEEDED,
|
||||
'description' => 'Rate limit for the current endpoint has been exceeded. Please try again after some time.',
|
||||
'code' => 429,
|
||||
],
|
||||
Exception::GENERAL_SMTP_DISABLED => [
|
||||
'name' => Exception::GENERAL_SMTP_DISABLED,
|
||||
'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_ARGUMENT_INVALID => [
|
||||
'name' => Exception::GENERAL_ARGUMENT_INVALID,
|
||||
'description' => 'The request contains one or more invalid arguments. Please refer to the endpoint documentation.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GENERAL_QUERY_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::GENERAL_QUERY_LIMIT_EXCEEDED,
|
||||
'description' => 'Query limit exceeded for the current attribute. Usage of more than 100 query values on a single attribute is prohibited.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GENERAL_QUERY_INVALID => [
|
||||
'name' => Exception::GENERAL_QUERY_INVALID,
|
||||
'description' => 'The query\'s syntax is invalid. Please check the query and try again.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GENERAL_ROUTE_NOT_FOUND => [
|
||||
'name' => Exception::GENERAL_ROUTE_NOT_FOUND,
|
||||
'description' => 'The requested route was not found. Please refer to the docs and try again.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::GENERAL_CURSOR_NOT_FOUND => [
|
||||
'name' => Exception::GENERAL_CURSOR_NOT_FOUND,
|
||||
'description' => 'The cursor is invalid. This can happen if the item represented by the cursor has been deleted.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GENERAL_SERVER_ERROR => [
|
||||
'name' => Exception::GENERAL_SERVER_ERROR,
|
||||
'description' => 'An internal server error occurred.',
|
||||
'code' => 500,
|
||||
],
|
||||
Exception::GENERAL_PROTOCOL_UNSUPPORTED => [
|
||||
'name' => Exception::GENERAL_PROTOCOL_UNSUPPORTED,
|
||||
'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
|
||||
'code' => 500,
|
||||
],
|
||||
|
||||
/** User Errors */
|
||||
Exception::USER_COUNT_EXCEEDED => [
|
||||
'name' => Exception::USER_COUNT_EXCEEDED,
|
||||
'description' => 'The current project has exceeded the maximum number of users. Please check your user limit in the Appwrite console.',
|
||||
'code' => 501,
|
||||
],
|
||||
Exception::USER_JWT_INVALID => [
|
||||
'name' => Exception::USER_JWT_INVALID,
|
||||
'description' => 'The JWT token is invalid. Please check the value of the X-Appwrite-JWT header to ensure the correct token is being used.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_ALREADY_EXISTS,
|
||||
'description' => 'A user with the same email ID already exists in your project.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::USER_BLOCKED => [
|
||||
'name' => Exception::USER_BLOCKED,
|
||||
'description' => 'The current user has been blocked. You can unblock the user from the Appwrite console.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_INVALID_TOKEN => [
|
||||
'name' => Exception::USER_INVALID_TOKEN,
|
||||
'description' => 'Invalid token passed in the request.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_PASSWORD_RESET_REQUIRED => [
|
||||
'name' => Exception::USER_PASSWORD_RESET_REQUIRED,
|
||||
'description' => 'The current user requires a password reset.',
|
||||
'code' => 412,
|
||||
],
|
||||
Exception::USER_EMAIL_NOT_WHITELISTED => [
|
||||
'name' => Exception::USER_EMAIL_NOT_WHITELISTED,
|
||||
'description' => 'The user\'s email is not part of the whitelist. Please check the _APP_CONSOLE_WHITELIST_EMAILS environment variable of your Appwrite server.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_IP_NOT_WHITELISTED => [
|
||||
'name' => Exception::USER_IP_NOT_WHITELISTED,
|
||||
'description' => 'The user\'s IP address is not part of the whitelist. Please check the _APP_CONSOLE_WHITELIST_IPS environment variable of your Appwrite server.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_INVALID_CREDENTIALS => [
|
||||
'name' => Exception::USER_INVALID_CREDENTIALS,
|
||||
'description' => 'Invalid credentials. Please check the email and password.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_ANONYMOUS_CONSOLE_PROHIBITED => [
|
||||
'name' => Exception::USER_ANONYMOUS_CONSOLE_PROHIBITED,
|
||||
'description' => 'Anonymous users cannot be created for the console project.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_SESSION_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_SESSION_ALREADY_EXISTS,
|
||||
'description' => 'Creation of anonymous users is prohibited when a session is active.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_NOT_FOUND => [
|
||||
'name' => Exception::USER_NOT_FOUND,
|
||||
'description' => 'User with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::USER_EMAIL_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_EMAIL_ALREADY_EXISTS,
|
||||
'description' => 'Another user with the same email already exists in the current project.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::USER_PASSWORD_MISMATCH => [
|
||||
'name' => Exception::USER_PASSWORD_MISMATCH,
|
||||
'description' => 'Passwords do not match. Please check the password and confirm password.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_SESSION_NOT_FOUND => [
|
||||
'name' => Exception::USER_SESSION_NOT_FOUND,
|
||||
'description' => 'The current user session could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::USER_UNAUTHORIZED => [
|
||||
'name' => Exception::USER_UNAUTHORIZED,
|
||||
'description' => 'The current user is not authorized to perform the requested action.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_AUTH_METHOD_UNSUPPORTED => [
|
||||
'name' => Exception::USER_AUTH_METHOD_UNSUPPORTED,
|
||||
'description' => 'The requested authentication method is either disabled or unsupported. Please check the supported authentication methods in the Appwrite console.',
|
||||
'code' => 501,
|
||||
],
|
||||
|
||||
/** Teams */
|
||||
Exception::TEAM_NOT_FOUND => [
|
||||
'name' => Exception::TEAM_NOT_FOUND,
|
||||
'description' => 'Team with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TEAM_INVITE_ALREADY_EXISTS => [
|
||||
'name' => Exception::TEAM_INVITE_ALREADY_EXISTS,
|
||||
'description' => 'The current user has already received an invitation to join the team.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::TEAM_INVITE_NOT_FOUND => [
|
||||
'name' => Exception::TEAM_INVITE_NOT_FOUND,
|
||||
'description' => 'The requested team invitation could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TEAM_INVALID_SECRET => [
|
||||
'name' => Exception::TEAM_INVALID_SECRET,
|
||||
'description' => 'The team invitation secret is invalid.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::TEAM_MEMBERSHIP_MISMATCH => [
|
||||
'name' => Exception::TEAM_MEMBERSHIP_MISMATCH,
|
||||
'description' => 'The membership ID does not belong to the team ID.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TEAM_INVITE_MISMATCH => [
|
||||
'name' => Exception::TEAM_INVITE_MISMATCH,
|
||||
'description' => 'The invite does not belong to the current user.',
|
||||
'code' => 401,
|
||||
],
|
||||
|
||||
|
||||
/** Membership */
|
||||
Exception::MEMBERSHIP_NOT_FOUND => [
|
||||
'name' => Exception::MEMBERSHIP_NOT_FOUND,
|
||||
'description' => 'Membership with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Avatars */
|
||||
Exception::AVATAR_SET_NOT_FOUND => [
|
||||
'name' => Exception::AVATAR_SET_NOT_FOUND,
|
||||
'description' => 'The requested avatar set could not be found.',
|
||||
'code' => 404
|
||||
],
|
||||
Exception::AVATAR_NOT_FOUND => [
|
||||
'name' => Exception::AVATAR_NOT_FOUND,
|
||||
'description' => 'The request avatar could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::AVATAR_IMAGE_NOT_FOUND => [
|
||||
'name' => Exception::AVATAR_IMAGE_NOT_FOUND,
|
||||
'description' => 'The requested image was not found at the URL.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::AVATAR_REMOTE_URL_FAILED => [
|
||||
'name' => Exception::AVATAR_REMOTE_URL_FAILED,
|
||||
'description' => 'Failed to fetch favicon from the requested URL.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::AVATAR_ICON_NOT_FOUND => [
|
||||
'name' => Exception::AVATAR_ICON_NOT_FOUND,
|
||||
'description' => 'The requested favicon could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Storage */
|
||||
Exception::STORAGE_FILE_NOT_FOUND => [
|
||||
'name' => Exception::STORAGE_FILE_NOT_FOUND,
|
||||
'description' => 'The requested file could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::STORAGE_DEVICE_NOT_FOUND => [
|
||||
'name' => Exception::STORAGE_DEVICE_NOT_FOUND,
|
||||
'description' => 'The requested storage device could not be found.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::STORAGE_FILE_EMPTY => [
|
||||
'name' => Exception::STORAGE_FILE_EMPTY,
|
||||
'description' => 'Empty file passed to the endpoint.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::STORAGE_FILE_TYPE_UNSUPPORTED => [
|
||||
'name' => Exception::STORAGE_FILE_TYPE_UNSUPPORTED,
|
||||
'description' => 'The file type is not supported.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::STORAGE_INVALID_FILE_SIZE => [
|
||||
'name' => Exception::STORAGE_INVALID_FILE_SIZE,
|
||||
'description' => 'The file size is either not valid or exceeds the maximum allowed size. Please check the file or the value of the _APP_STORAGE_LIMIT environment variable.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::STORAGE_INVALID_FILE => [
|
||||
'name' => Exception::STORAGE_INVALID_FILE,
|
||||
'description' => 'The uploaded file is invalid. Please check the file and try again.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::STORAGE_BUCKET_ALREADY_EXISTS => [
|
||||
'name' => Exception::STORAGE_BUCKET_ALREADY_EXISTS,
|
||||
'description' => 'A storage bucket with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::STORAGE_BUCKET_NOT_FOUND => [
|
||||
'name' => Exception::STORAGE_BUCKET_NOT_FOUND,
|
||||
'description' => 'Storage bucket with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::STORAGE_INVALID_CONTENT_RANGE => [
|
||||
'name' => Exception::STORAGE_INVALID_CONTENT_RANGE,
|
||||
'description' => 'The content range is invalid. Please check the value of the Content-Range header.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::STORAGE_INVALID_RANGE => [
|
||||
'name' => Exception::STORAGE_INVALID_RANGE,
|
||||
'description' => 'The requested range is not satisfiable. Please check the value of the Range header.',
|
||||
'code' => 416,
|
||||
],
|
||||
|
||||
/** Functions */
|
||||
Exception::FUNCTION_NOT_FOUND => [
|
||||
'name' => Exception::FUNCTION_NOT_FOUND,
|
||||
'description' => 'Function with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::FUNCTION_RUNTIME_UNSUPPORTED => [
|
||||
'name' => Exception::FUNCTION_RUNTIME_UNSUPPORTED,
|
||||
'description' => 'The requested runtime is either inactive or unsupported. Please check the value of the _APP_FUNCTIONS_RUNTIMES environment variable.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Builds */
|
||||
Exception::BUILD_NOT_FOUND => [
|
||||
'name' => Exception::BUILD_NOT_FOUND,
|
||||
'description' => 'Build with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::BUILD_NOT_READY => [
|
||||
'name' => Exception::BUILD_NOT_READY,
|
||||
'description' => 'Build with the requested ID is builing and not ready for execution.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::BUILD_IN_PROGRESS => [
|
||||
'name' => Exception::BUILD_IN_PROGRESS,
|
||||
'description' => 'Build with the requested ID is already in progress. Please wait before you can retry.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Deployments */
|
||||
Exception::DEPLOYMENT_NOT_FOUND => [
|
||||
'name' => Exception::DEPLOYMENT_NOT_FOUND,
|
||||
'description' => 'Deployment with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Executions */
|
||||
Exception::EXECUTION_NOT_FOUND => [
|
||||
'name' => Exception::EXECUTION_NOT_FOUND,
|
||||
'description' => 'Execution with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Collections */
|
||||
Exception::COLLECTION_NOT_FOUND => [
|
||||
'name' => Exception::COLLECTION_NOT_FOUND,
|
||||
'description' => 'Collection with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::COLLECTION_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLLECTION_ALREADY_EXISTS,
|
||||
'description' => 'A collection with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLLECTION_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::COLLECTION_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of collections has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Documents */
|
||||
Exception::DOCUMENT_NOT_FOUND => [
|
||||
'name' => Exception::DOCUMENT_NOT_FOUND,
|
||||
'description' => 'Document with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::DOCUMENT_INVALID_STRUCTURE => [
|
||||
'name' => Exception::DOCUMENT_INVALID_STRUCTURE,
|
||||
'description' => 'The document structure is invalid. Please ensure the attributes match the collection definition.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::DOCUMENT_MISSING_PAYLOAD => [
|
||||
'name' => Exception::DOCUMENT_MISSING_PAYLOAD,
|
||||
'description' => 'The document payload is missing.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::DOCUMENT_ALREADY_EXISTS => [
|
||||
'name' => Exception::DOCUMENT_ALREADY_EXISTS,
|
||||
'description' => 'Document with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Attributes */
|
||||
Exception::ATTRIBUTE_NOT_FOUND => [
|
||||
'name' => Exception::ATTRIBUTE_NOT_FOUND,
|
||||
'description' => 'Attribute with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::ATTRIBUTE_UNKNOWN => [
|
||||
'name' => Exception::ATTRIBUTE_UNKNOWN,
|
||||
'description' => 'The attribute required for the index could not be found. Please confirm all your attributes are in the <span class="tag">available</span> state.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_NOT_AVAILABLE => [
|
||||
'name' => Exception::ATTRIBUTE_NOT_AVAILABLE,
|
||||
'description' => 'The requested attribute is not yet <span class="tag">available</span>. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_FORMAT_UNSUPPORTED => [
|
||||
'name' => Exception::ATTRIBUTE_FORMAT_UNSUPPORTED,
|
||||
'description' => 'The requested attribute format is not supported.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED => [
|
||||
'name' => Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED,
|
||||
'description' => 'Default values cannot be set for <span class="tag">array</span> and <span class="tag">required</span> attributes.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_ALREADY_EXISTS => [
|
||||
'name' => Exception::ATTRIBUTE_ALREADY_EXISTS,
|
||||
'description' => 'Attribute with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::ATTRIBUTE_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of attributes has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_VALUE_INVALID => [
|
||||
'name' => Exception::ATTRIBUTE_VALUE_INVALID,
|
||||
'description' => 'The attribute value is invalid. Please check the type, range and value of the attribute.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Indexes */
|
||||
Exception::INDEX_NOT_FOUND => [
|
||||
'name' => Exception::INDEX_NOT_FOUND,
|
||||
'description' => 'Index with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::INDEX_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::INDEX_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of indexes has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::INDEX_ALREADY_EXISTS => [
|
||||
'name' => Exception::INDEX_ALREADY_EXISTS,
|
||||
'description' => 'Index with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Project Errors */
|
||||
Exception::PROJECT_NOT_FOUND => [
|
||||
'name' => Exception::PROJECT_NOT_FOUND,
|
||||
'description' => 'Project with the requested ID could not be found. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::PROJECT_UNKNOWN => [
|
||||
'name' => Exception::PROJECT_UNKNOWN,
|
||||
'description' => 'The project ID is either missing or not valid. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PROJECT_PROVIDER_DISABLED => [
|
||||
'name' => Exception::PROJECT_PROVIDER_DISABLED,
|
||||
'description' => 'The chosen OAuth provider is disabled. You can enable the OAuth provider using the Appwrite console.',
|
||||
'code' => 412,
|
||||
],
|
||||
Exception::PROJECT_PROVIDER_UNSUPPORTED => [
|
||||
'name' => Exception::PROJECT_PROVIDER_UNSUPPORTED,
|
||||
'description' => 'The chosen OAuth provider is unsupported. Please check <a href="/docs/client/account?sdk=web-default#accountCreateOAuth2Session"> the docs</a> for the complete list of supported OAuth providers.',
|
||||
'code' => 501,
|
||||
],
|
||||
Exception::PROJECT_INVALID_SUCCESS_URL => [
|
||||
'name' => Exception::PROJECT_INVALID_SUCCESS_URL,
|
||||
'description' => 'Invalid URL received for OAuth success redirect.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PROJECT_INVALID_FAILURE_URL => [
|
||||
'name' => Exception::PROJECT_INVALID_FAILURE_URL,
|
||||
'description' => 'Invalid URL received for OAuth failure redirect.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PROJECT_MISSING_USER_ID => [
|
||||
'name' => Exception::PROJECT_MISSING_USER_ID,
|
||||
'description' => 'Failed to obtain user ID from the OAuth provider.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::WEBHOOK_NOT_FOUND => [
|
||||
'name' => Exception::WEBHOOK_NOT_FOUND,
|
||||
'description' => 'Webhook with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::KEY_NOT_FOUND => [
|
||||
'name' => Exception::KEY_NOT_FOUND,
|
||||
'description' => 'Key with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::PLATFORM_NOT_FOUND => [
|
||||
'name' => Exception::PLATFORM_NOT_FOUND,
|
||||
'description' => 'Platform with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::DOMAIN_NOT_FOUND => [
|
||||
'name' => Exception::DOMAIN_NOT_FOUND,
|
||||
'description' => 'Domain with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::DOMAIN_ALREADY_EXISTS => [
|
||||
'name' => Exception::DOMAIN_ALREADY_EXISTS,
|
||||
'description' => 'A Domain with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOMAIN_VERIFICATION_FAILED => [
|
||||
'name' => Exception::DOMAIN_VERIFICATION_FAILED,
|
||||
'description' => 'Domain verification for the requested domain has failed.',
|
||||
'code' => 401,
|
||||
]
|
||||
];
|
||||
|
|
@ -7,264 +7,217 @@
|
|||
use Appwrite\Utopia\Response;
|
||||
|
||||
return [
|
||||
'account.create' => [
|
||||
'description' => 'This event triggers when the account is created.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
'users' => [
|
||||
'$model' => Response::MODEL_USER,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any user\'s event.',
|
||||
'sessions' => [
|
||||
'$model' => Response::MODEL_SESSION,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any user\'s sessions event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a session for a user is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a session for a user is deleted.'
|
||||
],
|
||||
],
|
||||
'recovery' => [
|
||||
'$model' => Response::MODEL_TOKEN,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any user\'s recovery token event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a recovery token for a user is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a recovery token for a user is validated.'
|
||||
],
|
||||
],
|
||||
'verification' => [
|
||||
'$model' => Response::MODEL_TOKEN,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any user\'s verification token event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a verification token for a user is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a verification token for a user is validated.'
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a user is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a user is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a user is updated.',
|
||||
'email' => [
|
||||
'$description' => 'This event triggers when a user\'s email address is updated.',
|
||||
],
|
||||
'name' => [
|
||||
'$description' => 'This event triggers when a user\'s name is updated.',
|
||||
],
|
||||
'password' => [
|
||||
'$description' => 'This event triggers when a user\'s password is updated.',
|
||||
],
|
||||
'status' => [
|
||||
'$description' => 'This event triggers when a user\'s status is updated.',
|
||||
],
|
||||
'prefs' => [
|
||||
'$description' => 'This event triggers when a user\'s preferences is updated.',
|
||||
],
|
||||
]
|
||||
],
|
||||
'account.update.email' => [
|
||||
'description' => 'This event triggers when the account email address is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
'collections' => [
|
||||
'$model' => Response::MODEL_COLLECTION,
|
||||
'$resource' => true,
|
||||
'$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 collection is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a collection is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a collection is updated.',
|
||||
]
|
||||
],
|
||||
'account.update.name' => [
|
||||
'description' => 'This event triggers when the account name is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
'buckets' => [
|
||||
'$model' => Response::MODEL_BUCKET,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any buckets event.',
|
||||
'files' => [
|
||||
'$model' => Response::MODEL_FILE,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any files event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a file is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a file is deleted.'
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a file is updated.'
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a bucket is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a bucket is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a bucket is updated.',
|
||||
]
|
||||
],
|
||||
'account.update.password' => [
|
||||
'description' => 'This event triggers when the account password is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
],
|
||||
'users.update.email' => [
|
||||
'description' => 'This event triggers when the user email address is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
],
|
||||
'users.update.name' => [
|
||||
'description' => 'This event triggers when the user name is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
],
|
||||
'users.update.password' => [
|
||||
'description' => 'This event triggers when the user password is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
],
|
||||
'account.update.prefs' => [
|
||||
'description' => 'This event triggers when the account preferences are updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
],
|
||||
'account.recovery.create' => [
|
||||
'description' => 'This event triggers when the account recovery token is created.',
|
||||
'model' => Response::MODEL_TOKEN,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'account.recovery.update' => [
|
||||
'description' => 'This event triggers when the account recovery token is validated.',
|
||||
'model' => Response::MODEL_TOKEN,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'account.verification.create' => [
|
||||
'description' => 'This event triggers when the account verification token is created.',
|
||||
'model' => Response::MODEL_TOKEN,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'account.verification.update' => [
|
||||
'description' => 'This event triggers when the account verification token is validated.',
|
||||
'model' => Response::MODEL_TOKEN,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'account.delete' => [
|
||||
'description' => 'This event triggers when the account is deleted.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => '',
|
||||
],
|
||||
'account.sessions.create' => [
|
||||
'description' => 'This event triggers when the account session is created.',
|
||||
'model' => Response::MODEL_SESSION,
|
||||
'note' => '',
|
||||
],
|
||||
'account.sessions.delete' => [
|
||||
'description' => 'This event triggers when the account session is deleted.',
|
||||
'model' => Response::MODEL_SESSION,
|
||||
'note' => '',
|
||||
],
|
||||
'account.sessions.update' => [
|
||||
'description' => 'This event triggers when the account session is updated.',
|
||||
'model' => Response::MODEL_SESSION,
|
||||
'note' => '',
|
||||
],
|
||||
'database.collections.create' => [
|
||||
'description' => 'This event triggers when a database collection is created.',
|
||||
'model' => Response::MODEL_COLLECTION,
|
||||
'note' => '',
|
||||
],
|
||||
'database.collections.update' => [
|
||||
'description' => 'This event triggers when a database collection is updated.',
|
||||
'model' => Response::MODEL_COLLECTION,
|
||||
'note' => '',
|
||||
],
|
||||
'database.collections.delete' => [
|
||||
'description' => 'This event triggers when a database collection is deleted.',
|
||||
'model' => Response::MODEL_COLLECTION,
|
||||
'note' => '',
|
||||
],
|
||||
'database.attributes.create' => [
|
||||
'description' => 'This event triggers when a collection attribute is created.',
|
||||
'model' => Response::MODEL_ATTRIBUTE,
|
||||
'note' => '',
|
||||
],
|
||||
'database.attributes.delete' => [
|
||||
'description' => 'This event triggers when a collection attribute is deleted.',
|
||||
'model' => Response::MODEL_ATTRIBUTE,
|
||||
'note' => '',
|
||||
],
|
||||
'database.indexes.create' => [
|
||||
'description' => 'This event triggers when a collection index is created.',
|
||||
'model' => Response::MODEL_INDEX,
|
||||
'note' => '',
|
||||
],
|
||||
'database.indexes.delete' => [
|
||||
'description' => 'This event triggers when a collection index is deleted.',
|
||||
'model' => Response::MODEL_INDEX,
|
||||
'note' => '',
|
||||
],
|
||||
'database.documents.create' => [
|
||||
'description' => 'This event triggers when a database document is created.',
|
||||
'model' => Response::MODEL_DOCUMENT,
|
||||
'note' => '',
|
||||
],
|
||||
'database.documents.update' => [
|
||||
'description' => 'This event triggers when a database document is updated.',
|
||||
'model' => Response::MODEL_DOCUMENT,
|
||||
'note' => '',
|
||||
],
|
||||
'database.documents.delete' => [
|
||||
'description' => 'This event triggers when a database document is deleted.',
|
||||
'model' => Response::MODEL_DOCUMENT,
|
||||
'note' => '',
|
||||
],
|
||||
'functions.create' => [
|
||||
'description' => 'This event triggers when a function is created.',
|
||||
'model' => Response::MODEL_FUNCTION,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'functions.update' => [
|
||||
'description' => 'This event triggers when a function is updated.',
|
||||
'model' => Response::MODEL_FUNCTION,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'functions.delete' => [
|
||||
'description' => 'This event triggers when a function is deleted.',
|
||||
'model' => Response::MODEL_ANY,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'functions.tags.create' => [
|
||||
'description' => 'This event triggers when a function tag is created.',
|
||||
'model' => Response::MODEL_TAG,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'functions.tags.update' => [
|
||||
'description' => 'This event triggers when a function tag is updated.',
|
||||
'model' => Response::MODEL_FUNCTION,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'functions.tags.delete' => [
|
||||
'description' => 'This event triggers when a function tag is deleted.',
|
||||
'model' => Response::MODEL_ANY,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'functions.executions.create' => [
|
||||
'description' => 'This event triggers when a function execution is created.',
|
||||
'model' => Response::MODEL_EXECUTION,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'functions.executions.update' => [
|
||||
'description' => 'This event triggers when a function execution is updated.',
|
||||
'model' => Response::MODEL_EXECUTION,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'storage.files.create' => [
|
||||
'description' => 'This event triggers when a storage file is created.',
|
||||
'model' => Response::MODEL_FILE,
|
||||
'note' => '',
|
||||
],
|
||||
'storage.files.update' => [
|
||||
'description' => 'This event triggers when a storage file is updated.',
|
||||
'model' => Response::MODEL_FILE,
|
||||
'note' => '',
|
||||
],
|
||||
'storage.files.delete' => [
|
||||
'description' => 'This event triggers when a storage file is deleted.',
|
||||
'model' => Response::MODEL_FILE,
|
||||
'note' => '',
|
||||
],
|
||||
'users.create' => [
|
||||
'description' => 'This event triggers when a user is created from the users API.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'users.update.prefs' => [
|
||||
'description' => 'This event triggers when a user preference is updated from the users API.',
|
||||
'model' => Response::MODEL_ANY,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'users.update.email' => [
|
||||
'description' => 'This event triggers when the user email address is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => 'version >= 0.10',
|
||||
],
|
||||
'users.update.name' => [
|
||||
'description' => 'This event triggers when the user name is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => 'version >= 0.10',
|
||||
],
|
||||
'users.update.password' => [
|
||||
'description' => 'This event triggers when the user password is updated.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => 'version >= 0.10',
|
||||
],
|
||||
'users.update.status' => [
|
||||
'description' => 'This event triggers when a user status is updated from the users API.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'users.delete' => [
|
||||
'description' => 'This event triggers when a user is deleted from users API.',
|
||||
'model' => Response::MODEL_USER,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'users.sessions.delete' => [
|
||||
'description' => 'This event triggers when a user session is deleted from users API.',
|
||||
'model' => Response::MODEL_SESSION,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'teams.create' => [
|
||||
'description' => 'This event triggers when a team is created.',
|
||||
'model' => Response::MODEL_TEAM,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'teams.update' => [
|
||||
'description' => 'This event triggers when a team is updated.',
|
||||
'model' => Response::MODEL_TEAM,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'teams.delete' => [
|
||||
'description' => 'This event triggers when a team is deleted.',
|
||||
'model' => Response::MODEL_TEAM,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'teams.memberships.create' => [
|
||||
'description' => 'This event triggers when a team memberships is created.',
|
||||
'model' => Response::MODEL_MEMBERSHIP,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'teams.memberships.update' => [
|
||||
'description' => 'This event triggers when a team membership is updated.',
|
||||
'model' => Response::MODEL_MEMBERSHIP,
|
||||
'note' => 'version >= 0.8',
|
||||
],
|
||||
'teams.memberships.update.status' => [
|
||||
'description' => 'This event triggers when a team memberships status is updated.',
|
||||
'model' => Response::MODEL_MEMBERSHIP,
|
||||
'note' => 'version >= 0.7',
|
||||
],
|
||||
'teams.memberships.delete' => [
|
||||
'description' => 'This event triggers when a team memberships is deleted.',
|
||||
'model' => Response::MODEL_MEMBERSHIP,
|
||||
'note' => 'version >= 0.7',
|
||||
'teams' => [
|
||||
'$model' => Response::MODEL_TEAM,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any teams event.',
|
||||
'memberships' => [
|
||||
'$model' => Response::MODEL_MEMBERSHIP,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any team memberships event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a membership is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a membership is deleted.'
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a membership is updated.',
|
||||
'status' => [
|
||||
'$description' => 'This event triggers when a team memberships status is updated.'
|
||||
]
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a bucket is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a bucket is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a bucket is updated.',
|
||||
]
|
||||
],
|
||||
'functions' => [
|
||||
'$model' => Response::MODEL_FUNCTION,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any functions event.',
|
||||
'deployments' => [
|
||||
'$model' => Response::MODEL_DEPLOYMENT,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any deployments event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a deployment is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a deployment is deleted.'
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a deployment is updated.'
|
||||
],
|
||||
],
|
||||
'executions' => [
|
||||
'$model' => Response::MODEL_EXECUTION,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any executions event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when an execution is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when an execution is deleted.'
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when an execution is updated.'
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a function is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a function is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a function is updated.',
|
||||
]
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ return [
|
|||
'or', // Oriya
|
||||
'tl', // Filipino
|
||||
'pl', // Polish
|
||||
'pt-br', // Portuguese - Brazil
|
||||
'pt-br', // Portuguese - Brazil
|
||||
'pt-pt', // Portuguese - Portugal
|
||||
'pa', // Punjabi
|
||||
'ro', // Romanian
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@
|
|||
"emails.invitation.footer": "If you are not interested, you can ignore this message.",
|
||||
"emails.invitation.thanks": "Thanks",
|
||||
"emails.invitation.signature": "{{project}} team",
|
||||
"emails.certificate.subject": "Certificate failure for %s",
|
||||
"emails.certificate.hello": "Hello",
|
||||
"emails.certificate.body": "Certificate for your domain '{{domain}}' could not be generated. This is attempt no. {{attempt}}, and the failure was caused by: {{error}}",
|
||||
"emails.certificate.footer": "Your previous certificate willl be valid for 30 days since the first failure. We highly recommend investigating this case, otherwise your domain will end up without a valid SSL communication.",
|
||||
"emails.certificate.thanks": "Thanks",
|
||||
"emails.certificate.signature": "{{project}} team",
|
||||
"locale.country.unknown": "Unknown",
|
||||
"countries.af": "Afghanistan",
|
||||
"countries.ao": "Angola",
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@
|
|||
"emails.magicSession.signature": "Equipo de {{project}}",
|
||||
"emails.recovery.subject": "Restablecer contraseña",
|
||||
"emails.recovery.hello": "Hola {{name}}",
|
||||
"emails.recovery.body": "Haz clic en este enlace para restablecer la contraseña de tu proyecto {{project}}.",
|
||||
"emails.recovery.body": "Haz clic en este enlace para restablecer la contraseña de {{project}}.",
|
||||
"emails.recovery.footer": "Si no has solicitado restablecer la contraseña, puedes ignorar este mensaje.",
|
||||
"emails.recovery.thanks": "Gracias",
|
||||
"emails.recovery.signature": "Equipo de {{project}}",
|
||||
"emails.invitation.subject": "Invitación al equipo %s en %s",
|
||||
"emails.invitation.hello": "Hola",
|
||||
"emails.invitation.body": "Este correo ha sido enviado a petición de {{owner}} quien quiere invitarte a formar parte del equipo {{team}} en el proyecto {{project}}.",
|
||||
"emails.invitation.body": "Este correo ha sido enviado a petición de {{owner}} quien quiere invitarte a formar parte del equipo {{team}} en {{project}}.",
|
||||
"emails.invitation.footer": "Si no estas interesado, puedes ignorar este mensaje.",
|
||||
"emails.invitation.thanks": "Gracias",
|
||||
"emails.invitation.signature": "Equipo de {{project}}",
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
"emails.recovery.subject": "രഹസ്യവാക്ക് പുനക്രമീകരണം",
|
||||
"emails.recovery.hello": "നമസ്കാരം {{name}}",
|
||||
"emails.recovery.body": "നിങ്ങളുടെ {{Project}} രഹസ്യവാക്ക് പുനക്രമീകരിക്കുന്നതിന് ഈ ലിങ്ക് പിന്തുടരുക.",
|
||||
"emails.recovery.footer": "നിങ്ങളുടെ പാസ്വേഡ് പുനക്രമീകരിക്കാന് നിങ്ങൾ ആവശ്യപ്പെട്ടില്ലെങ്കിൽ, ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.",
|
||||
"emails.recovery.footer": "നിങ്ങളുടെ രഹസ്യവാക്ക് പുനക്രമീകരിക്കാന് നിങ്ങൾ ആവശ്യപ്പെട്ടില്ലെങ്കിൽ, ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.",
|
||||
"emails.recovery.thanks": "നന്ദി",
|
||||
"emails.recovery.signature": "{{project}} ടീം",
|
||||
"emails.invitation.subject": "%s -ലെ %s ടീമിലേക്കുള്ള ക്ഷണം",
|
||||
"emails.invitation.hello": "നമസ്കാരം",
|
||||
"emails.invitation.body": "നിങ്ങളെ {{project}} -ലെ {{team}} ടീമിലെ അംഗമാകുവാന് ക്ഷണിക്കാൻ {{owner}} ആഗ്രഹിക്കുതിനാലാണ് ഈ മെയിൽ നിങ്ങൾക്ക് അയക്കുന്നത്.",
|
||||
"emails.invitation.body": "നിങ്ങളെ {{project}} -ലെ {{team}} ടീമിലെ അംഗമാകുവാന് ക്ഷണിക്കാൻ {{owner}} ആഗ്രഹിക്കുന്നതിനാലാണ് ഈ മെയിൽ നിങ്ങൾക്ക് അയക്കുന്നത്.",
|
||||
"emails.invitation.footer": "നിങ്ങൾക്ക് താൽപ്പര്യമില്ലെങ്കിൽ, ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.",
|
||||
"emails.invitation.thanks": "നന്ദി",
|
||||
"emails.invitation.signature": "{{project}} ടീം",
|
||||
|
|
@ -229,4 +229,4 @@
|
|||
"continents.na": "വടക്കേ അമേരിക്ക",
|
||||
"continents.oc": "ഓഷ്യാനിയ",
|
||||
"continents.sa": "തെക്കേ അമേരിക്ക"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ return [
|
|||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '6.0.1',
|
||||
'version' => '8.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' => '3.0.1',
|
||||
'version' => '5.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.2.1',
|
||||
'version' => '0.5.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.3.3',
|
||||
'version' => '0.6.1',
|
||||
'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' => '4.0.4',
|
||||
'version' => '5.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
|
|
@ -177,6 +177,24 @@ return [
|
|||
'gitRepoName' => 'sdk-for-console',
|
||||
'gitUserName' => 'appwrite',
|
||||
],
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '0.17.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'prism' => 'bash',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-cli'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-cli.git',
|
||||
'gitRepoName' => 'sdk-for-cli',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
@ -190,7 +208,7 @@ return [
|
|||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '4.0.2',
|
||||
'version' => '6.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -208,7 +226,7 @@ return [
|
|||
[
|
||||
'key' => 'deno',
|
||||
'name' => 'Deno',
|
||||
'version' => '2.0.2',
|
||||
'version' => '4.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-deno',
|
||||
'package' => 'https://deno.land/x/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -226,7 +244,7 @@ return [
|
|||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '3.0.0',
|
||||
'version' => '5.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -244,7 +262,7 @@ return [
|
|||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '0.6.0',
|
||||
'version' => '0.9.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
|
|
@ -262,7 +280,7 @@ return [
|
|||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '3.0.0',
|
||||
'version' => '5.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -334,7 +352,7 @@ return [
|
|||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '3.0.2',
|
||||
'version' => '5.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -349,28 +367,10 @@ return [
|
|||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '0.13.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'bash',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-cli'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-cli.git',
|
||||
'gitRepoName' => 'sdk-for-cli',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '0.2.5',
|
||||
'version' => '0.5.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.2.1',
|
||||
'version' => '0.5.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,16 @@ return [ // Ordered by ABC.
|
|||
'beta' => true,
|
||||
'mock' => false,
|
||||
],
|
||||
'auth0' => [
|
||||
'name' => 'Auth0',
|
||||
'developers' => 'https://auth0.com/developers',
|
||||
'icon' => 'icon-auth0',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => 'auth0.phtml',
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'bitbucket' => [
|
||||
'name' => 'BitBucket',
|
||||
'developers' => 'https://developer.atlassian.com/bitbucket',
|
||||
|
|
@ -84,7 +94,7 @@ return [ // Ordered by ABC.
|
|||
'github' => [
|
||||
'name' => 'GitHub',
|
||||
'developers' => 'https://developer.github.com/',
|
||||
'icon' => 'icon-github-circled',
|
||||
'icon' => 'icon-github',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
|
|
@ -141,6 +151,16 @@ return [ // Ordered by ABC.
|
|||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'okta' => [
|
||||
'name' => 'Okta',
|
||||
'developers' => 'https://developer.okta.com/',
|
||||
'icon' => 'icon-okta',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => 'okta.phtml',
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'paypal' => [
|
||||
'name' => 'PayPal',
|
||||
'developers' => 'https://developer.paypal.com/docs/api/overview/',
|
||||
|
|
@ -221,10 +241,10 @@ return [ // Ordered by ABC.
|
|||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'vk' => [
|
||||
'name' => 'VK',
|
||||
'developers' => 'https://vk.com/dev',
|
||||
'icon' => 'icon-vk',
|
||||
'zoom' => [
|
||||
'name' => 'Zoom',
|
||||
'developers' => 'https://marketplace.zoom.us/docs/guides/auth/oauth/',
|
||||
'icon' => 'icon-zoom',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ $admins = [
|
|||
'documents.write',
|
||||
'files.read',
|
||||
'files.write',
|
||||
'buckets.read',
|
||||
'buckets.write',
|
||||
'users.read',
|
||||
'users.write',
|
||||
'collections.read',
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
<?php
|
||||
|
||||
use Utopia\App;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
|
||||
/**
|
||||
* List of Appwrite Cloud Functions supported runtimes
|
||||
*/
|
||||
$runtimes = new Runtimes();
|
||||
|
||||
use Utopia\App;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
|
||||
$runtimes = new Runtimes('v1');
|
||||
|
||||
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
|
||||
|
||||
$runtimes = $runtimes->getAll(true, $allowList);
|
||||
|
||||
return $runtimes;
|
||||
return $runtimes;
|
||||
|
|
|
|||
|
|
@ -43,11 +43,17 @@ return [ // List of publicly visible scopes
|
|||
'files.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s storage files',
|
||||
],
|
||||
'buckets.read' => [
|
||||
'description' => 'Access to read your project\'s storage buckets',
|
||||
],
|
||||
'buckets.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s storage buckets',
|
||||
],
|
||||
'functions.read' => [
|
||||
'description' => 'Access to read your project\'s functions and code tags',
|
||||
'description' => 'Access to read your project\'s functions and code deployments',
|
||||
],
|
||||
'functions.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s functions and code tags',
|
||||
'description' => 'Access to create, update, and delete your project\'s functions and code deployments',
|
||||
],
|
||||
'execution.read' => [
|
||||
'description' => 'Access to read your project\'s execution logs',
|
||||
|
|
@ -64,4 +70,4 @@ return [ // List of publicly visible scopes
|
|||
'health.read' => [
|
||||
'description' => 'Access to read your project\'s health status',
|
||||
],
|
||||
];;
|
||||
];
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ return [
|
|||
'avatars' => [
|
||||
'key' => 'avatars',
|
||||
'name' => 'Avatars',
|
||||
'subtitle'=> 'The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.',
|
||||
'subtitle' => 'The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.',
|
||||
'description' => '/docs/services/avatars.md',
|
||||
'controller' => 'api/avatars.php',
|
||||
'sdk' => true,
|
||||
|
|
|
|||
1
app/config/specs/open-api3-0.13.x-client.json
Normal file
1
app/config/specs/open-api3-0.13.x-console.json
Normal file
1
app/config/specs/open-api3-0.13.x-server.json
Normal file
1
app/config/specs/open-api3-0.14.x-client.json
Normal file
1
app/config/specs/open-api3-0.14.x-console.json
Normal file
1
app/config/specs/open-api3-0.14.x-server.json
Normal file
1
app/config/specs/swagger2-0.13.x-client.json
Normal file
1
app/config/specs/swagger2-0.13.x-console.json
Normal file
1
app/config/specs/swagger2-0.13.x-server.json
Normal file
1
app/config/specs/swagger2-0.14.x-client.json
Normal file
1
app/config/specs/swagger2-0.14.x-console.json
Normal file
1
app/config/specs/swagger2-0.14.x-server.json
Normal file
|
|
@ -5,4 +5,4 @@ return [ // Accepted inputs files
|
|||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
];
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,43 +1,48 @@
|
|||
<?php
|
||||
|
||||
return [ // Based on this list @see http://stackoverflow.com/a/4212908/2299554
|
||||
'default' => __DIR__.'/logos/none.png',
|
||||
|
||||
'default' => __DIR__ . '/logos/none.png',
|
||||
'default_image' => __DIR__ . '/logos/image.png',
|
||||
|
||||
// Video Files
|
||||
'video/mp4' => __DIR__.'/logos/video.png',
|
||||
'video/x-flv' => __DIR__.'/logos/video.png',
|
||||
'application/x-mpegURL' => __DIR__.'/logos/video.png',
|
||||
'video/MP2T' => __DIR__.'/logos/video.png',
|
||||
'video/3gpp' => __DIR__.'/logos/video.png',
|
||||
'video/quicktime' => __DIR__.'/logos/video.png',
|
||||
'video/x-msvideo' => __DIR__.'/logos/video.png',
|
||||
'video/x-ms-wmv' => __DIR__.'/logos/video.png',
|
||||
|
||||
'video/mp4' => __DIR__ . '/logos/video.png',
|
||||
'video/x-flv' => __DIR__ . '/logos/video.png',
|
||||
'video/webm' => __DIR__ . '/logos/video.png',
|
||||
'application/x-mpegURL' => __DIR__ . '/logos/video.png',
|
||||
'video/MP2T' => __DIR__ . '/logos/video.png',
|
||||
'video/3gpp' => __DIR__ . '/logos/video.png',
|
||||
'video/quicktime' => __DIR__ . '/logos/video.png',
|
||||
'video/x-msvideo' => __DIR__ . '/logos/video.png',
|
||||
'video/x-ms-wmv' => __DIR__ . '/logos/video.png',
|
||||
|
||||
|
||||
|
||||
// // Microsoft Word
|
||||
'application/msword' => __DIR__.'/logos/word.png',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => __DIR__.'/logos/word.png',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => __DIR__.'/logos/word.png',
|
||||
'application/vnd.ms-word.document.macroEnabled.12' => __DIR__.'/logos/word.png',
|
||||
// 'application/msword' => __DIR__.'/logos/word.png',
|
||||
// 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => __DIR__.'/logos/word.png',
|
||||
// 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => __DIR__.'/logos/word.png',
|
||||
// 'application/vnd.ms-word.document.macroEnabled.12' => __DIR__.'/logos/word.png',
|
||||
|
||||
// // Microsoft Excel
|
||||
'application/vnd.ms-excel' => __DIR__.'/logos/excel.png',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => __DIR__.'/logos/excel.png',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => __DIR__.'/logos/excel.png',
|
||||
'application/vnd.ms-excel.sheet.macroEnabled.12' => __DIR__.'/logos/excel.png',
|
||||
'application/vnd.ms-excel.template.macroEnabled.12' => __DIR__.'/logos/excel.png',
|
||||
'application/vnd.ms-excel.addin.macroEnabled.12' => __DIR__.'/logos/excel.png',
|
||||
'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => __DIR__.'/logos/excel.png',
|
||||
// 'application/vnd.ms-excel' => __DIR__.'/logos/excel.png',
|
||||
// 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => __DIR__.'/logos/excel.png',
|
||||
// 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => __DIR__.'/logos/excel.png',
|
||||
// 'application/vnd.ms-excel.sheet.macroEnabled.12' => __DIR__.'/logos/excel.png',
|
||||
// 'application/vnd.ms-excel.template.macroEnabled.12' => __DIR__.'/logos/excel.png',
|
||||
// 'application/vnd.ms-excel.addin.macroEnabled.12' => __DIR__.'/logos/excel.png',
|
||||
// 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => __DIR__.'/logos/excel.png',
|
||||
|
||||
// // Microsoft Power Point
|
||||
'application/vnd.ms-powerpoint' => __DIR__.'/logos/ppt.png',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => __DIR__.'/logos/ppt.png',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.template' => __DIR__.'/logos/ppt.png',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => __DIR__.'/logos/ppt.png',
|
||||
'application/vnd.ms-powerpoint.addin.macroEnabled.12' => __DIR__.'/logos/ppt.png',
|
||||
'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => __DIR__.'/logos/ppt.png',
|
||||
'application/vnd.ms-powerpoint.template.macroEnabled.12' => __DIR__.'/logos/ppt.png',
|
||||
'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => __DIR__.'/logos/ppt.png',
|
||||
// 'application/vnd.ms-powerpoint' => __DIR__.'/logos/ppt.png',
|
||||
// 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => __DIR__.'/logos/ppt.png',
|
||||
// 'application/vnd.openxmlformats-officedocument.presentationml.template' => __DIR__.'/logos/ppt.png',
|
||||
// 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => __DIR__.'/logos/ppt.png',
|
||||
// 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => __DIR__.'/logos/ppt.png',
|
||||
// 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => __DIR__.'/logos/ppt.png',
|
||||
// 'application/vnd.ms-powerpoint.template.macroEnabled.12' => __DIR__.'/logos/ppt.png',
|
||||
// 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => __DIR__.'/logos/ppt.png',
|
||||
|
||||
// Adobe PDF
|
||||
'application/pdf' => __DIR__.'/logos/pdf.png',
|
||||
];
|
||||
// 'application/pdf' => __DIR__.'/logos/pdf.png',
|
||||
];
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 16 KiB |
BIN
app/config/storage/logos/image.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
|
@ -10,6 +10,7 @@ return [
|
|||
// Video Files
|
||||
'video/mp4',
|
||||
'video/x-flv',
|
||||
'video/webm',
|
||||
'application/x-mpegURL',
|
||||
'video/MP2T',
|
||||
'video/3gpp',
|
||||
|
|
@ -30,7 +31,7 @@ return [
|
|||
'audio/ogg', // Ogg Vorbis RFC 5334
|
||||
'audio/vorbis', // Vorbis RFC 5215
|
||||
'audio/vnd.wav', // wav RFC 2361
|
||||
|
||||
|
||||
// Microsoft Word
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
|
|
@ -61,4 +62,4 @@ return [
|
|||
|
||||
// Adobe PDF
|
||||
'application/pdf',
|
||||
];
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ return [ // Accepted outputs files
|
|||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'webp' => 'image/webp',
|
||||
];
|
||||
];
|
||||
|
|
|
|||
|
|
@ -176,6 +176,15 @@ return [
|
|||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_WORKER_PER_CORE',
|
||||
'description' => 'Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 6,
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
],
|
||||
],
|
||||
|
|
@ -386,9 +395,18 @@ return [
|
|||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_STORAGE_LIMIT',
|
||||
'description' => 'Maximum file size allowed for file upload. The default value is 10MB limitation. You should pass your size limit value in bytes.',
|
||||
'description' => 'Maximum file size allowed for file upload. The default value is 30MB. You should pass your size limit value in bytes.',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '10000000',
|
||||
'default' => '30000000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_PREVIEW_LIMIT',
|
||||
'description' => 'Maximum file size allowed for file image preview. The default value is 20MB. You should pass your size limit value in bytes.',
|
||||
'introduction' => '0.13.4',
|
||||
'default' => '20000000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
|
|
@ -420,12 +438,189 @@ return [
|
|||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_DEVICE',
|
||||
'description' => 'Select default storage device. The default value is \'Local\'. List of supported adapters are \'Local\', \'S3\', \'DOSpaces\', \'Backblaze\', \'Linode\' and \'Wasabi\'.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'Local',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_ACCESS_KEY',
|
||||
'description' => 'AWS S3 storage access key. Required when the storage adapter is set to S3. You can get your access key from your AWS console',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_SECRET',
|
||||
'description' => 'AWS S3 storage secret key. Required when the storage adapter is set to S3. You can get your secret key from your AWS console.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_REGION',
|
||||
'description' => 'AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'us-east-1',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_S3_BUCKET',
|
||||
'description' => 'AWS S3 storage bucket. Required when storage adapter is set to S3. You can create buckets in your AWS console.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_DO_SPACES_ACCESS_KEY',
|
||||
'description' => 'DigitalOcean spaces access key. Required when the storage adapter is set to DOSpaces. You can get your access key from your DigitalOcean console.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_DO_SPACES_SECRET',
|
||||
'description' => 'DigitalOcean spaces secret key. Required when the storage adapter is set to DOSpaces. You can get your secret key from your DigitalOcean console.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_DO_SPACES_REGION',
|
||||
'description' => 'DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'us-east-1',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_DO_SPACES_BUCKET',
|
||||
'description' => 'DigitalOcean spaces bucket. Required when storage adapter is set to DOSpaces. You can create spaces in your DigitalOcean console.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_BACKBLAZE_ACCESS_KEY',
|
||||
'description' => 'Backblaze access key. Required when the storage adapter is set to Backblaze. Your Backblaze keyID will be your access key. You can get your keyID from your Backblaze console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_BACKBLAZE_SECRET',
|
||||
'description' => 'Backblaze secret key. Required when the storage adapter is set to Backblaze. Your Backblaze applicationKey will be your secret key. You can get your applicationKey from your Backblaze console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_BACKBLAZE_REGION',
|
||||
'description' => 'Backblaze region. Required when storage adapter is set to Backblaze. You can find your region info from your Backblaze console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => 'us-west-004',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_BACKBLAZE_BUCKET',
|
||||
'description' => 'Backblaze bucket. Required when storage adapter is set to Backblaze. You can create your bucket from your Backblaze console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_LINODE_ACCESS_KEY',
|
||||
'description' => 'Linode object storage access key. Required when the storage adapter is set to Linode. You can get your access key from your Linode console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_LINODE_SECRET',
|
||||
'description' => 'Linode object storage secret key. Required when the storage adapter is set to Linode. You can get your secret key from your Linode console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_LINODE_REGION',
|
||||
'description' => 'Linode object storage region. Required when storage adapter is set to Linode. You can find your region info from your Linode console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => 'eu-central-1',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_LINODE_BUCKET',
|
||||
'description' => 'Linode object storage bucket. Required when storage adapter is set to Linode. You can create buckets in your Linode console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_WASABI_ACCESS_KEY',
|
||||
'description' => 'Wasabi access key. Required when the storage adapter is set to Wasabi. You can get your access key from your Wasabi console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_WASABI_SECRET',
|
||||
'description' => 'Wasabi secret key. Required when the storage adapter is set to Wasabi. You can get your secret key from your Wasabi console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_WASABI_REGION',
|
||||
'description' => 'Wasabi region. Required when storage adapter is set to Wasabi. You can find your region info from your Wasabi console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => 'eu-central-1',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
[
|
||||
'name' => '_APP_STORAGE_WASABI_BUCKET',
|
||||
'description' => 'Wasabi bucket. Required when storage adapter is set to Wasabi. You can create buckets in your Wasabi console.',
|
||||
'introduction' => '0.14.2',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'Functions',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_SIZE_LIMIT',
|
||||
'description' => 'The maximum size deployment in bytes. The default value is 30MB.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '30000000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_TIMEOUT',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds.',
|
||||
|
|
@ -435,6 +630,15 @@ return [
|
|||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_BUILD_TIMEOUT',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when building a new function. The default value is 900 seconds.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '900',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_CONTAINERS',
|
||||
'description' => 'The maximum number of containers Appwrite is allowed to keep alive in the background for function environments. Running containers allow faster execution time as there is no need to recreate each container every time a function gets executed. The default value is 10.',
|
||||
|
|
@ -448,7 +652,7 @@ return [
|
|||
'name' => '_APP_FUNCTIONS_CPUS',
|
||||
'description' => 'The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it\'s empty, CPU limit will be disabled.',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
|
|
@ -457,7 +661,7 @@ return [
|
|||
'name' => '_APP_FUNCTIONS_MEMORY',
|
||||
'description' => 'The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty, memory limit will be disabled.',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '256',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
|
|
@ -466,7 +670,7 @@ return [
|
|||
'name' => '_APP_FUNCTIONS_MEMORY_SWAP',
|
||||
'description' => 'The maximum amount of swap memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty, swap memory limit will be disabled.',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '256',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
|
|
@ -475,7 +679,34 @@ 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'))),
|
||||
'introduction' => '0.8.0',
|
||||
'default' => 'node-16.0,php-8.0,python-3.9,ruby-3.0,java-16.0',
|
||||
'default' => 'node-16.0,php-8.0,python-3.9,ruby-3.0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_EXECUTOR_SECRET',
|
||||
'description' => 'The secret key used by Appwrite to communicate with the function executor. Make sure to change this!',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'your-secret-key',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_EXECUTOR_HOST',
|
||||
'description' => 'The host used by Appwrite to communicate with the function executor!',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'http://appwrite-executor/v1',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_EXECUTOR_RUNTIME_NETWORK',
|
||||
'description' => 'Deprecated with 0.14.0, use \'OPEN_RUNTIMES_NETWORK\' instead!',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'appwrite_runtimes',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
|
|
@ -484,7 +715,16 @@ return [
|
|||
'name' => '_APP_FUNCTIONS_ENVS',
|
||||
'description' => 'Deprecated with 0.8.0, use \'_APP_FUNCTIONS_RUNTIMES\' instead!',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => 'node-16.0,php-7.4,python-3.9,ruby-3.0,java-16.0',
|
||||
'default' => 'node-16.0,php-7.4,python-3.9,ruby-3.0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_INACTIVE_THRESHOLD',
|
||||
'description' => 'The minimum time a function can be inactive before it\'s container is shutdown and put to sleep. The default value is 60 seconds',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '60',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
|
|
@ -516,48 +756,57 @@ return [
|
|||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'Maintenance',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_INTERVAL',
|
||||
'description' => 'Interval value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is 86400 seconds (1 day).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '86400',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_EXECUTION',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain execution logs. The default value is 1209600 seconds (14 days).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '1209600',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_AUDIT',
|
||||
'description' => 'IThe maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '1209600',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_ABUSE',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain abuse logs. The default value is 86400 seconds (1 day).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '86400',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
[
|
||||
'name' => 'OPEN_RUNTIMES_NETWORK',
|
||||
'description' => 'The docker network used for communication between the executor and runtimes. Change this if you have altered the default network names.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => 'appwrite_runtimes',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'Maintenance',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_INTERVAL',
|
||||
'description' => 'Interval value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is 86400 seconds (1 day).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '86400',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_EXECUTION',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain execution logs. The default value is 1209600 seconds (14 days).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '1209600',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_AUDIT',
|
||||
'description' => 'IThe maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '1209600',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_ABUSE',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain abuse logs. The default value is 86400 seconds (1 day).',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => '86400',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\URL\URL as URLParse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
|
|
@ -8,42 +10,40 @@ use Utopia\App;
|
|||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\HexColor;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
$avatarCallback = function ($type, $code, $width, $height, $quality, $response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
$avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) {
|
||||
|
||||
$code = \strtolower($code);
|
||||
$type = \strtolower($type);
|
||||
$set = Config::getParam('avatar-' . $type, []);
|
||||
|
||||
if (empty($set)) {
|
||||
throw new Exception('Avatar set not found', 404);
|
||||
throw new Exception('Avatar set not found', 404, Exception::AVATAR_SET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!\array_key_exists($code, $set)) {
|
||||
throw new Exception('Avatar not found', 404);
|
||||
throw new Exception('Avatar not found', 404, Exception::AVATAR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception('Imagick extension is missing', 500);
|
||||
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$output = 'png';
|
||||
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache
|
||||
$key = \md5('/v1/avatars/'.$type.'/:code-' . $code . $width . $height . $quality . $output);
|
||||
$key = \md5('/v1/avatars/' . $type . '/:code-' . $code . $width . $height . $quality . $output);
|
||||
$path = $set[$code];
|
||||
$type = 'png';
|
||||
|
||||
if (!\is_readable($path)) {
|
||||
throw new Exception('File not readable in ' . $path, 500);
|
||||
throw new Exception('File not readable in ' . $path, 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$cache = new Cache(new Filesystem(APP_STORAGE_CACHE . '/app-0')); // Limit file number or size
|
||||
|
|
@ -56,8 +56,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, $response)
|
|||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->send($data)
|
||||
;
|
||||
->send($data);
|
||||
}
|
||||
|
||||
$image = new Image(\file_get_contents($path));
|
||||
|
|
@ -95,7 +94,7 @@ App::get('/v1/avatars/credit-cards/:code')
|
|||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
|
||||
->inject('response')
|
||||
->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
|
||||
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
|
||||
|
||||
App::get('/v1/avatars/browsers/:code')
|
||||
->desc('Get Browser Icon')
|
||||
|
|
@ -113,7 +112,7 @@ App::get('/v1/avatars/browsers/:code')
|
|||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
|
||||
->inject('response')
|
||||
->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
|
||||
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
|
||||
|
||||
App::get('/v1/avatars/flags/:code')
|
||||
->desc('Get Country Flag')
|
||||
|
|
@ -131,7 +130,7 @@ App::get('/v1/avatars/flags/:code')
|
|||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
|
||||
->inject('response')
|
||||
->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
|
||||
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
|
||||
|
||||
App::get('/v1/avatars/image')
|
||||
->desc('Get Image from URL')
|
||||
|
|
@ -145,11 +144,10 @@ App::get('/v1/avatars/image')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
|
||||
->param('url', '', new URL(['http', 'https']), 'Image URL which you want to crop.')
|
||||
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000.', true)
|
||||
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000.', true)
|
||||
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000. Defaults to 400.', true)
|
||||
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000. Defaults to 400.', true)
|
||||
->inject('response')
|
||||
->action(function ($url, $width, $height, $response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (string $url, int $width, int $height, Response $response) {
|
||||
|
||||
$quality = 80;
|
||||
$output = 'png';
|
||||
|
|
@ -164,24 +162,23 @@ App::get('/v1/avatars/image')
|
|||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->send($data)
|
||||
;
|
||||
->send($data);
|
||||
}
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception('Imagick extension is missing', 500);
|
||||
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$fetch = @\file_get_contents($url, false);
|
||||
|
||||
if (!$fetch) {
|
||||
throw new Exception('Image not found', 404);
|
||||
throw new Exception('Image not found', 404, Exception::AVATAR_IMAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$image = new Image($fetch);
|
||||
} catch (\Exception$exception) {
|
||||
throw new Exception('Unable to parse image', 500);
|
||||
} catch (\Exception $exception) {
|
||||
throw new Exception('Unable to parse image', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$image->crop((int) $width, (int) $height);
|
||||
|
|
@ -197,7 +194,6 @@ App::get('/v1/avatars/image')
|
|||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'miss')
|
||||
->send($data);
|
||||
;
|
||||
|
||||
unset($image);
|
||||
});
|
||||
|
|
@ -215,8 +211,7 @@ App::get('/v1/avatars/favicon')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
|
||||
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.')
|
||||
->inject('response')
|
||||
->action(function ($url, $response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (string $url, Response $response) {
|
||||
|
||||
$width = 56;
|
||||
$height = 56;
|
||||
|
|
@ -233,12 +228,11 @@ App::get('/v1/avatars/favicon')
|
|||
->setContentType('image/png')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->send($data)
|
||||
;
|
||||
->send($data);
|
||||
}
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception('Imagick extension is missing', 500);
|
||||
throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$curl = \curl_init();
|
||||
|
|
@ -248,7 +242,8 @@ App::get('/v1/avatars/favicon')
|
|||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_USERAGENT => \sprintf(APP_USERAGENT,
|
||||
CURLOPT_USERAGENT => \sprintf(
|
||||
APP_USERAGENT,
|
||||
App::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
|
||||
),
|
||||
|
|
@ -259,7 +254,7 @@ App::get('/v1/avatars/favicon')
|
|||
\curl_close($curl);
|
||||
|
||||
if (!$html) {
|
||||
throw new Exception('Failed to fetch remote URL', 404);
|
||||
throw new Exception('Failed to fetch remote URL', 404, Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
|
|
@ -317,7 +312,7 @@ App::get('/v1/avatars/favicon')
|
|||
$data = @\file_get_contents($outputHref, false);
|
||||
|
||||
if (empty($data) || (\mb_substr($data, 0, 5) === '<html') || \mb_substr($data, 0, 5) === '<!doc') {
|
||||
throw new Exception('Favicon not found', 404);
|
||||
throw new Exception('Favicon not found', 404, Exception::AVATAR_ICON_NOT_FOUND);
|
||||
}
|
||||
|
||||
$cache->save($key, $data);
|
||||
|
|
@ -326,14 +321,13 @@ App::get('/v1/avatars/favicon')
|
|||
->setContentType('image/x-icon')
|
||||
->addHeader('Expires', $date)
|
||||
->addHeader('X-Appwrite-Cache', 'miss')
|
||||
->send($data)
|
||||
;
|
||||
->send($data);
|
||||
}
|
||||
|
||||
$fetch = @\file_get_contents($outputHref, false);
|
||||
|
||||
if (!$fetch) {
|
||||
throw new Exception('Icon not found', 404);
|
||||
throw new Exception('Icon not found', 404, Exception::AVATAR_ICON_NOT_FOUND);
|
||||
}
|
||||
|
||||
$image = new Image($fetch);
|
||||
|
|
@ -367,12 +361,11 @@ App::get('/v1/avatars/qr')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
|
||||
->param('text', '', new Text(512), 'Plain text to be converted to QR code image.')
|
||||
->param('size', 400, new Range(0, 1000), 'QR code size. Pass an integer between 0 to 1000. Defaults to 400.', true)
|
||||
->param('size', 400, new Range(1, 1000), 'QR code size. Pass an integer between 1 to 1000. Defaults to 400.', true)
|
||||
->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true)
|
||||
->param('download', false, new Boolean(true), 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true)
|
||||
->inject('response')
|
||||
->action(function ($text, $size, $margin, $download, $response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (string $text, int $size, int $margin, bool $download, Response $response) {
|
||||
|
||||
$download = ($download === '1' || $download === 'true' || $download === 1 || $download === true);
|
||||
$options = new QROptions([
|
||||
|
|
@ -394,8 +387,7 @@ App::get('/v1/avatars/qr')
|
|||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->send($image->output('png', 9))
|
||||
;
|
||||
->send($image->output('png', 9));
|
||||
});
|
||||
|
||||
App::get('/v1/avatars/initials')
|
||||
|
|
@ -416,9 +408,7 @@ App::get('/v1/avatars/initials')
|
|||
->param('background', '', new HexColor(), 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->action(function ($name, $width, $height, $color, $background, $response, $user) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
->action(function (string $name, int $width, int $height, string $color, string $background, Response $response, Document $user) {
|
||||
|
||||
$themes = [
|
||||
['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET
|
||||
|
|
@ -438,8 +428,8 @@ App::get('/v1/avatars/initials')
|
|||
$name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
|
||||
$words = \explode(' ', \strtoupper($name));
|
||||
// if there is no space, try to split by `_` underscore
|
||||
$words = (count($words) == 1 ) ? \explode('_', \strtoupper($name)) : $words;
|
||||
|
||||
$words = (count($words) == 1) ? \explode('_', \strtoupper($name)) : $words;
|
||||
|
||||
$initials = null;
|
||||
$code = 0;
|
||||
|
||||
|
|
@ -478,6 +468,5 @@ App::get('/v1/avatars/initials')
|
|||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->send($image->getImageBlob())
|
||||
;
|
||||
->send($image->getImageBlob());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?php
|
||||
|
||||
use Utopia\App;
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* 1. Map all objects, object-params, object-fields
|
||||
|
|
@ -12,12 +10,15 @@ use Utopia\App;
|
|||
* 6. Write tests!
|
||||
*/
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\App;
|
||||
|
||||
App::post('/v1/graphql')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['api', 'graphql'])
|
||||
->label('scope', 'public')
|
||||
->action(
|
||||
function () {
|
||||
throw new Exception('GraphQL support is coming soon!', 502);
|
||||
throw new Exception('GraphQL support is coming soon!', 502, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
|
||||
App::get('/v1/health')
|
||||
->desc('Get HTTP')
|
||||
|
|
@ -21,8 +23,7 @@ App::get('/v1/health')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$output = [
|
||||
'status' => 'pass',
|
||||
|
|
@ -40,8 +41,7 @@ App::get('/v1/health/version')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_VERSION)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION);
|
||||
});
|
||||
|
|
@ -59,9 +59,7 @@ App::get('/v1/health/db')
|
|||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('utopia')
|
||||
->action(function ($response, $utopia) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\App $utopia */
|
||||
->action(function (Response $response, App $utopia) {
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
|
|
@ -70,12 +68,12 @@ App::get('/v1/health/db')
|
|||
|
||||
// Run a small test to check the connection
|
||||
$statement = $db->prepare("SELECT 1;");
|
||||
|
||||
|
||||
$statement->closeCursor();
|
||||
|
||||
|
||||
$statement->execute();
|
||||
} catch (Exception $_e) {
|
||||
throw new Exception('Database is not available', 500);
|
||||
throw new Exception('Database is not available', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$output = [
|
||||
|
|
@ -99,17 +97,14 @@ App::get('/v1/health/cache')
|
|||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->inject('utopia')
|
||||
->action(function ($response, $utopia) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Redis */
|
||||
->action(function (Response $response, App $utopia) {
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
$redis = $utopia->getResource('cache');
|
||||
|
||||
if (!$redis->ping(true)) {
|
||||
throw new Exception('Cache is not available', 500);
|
||||
throw new Exception('Cache is not available', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$output = [
|
||||
|
|
@ -132,8 +127,7 @@ App::get('/v1/health/time')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_TIME)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
/*
|
||||
* Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server
|
||||
|
|
@ -147,7 +141,7 @@ App::get('/v1/health/time')
|
|||
\socket_connect($sock, $host, 123);
|
||||
|
||||
/* Send request */
|
||||
$msg = "\010".\str_repeat("\0", 47);
|
||||
$msg = "\010" . \str_repeat("\0", 47);
|
||||
|
||||
\socket_send($sock, $msg, \strlen($msg), 0);
|
||||
|
||||
|
|
@ -166,7 +160,7 @@ App::get('/v1/health/time')
|
|||
$diff = ($timestamp - \time());
|
||||
|
||||
if ($diff > $gap || $diff < ($gap * -1)) {
|
||||
throw new Exception('Server time gaps detected');
|
||||
throw new Exception('Server time gaps detected', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$output = [
|
||||
|
|
@ -190,8 +184,7 @@ App::get('/v1/health/queue/webhooks')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->dynamic(new Document([ 'size' => Resque::size(Event::WEBHOOK_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
|
@ -208,30 +201,11 @@ App::get('/v1/health/queue/logs')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->dynamic(new Document([ 'size' => Resque::size(Event::AUDITS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/usage')
|
||||
->desc('Get Usage Queue')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getQueueUsage')
|
||||
->label('sdk.description', '/docs/references/health/get-queue-usage.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
|
||||
$response->dynamic(new Document([ 'size' => Resque::size(Event::USAGE_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/certificates')
|
||||
->desc('Get Certificates Queue')
|
||||
->groups(['api', 'health'])
|
||||
|
|
@ -244,8 +218,7 @@ App::get('/v1/health/queue/certificates')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->dynamic(new Document([ 'size' => Resque::size(Event::CERTIFICATES_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
|
@ -262,8 +235,7 @@ App::get('/v1/health/queue/functions')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->dynamic(new Document([ 'size' => Resque::size(Event::FUNCTIONS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
|
@ -280,25 +252,26 @@ App::get('/v1/health/storage/local')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
foreach ([
|
||||
foreach (
|
||||
[
|
||||
'Uploads' => APP_STORAGE_UPLOADS,
|
||||
'Cache' => APP_STORAGE_CACHE,
|
||||
'Config' => APP_STORAGE_CONFIG,
|
||||
'Certs' => APP_STORAGE_CERTIFICATES
|
||||
] as $key => $volume) {
|
||||
] as $key => $volume
|
||||
) {
|
||||
$device = new Local($volume);
|
||||
|
||||
if (!\is_readable($device->getRoot())) {
|
||||
throw new Exception('Device '.$key.' dir is not readable');
|
||||
throw new Exception('Device ' . $key . ' dir is not readable', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if (!\is_writable($device->getRoot())) {
|
||||
throw new Exception('Device '.$key.' dir is not writable');
|
||||
throw new Exception('Device ' . $key . ' dir is not writable', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,8 +295,7 @@ App::get('/v1/health/anti-virus')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_ANTIVIRUS)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$output = [
|
||||
'status' => '',
|
||||
|
|
@ -334,14 +306,16 @@ App::get('/v1/health/anti-virus')
|
|||
$output['status'] = 'disabled';
|
||||
$output['version'] = '';
|
||||
} else {
|
||||
$antivirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
|
||||
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310));
|
||||
$antivirus = new Network(
|
||||
App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
|
||||
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310)
|
||||
);
|
||||
|
||||
try {
|
||||
$output['version'] = @$antivirus->version();
|
||||
$output['status'] = (@$antivirus->ping()) ? 'pass' : 'fail';
|
||||
} catch( \Exception $e) {
|
||||
throw new Exception('Antivirus is not available', 500);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception('Antivirus is not available', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -358,11 +332,9 @@ App::get('/v1/health/stats') // Currently only used internally
|
|||
->label('docs', false)
|
||||
->inject('response')
|
||||
->inject('register')
|
||||
->action(function ($response, $register) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
->inject('deviceFiles')
|
||||
->action(function (Response $response, Registry $register, Device $deviceFiles) {
|
||||
|
||||
$device = Storage::getDevice('files');
|
||||
$cache = $register->get('cache');
|
||||
|
||||
$cacheStats = $cache->info();
|
||||
|
|
@ -370,9 +342,9 @@ App::get('/v1/health/stats') // Currently only used internally
|
|||
$response
|
||||
->json([
|
||||
'storage' => [
|
||||
'used' => Storage::human($device->getDirectorySize($device->getRoot().'/')),
|
||||
'partitionTotal' => Storage::human($device->getPartitionTotalSpace()),
|
||||
'partitionFree' => Storage::human($device->getPartitionFreeSpace()),
|
||||
'used' => Storage::human($deviceFiles->getDirectorySize($deviceFiles->getRoot() . '/')),
|
||||
'partitionTotal' => Storage::human($deviceFiles->getPartitionTotalSpace()),
|
||||
'partitionFree' => Storage::human($deviceFiles->getPartitionFreeSpace()),
|
||||
],
|
||||
'cache' => [
|
||||
'uptime' => $cacheStats['uptime_in_seconds'] ?? 0,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
<?php
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Locale\Locale;
|
||||
|
||||
App::get('/v1/locale')
|
||||
->desc('Get User Locale')
|
||||
|
|
@ -20,12 +23,7 @@ App::get('/v1/locale')
|
|||
->inject('response')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function ($request, $response, $locale, $geodb) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
/** @var MaxMind\Db\Reader $geodb */
|
||||
|
||||
->action(function (Request $request, Response $response, Locale $locale, Reader $geodb) {
|
||||
$eu = Config::getParam('locale-eu');
|
||||
$currencies = Config::getParam('locale-currencies');
|
||||
$output = [];
|
||||
|
|
@ -40,8 +38,8 @@ App::get('/v1/locale')
|
|||
|
||||
if ($record) {
|
||||
$output['countryCode'] = $record['country']['iso_code'];
|
||||
$output['country'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
$output['continent'] = $locale->getText('continents.'.strtolower($record['continent']['code']), $locale->getText('locale.country.unknown'));
|
||||
$output['country'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
$output['continent'] = $locale->getText('continents.' . strtolower($record['continent']['code']), $locale->getText('locale.country.unknown'));
|
||||
$output['continent'] = (isset($continents[$record['continent']['code']])) ? $continents[$record['continent']['code']] : $locale->getText('locale.country.unknown');
|
||||
$output['continentCode'] = $record['continent']['code'];
|
||||
$output['eu'] = (\in_array($record['country']['iso_code'], $eu)) ? true : false;
|
||||
|
|
@ -63,8 +61,8 @@ App::get('/v1/locale')
|
|||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'public, max-age='.$time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
;
|
||||
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
|
||||
});
|
||||
|
|
@ -82,16 +80,13 @@ App::get('/v1/locale/countries')
|
|||
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function ($response, $locale) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
$list = Config::getParam('locale-countries'); /* @var $list array */
|
||||
$output = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
$output[] = new Document([
|
||||
'name' => $locale->getText('countries.'.strtolower($value)),
|
||||
'name' => $locale->getText('countries.' . strtolower($value)),
|
||||
'code' => $value,
|
||||
]);
|
||||
}
|
||||
|
|
@ -100,7 +95,7 @@ App::get('/v1/locale/countries')
|
|||
return strcmp($a->getAttribute('name'), $b->getAttribute('name'));
|
||||
});
|
||||
|
||||
$response->dynamic(new Document(['countries' => $output, 'sum' => \count($output)]), Response::MODEL_COUNTRY_LIST);
|
||||
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/countries/eu')
|
||||
|
|
@ -116,17 +111,14 @@ App::get('/v1/locale/countries/eu')
|
|||
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function ($response, $locale) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
$eu = Config::getParam('locale-eu');
|
||||
$output = [];
|
||||
|
||||
foreach ($eu as $code) {
|
||||
if ($locale->getText('countries.'.strtolower($code), false) !== false) {
|
||||
if ($locale->getText('countries.' . strtolower($code), false) !== false) {
|
||||
$output[] = new Document([
|
||||
'name' => $locale->getText('countries.'.strtolower($code)),
|
||||
'name' => $locale->getText('countries.' . strtolower($code)),
|
||||
'code' => $code,
|
||||
]);
|
||||
}
|
||||
|
|
@ -136,7 +128,7 @@ App::get('/v1/locale/countries/eu')
|
|||
return strcmp($a->getAttribute('name'), $b->getAttribute('name'));
|
||||
});
|
||||
|
||||
$response->dynamic(new Document(['countries' => $output, 'sum' => \count($output)]), Response::MODEL_COUNTRY_LIST);
|
||||
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/countries/phones')
|
||||
|
|
@ -152,26 +144,23 @@ App::get('/v1/locale/countries/phones')
|
|||
->label('sdk.response.model', Response::MODEL_PHONE_LIST)
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function ($response, $locale) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
$list = Config::getParam('locale-phones'); /* @var $list array */
|
||||
$output = [];
|
||||
|
||||
\asort($list);
|
||||
|
||||
foreach ($list as $code => $name) {
|
||||
if ($locale->getText('countries.'.strtolower($code), false) !== false) {
|
||||
if ($locale->getText('countries.' . strtolower($code), false) !== false) {
|
||||
$output[] = new Document([
|
||||
'code' => '+'.$list[$code],
|
||||
'code' => '+' . $list[$code],
|
||||
'countryCode' => $code,
|
||||
'countryName' => $locale->getText('countries.'.strtolower($code)),
|
||||
'countryName' => $locale->getText('countries.' . strtolower($code)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document(['phones' => $output, 'sum' => \count($output)]), Response::MODEL_PHONE_LIST);
|
||||
$response->dynamic(new Document(['phones' => $output, 'total' => \count($output)]), Response::MODEL_PHONE_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/continents')
|
||||
|
|
@ -187,15 +176,12 @@ App::get('/v1/locale/continents')
|
|||
->label('sdk.response.model', Response::MODEL_CONTINENT_LIST)
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->action(function ($response, $locale) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
$list = Config::getParam('locale-continents');
|
||||
|
||||
$list = Config::getParam('locale-continents'); /* @var $list array */
|
||||
|
||||
foreach ($list as $key => $value) {
|
||||
foreach ($list as $value) {
|
||||
$output[] = new Document([
|
||||
'name' => $locale->getText('continents.'.strtolower($value)),
|
||||
'name' => $locale->getText('continents.' . strtolower($value)),
|
||||
'code' => $value,
|
||||
]);
|
||||
}
|
||||
|
|
@ -204,7 +190,7 @@ App::get('/v1/locale/continents')
|
|||
return strcmp($a->getAttribute('name'), $b->getAttribute('name'));
|
||||
});
|
||||
|
||||
$response->dynamic(new Document(['continents' => $output, 'sum' => \count($output)]), Response::MODEL_CONTINENT_LIST);
|
||||
$response->dynamic(new Document(['continents' => $output, 'total' => \count($output)]), Response::MODEL_CONTINENT_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/currencies')
|
||||
|
|
@ -219,14 +205,12 @@ App::get('/v1/locale/currencies')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_CURRENCY_LIST)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
|
||||
->action(function (Response $response) {
|
||||
$list = Config::getParam('locale-currencies');
|
||||
|
||||
$list = array_map(fn($node) => new Document($node), $list);
|
||||
$list = array_map(fn ($node) => new Document($node), $list);
|
||||
|
||||
$response->dynamic(new Document(['currencies' => $list, 'sum' => \count($list)]), Response::MODEL_CURRENCY_LIST);
|
||||
$response->dynamic(new Document(['currencies' => $list, 'total' => \count($list)]), Response::MODEL_CURRENCY_LIST);
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -242,12 +226,10 @@ App::get('/v1/locale/languages')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LANGUAGE_LIST)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
|
||||
->action(function (Response $response) {
|
||||
$list = Config::getParam('locale-languages');
|
||||
|
||||
$list = array_map(fn ($node) => new Document($node), $list);
|
||||
|
||||
$response->dynamic(new Document(['languages' => $list, 'sum' => \count($list)]), Response::MODEL_LANGUAGE_LIST);
|
||||
});
|
||||
$response->dynamic(new Document(['languages' => $list, 'total' => \count($list)]), Response::MODEL_LANGUAGE_LIST);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@
|
|||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\Network\Validator\Domain as DomainValidator;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
|
|
@ -17,19 +21,19 @@ use Utopia\Database\Query;
|
|||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Registry\Registry;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::init(function ($project) {
|
||||
/** @var Utopia\Database\Document $project */
|
||||
App::init(function (Document $project) {
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
throw new Exception('Access to this API is forbidden.', 401);
|
||||
throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN);
|
||||
}
|
||||
}, ['project'], 'projects');
|
||||
|
||||
|
|
@ -58,15 +62,12 @@ App::post('/v1/projects')
|
|||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForProject')
|
||||
->action(function ($projectId, $name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForProject) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
->action(function (string $projectId, string $name, string $teamId, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Database $dbForProject) {
|
||||
|
||||
$team = $dbForConsole->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auth = Config::getParam('auth', []);
|
||||
|
|
@ -76,6 +77,9 @@ 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,
|
||||
'$read' => ['team:' . $teamId],
|
||||
|
|
@ -94,17 +98,17 @@ App::post('/v1/projects')
|
|||
'legalTaxId' => $legalTaxId,
|
||||
'services' => new stdClass(),
|
||||
'platforms' => null,
|
||||
'providers' => [],
|
||||
'authProviders' => [],
|
||||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'domains' => null,
|
||||
'auths' => $auths,
|
||||
'search' => implode(' ', [$projectId, $name]),
|
||||
]));
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
$collections = Config::getParam('collections', []); /** @var array $collections */
|
||||
|
||||
$dbForProject->setNamespace('_project_' . $project->getId());
|
||||
$dbForProject->setNamespace("_{$project->getId()}");
|
||||
$dbForProject->create('appwrite');
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
|
|
@ -114,6 +118,9 @@ App::post('/v1/projects')
|
|||
$adapter->setup();
|
||||
|
||||
foreach ($collections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
|
||||
|
|
@ -126,6 +133,8 @@ App::post('/v1/projects')
|
|||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -164,15 +173,13 @@ App::get('/v1/projects')
|
|||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForConsole) {
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorProject = $dbForConsole->getDocument('projects', $cursor);
|
||||
|
||||
if ($cursorProject->isEmpty()) {
|
||||
throw new Exception("Project '{$cursor}' for the 'cursor' value not found.", 400);
|
||||
throw new Exception("Project '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,11 +190,11 @@ App::get('/v1/projects')
|
|||
}
|
||||
|
||||
$results = $dbForConsole->find('projects', $queries, $limit, $offset, [], [$orderType], $cursorProject ?? null, $cursorDirection);
|
||||
$sum = $dbForConsole->count('projects', $queries, APP_LIMIT_COUNT);
|
||||
$total = $dbForConsole->count('projects', $queries, APP_LIMIT_COUNT);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'projects' => $results,
|
||||
'sum' => $sum,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_PROJECT_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -204,14 +211,12 @@ App::get('/v1/projects/:projectId')
|
|||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
|
|
@ -233,16 +238,12 @@ App::get('/v1/projects/:projectId/usage')
|
|||
->inject('dbForConsole')
|
||||
->inject('dbForProject')
|
||||
->inject('register')
|
||||
->action(function ($projectId, $range, $response, $dbForConsole, $dbForProject, $register) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
->action(function (string $projectId, string $range, Response $response, Database $dbForConsole, Database $dbForProject, Registry $register) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$usage = [];
|
||||
|
|
@ -266,7 +267,7 @@ App::get('/v1/projects/:projectId/usage')
|
|||
],
|
||||
];
|
||||
|
||||
$dbForProject->setNamespace('_project_' . $projectId);
|
||||
$dbForProject->setNamespace("_{$projectId}");
|
||||
|
||||
$metrics = [
|
||||
'requests',
|
||||
|
|
@ -280,7 +281,7 @@ App::get('/v1/projects/:projectId/usage')
|
|||
|
||||
$stats = [];
|
||||
|
||||
Authorization::skip(function() use ($dbForProject, $periods, $range, $metrics, &$stats) {
|
||||
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$limit = $periods[$range]['limit'];
|
||||
$period = $periods[$range]['period'];
|
||||
|
|
@ -302,7 +303,7 @@ App::get('/v1/projects/:projectId/usage')
|
|||
$backfill = $limit - \count($requestDocs);
|
||||
while ($backfill > 0) {
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match($period) { // convert period to seconds for unix timestamp math
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1d' => 86400,
|
||||
};
|
||||
|
|
@ -354,14 +355,12 @@ App::patch('/v1/projects/:projectId')
|
|||
->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
|
|
@ -375,8 +374,7 @@ App::patch('/v1/projects/:projectId')
|
|||
->setAttribute('legalCity', $legalCity)
|
||||
->setAttribute('legalAddress', $legalAddress)
|
||||
->setAttribute('legalTaxId', $legalTaxId)
|
||||
->setAttribute('search', implode(' ', [$projectId, $name]))
|
||||
);
|
||||
->setAttribute('search', implode(' ', [$projectId, $name])));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
|
@ -392,19 +390,16 @@ App::patch('/v1/projects/:projectId/service')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('service', '', new WhiteList(array_keys(array_filter(Config::getParam('services'), function ($element) {return $element['optional'];})), true), 'Service name.')
|
||||
->param('service', '', new WhiteList(array_keys(array_filter(Config::getParam('services'), fn($element) => $element['optional'])), true), 'Service name.')
|
||||
->param('status', null, new Boolean(), 'Service status.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $service, $status, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Boolean $status */
|
||||
->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$services = $project->getAttribute('services', []);
|
||||
|
|
@ -431,21 +426,19 @@ App::patch('/v1/projects/:projectId/oauth2')
|
|||
->param('secret', '', new text(512), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $provider, $appId, $secret, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $provider, string $appId, string $secret, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$providers = $project->getAttribute('providers', []);
|
||||
$providers = $project->getAttribute('authProviders', []);
|
||||
$providers[$provider . 'Appid'] = $appId;
|
||||
$providers[$provider . 'Secret'] = $secret;
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('providers', $providers));
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('authProviders', $providers));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
|
@ -464,22 +457,19 @@ App::patch('/v1/projects/:projectId/auth/limit')
|
|||
->param('limit', false, new Range(0, APP_LIMIT_USERS), 'Set the max number of users allowed in this project. Use 0 for unlimited.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $limit, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['limit'] = $limit;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths)
|
||||
);
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
|
@ -499,9 +489,7 @@ App::patch('/v1/projects/:projectId/auth/:method')
|
|||
->param('status', false, new Boolean(true), 'Set the status of this auth method.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $method, $status, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$auth = Config::getParam('auth')[$method] ?? [];
|
||||
|
|
@ -509,7 +497,7 @@ App::patch('/v1/projects/:projectId/auth/:method')
|
|||
$status = ($status === '1' || $status === 'true' || $status === 1 || $status === true);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
|
|
@ -535,33 +523,29 @@ App::delete('/v1/projects/:projectId')
|
|||
->inject('user')
|
||||
->inject('dbForConsole')
|
||||
->inject('deletes')
|
||||
->action(function ($projectId, $password, $response, $user, $dbForConsole, $deletes) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Appwrite\Event\Event $deletes */
|
||||
->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Delete $deletes) {
|
||||
|
||||
if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
|
||||
throw new Exception('Invalid credentials', 401);
|
||||
throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deletes
|
||||
->setParam('type', DELETE_TYPE_DOCUMENT)
|
||||
->setParam('document', $project)
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($project)
|
||||
;
|
||||
|
||||
if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) {
|
||||
throw new Exception('Failed to remove project team from DB', 500);
|
||||
throw new Exception('Failed to remove project team from DB', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if (!$dbForConsole->deleteDocument('projects', $projectId)) {
|
||||
throw new Exception('Failed to remove project from DB', 500);
|
||||
throw new Exception('Failed to remove project from DB', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$response->noContent();
|
||||
|
|
@ -581,24 +565,22 @@ App::post('/v1/projects/:projectId/webhooks')
|
|||
->label('sdk.response.model', Response::MODEL_WEBHOOK)
|
||||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.')
|
||||
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
|
||||
->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)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $name, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
|
||||
$security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
$webhook = new Document([
|
||||
'$id' => $dbForConsole->getId(),
|
||||
|
|
@ -634,23 +616,21 @@ App::get('/v1/projects/:projectId/webhooks')
|
|||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhooks = $dbForConsole->find('webhooks', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
]);
|
||||
], 5000);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'webhooks' => $webhooks,
|
||||
'sum' => count($webhooks),
|
||||
'total' => count($webhooks),
|
||||
]), Response::MODEL_WEBHOOK_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -668,14 +648,12 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
->param('webhookId', null, new UID(), 'Webhook unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $webhookId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $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);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
|
|
@ -684,7 +662,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
throw new Exception('Webhook not found', 404);
|
||||
throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
|
|
@ -703,21 +681,19 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->param('webhookId', null, new UID(), 'Webhook unique ID.')
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.')
|
||||
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
|
||||
->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)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\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);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
|
||||
|
|
@ -728,7 +704,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
]);
|
||||
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
throw new Exception('Webhook not found', 404);
|
||||
throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook
|
||||
|
|
@ -760,14 +736,12 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
->param('webhookId', null, new UID(), 'Webhook unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $webhookId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $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);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook = $dbForConsole->findOne('webhooks', [
|
||||
|
|
@ -775,8 +749,8 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
]);
|
||||
|
||||
if($webhook === false || $webhook->isEmpty()) {
|
||||
throw new Exception('Webhook not found', 404);
|
||||
if ($webhook === false || $webhook->isEmpty()) {
|
||||
throw new Exception('Webhook not found', 404, Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('webhooks', $webhook->getId());
|
||||
|
|
@ -800,17 +774,15 @@ App::post('/v1/projects/:projectId/keys')
|
|||
->label('sdk.response.model', Response::MODEL_KEY)
|
||||
->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)), 'Key scopes list.')
|
||||
->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.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $name, $scopes, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $name, array $scopes, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$key = new Document([
|
||||
|
|
@ -844,14 +816,12 @@ App::get('/v1/projects/:projectId/keys')
|
|||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$keys = $dbForConsole->find('keys', [
|
||||
|
|
@ -860,7 +830,7 @@ App::get('/v1/projects/:projectId/keys')
|
|||
|
||||
$response->dynamic(new Document([
|
||||
'keys' => $keys,
|
||||
'sum' => count($keys),
|
||||
'total' => count($keys),
|
||||
]), Response::MODEL_KEY_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -878,14 +848,12 @@ App::get('/v1/projects/:projectId/keys/:keyId')
|
|||
->param('keyId', null, new UID(), 'Key unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $keyId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
|
|
@ -894,7 +862,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
|
|||
]);
|
||||
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
throw new Exception('Key not found', 404);
|
||||
throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($key, Response::MODEL_KEY);
|
||||
|
|
@ -913,17 +881,15 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
|||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->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)), 'Key scopes list')
|
||||
->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.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $keyId, $name, $scopes, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
|
|
@ -932,7 +898,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
|||
]);
|
||||
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
throw new Exception('Key not found', 404);
|
||||
throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$key
|
||||
|
|
@ -960,14 +926,12 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
|
|||
->param('keyId', null, new UID(), 'Key unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $keyId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$key = $dbForConsole->findOne('keys', [
|
||||
|
|
@ -975,8 +939,8 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
|
|||
new Query('projectId', Query::TYPE_EQUAL, [$project->getId()])
|
||||
]);
|
||||
|
||||
if($key === false || $key->isEmpty()) {
|
||||
throw new Exception('Key not found', 404);
|
||||
if ($key === false || $key->isEmpty()) {
|
||||
throw new Exception('Key not found', 404, Exception::KEY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('keys', $key->getId());
|
||||
|
|
@ -999,21 +963,18 @@ App::post('/v1/projects/:projectId/platforms')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PLATFORM)
|
||||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->param('type', null, new WhiteList(['web', 'flutter-ios', 'flutter-android', 'flutter-linux', 'flutter-macos', 'flutter-windows', 'apple-ios', 'apple-macos', 'apple-watchos', 'apple-tvos', 'android', 'unity'], true), 'Platform type.')
|
||||
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.')
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
|
||||
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
|
||||
->param('hostname', '', new Text(256), 'Platform client hostname. Max length: 256 chars.', true)
|
||||
->param('hostname', '', new Hostname(), 'Platform client hostname. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $type, $name, $key, $store, $hostname, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
|
||||
->action(function (string $projectId, string $type, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) {
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform = new Document([
|
||||
|
|
@ -1051,14 +1012,12 @@ App::get('/v1/projects/:projectId/platforms')
|
|||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platforms = $dbForConsole->find('platforms', [
|
||||
|
|
@ -1067,7 +1026,7 @@ App::get('/v1/projects/:projectId/platforms')
|
|||
|
||||
$response->dynamic(new Document([
|
||||
'platforms' => $platforms,
|
||||
'sum' => count($platforms),
|
||||
'total' => count($platforms),
|
||||
]), Response::MODEL_PLATFORM_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -1085,14 +1044,12 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
|
|||
->param('platformId', null, new UID(), 'Platform unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $platformId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
|
|
@ -1101,7 +1058,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
|
|||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
throw new Exception('Platform not found', 404);
|
||||
throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
|
|
@ -1122,17 +1079,14 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
|
|||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('key', '', new Text(256), 'Package name for android or bundle ID for iOS. Max length: 256 chars.', true)
|
||||
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
|
||||
->param('hostname', '', new Text(256), 'Platform client URL. Max length: 256 chars.', true)
|
||||
->param('hostname', '', new Hostname(), 'Platform client URL. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $platformId, $name, $key, $store, $hostname, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
|
||||
->action(function (string $projectId, string $platformId, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) {
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
|
|
@ -1141,7 +1095,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
|
|||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
throw new Exception('Platform not found', 404);
|
||||
throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform
|
||||
|
|
@ -1172,14 +1126,12 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
|
|||
->param('platformId', null, new UID(), 'Platform unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $platformId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform = $dbForConsole->findOne('platforms', [
|
||||
|
|
@ -1188,7 +1140,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
|
|||
]);
|
||||
|
||||
if ($platform === false || $platform->isEmpty()) {
|
||||
throw new Exception('Platform not found', 404);
|
||||
throw new Exception('Platform not found', 404, Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('platforms', $platformId);
|
||||
|
|
@ -1214,14 +1166,12 @@ App::post('/v1/projects/:projectId/domains')
|
|||
->param('domain', null, new DomainValidator(), 'Domain name.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $domain, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $domain, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$document = $dbForConsole->findOne('domains', [
|
||||
|
|
@ -1230,13 +1180,13 @@ App::post('/v1/projects/:projectId/domains')
|
|||
]);
|
||||
|
||||
if ($document && !$document->isEmpty()) {
|
||||
throw new Exception('Domain already exists', 409);
|
||||
throw new Exception('Domain already exists', 409, Exception::DOMAIN_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
|
||||
if (!$target->isKnown() || $target->isTest()) {
|
||||
throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500);
|
||||
throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$domain = new Domain($domain);
|
||||
|
|
@ -1275,14 +1225,12 @@ App::get('/v1/projects/:projectId/domains')
|
|||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$domains = $dbForConsole->find('domains', [
|
||||
|
|
@ -1291,7 +1239,7 @@ App::get('/v1/projects/:projectId/domains')
|
|||
|
||||
$response->dynamic(new Document([
|
||||
'domains' => $domains,
|
||||
'sum' => count($domains),
|
||||
'total' => count($domains),
|
||||
]), Response::MODEL_DOMAIN_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -1309,14 +1257,12 @@ App::get('/v1/projects/:projectId/domains/:domainId')
|
|||
->param('domainId', null, new UID(), 'Domain unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $domainId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
|
|
@ -1325,7 +1271,7 @@ App::get('/v1/projects/:projectId/domains/:domainId')
|
|||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
throw new Exception('Domain not found', 404);
|
||||
throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($domain, Response::MODEL_DOMAIN);
|
||||
|
|
@ -1345,14 +1291,12 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
|
|||
->param('domainId', null, new UID(), 'Domain unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function ($projectId, $domainId, $response, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
|
|
@ -1361,13 +1305,13 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
|
|||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
throw new Exception('Domain not found', 404);
|
||||
throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
|
||||
if (!$target->isKnown() || $target->isTest()) {
|
||||
throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500);
|
||||
throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if ($domain->getAttribute('verification') === true) {
|
||||
|
|
@ -1377,7 +1321,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
|
|||
$validator = new CNAME($target->get()); // Verify Domain with DNS records
|
||||
|
||||
if (!$validator->isValid($domain->getAttribute('domain', ''))) {
|
||||
throw new Exception('Failed to verify domain', 401);
|
||||
throw new Exception('Failed to verify domain', 401, Exception::DOMAIN_VERIFICATION_FAILED);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1385,10 +1329,10 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
|
|||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
|
||||
// Issue a TLS certificate when domain is verified
|
||||
Resque::enqueue('v1-certificates', 'CertificatesV1', [
|
||||
'document' => $domain->getArrayCopy(),
|
||||
'domain' => $domain->getAttribute('domain'),
|
||||
]);
|
||||
$event = new Certificate();
|
||||
$event
|
||||
->setDomain($domain)
|
||||
->trigger();
|
||||
|
||||
$response->dynamic($domain, Response::MODEL_DOMAIN);
|
||||
});
|
||||
|
|
@ -1407,14 +1351,12 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
|||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('deletes')
|
||||
->action(function ($projectId, $domainId, $response, $dbForConsole, $deletes) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, Delete $deletes) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$domain = $dbForConsole->findOne('domains', [
|
||||
|
|
@ -1423,7 +1365,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
|||
]);
|
||||
|
||||
if ($domain === false || $domain->isEmpty()) {
|
||||
throw new Exception('Domain not found', 404);
|
||||
throw new Exception('Domain not found', 404, Exception::DOMAIN_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('domains', $domain->getId());
|
||||
|
|
@ -1431,9 +1373,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
|||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
|
||||
$deletes
|
||||
->setParam('type', DELETE_TYPE_CERTIFICATES)
|
||||
->setParam('document', $domain)
|
||||
;
|
||||
->setType(DELETE_TYPE_CERTIFICATES)
|
||||
->setDocument($domain);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,12 +2,20 @@
|
|||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Audit as EventAudit;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
|
@ -17,7 +25,7 @@ use Utopia\Database\Query;
|
|||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\ArrayList;
|
||||
|
|
@ -26,7 +34,7 @@ use Utopia\Validator\WhiteList;
|
|||
App::post('/v1/teams')
|
||||
->desc('Create Team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.create')
|
||||
->label('event', 'teams.[teamId].create')
|
||||
->label('scope', 'teams.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
|
|
@ -37,16 +45,13 @@ App::post('/v1/teams')
|
|||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->param('teamId', '', new CustomId(), 'Team 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('name', null, new Text(128), 'Team name. Max length: 128 chars.')
|
||||
->param('roles', ['owner'], new ArrayList(new Key()), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.', true)
|
||||
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function ($teamId, $name, $roles, $response, $user, $dbForProject, $events) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $events */
|
||||
->inject('audits')
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $events, Event $audits) {
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
|
@ -54,10 +59,10 @@ App::post('/v1/teams')
|
|||
$teamId = $teamId == 'unique()' ? $dbForProject->getId() : $teamId;
|
||||
$team = Authorization::skip(fn() => $dbForProject->createDocument('teams', new Document([
|
||||
'$id' => $teamId ,
|
||||
'$read' => ['team:'.$teamId],
|
||||
'$write' => ['team:'.$teamId .'/owner'],
|
||||
'$read' => ['team:' . $teamId],
|
||||
'$write' => ['team:' . $teamId . '/owner'],
|
||||
'name' => $name,
|
||||
'sum' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
'dateCreated' => \time(),
|
||||
'search' => implode(' ', [$teamId, $name]),
|
||||
])));
|
||||
|
|
@ -66,8 +71,8 @@ App::post('/v1/teams')
|
|||
$membershipId = $dbForProject->getId();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$read' => ['user:'.$user->getId(), 'team:'.$team->getId()],
|
||||
'$write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'],
|
||||
'$read' => ['user:' . $user->getId(), 'team:' . $team->getId()],
|
||||
'$write' => ['user:' . $user->getId(), 'team:' . $team->getId() . '/owner'],
|
||||
'userId' => $user->getId(),
|
||||
'teamId' => $team->getId(),
|
||||
'roles' => $roles,
|
||||
|
|
@ -79,16 +84,21 @@ App::post('/v1/teams')
|
|||
]);
|
||||
|
||||
$membership = $dbForProject->createDocument('memberships', $membership);
|
||||
|
||||
// Attach user to team
|
||||
$user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
}
|
||||
|
||||
$events->setParam('teamId', $team->getId());
|
||||
|
||||
if (!empty($user->getId())) {
|
||||
$events->setParam('userId', $user->getId());
|
||||
}
|
||||
|
||||
$audits
|
||||
->setParam('event', 'teams.create')
|
||||
->setParam('resource', 'team/' . $teamId)
|
||||
->setParam('data', $team->getArrayCopy())
|
||||
;
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
|
@ -112,15 +122,13 @@ App::get('/v1/teams')
|
|||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorTeam = $dbForProject->getDocument('teams', $cursor);
|
||||
|
||||
if ($cursorTeam->isEmpty()) {
|
||||
throw new Exception("Team '{$cursor}' for the 'cursor' value not found.", 400);
|
||||
throw new Exception("Team '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,11 +139,11 @@ App::get('/v1/teams')
|
|||
}
|
||||
|
||||
$results = $dbForProject->find('teams', $queries, $limit, $offset, [], [$orderType], $cursorTeam ?? null, $cursorDirection);
|
||||
$sum = $dbForProject->count('teams', $queries, APP_LIMIT_COUNT);
|
||||
$total = $dbForProject->count('teams', $queries, APP_LIMIT_COUNT);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'teams' => $results,
|
||||
'sum' => $sum,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_TEAM_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -153,14 +161,12 @@ App::get('/v1/teams/:teamId')
|
|||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function ($teamId, $response, $dbForProject) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
|
|
@ -169,7 +175,7 @@ App::get('/v1/teams/:teamId')
|
|||
App::put('/v1/teams/:teamId')
|
||||
->desc('Update Team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.update')
|
||||
->label('event', 'teams.[teamId].update')
|
||||
->label('scope', 'teams.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
|
|
@ -182,20 +188,22 @@ App::put('/v1/teams/:teamId')
|
|||
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function ($teamId, $name, $response, $dbForProject) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
->inject('events')
|
||||
->inject('audits')
|
||||
->action(function (string $teamId, string $name, Response $response, Database $dbForProject, Event $events, EventAudit $audits) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = $dbForProject->updateDocument('teams', $team->getId(),$team
|
||||
$team = $dbForProject->updateDocument('teams', $team->getId(), $team
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', implode(' ', [$teamId, $name]))
|
||||
);
|
||||
->setAttribute('search', implode(' ', [$teamId, $name])));
|
||||
|
||||
$events->setParam('teamId', $team->getId());
|
||||
$audits->setResource('team/' . $team->getId());
|
||||
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
|
@ -203,7 +211,7 @@ App::put('/v1/teams/:teamId')
|
|||
App::delete('/v1/teams/:teamId')
|
||||
->desc('Delete Team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.delete')
|
||||
->label('event', 'teams.[teamId].delete')
|
||||
->label('scope', 'teams.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
|
|
@ -216,16 +224,13 @@ App::delete('/v1/teams/:teamId')
|
|||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->action(function ($teamId, $response, $dbForProject, $events, $deletes) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $events */
|
||||
/** @var Appwrite\Event\Event $deletes */
|
||||
->inject('audits')
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject, Event $events, Delete $deletes, EventAudit $audits) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$memberships = $dbForProject->find('memberships', [
|
||||
|
|
@ -235,21 +240,27 @@ App::delete('/v1/teams/:teamId')
|
|||
// TODO delete all members individually from the user object
|
||||
foreach ($memberships as $membership) {
|
||||
if (!$dbForProject->deleteDocument('memberships', $membership->getId())) {
|
||||
throw new Exception('Failed to remove membership for team from DB', 500);
|
||||
throw new Exception('Failed to remove membership for team from DB', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('teams', $teamId)) {
|
||||
throw new Exception('Failed to remove team from DB', 500);
|
||||
throw new Exception('Failed to remove team from DB', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$deletes
|
||||
->setParam('type', DELETE_TYPE_DOCUMENT)
|
||||
->setParam('document', $team)
|
||||
;
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($team);
|
||||
|
||||
$events
|
||||
->setParam('eventData', $response->output($team, Response::MODEL_TEAM))
|
||||
->setParam('teamId', $team->getId())
|
||||
->setPayload($response->output($team, Response::MODEL_TEAM))
|
||||
;
|
||||
|
||||
$audits
|
||||
->setParam('event', 'teams.delete')
|
||||
->setParam('resource', 'team/' . $teamId)
|
||||
->setParam('data', $team->getArrayCopy())
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
|
|
@ -258,7 +269,7 @@ App::delete('/v1/teams/:teamId')
|
|||
App::post('/v1/teams/:teamId/memberships')
|
||||
->desc('Create Team Membership')
|
||||
->groups(['api', 'teams', 'auth'])
|
||||
->label('event', 'teams.memberships.create')
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].create')
|
||||
->label('scope', 'teams.write')
|
||||
->label('auth.type', 'invites')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
|
|
@ -271,8 +282,8 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->label('abuse-limit', 10)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('email', '', new Email(), 'Email of the new team member.')
|
||||
->param('roles', [], new ArrayList(new Key()), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.')
|
||||
->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
|
|
@ -281,40 +292,34 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->inject('locale')
|
||||
->inject('audits')
|
||||
->inject('mails')
|
||||
->action(function ($teamId, $email, $roles, $url, $name, $response, $project, $user, $dbForProject, $locale, $audits, $mails) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
/** @var Appwrite\Event\Event $mails */
|
||||
|
||||
if(empty(App::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception('SMTP Disabled', 503);
|
||||
}
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, EventAudit $audits, Mail $mails, Event $events) {
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser && empty(App::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED);
|
||||
}
|
||||
|
||||
$email = \strtolower($email);
|
||||
$name = (empty($name)) ? $email : $name;
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$invitee = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
|
||||
|
||||
if (empty($invitee)) { // Create new user if no user with same email found
|
||||
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
||||
if ($limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed.
|
||||
$sum = $dbForProject->count('users', [], APP_LIMIT_USERS);
|
||||
$total = $dbForProject->count('users', [], APP_LIMIT_USERS);
|
||||
|
||||
if($sum >= $limit) {
|
||||
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501);
|
||||
if ($total >= $limit) {
|
||||
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,36 +327,36 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$userId = $dbForProject->getId();
|
||||
$invitee = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['user:'.$userId, 'role:all'],
|
||||
'$write' => ['user:'.$userId],
|
||||
'$read' => ['user:' . $userId, 'role:all'],
|
||||
'$write' => ['user:' . $userId],
|
||||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator()),
|
||||
/**
|
||||
* Set the password update time to 0 for users created using
|
||||
* team invite and OAuth to allow password updates without an
|
||||
* old password
|
||||
/**
|
||||
* Set the password update time to 0 for users created using
|
||||
* team invite and OAuth to allow password updates without an
|
||||
* old password
|
||||
*/
|
||||
'passwordUpdate' => 0,
|
||||
'registration' => \time(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => [],
|
||||
'tokens' => [],
|
||||
'memberships' => [],
|
||||
'search' => implode(' ', [$userId, $email, $name]),
|
||||
'sessions' => null,
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name])
|
||||
])));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('Account already exists', 409);
|
||||
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
$isOwner = Authorization::isRole('team:'.$team->getId().'/owner');;
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception('User is not allowed to send invitations for this team', 401);
|
||||
throw new Exception('User is not allowed to send invitations for this team', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$secret = Auth::tokenGenerator();
|
||||
|
|
@ -360,7 +365,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'],
|
||||
'$write' => ['user:' . $invitee->getId(), 'team:' . $team->getId() . '/owner'],
|
||||
'userId' => $invitee->getId(),
|
||||
'teamId' => $team->getId(),
|
||||
'roles' => $roles,
|
||||
|
|
@ -375,20 +380,17 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
try {
|
||||
$membership = Authorization::skip(fn() => $dbForProject->createDocument('memberships', $membership));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('User has already been invited or is already a member of this team', 409);
|
||||
throw new Exception('User is already a member of this team', 409, Exception::TEAM_INVITE_ALREADY_EXISTS);
|
||||
}
|
||||
$team->setAttribute('sum', $team->getAttribute('sum', 0) + 1);
|
||||
$team->setAttribute('total', $team->getAttribute('total', 0) + 1);
|
||||
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
|
||||
|
||||
// Attach user to team
|
||||
$invitee->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
|
||||
|
||||
$invitee = Authorization::skip(fn() => $dbForProject->updateDocument('users', $invitee->getId(), $invitee));
|
||||
$dbForProject->deleteCachedDocument('users', $invitee->getId());
|
||||
} else {
|
||||
try {
|
||||
$membership = $dbForProject->createDocument('memberships', $membership);
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('User has already been invited or is already a member of this team', 409);
|
||||
throw new Exception('User has already been invited or is already a member of this team', 409, Exception::TEAM_INVITE_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -398,31 +400,34 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode
|
||||
$mails
|
||||
->setParam('event', 'teams.memberships.create')
|
||||
->setParam('from', $project->getId())
|
||||
->setParam('recipient', $email)
|
||||
->setParam('name', $name)
|
||||
->setParam('url', $url)
|
||||
->setParam('locale', $locale->default)
|
||||
->setParam('project', $project->getAttribute('name', ['[APP-NAME]']))
|
||||
->setParam('owner', $user->getAttribute('name', ''))
|
||||
->setParam('team', $team->getAttribute('name', '[TEAM-NAME]'))
|
||||
->setParam('type', MAIL_TYPE_INVITATION)
|
||||
->setType(MAIL_TYPE_INVITATION)
|
||||
->setRecipient($email)
|
||||
->setUrl($url)
|
||||
->setName($name)
|
||||
->setLocale($locale->default)
|
||||
->setTeam($team)
|
||||
->setUser($user)
|
||||
->trigger()
|
||||
;
|
||||
}
|
||||
|
||||
$audits
|
||||
->setParam('userId', $invitee->getId())
|
||||
->setParam('event', 'teams.memberships.create')
|
||||
->setParam('resource', 'team/'.$teamId)
|
||||
->setResource('team/' . $teamId)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('teamId', $team->getId())
|
||||
->setParam('membershipId', $membership->getId())
|
||||
;
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($membership
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('name', $name)
|
||||
, Response::MODEL_MEMBERSHIP);
|
||||
$response->dynamic(
|
||||
$membership
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email')),
|
||||
Response::MODEL_MEMBERSHIP
|
||||
);
|
||||
});
|
||||
|
||||
App::get('/v1/teams/:teamId/memberships')
|
||||
|
|
@ -445,21 +450,19 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function ($teamId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
->action(function (string $teamId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorMembership = $dbForProject->getDocument('memberships', $cursor);
|
||||
|
||||
if ($cursorMembership->isEmpty()) {
|
||||
throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400);
|
||||
throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -479,7 +482,7 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
cursorDirection: $cursorDirection
|
||||
);
|
||||
|
||||
$sum = $dbForProject->count(
|
||||
$total = $dbForProject->count(
|
||||
collection:'memberships',
|
||||
queries: $queries,
|
||||
max: APP_LIMIT_COUNT
|
||||
|
|
@ -487,12 +490,13 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
|
||||
$memberships = array_filter($memberships, fn(Document $membership) => !empty($membership->getAttribute('userId')));
|
||||
|
||||
$memberships = array_map(function($membership) use ($dbForProject) {
|
||||
$memberships = array_map(function ($membership) use ($dbForProject, $team) {
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
|
||||
$membership
|
||||
->setAttribute('name', $user->getAttribute('name'))
|
||||
->setAttribute('email', $user->getAttribute('email'))
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email'))
|
||||
;
|
||||
|
||||
return $membership;
|
||||
|
|
@ -500,7 +504,7 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
|
||||
$response->dynamic(new Document([
|
||||
'memberships' => $memberships,
|
||||
'sum' => $sum,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_MEMBERSHIP_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -519,36 +523,35 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
|||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function ($teamId, $membershipId, $response, $dbForProject) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
if($membership->isEmpty() || empty($membership->getAttribute('userId'))) {
|
||||
throw new Exception('Membership not found', 404);
|
||||
if ($membership->isEmpty() || empty($membership->getAttribute('userId'))) {
|
||||
throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
|
||||
$membership
|
||||
->setAttribute('name', $user->getAttribute('name'))
|
||||
->setAttribute('email', $user->getAttribute('email'))
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email'))
|
||||
;
|
||||
|
||||
$response->dynamic($membership, Response::MODEL_MEMBERSHIP );
|
||||
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
|
||||
});
|
||||
|
||||
App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Update Membership Roles')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.memberships.update')
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].update')
|
||||
->label('scope', 'teams.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
|
|
@ -559,40 +562,36 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
|||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->param('roles', [], new ArrayList(new Key()), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Max length for each role is 32 chars.')
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForProject, $audits) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, EventAudit $audits, Event $events) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
if ($membership->isEmpty()) {
|
||||
throw new Exception('Membership not found', 404);
|
||||
throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$profile = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
if ($profile->isEmpty()) {
|
||||
throw new Exception('User not found', 404);
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');;
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception('User is not allowed to modify roles', 401);
|
||||
throw new Exception('User is not allowed to modify roles', 401, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -604,23 +603,19 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
|||
/**
|
||||
* Replace membership on profile
|
||||
*/
|
||||
$memberships = array_filter($profile->getAttribute('memberships'), fn (Document $m) => $m->getId() !== $membership->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
|
||||
$profile
|
||||
->setAttribute('memberships', $memberships)
|
||||
->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
|
||||
$audits->setResource('team/' . $teamId);
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('users', $profile->getId(), $profile));
|
||||
|
||||
$audits
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('event', 'teams.memberships.update')
|
||||
->setParam('resource', 'team/' . $teamId);
|
||||
$events
|
||||
->setParam('teamId', $team->getId())
|
||||
->setParam('membershipId', $membership->getId());
|
||||
|
||||
$response->dynamic(
|
||||
$membership
|
||||
->setAttribute('email', $profile->getAttribute('email'))
|
||||
->setAttribute('name', $profile->getAttribute('name')),
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $profile->getAttribute('name'))
|
||||
->setAttribute('userEmail', $profile->getAttribute('email')),
|
||||
Response::MODEL_MEMBERSHIP
|
||||
);
|
||||
});
|
||||
|
|
@ -628,7 +623,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
|||
App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->desc('Update Team Membership Status')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.memberships.update.status')
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].update.status')
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
|
|
@ -647,38 +642,32 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
->inject('dbForProject')
|
||||
->inject('geodb')
|
||||
->inject('audits')
|
||||
->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $dbForProject, $geodb, $audits) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var MaxMind\Db\Reader $geodb */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Reader $geodb, EventAudit $audits, Event $events) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
if ($membership->isEmpty()) {
|
||||
throw new Exception('Membership not found', 404);
|
||||
throw new Exception('Membership not found', 404, Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('teamId') !== $teamId) {
|
||||
throw new Exception('Team IDs don\'t match', 404);
|
||||
throw new Exception('Team IDs don\'t match', 404, Exception::TEAM_MEMBERSHIP_MISMATCH);
|
||||
}
|
||||
|
||||
$team = Authorization::skip(fn() => $dbForProject->getDocument('teams', $teamId));
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
|
||||
throw new Exception('Secret key not valid', 401);
|
||||
throw new Exception('Secret key not valid', 401, Exception::TEAM_INVALID_SECRET);
|
||||
}
|
||||
|
||||
if ($userId != $membership->getAttribute('userId')) {
|
||||
throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401);
|
||||
if ($userId !== $membership->getAttribute('userId')) {
|
||||
throw new Exception('Invite does not belong to current user (' . $user->getAttribute('email') . ')', 401, Exception::TEAM_INVITE_MISMATCH);
|
||||
}
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
|
|
@ -686,7 +675,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
}
|
||||
|
||||
if ($membership->getAttribute('userId') !== $user->getId()) {
|
||||
throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401);
|
||||
throw new Exception('Invite does not belong to current user (' . $user->getAttribute('email') . ')', 401, Exception::TEAM_INVITE_MISMATCH);
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('confirm') === true) {
|
||||
throw new Exception('Membership already confirmed', 409);
|
||||
}
|
||||
|
||||
$membership // Attach user to team
|
||||
|
|
@ -696,12 +689,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
|
||||
$user
|
||||
->setAttribute('emailVerification', true)
|
||||
->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND)
|
||||
;
|
||||
|
||||
// Log user in
|
||||
|
||||
Authorization::setRole('user:'.$user->getId());
|
||||
Authorization::setRole('user:' . $user->getId());
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
|
|
@ -720,23 +712,24 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$read', ['user:'.$user->getId()])
|
||||
->setAttribute('$write', ['user:'.$user->getId()])
|
||||
);
|
||||
->setAttribute('$read', ['user:' . $user->getId()])
|
||||
->setAttribute('$write', ['user:' . $user->getId()]));
|
||||
|
||||
$user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND);
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
Authorization::setRole('user:'.$userId);
|
||||
Authorization::setRole('user:' . $userId);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
|
||||
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('sum', $team->getAttribute('sum', 0) + 1)));
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$audits
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('event', 'teams.memberships.update.status')
|
||||
->setParam('resource', 'team/'.$teamId)
|
||||
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('total', $team->getAttribute('total', 0) + 1)));
|
||||
|
||||
$audits->setResource('team/' . $teamId);
|
||||
|
||||
$events
|
||||
->setParam('teamId', $team->getId())
|
||||
->setParam('membershipId', $membership->getId())
|
||||
;
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
|
|
@ -746,20 +739,23 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
}
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->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'))
|
||||
;
|
||||
|
||||
$response->dynamic($membership
|
||||
->setAttribute('email', $user->getAttribute('email'))
|
||||
->setAttribute('name', $user->getAttribute('name'))
|
||||
, Response::MODEL_MEMBERSHIP);
|
||||
$response->dynamic(
|
||||
$membership
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email')),
|
||||
Response::MODEL_MEMBERSHIP
|
||||
);
|
||||
});
|
||||
|
||||
App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->desc('Delete Team Membership')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.memberships.delete')
|
||||
->label('event', 'teams.[teamId].memberships.[membershipId].delete')
|
||||
->label('scope', 'teams.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
|
|
@ -773,16 +769,12 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
|||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->inject('events')
|
||||
->action(function ($teamId, $membershipId, $response, $dbForProject, $audits, $events) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
/** @var Appwrite\Event\Event $events */
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, EventAudit $audits, Event $events) {
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
if ($membership->isEmpty()) {
|
||||
throw new Exception('Invite not found', 404);
|
||||
throw new Exception('Invite not found', 404, Exception::TEAM_INVITE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('teamId') !== $teamId) {
|
||||
|
|
@ -792,52 +784,122 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
|||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404);
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404);
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->deleteDocument('memberships', $membership->getId());
|
||||
} catch (AuthorizationException $exception) {
|
||||
throw new Exception('Unauthorized permissions', 401);
|
||||
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
|
||||
} catch (\Exception $exception) {
|
||||
throw new Exception('Failed to remove membership from DB', 500);
|
||||
throw new Exception('Failed to remove membership from DB', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$memberships = $user->getAttribute('memberships', []);
|
||||
|
||||
foreach ($memberships as $key => $child) {
|
||||
/** @var Document $child */
|
||||
|
||||
if ($membershipId == $child->getId()) {
|
||||
unset($memberships[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$user->setAttribute('memberships', $memberships);
|
||||
|
||||
Authorization::skip(fn() => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
if ($membership->getAttribute('confirm')) { // Count only confirmed members
|
||||
$team->setAttribute('sum', \max($team->getAttribute('sum', 0) - 1, 0));
|
||||
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0));
|
||||
Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
|
||||
}
|
||||
|
||||
$audits
|
||||
->setParam('userId', $membership->getAttribute('userId'))
|
||||
->setParam('event', 'teams.memberships.delete')
|
||||
->setParam('resource', 'team/'.$teamId)
|
||||
;
|
||||
$audits->setResource('team/' . $teamId);
|
||||
|
||||
$events
|
||||
->setParam('eventData', $response->output($membership, Response::MODEL_MEMBERSHIP))
|
||||
->setParam('teamId', $team->getId())
|
||||
->setParam('membershipId', $membership->getId())
|
||||
->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP))
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/teams/:teamId/logs')
|
||||
->desc('List Team Logs')
|
||||
->groups(['api', 'teams'])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'listLogs')
|
||||
->label('sdk.description', '/docs/references/teams/get-team-logs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOG_LIST)
|
||||
->param('teamId', null, new UID(), 'Team ID.')
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of logs 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)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function ($teamId, $limit, $offset, $response, $dbForProject, $locale, $geodb) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
/** @var MaxMind\Db\Reader $geodb */
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'team/' . $team->getId();
|
||||
$logs = $audit->getLogsByResource($resource, $limit, $offset);
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($logs as $i => &$log) {
|
||||
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
|
||||
|
||||
$detector = new Detector($log['userAgent']);
|
||||
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
|
||||
|
||||
$os = $detector->getOS();
|
||||
$client = $detector->getClient();
|
||||
$device = $detector->getDevice();
|
||||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
'userId' => $log['userId'],
|
||||
'userEmail' => $log['data']['userEmail'] ?? null,
|
||||
'userName' => $log['data']['userName'] ?? null,
|
||||
'mode' => $log['data']['mode'] ?? null,
|
||||
'ip' => $log['ip'],
|
||||
'time' => $log['time'],
|
||||
'osCode' => $os['osCode'],
|
||||
'osName' => $os['osName'],
|
||||
'osVersion' => $os['osVersion'],
|
||||
'clientType' => $client['clientType'],
|
||||
'clientCode' => $client['clientCode'],
|
||||
'clientName' => $client['clientName'],
|
||||
'clientVersion' => $client['clientVersion'],
|
||||
'clientEngine' => $client['clientEngine'],
|
||||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel']
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
|
||||
if ($record) {
|
||||
$output[$i]['countryCode'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
|
||||
$output[$i]['countryName'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
} else {
|
||||
$output[$i]['countryCode'] = '--';
|
||||
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
|
||||
}
|
||||
}
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource),
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,13 +3,18 @@
|
|||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Audit as EventAudit;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Stats\Stats;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Locale\Locale;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Validator\UID;
|
||||
|
|
@ -21,11 +26,12 @@ use Utopia\Validator\WhiteList;
|
|||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Boolean;
|
||||
use MaxMind\Db\Reader;
|
||||
|
||||
App::post('/v1/users')
|
||||
->desc('Create User')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.create')
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -41,10 +47,8 @@ App::post('/v1/users')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $email, $password, $name, $response, $dbForProject, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
|
||||
|
|
@ -53,7 +57,7 @@ App::post('/v1/users')
|
|||
$user = $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:'.$userId],
|
||||
'$write' => ['user:' . $userId],
|
||||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
|
|
@ -63,20 +67,23 @@ App::post('/v1/users')
|
|||
'reset' => false,
|
||||
'name' => $name,
|
||||
'prefs' => new \stdClass(),
|
||||
'sessions' => [],
|
||||
'tokens' => [],
|
||||
'memberships' => [],
|
||||
'search' => implode(' ', [$userId, $email, $name]),
|
||||
'deleted' => false
|
||||
'sessions' => null,
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name])
|
||||
]));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception('Account already exists', 409);
|
||||
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$usage
|
||||
->setParam('users.create', 1)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
|
@ -101,22 +108,17 @@ App::get('/v1/users')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) {
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorUser = $dbForProject->getDocument('users', $cursor);
|
||||
|
||||
if ($cursorUser->isEmpty()) {
|
||||
throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400);
|
||||
throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
$queries = [
|
||||
new Query('deleted', Query::TYPE_EQUAL, [false])
|
||||
];
|
||||
$queries = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
|
|
@ -128,7 +130,7 @@ App::get('/v1/users')
|
|||
|
||||
$response->dynamic(new Document([
|
||||
'users' => $dbForProject->find('users', $queries, $limit, $offset, [], [$orderType], $cursorUser ?? null, $cursorDirection),
|
||||
'sum' => $dbForProject->count('users', $queries, APP_LIMIT_COUNT),
|
||||
'total' => $dbForProject->count('users', $queries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_USER_LIST);
|
||||
});
|
||||
|
||||
|
|
@ -147,15 +149,12 @@ App::get('/v1/users/:userId')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $response, $dbForProject, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Stats $usage) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$usage
|
||||
|
|
@ -179,15 +178,12 @@ App::get('/v1/users/:userId/prefs')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $response, $dbForProject, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Stats $usage) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$prefs = $user->getAttribute('prefs', new \stdClass());
|
||||
|
|
@ -214,24 +210,20 @@ App::get('/v1/users/:userId/sessions')
|
|||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $response, $dbForProject, $locale, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Locale $locale, Stats $usage) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
foreach ($sessions as $key => $session) {
|
||||
foreach ($sessions as $key => $session) {
|
||||
/** @var Document $session */
|
||||
|
||||
$countryName = $locale->getText('countries.'.strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
|
||||
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
|
||||
$session->setAttribute('countryName', $countryName);
|
||||
$session->setAttribute('current', false);
|
||||
|
||||
|
|
@ -243,10 +235,49 @@ App::get('/v1/users/:userId/sessions')
|
|||
;
|
||||
$response->dynamic(new Document([
|
||||
'sessions' => $sessions,
|
||||
'sum' => count($sessions),
|
||||
'total' => count($sessions),
|
||||
]), Response::MODEL_SESSION_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/memberships')
|
||||
->desc('Get User Memberships')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'getMemberships')
|
||||
->label('sdk.description', '/docs/references/users/get-user-memberships.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$memberships = array_map(function ($membership) use ($dbForProject, $user) {
|
||||
$team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId'));
|
||||
|
||||
$membership
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email'));
|
||||
|
||||
return $membership;
|
||||
}, $user->getAttribute('memberships', []));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'memberships' => $memberships,
|
||||
'total' => count($memberships),
|
||||
]), Response::MODEL_MEMBERSHIP_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/logs')
|
||||
->desc('Get User Logs')
|
||||
->groups(['api', 'users'])
|
||||
|
|
@ -266,41 +297,17 @@ App::get('/v1/users/:userId/logs')
|
|||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $limit, $offset, $response, $dbForProject, $locale, $geodb, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
/** @var MaxMind\Db\Reader $geodb */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->action(function (string $userId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Stats $usage) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$auditEvents = [
|
||||
'account.create',
|
||||
'account.delete',
|
||||
'account.update.name',
|
||||
'account.update.email',
|
||||
'account.update.password',
|
||||
'account.update.prefs',
|
||||
'account.sessions.create',
|
||||
'account.sessions.update',
|
||||
'account.sessions.delete',
|
||||
'account.recovery.create',
|
||||
'account.recovery.update',
|
||||
'account.verification.create',
|
||||
'account.verification.update',
|
||||
'teams.membership.create',
|
||||
'teams.membership.update',
|
||||
'teams.membership.delete',
|
||||
];
|
||||
|
||||
$logs = $audit->getLogsByUserAndEvents($user->getId(), $auditEvents, $limit, $offset);
|
||||
$logs = $audit->getLogsByUser($user->getId(), $limit, $offset);
|
||||
|
||||
$output = [];
|
||||
|
||||
|
|
@ -335,8 +342,8 @@ App::get('/v1/users/:userId/logs')
|
|||
$record = $geodb->get($log['ip']);
|
||||
|
||||
if ($record) {
|
||||
$output[$i]['countryCode'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
|
||||
$output[$i]['countryName'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
$output[$i]['countryCode'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
|
||||
$output[$i]['countryName'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
} else {
|
||||
$output[$i]['countryCode'] = '--';
|
||||
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
|
||||
|
|
@ -348,7 +355,7 @@ App::get('/v1/users/:userId/logs')
|
|||
;
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'sum' => $audit->countLogsByUserAndEvents($user->getId(), $auditEvents),
|
||||
'total' => $audit->countLogsByUser($user->getId()),
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
|
@ -356,7 +363,7 @@ App::get('/v1/users/:userId/logs')
|
|||
App::patch('/v1/users/:userId/status')
|
||||
->desc('Update User Status')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.update.status')
|
||||
->label('event', 'users.[userId].update.status')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -370,15 +377,13 @@ App::patch('/v1/users/:userId/status')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $status, $response, $dbForProject, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->inject('events')
|
||||
->action(function (string $userId, bool $status, Response $response, Database $dbForProject, Stats $usage, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status));
|
||||
|
|
@ -386,13 +391,18 @@ App::patch('/v1/users/:userId/status')
|
|||
$usage
|
||||
->setParam('users.update', 1)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/verification')
|
||||
->desc('Update Email Verification')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.update.verification')
|
||||
->label('event', 'users.[userId].update.verification')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -406,15 +416,13 @@ App::patch('/v1/users/:userId/verification')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $emailVerification, $response, $dbForProject, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->inject('events')
|
||||
->action(function (string $userId, bool $emailVerification, Response $response, Database $dbForProject, Stats $usage, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification));
|
||||
|
|
@ -422,13 +430,18 @@ App::patch('/v1/users/:userId/verification')
|
|||
$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'])
|
||||
->label('event', 'users.update.name')
|
||||
->label('event', 'users.[userId].update.name')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -442,23 +455,28 @@ App::patch('/v1/users/:userId/name')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->action(function ($userId, $name, $response, $dbForProject, $audits) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $name, Response $response, Database $dbForProject, EventAudit $audits, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('name', $name));
|
||||
$user
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email'), $name]));
|
||||
;
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('event', 'users.update.name')
|
||||
->setParam('resource', 'user/'.$user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
|
|
@ -467,7 +485,7 @@ App::patch('/v1/users/:userId/name')
|
|||
App::patch('/v1/users/:userId/password')
|
||||
->desc('Update Password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.update.password')
|
||||
->label('event', 'users.[userId].update.password')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -481,15 +499,13 @@ App::patch('/v1/users/:userId/password')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->action(function ($userId, $password, $response, $dbForProject, $audits) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $password, Response $response, Database $dbForProject, EventAudit $audits, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user
|
||||
|
|
@ -499,9 +515,11 @@ App::patch('/v1/users/:userId/password')
|
|||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('event', 'users.update.password')
|
||||
->setParam('resource', 'user/'.$user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
|
|
@ -510,7 +528,7 @@ App::patch('/v1/users/:userId/password')
|
|||
App::patch('/v1/users/:userId/email')
|
||||
->desc('Update Email')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.update.email')
|
||||
->label('event', 'users.[userId].update.email')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -524,15 +542,13 @@ App::patch('/v1/users/:userId/email')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('audits')
|
||||
->action(function ($userId, $email, $response, $dbForProject, $audits) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, Response $response, Database $dbForProject, EventAudit $audits, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
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
|
||||
|
|
@ -542,16 +558,24 @@ App::patch('/v1/users/:userId/email')
|
|||
|
||||
$email = \strtolower($email);
|
||||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name')]))
|
||||
;
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('email', $email));
|
||||
} catch(Duplicate $th) {
|
||||
throw new Exception('Email already exists', 409);
|
||||
$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())
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('event', 'users.update.email')
|
||||
->setParam('resource', 'user/'.$user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
|
|
@ -560,7 +584,7 @@ App::patch('/v1/users/:userId/email')
|
|||
App::patch('/v1/users/:userId/prefs')
|
||||
->desc('Update User Preferences')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.update.prefs')
|
||||
->label('event', 'users.[userId].update.prefs')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -574,15 +598,13 @@ App::patch('/v1/users/:userId/prefs')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $prefs, $response, $dbForProject, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->inject('events')
|
||||
->action(function (string $userId, array $prefs, Response $response, Database $dbForProject, Stats $usage, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
|
||||
|
|
@ -590,13 +612,18 @@ App::patch('/v1/users/:userId/prefs')
|
|||
$usage
|
||||
->setParam('users.update', 1)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
;
|
||||
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
->desc('Delete User Session')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.sessions.delete')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -610,49 +637,41 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
|
|||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $sessionId, $response, $dbForProject, $events, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $events */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->action(function (string $userId, string $sessionId, Response $response, Database $dbForProject, Event $events, Stats $usage) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$session = $dbForProject->getDocument('sessions', $sessionId);
|
||||
|
||||
foreach ($sessions as $key => $session) { /** @var Document $session */
|
||||
|
||||
if ($sessionId == $session->getId()) {
|
||||
unset($sessions[$key]);
|
||||
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
|
||||
$user->setAttribute('sessions', $sessions);
|
||||
|
||||
$events
|
||||
->setParam('eventData', $response->output($user, Response::MODEL_USER))
|
||||
;
|
||||
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
}
|
||||
if ($session->isEmpty()) {
|
||||
throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
|
||||
$usage
|
||||
->setParam('users.update', 1)
|
||||
->setParam('users.sessions.delete', 1)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $sessionId)
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/sessions')
|
||||
->desc('Delete User Sessions')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.sessions.delete')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].delete')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -665,41 +684,40 @@ App::delete('/v1/users/:userId/sessions')
|
|||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $response, $dbForProject, $events, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $events */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $events, Stats $usage) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
foreach ($sessions as $key => $session) { /** @var Document $session */
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
//TODO: fix this
|
||||
}
|
||||
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$events
|
||||
->setParam('eventData', $response->output($user, Response::MODEL_USER))
|
||||
->setParam('userId', $user->getId())
|
||||
->setPayload($response->output($user, Response::MODEL_USER))
|
||||
;
|
||||
|
||||
$usage
|
||||
->setParam('users.update', 1)
|
||||
->setParam('users.sessions.delete', 1)
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId')
|
||||
->desc('Delete User')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.delete')
|
||||
->label('event', 'users.[userId].delete')
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
|
@ -707,51 +725,33 @@ App::delete('/v1/users/:userId')
|
|||
->label('sdk.description', '/docs/references/users/delete.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('userId', '', function () {return new UID();}, 'User ID.')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->inject('usage')
|
||||
->action(function ($userId, $response, $dbForProject, $events, $deletes, $usage) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Appwrite\Event\Event $events */
|
||||
/** @var Appwrite\Event\Event $deletes */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $events, Delete $deletes, Stats $usage) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty() || $user->getAttribute('deleted')) {
|
||||
throw new Exception('User not found', 404);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT DELETE THE USER RECORD ITSELF.
|
||||
* WE RETAIN THE USER RECORD TO RESERVE THE USER ID AND ENSURE THAT THE USER ID IS NOT REUSED.
|
||||
*/
|
||||
|
||||
// clone user object to send to workers
|
||||
$clone = clone $user;
|
||||
|
||||
$user
|
||||
->setAttribute("name", null)
|
||||
->setAttribute("email", null)
|
||||
->setAttribute("password", null)
|
||||
->setAttribute("deleted", true)
|
||||
->setAttribute("tokens", [])
|
||||
->setAttribute("search", null)
|
||||
;
|
||||
|
||||
$dbForProject->updateDocument('users', $userId, $user);
|
||||
$dbForProject->deleteDocument('users', $userId);
|
||||
|
||||
$deletes
|
||||
->setParam('type', DELETE_TYPE_DOCUMENT)
|
||||
->setParam('document', $clone)
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($clone)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('eventData', $response->output($clone, Response::MODEL_USER))
|
||||
->setParam('userId', $user->getId())
|
||||
->setPayload($response->output($clone, Response::MODEL_USER))
|
||||
;
|
||||
|
||||
$usage
|
||||
|
|
@ -772,13 +772,11 @@ App::get('/v1/users/usage')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
|
||||
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
|
||||
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn($value) => "oauth-".$value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
|
||||
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('register')
|
||||
->action(function ($range, $provider, $response, $dbForProject) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
->action(function (string $range, string $provider, Response $response, Database $dbForProject) {
|
||||
|
||||
$usage = [];
|
||||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
|
|
@ -814,7 +812,7 @@ App::get('/v1/users/usage')
|
|||
|
||||
$stats = [];
|
||||
|
||||
Authorization::skip(function() use ($dbForProject, $periods, $range, $metrics, &$stats) {
|
||||
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$limit = $periods[$range]['limit'];
|
||||
$period = $periods[$range]['period'];
|
||||
|
|
@ -823,7 +821,7 @@ App::get('/v1/users/usage')
|
|||
new Query('period', Query::TYPE_EQUAL, [$period]),
|
||||
new Query('metric', Query::TYPE_EQUAL, [$metric]),
|
||||
], $limit, 0, ['time'], [Database::ORDER_DESC]);
|
||||
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
$stats[$metric][] = [
|
||||
|
|
@ -835,9 +833,8 @@ App::get('/v1/users/usage')
|
|||
// backfill metrics with empty values for graphs
|
||||
$backfill = $limit - \count($requestDocs);
|
||||
while ($backfill > 0) {
|
||||
|
||||
$last = $limit - $backfill - 1; // array index of last added metric
|
||||
$diff = match($period) { // convert period to seconds for unix timestamp math
|
||||
$diff = match ($period) { // convert period to seconds for unix timestamp math
|
||||
'30m' => 1800,
|
||||
'1d' => 86400,
|
||||
};
|
||||
|
|
@ -848,7 +845,7 @@ App::get('/v1/users/usage')
|
|||
$backfill--;
|
||||
}
|
||||
$stats[$metric] = array_reverse($stats[$metric]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$usage = new Document([
|
||||
|
|
@ -862,8 +859,7 @@ App::get('/v1/users/usage')
|
|||
'sessionsProviderCreate' => $stats["users.sessions.$provider.create"],
|
||||
'sessionsDelete' => $stats["users.sessions.delete"]
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
$response->dynamic($usage, Response::MODEL_USAGE_USERS);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,40 +1,40 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../init.php';
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Domains\Domain;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Response\Filters\V11;
|
||||
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 Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Appwrite\Utopia\Request\Filters\V12;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Appwrite\Utopia\Request\Filters\V12 as RequestV12;
|
||||
use Appwrite\Utopia\Request\Filters\V13 as RequestV13;
|
||||
use Appwrite\Utopia\Request\Filters\V14 as RequestV14;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
App::init(function ($utopia, $request, $response, $console, $project, $dbForConsole, $user, $locale, $clients) {
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $console */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Locale\Locale $locale */
|
||||
/** @var array $clients */
|
||||
App::init(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
|
||||
|
||||
/*
|
||||
* Request format
|
||||
|
|
@ -44,9 +44,15 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
|
||||
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($requestFormat) {
|
||||
switch($requestFormat) {
|
||||
case version_compare ($requestFormat , '0.12.0', '<') :
|
||||
Request::setFilter(new V12());
|
||||
switch ($requestFormat) {
|
||||
case version_compare($requestFormat, '0.12.0', '<'):
|
||||
Request::setFilter(new RequestV12());
|
||||
break;
|
||||
case version_compare($requestFormat, '0.13.0', '<'):
|
||||
Request::setFilter(new RequestV13());
|
||||
break;
|
||||
case version_compare($requestFormat, '0.14.0', '<'):
|
||||
Request::setFilter(new RequestV14());
|
||||
break;
|
||||
default:
|
||||
Request::setFilter(null);
|
||||
|
|
@ -63,36 +69,45 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$domains[$domain->get()] = false;
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
} elseif(str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
} else {
|
||||
Authorization::disable();
|
||||
|
||||
$domainDocument = $dbForConsole->findOne('domains', [
|
||||
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
|
||||
]);
|
||||
|
||||
if (!$domainDocument) {
|
||||
$domainDocument = new Document([
|
||||
'domain' => $domain->get(),
|
||||
'tld' => $domain->getSuffix(),
|
||||
'registerable' => $domain->getRegisterable(),
|
||||
'verification' => false,
|
||||
'certificateId' => null,
|
||||
]);
|
||||
|
||||
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in a few seconds...');
|
||||
|
||||
Resque::enqueue('v1-certificates', 'CertificatesV1', [
|
||||
'document' => $domainDocument,
|
||||
'domain' => $domain->get(),
|
||||
'validateTarget' => false,
|
||||
'validateCNAME' => false,
|
||||
]);
|
||||
$envDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
$mainDomain = null;
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
$mainDomain = $envDomain;
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
|
||||
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
|
||||
}
|
||||
|
||||
if ($mainDomain !== $domain->get()) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('domains', [
|
||||
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
|
||||
]);
|
||||
|
||||
if (!$domainDocument) {
|
||||
$domainDocument = new Document([
|
||||
'domain' => $domain->get(),
|
||||
'tld' => $domain->getSuffix(),
|
||||
'registerable' => $domain->getRegisterable(),
|
||||
'verification' => false,
|
||||
'certificateId' => null,
|
||||
]);
|
||||
|
||||
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
|
||||
(new Certificate())
|
||||
->setDomain($domainDocument)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
$domains[$domain->get()] = true;
|
||||
|
||||
Authorization::reset(); // ensure authorization is re-enabled
|
||||
|
|
@ -106,11 +121,11 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
}
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) {
|
||||
throw new Exception('Missing or unknown project ID', 400);
|
||||
throw new AppwriteException('Missing or unknown project ID', 400, AppwriteException::PROJECT_UNKNOWN);
|
||||
}
|
||||
|
||||
$referrer = $request->getReferer();
|
||||
|
|
@ -118,47 +133,49 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
|
||||
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
|
||||
|
||||
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()).'://'.((\in_array($origin, $clients))
|
||||
? $origin : 'localhost').(!empty($port) ? ':'.$port : '');
|
||||
$refDomainOrigin = 'localhost';
|
||||
$validator = new Hostname($clients);
|
||||
if ($validator->isValid($origin)) {
|
||||
$refDomainOrigin = $origin;
|
||||
}
|
||||
|
||||
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
|
||||
|
||||
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
|
||||
? $refDomain
|
||||
: (!empty($protocol) ? $protocol : $request->getProtocol()).'://'.$origin.(!empty($port) ? ':'.$port : '');
|
||||
: (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
|
||||
|
||||
$selfDomain = new Domain($request->getHostname());
|
||||
$endDomain = new Domain((string)$origin);
|
||||
|
||||
// var_dump('referer', $referrer);
|
||||
// var_dump('origin', $origin);
|
||||
// var_dump('port', $request->getPort());
|
||||
// var_dump('hostname', $request->getHostname());
|
||||
// var_dump('protocol', $request->getProtocol());
|
||||
// var_dump('method', $request->getMethod());
|
||||
// var_dump('ip', $request->getIP());
|
||||
// var_dump('-----------------');
|
||||
// var_dump($request->debug());
|
||||
|
||||
Config::setParam('domainVerification',
|
||||
Config::setParam(
|
||||
'domainVerification',
|
||||
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
|
||||
$endDomain->getRegisterable() !== '');
|
||||
$endDomain->getRegisterable() !== ''
|
||||
);
|
||||
|
||||
Config::setParam('cookieDomain', (
|
||||
$request->getHostname() === 'localhost' ||
|
||||
$request->getHostname() === 'localhost:'.$request->getPort() ||
|
||||
$request->getHostname() === 'localhost:' . $request->getPort() ||
|
||||
(\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false)
|
||||
)
|
||||
? null
|
||||
: '.'.$request->getHostname()
|
||||
);
|
||||
: '.' . $request->getHostname());
|
||||
|
||||
/*
|
||||
/*
|
||||
* Response format
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
switch($responseFormat) {
|
||||
case version_compare ($responseFormat , '0.11.0', '<=') :
|
||||
Response::setFilter(new V11());
|
||||
switch ($responseFormat) {
|
||||
case version_compare($responseFormat, '0.11.2', '<='):
|
||||
Response::setFilter(new ResponseV11());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.12.4', '<='):
|
||||
Response::setFilter(new ResponseV12());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.13.4', '<='):
|
||||
Response::setFilter(new ResponseV13());
|
||||
break;
|
||||
default:
|
||||
Response::setFilter(null);
|
||||
|
|
@ -175,17 +192,21 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
*/
|
||||
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https') {
|
||||
return $response->redirect('https://'.$request->getHostname().$request->getURI());
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException('Method unsupported over HTTP.', 500, AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED);
|
||||
}
|
||||
|
||||
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
}
|
||||
|
||||
$response->addHeader('Strict-Transport-Security', 'max-age='.(60 * 60 * 24 * 126)); // 126 days
|
||||
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $refDomain)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
|
|
@ -199,11 +220,13 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
$origin = $request->getOrigin($request->getReferer(''));
|
||||
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
|
||||
|
||||
if (!$originValidator->isValid($origin)
|
||||
if (
|
||||
!$originValidator->isValid($origin)
|
||||
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
|
||||
&& $route->getLabel('origin', false) !== '*'
|
||||
&& empty($request->getHeader('x-appwrite-key', ''))) {
|
||||
throw new Exception($originValidator->getDescription(), 403);
|
||||
&& empty($request->getHeader('x-appwrite-key', ''))
|
||||
) {
|
||||
throw new AppwriteException($originValidator->getDescription(), 403, AppwriteException::GENERAL_UNKNOWN_ORIGIN);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -248,7 +271,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(),
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
|
|
@ -256,85 +279,82 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
|
|||
$role = Auth::USER_ROLE_APP;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
|
||||
|
||||
Authorization::setRole('role:'.Auth::USER_ROLE_APP);
|
||||
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
}
|
||||
}
|
||||
|
||||
Authorization::setRole('role:'.$role);
|
||||
Authorization::setRole('role:' . $role);
|
||||
|
||||
foreach (Auth::getRoles($user) as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
}
|
||||
|
||||
$service = $route->getLabel('sdk.namespace','');
|
||||
if(!empty($service)) {
|
||||
if(array_key_exists($service, $project->getAttribute('services',[]))
|
||||
&& !$project->getAttribute('services',[])[$service]
|
||||
&& !Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
throw new Exception('Service is disabled', 503);
|
||||
$service = $route->getLabel('sdk.namespace', '');
|
||||
if (!empty($service)) {
|
||||
$roles = Authorization::getRoles();
|
||||
if (
|
||||
array_key_exists($service, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$service]
|
||||
&& !(Auth::isPrivilegedUser($roles) || Auth::isAppUser($roles))
|
||||
) {
|
||||
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!\in_array($scope, $scopes)) {
|
||||
if ($project->isEmpty()) { // Check if permission is denied because project is missing
|
||||
throw new Exception('Project not found', 404);
|
||||
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
throw new Exception($user->getAttribute('email', 'User').' (role: '.\strtolower($roles[$role]['label']).') missing scope ('.$scope.')', 401);
|
||||
throw new AppwriteException($user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')', 401, AppwriteException::GENERAL_UNAUTHORIZED_SCOPE);
|
||||
}
|
||||
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
throw new Exception('Invalid credentials. User is blocked', 401);
|
||||
throw new AppwriteException('Invalid credentials. User is blocked', 401, AppwriteException::USER_BLOCKED);
|
||||
}
|
||||
|
||||
if ($user->getAttribute('reset')) {
|
||||
throw new Exception('Password reset is required', 412);
|
||||
throw new AppwriteException('Password reset is required', 412, AppwriteException::USER_PASSWORD_RESET_REQUIRED);
|
||||
}
|
||||
|
||||
}, ['utopia', 'request', 'response', 'console', 'project', 'dbForConsole', 'user', 'locale', 'clients']);
|
||||
|
||||
App::options(function ($request, $response) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
App::options(function (Request $request, Response $response) {
|
||||
|
||||
$origin = $request->getOrigin();
|
||||
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
->noContent();
|
||||
}, ['request', 'response']);
|
||||
|
||||
App::error(function ($error, $utopia, $request, $response, $layout, $project, $logger, $loggerBreadcrumbs) {
|
||||
/** @var Exception $error */
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Logger\Logger $logger */
|
||||
/** @var Utopia\Logger\Log\Breadcrumb[] $loggerBreadcrumbs */
|
||||
App::error(function (Throwable $error, App $utopia, Request $request, Response $response, View $layout, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
|
||||
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->match($request);
|
||||
|
||||
if($logger) {
|
||||
if($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
/** Delegate PDO exceptions to the global handler so the database connection can be returned to the pool */
|
||||
if ($error instanceof PDOException) {
|
||||
throw $error;
|
||||
}
|
||||
|
||||
if ($logger) {
|
||||
if ($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
} catch(\Throwable $th) {
|
||||
} catch (\Throwable $th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$log = new Utopia\Logger\Log();
|
||||
|
||||
if(isset($user) && !$user->isEmpty()) {
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
}
|
||||
|
||||
|
|
@ -345,7 +365,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('projectId', $project->getId());
|
||||
|
|
@ -355,6 +375,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
$log->addExtra('roles', Authorization::$roles);
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
|
|
@ -363,36 +384,54 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
foreach($loggerBreadcrumbs as $loggerBreadcrumb) {
|
||||
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
|
||||
$log->addBreadcrumb($loggerBreadcrumb);
|
||||
}
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Log pushed with status code: '.$responseCode);
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
if ($error instanceof PDOException) {
|
||||
throw $error;
|
||||
}
|
||||
|
||||
$template = ($route) ? $route->getLabel('error', null) : null;
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
$file = $error->getFile();
|
||||
$line = $error->getLine();
|
||||
$trace = $error->getTrace();
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
Console::error('[Error] Timestamp: '.date('c', time()));
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
|
||||
if($route) {
|
||||
Console::error('[Error] Method: '.$route->getMethod());
|
||||
Console::error('[Error] URL: '.$route->getPath());
|
||||
if ($route) {
|
||||
Console::error('[Error] Method: ' . $route->getMethod());
|
||||
Console::error('[Error] URL: ' . $route->getPath());
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: '.get_class($error));
|
||||
Console::error('[Error] Message: '.$error->getMessage());
|
||||
Console::error('[Error] File: '.$error->getFile());
|
||||
Console::error('[Error] Line: '.$error->getLine());
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $message);
|
||||
Console::error('[Error] File: ' . $file);
|
||||
Console::error('[Error] Line: ' . $line);
|
||||
}
|
||||
|
||||
switch ($error->getCode()) { // Don't show 500 errors!
|
||||
/** Handle Utopia Errors */
|
||||
if ($error instanceof Utopia\Exception) {
|
||||
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
|
||||
switch ($code) {
|
||||
case 400:
|
||||
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
|
||||
break;
|
||||
case 404:
|
||||
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrap all exceptions inside Appwrite\Extend\Exception */
|
||||
if (!($error instanceof AppwriteException)) {
|
||||
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
|
||||
}
|
||||
|
||||
switch ($code) { // Don't show 500 errors!
|
||||
case 400: // Error allowed publicly
|
||||
case 401: // Error allowed publicly
|
||||
case 402: // Error allowed publicly
|
||||
|
|
@ -400,11 +439,10 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
case 404: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 416: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
case 503: // Error allowed publicly
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
|
|
@ -413,17 +451,21 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
|
||||
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
|
||||
|
||||
$type = $error->getType();
|
||||
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'file' => $error->getFile(),
|
||||
'line' => $error->getLine(),
|
||||
'trace' => $error->getTrace(),
|
||||
'message' => $message,
|
||||
'code' => $code,
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
'trace' => $trace,
|
||||
'version' => $version,
|
||||
'type' => $type,
|
||||
] : [
|
||||
'message' => $message,
|
||||
'code' => $code,
|
||||
'version' => $version,
|
||||
'type' => $type,
|
||||
];
|
||||
|
||||
$response
|
||||
|
|
@ -433,6 +475,8 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
->setStatusCode($code)
|
||||
;
|
||||
|
||||
$template = ($route) ? $route->getLabel('error', null) : null;
|
||||
|
||||
if ($template) {
|
||||
$comp = new View($template);
|
||||
|
||||
|
|
@ -442,11 +486,11 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
->setParam('projectURL', $project->getAttribute('url'))
|
||||
->setParam('message', $error->getMessage())
|
||||
->setParam('code', $code)
|
||||
->setParam('trace', $error->getTrace())
|
||||
->setParam('trace', $trace)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', $project->getAttribute('name').' - Error')
|
||||
->setParam('title', $project->getAttribute('name') . ' - Error')
|
||||
->setParam('description', 'No Description')
|
||||
->setParam('body', $comp)
|
||||
->setParam('version', $version)
|
||||
|
|
@ -456,8 +500,10 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project, $l
|
|||
$response->html($layout->render());
|
||||
}
|
||||
|
||||
$response->dynamic(new Document($output),
|
||||
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR);
|
||||
$response->dynamic(
|
||||
new Document($output),
|
||||
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
|
||||
);
|
||||
}, ['error', 'utopia', 'request', 'response', 'layout', 'project', 'logger', 'loggerBreadcrumbs']);
|
||||
|
||||
App::get('/manifest.json')
|
||||
|
|
@ -465,8 +511,7 @@ App::get('/manifest.json')
|
|||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->json([
|
||||
'name' => APP_NAME,
|
||||
|
|
@ -492,8 +537,8 @@ App::get('/robots.txt')
|
|||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
$template = new View(__DIR__.'/../views/general/robots.phtml');
|
||||
->action(function (Response $response) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
});
|
||||
|
||||
|
|
@ -502,8 +547,8 @@ App::get('/humans.txt')
|
|||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
$template = new View(__DIR__.'/../views/general/humans.phtml');
|
||||
->action(function (Response $response) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
});
|
||||
|
||||
|
|
@ -513,7 +558,7 @@ App::get('/.well-known/acme-challenge')
|
|||
->label('docs', false)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function ($request, $response) {
|
||||
->action(function (Request $request, Response $response) {
|
||||
$uriChunks = \explode('/', $request->getURI());
|
||||
$token = $uriChunks[\count($uriChunks) - 1];
|
||||
|
||||
|
|
@ -526,32 +571,32 @@ App::get('/.well-known/acme-challenge')
|
|||
]);
|
||||
|
||||
if (!$validator->isValid($token) || \count($uriChunks) !== 4) {
|
||||
throw new Exception('Invalid challenge token.', 400);
|
||||
throw new AppwriteException('Invalid challenge token.', 400);
|
||||
}
|
||||
|
||||
$base = \realpath(APP_STORAGE_CERTIFICATES);
|
||||
$absolute = \realpath($base.'/.well-known/acme-challenge/'.$token);
|
||||
$absolute = \realpath($base . '/.well-known/acme-challenge/' . $token);
|
||||
|
||||
if (!$base) {
|
||||
throw new Exception('Storage error', 500);
|
||||
throw new AppwriteException('Storage error', 500, AppwriteException::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if (!$absolute) {
|
||||
throw new Exception('Unknown path', 404);
|
||||
throw new AppwriteException('Unknown path', 404);
|
||||
}
|
||||
|
||||
if (!\substr($absolute, 0, \strlen($base)) === $base) {
|
||||
throw new Exception('Invalid path', 401);
|
||||
throw new AppwriteException('Invalid path', 401);
|
||||
}
|
||||
|
||||
if (!\file_exists($absolute)) {
|
||||
throw new Exception('Unknown path', 404);
|
||||
throw new AppwriteException('Unknown path', 404);
|
||||
}
|
||||
|
||||
$content = @\file_get_contents($absolute);
|
||||
|
||||
if (!$content) {
|
||||
throw new Exception('Failed to get contents', 500);
|
||||
throw new AppwriteException('Failed to get contents', 500, AppwriteException::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$response->text($content);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
global $utopia, $request, $response;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Document;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Validator\ArrayList;
|
||||
|
|
@ -26,7 +28,7 @@ App::get('/v1/mock/tests/foo')
|
|||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -44,7 +46,7 @@ App::post('/v1/mock/tests/foo')
|
|||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -62,7 +64,7 @@ App::patch('/v1/mock/tests/foo')
|
|||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -80,7 +82,7 @@ App::put('/v1/mock/tests/foo')
|
|||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -98,7 +100,7 @@ App::delete('/v1/mock/tests/foo')
|
|||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -116,7 +118,7 @@ App::get('/v1/mock/tests/bar')
|
|||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -134,7 +136,7 @@ App::post('/v1/mock/tests/bar')
|
|||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -152,7 +154,7 @@ App::patch('/v1/mock/tests/bar')
|
|||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -170,7 +172,7 @@ App::put('/v1/mock/tests/bar')
|
|||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -188,7 +190,7 @@ App::delete('/v1/mock/tests/bar')
|
|||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
|
|
@ -205,13 +207,12 @@ App::get('/v1/mock/tests/general/download')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response
|
||||
->setContentType('text/plain')
|
||||
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->send("Download test passed.")
|
||||
;
|
||||
|
|
@ -232,77 +233,75 @@ App::post('/v1/mock/tests/general/upload')
|
|||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->param('file', [], new File(), 'Sample file param', false)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function ($x, $y, $z, $file, $request, $response) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Swoole\Response $response */
|
||||
|
||||
->action(function (string $x, int $y, array $z, array $file, Request $request, Response $response) {
|
||||
|
||||
$file = $request->getFiles('file');
|
||||
|
||||
|
||||
$contentRange = $request->getHeader('content-range');
|
||||
if(!empty($contentRange)) {
|
||||
|
||||
$chunkSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
if (!empty($contentRange)) {
|
||||
$start = $request->getContentRangeStart();
|
||||
$end = $request->getContentRangeEnd();
|
||||
$size = $request->getContentRangeSize();
|
||||
$id = $request->getHeader('x-appwrite-id', '');
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'] : [$file['size']];
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
if(is_null($start) || is_null($end) || is_null($size)) {
|
||||
throw new Exception('Invalid content-range header', 400);
|
||||
if (is_null($start) || is_null($end) || is_null($size)) {
|
||||
throw new Exception('Invalid content-range header', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
if($start > $end || $end > $size) {
|
||||
throw new Exception('Invalid content-range header', 400);
|
||||
if ($start > $end || $end > $size) {
|
||||
throw new Exception('Invalid content-range header', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
if($start === 0 && !empty($id)) {
|
||||
throw new Exception('First chunked request cannot have id header', 400);
|
||||
if ($start === 0 && !empty($id)) {
|
||||
throw new Exception('First chunked request cannot have id header', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
if($start !== 0 && $id !== 'newfileid') {
|
||||
throw new Exception('All chunked request must have id header (except first)', 400);
|
||||
if ($start !== 0 && $id !== 'newfileid') {
|
||||
throw new Exception('All chunked request must have id header (except first)', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
if($end !== $size && $end-$start+1 !== 5*1024*1024) {
|
||||
throw new Exception('Chunk size must be 5MB (except last chunk)', 400);
|
||||
if ($end !== $size && $end - $start + 1 !== $chunkSize) {
|
||||
throw new Exception('Chunk size must be 5MB (except last chunk)', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
foreach ($file['size'] as $i => $sz) {
|
||||
if ($end !== $size && $sz !== 5*1024*1024) {
|
||||
throw new Exception('Wrong chunk size', 400);
|
||||
}
|
||||
|
||||
if($sz > 5*1024*1024) {
|
||||
throw new Exception('Chunk size must be 5MB or less', 400);
|
||||
}
|
||||
if ($end !== $size && $file['size'] !== $chunkSize) {
|
||||
throw new Exception('Wrong chunk size', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
if($end !== $size) {
|
||||
$response->json(['$id'=> 'newfileid']);
|
||||
|
||||
if ($file['size'] > $chunkSize) {
|
||||
throw new Exception('Chunk size must be 5MB or less', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
if ($end !== $size) {
|
||||
$response->json([
|
||||
'$id' => 'newfileid',
|
||||
'chunksTotal' => $file['size'] / $chunkSize,
|
||||
'chunksUploaded' => $start / $chunkSize
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'] : [$file['tmp_name']];
|
||||
$file['name'] = (\is_array($file['name'])) ? $file['name'] : [$file['name']];
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'] : [$file['size']];
|
||||
|
||||
foreach ($file['name'] as $i => $name) {
|
||||
if ($name !== 'file.png') {
|
||||
throw new Exception('Wrong file name', 400);
|
||||
}
|
||||
$file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'][0] : $file['tmp_name'];
|
||||
$file['name'] = (\is_array($file['name'])) ? $file['name'][0] : $file['name'];
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
if ($file['name'] !== 'file.png') {
|
||||
throw new Exception('Wrong file name', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
foreach ($file['size'] as $i => $size) {
|
||||
if ($size !== 38756) {
|
||||
throw new Exception('Wrong file size', 400);
|
||||
}
|
||||
|
||||
if ($file['size'] !== 38756) {
|
||||
throw new Exception('Wrong file size', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
foreach ($file['tmp_name'] as $i => $tmpName) {
|
||||
if (\md5(\file_get_contents($tmpName)) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
|
||||
throw new Exception('Wrong file uploaded', 400);
|
||||
}
|
||||
|
||||
if (\md5(\file_get_contents($file['tmp_name'])) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
|
||||
throw new Exception('Wrong file uploaded', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -320,8 +319,7 @@ App::get('/v1/mock/tests/general/redirect')
|
|||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->redirect('/v1/mock/tests/general/redirect/done');
|
||||
});
|
||||
|
|
@ -355,9 +353,7 @@ App::get('/v1/mock/tests/general/set-cookie')
|
|||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->action(function ($response, $request) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
->action(function (Response $response, Request $request) {
|
||||
|
||||
$response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', $request->getHostname(), true, true);
|
||||
});
|
||||
|
|
@ -375,11 +371,10 @@ App::get('/v1/mock/tests/general/get-cookie')
|
|||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->action(function ($request) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
->action(function (Request $request) {
|
||||
|
||||
if ($request->getCookie('cookieName', '') !== 'cookieValue') {
|
||||
throw new Exception('Missing cookie value', 400);
|
||||
throw new Exception('Missing cookie value', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -395,8 +390,7 @@ App::get('/v1/mock/tests/general/empty')
|
|||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
|
@ -414,7 +408,7 @@ App::get('/v1/mock/tests/general/400-error')
|
|||
->label('sdk.response.model', Response::MODEL_ERROR)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
throw new Exception('Mock 400 error', 400);
|
||||
throw new Exception('Mock 400 error', 400, Exception::GENERAL_MOCK);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/500-error')
|
||||
|
|
@ -430,7 +424,7 @@ App::get('/v1/mock/tests/general/500-error')
|
|||
->label('sdk.response.model', Response::MODEL_ERROR)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
throw new Exception('Mock 500 error', 500);
|
||||
throw new Exception('Mock 500 error', 500, Exception::GENERAL_MOCK);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/502-error')
|
||||
|
|
@ -446,8 +440,7 @@ App::get('/v1/mock/tests/general/502-error')
|
|||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response
|
||||
->setStatusCode(502)
|
||||
|
|
@ -466,10 +459,9 @@ App::get('/v1/mock/tests/general/oauth2')
|
|||
->param('scope', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('state', '', new Text(1024), 'OAuth2 state.')
|
||||
->inject('response')
|
||||
->action(function ($client_id, $redirectURI, $scope, $state, $response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (string $client_id, string $redirectURI, string $scope, string $state, Response $response) {
|
||||
|
||||
$response->redirect($redirectURI.'?'.\http_build_query(['code' => 'abcdef', 'state' => $state]));
|
||||
$response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state]));
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2/token')
|
||||
|
|
@ -485,15 +477,14 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
|||
->param('code', '', new Text(100), 'OAuth2 state.', true)
|
||||
->param('refresh_token', '', new Text(100), 'OAuth2 refresh token.', true)
|
||||
->inject('response')
|
||||
->action(function ($client_id, $client_secret, $grantType, $redirectURI, $code, $refreshToken, $response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (string $client_id, string $client_secret, string $grantType, string $redirectURI, string $code, string $refreshToken, Response $response) {
|
||||
|
||||
if ($client_id != '1') {
|
||||
throw new Exception('Invalid client ID');
|
||||
throw new Exception('Invalid client ID', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
if ($client_secret != '123456') {
|
||||
throw new Exception('Invalid client secret');
|
||||
throw new Exception('Invalid client secret', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
$responseJson = [
|
||||
|
|
@ -502,20 +493,20 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
|||
'expires_in' => 14400
|
||||
];
|
||||
|
||||
if($grantType === 'authorization_code') {
|
||||
if ($grantType === 'authorization_code') {
|
||||
if ($code !== 'abcdef') {
|
||||
throw new Exception('Invalid token');
|
||||
throw new Exception('Invalid token', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
$response->json($responseJson);
|
||||
} else if($grantType === 'refresh_token') {
|
||||
} elseif ($grantType === 'refresh_token') {
|
||||
if ($refreshToken !== 'tuvwxyz') {
|
||||
throw new Exception('Invalid refresh token');
|
||||
throw new Exception('Invalid refresh token', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
$response->json($responseJson);
|
||||
} else {
|
||||
throw new Exception('Invalid grant type');
|
||||
throw new Exception('Invalid grant type', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -526,11 +517,10 @@ App::get('/v1/mock/tests/general/oauth2/user')
|
|||
->label('docs', false)
|
||||
->param('token', '', new Text(100), 'OAuth2 Access Token.')
|
||||
->inject('response')
|
||||
->action(function ($token, $response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (string $token, Response $response) {
|
||||
|
||||
if ($token != '123456') {
|
||||
throw new Exception('Invalid token');
|
||||
throw new Exception('Invalid token', 400, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
$response->json([
|
||||
|
|
@ -546,8 +536,7 @@ App::get('/v1/mock/tests/general/oauth2/success')
|
|||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->json([
|
||||
'result' => 'success',
|
||||
|
|
@ -560,8 +549,7 @@ App::get('/v1/mock/tests/general/oauth2/failure')
|
|||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_BAD_REQUEST)
|
||||
|
|
@ -570,18 +558,15 @@ App::get('/v1/mock/tests/general/oauth2/failure')
|
|||
]);
|
||||
});
|
||||
|
||||
App::shutdown(function($utopia, $response, $request) {
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
App::shutdown(function (App $utopia, Response $response, Request $request) {
|
||||
|
||||
$result = [];
|
||||
$route = $utopia->match($request);
|
||||
$path = APP_STORAGE_CACHE.'/tests.json';
|
||||
$path = APP_STORAGE_CACHE . '/tests.json';
|
||||
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
|
||||
|
||||
|
||||
if (!\is_array($tests)) {
|
||||
throw new Exception('Failed to read results', 500);
|
||||
throw new Exception('Failed to read results', 500, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
$result[$route->getMethod() . ':' . $route->getPath()] = true;
|
||||
|
|
@ -589,8 +574,8 @@ App::shutdown(function($utopia, $response, $request) {
|
|||
$tests = \array_merge($tests, $result);
|
||||
|
||||
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
|
||||
throw new Exception('Failed to save results', 500);
|
||||
throw new Exception('Failed to save results', 500, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
|
||||
}, ['utopia', 'response', 'request'], 'mock');
|
||||
}, ['utopia', 'response', 'request'], 'mock');
|
||||
|
|
|
|||
|
|
@ -1,38 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Stats\Stats;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Utopia\App;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $usage, $deletes, $database, $dbForProject, $mode) {
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
/** @var Appwrite\Event\Event $events */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
/** @var Appwrite\Event\Event $deletes */
|
||||
/** @var Appwrite\Event\Event $database */
|
||||
/** @var Appwrite\Event\Event $functions */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
|
||||
Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId()));
|
||||
Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId()));
|
||||
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) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
|
||||
throw new Exception('Missing or unknown project ID', 400);
|
||||
throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -49,7 +40,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
|
|||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname().$route->getPath());
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath());
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
|
||||
|
|
@ -61,8 +52,8 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
|
|||
|
||||
foreach ($timeLimitArray as $timeLimit) {
|
||||
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
|
||||
if(!empty($value)) {
|
||||
$timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value);
|
||||
if (!empty($value)) {
|
||||
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,11 +68,12 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
|
|||
;
|
||||
}
|
||||
|
||||
if (($abuse->check() // Route is rate-limited
|
||||
&& App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not disabled
|
||||
&& (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key
|
||||
{
|
||||
throw new Exception('Too many requests', 429);
|
||||
if (
|
||||
(App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
|
||||
&& $abuse->check()) // Abuse is not disabled
|
||||
&& (!$isAppUser && !$isPrivilegedUser)
|
||||
) { // User is not an admin or API key
|
||||
throw new Exception('Too many requests', 429, Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,180 +81,170 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
|
|||
* Background Jobs
|
||||
*/
|
||||
$events
|
||||
->setParam('projectId', $project->getId())
|
||||
->setParam('webhooks', $project->getAttribute('webhooks', []))
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('event', $route->getLabel('event', ''))
|
||||
->setParam('eventData', [])
|
||||
->setParam('functionId', null)
|
||||
->setParam('executionId', null)
|
||||
->setParam('trigger', 'event')
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$mails
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$audits
|
||||
->setParam('projectId', $project->getId())
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('userEmail', $user->getAttribute('email'))
|
||||
->setParam('userName', $user->getAttribute('name'))
|
||||
->setParam('mode', $mode)
|
||||
->setParam('event', '')
|
||||
->setParam('resource', '')
|
||||
->setParam('userAgent', $request->getUserAgent(''))
|
||||
->setParam('ip', $request->getIP())
|
||||
->setParam('data', [])
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
->setIP($request->getIP())
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$usage
|
||||
->setParam('projectId', $project->getId())
|
||||
->setParam('httpRequest', 1)
|
||||
->setParam('httpUrl', $request->getHostname().$request->getURI())
|
||||
->setParam('httpUrl', $request->getHostname() . $request->getURI())
|
||||
->setParam('httpMethod', $request->getMethod())
|
||||
->setParam('httpPath', $route->getPath())
|
||||
->setParam('networkRequestSize', 0)
|
||||
->setParam('networkResponseSize', 0)
|
||||
->setParam('storage', 0)
|
||||
;
|
||||
|
||||
$deletes
|
||||
->setParam('projectId', $project->getId())
|
||||
;
|
||||
|
||||
$database
|
||||
->setParam('projectId', $project->getId())
|
||||
;
|
||||
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api');
|
||||
$deletes->setProject($project);
|
||||
$database->setProject($project);
|
||||
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'mails', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api');
|
||||
|
||||
App::init(function ($utopia, $request, $project) {
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
App::init(function (App $utopia, Request $request, Document $project) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
switch ($route->getLabel('auth.type', '')) {
|
||||
case 'emailPassword':
|
||||
if(($auths['emailPassword'] ?? true) === false) {
|
||||
throw new Exception('Email / Password authentication is disabled for this project', 501);
|
||||
if (($auths['emailPassword'] ?? true) === false) {
|
||||
throw new Exception('Email / Password authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'magic-url':
|
||||
if($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||
throw new Exception('Magic URL authentication is disabled for this project', 501);
|
||||
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||
throw new Exception('Magic URL authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'anonymous':
|
||||
if(($auths['anonymous'] ?? true) === false) {
|
||||
throw new Exception('Anonymous authentication is disabled for this project', 501);
|
||||
if (($auths['anonymous'] ?? true) === false) {
|
||||
throw new Exception('Anonymous authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invites':
|
||||
if(($auths['invites'] ?? true) === false) {
|
||||
throw new Exception('Invites authentication is disabled for this project', 501);
|
||||
if (($auths['invites'] ?? true) === false) {
|
||||
throw new Exception('Invites authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'jwt':
|
||||
if(($auths['JWT'] ?? true) === false) {
|
||||
throw new Exception('JWT authentication is disabled for this project', 501);
|
||||
if (($auths['JWT'] ?? true) === false) {
|
||||
throw new Exception('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Unsupported authentication route');
|
||||
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
break;
|
||||
}
|
||||
|
||||
}, ['utopia', 'request', 'project'], 'auth');
|
||||
|
||||
App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $database, $mode) {
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Appwrite\Event\Event $events */
|
||||
/** @var Appwrite\Event\Event $audits */
|
||||
/** @var Appwrite\Stats\Stats $usage */
|
||||
/** @var Appwrite\Event\Event $deletes */
|
||||
/** @var Appwrite\Event\Event $database */
|
||||
/** @var bool $mode */
|
||||
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) {
|
||||
|
||||
if (!empty($events->getParam('event'))) {
|
||||
if (empty($events->getParam('eventData'))) {
|
||||
$events->setParam('eventData', $response->getPayload());
|
||||
if (!empty($events->getEvent())) {
|
||||
if (empty($events->getPayload())) {
|
||||
$events->setPayload($response->getPayload());
|
||||
}
|
||||
|
||||
$webhooks = clone $events;
|
||||
$functions = clone $events;
|
||||
|
||||
$webhooks
|
||||
->setQueue('v1-webhooks')
|
||||
->setClass('WebhooksV1')
|
||||
/**
|
||||
* Trigger functions.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::FUNCTIONS_CLASS_NAME)
|
||||
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
|
||||
->trigger();
|
||||
|
||||
$functions
|
||||
->setQueue('v1-functions')
|
||||
->setClass('FunctionsV1')
|
||||
/**
|
||||
* Trigger webhooks.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
->trigger();
|
||||
|
||||
/**
|
||||
* Trigger realtime.
|
||||
*/
|
||||
if ($project->getId() !== 'console') {
|
||||
$payload = new Document($response->getPayload());
|
||||
$collection = new Document($events->getParam('collection') ?? []);
|
||||
$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;
|
||||
|
||||
$target = Realtime::fromPayload(
|
||||
event: $events->getParam('event'),
|
||||
payload: $payload,
|
||||
project: $project,
|
||||
collection: $collection
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $project,
|
||||
collection: $collection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
$target['projectId'] ?? $project->getId(),
|
||||
$response->getPayload(),
|
||||
$events->getParam('event'),
|
||||
$target['channels'],
|
||||
$target['roles'],
|
||||
[
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
projectId: $target['projectId'] ?? $project->getId(),
|
||||
payload: $events->getPayload(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
'userId' => $events->getParam('userId')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($audits->getParam('event'))) {
|
||||
if (!empty($audits->getResource())) {
|
||||
foreach ($events->getParams() as $key => $value) {
|
||||
$audits->setParam($key, $value);
|
||||
}
|
||||
$audits->trigger();
|
||||
}
|
||||
|
||||
if (!empty($deletes->getParam('type')) && !empty($deletes->getParam('document'))) {
|
||||
if (!empty($deletes->getType())) {
|
||||
$deletes->trigger();
|
||||
}
|
||||
|
||||
if (!empty($database->getParam('type')) && !empty($database->getParam('document'))) {
|
||||
if (!empty($database->getType())) {
|
||||
$database->trigger();
|
||||
}
|
||||
|
||||
$route = $utopia->match($request);
|
||||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
|
||||
if (
|
||||
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
|
||||
&& $project->getId()
|
||||
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
|
||||
&& !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage on admin mode
|
||||
|
||||
&& !empty($route->getLabel('sdk.namespace', null))
|
||||
) { // Don't calculate console usage on admin mode
|
||||
$usage
|
||||
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
|
||||
->setParam('networkResponseSize', $response->getSize())
|
||||
->submit()
|
||||
;
|
||||
->submit();
|
||||
}
|
||||
|
||||
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode'], 'api');
|
||||
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode', 'dbForProject'], 'api');
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\View;
|
||||
|
||||
App::init(function ($utopia, $request, $response, $layout) {
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
App::init(function (App $utopia, Request $request, Response $response, View $layout) {
|
||||
|
||||
/* AJAX check */
|
||||
if (!empty($request->getQuery('version', ''))) {
|
||||
|
|
@ -48,10 +47,10 @@ App::init(function ($utopia, $request, $response, $layout) {
|
|||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$route->label('error', __DIR__.'/../../views/general/error.phtml');
|
||||
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
|
||||
|
||||
$scope = $route->getLabel('scope', '');
|
||||
|
||||
|
||||
$layout
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
->setParam('isDev', App::isDevelopment())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
|
|
@ -7,8 +9,7 @@ use Utopia\Domains\Domain;
|
|||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Storage\Storage;
|
||||
|
||||
App::init(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
App::init(function (View $layout) {
|
||||
|
||||
$layout
|
||||
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
|
||||
|
|
@ -16,12 +17,10 @@ App::init(function ($layout) {
|
|||
;
|
||||
}, ['layout'], 'console');
|
||||
|
||||
App::shutdown(function ($response, $layout) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
App::shutdown(function (Response $response, View $layout) {
|
||||
|
||||
$header = new View(__DIR__.'/../../views/console/comps/header.phtml');
|
||||
$footer = new View(__DIR__.'/../../views/console/comps/footer.phtml');
|
||||
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
|
||||
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
|
||||
|
||||
$footer
|
||||
->setParam('home', App::getEnv('_APP_HOME', ''))
|
||||
|
|
@ -42,17 +41,16 @@ App::get('/error/:code')
|
|||
->label('scope', 'home')
|
||||
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
|
||||
->inject('layout')
|
||||
->action(function ($code, $layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (int $code, View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/error.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/error.phtml');
|
||||
|
||||
$page
|
||||
->setParam('code', $code)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Error')
|
||||
->setParam('title', APP_NAME . ' - Error')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -61,17 +59,16 @@ App::get('/console')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('home', App::getEnv('_APP_HOME', ''))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Console')
|
||||
->setParam('title', APP_NAME . ' - Console')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -80,19 +77,18 @@ App::get('/console/account')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/account/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/account/index.phtml');
|
||||
|
||||
$cc = new View(__DIR__.'/../../views/console/forms/credit-card.phtml');
|
||||
$cc = new View(__DIR__ . '/../../views/console/forms/credit-card.phtml');
|
||||
|
||||
$page
|
||||
->setParam('cc', $cc)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Account - '.APP_NAME)
|
||||
->setParam('title', 'Account - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -101,13 +97,12 @@ App::get('/console/notifications')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/v1/console/notifications/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/v1/console/notifications/index.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Notifications')
|
||||
->setParam('title', APP_NAME . ' - Notifications')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -116,14 +111,13 @@ App::get('/console/home')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/home/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/home/index.phtml');
|
||||
$page
|
||||
->setParam('usageStatsEnabled',App::getEnv('_APP_USAGE_STATS','enabled') == 'enabled');
|
||||
->setParam('usageStatsEnabled', App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled');
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Console')
|
||||
->setParam('title', APP_NAME . ' - Console')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -132,22 +126,20 @@ App::get('/console/settings')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/settings/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/settings/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('services', array_filter(Config::getParam('services'), function($element) {return $element['optional'];}))
|
||||
$page->setParam('services', array_filter(Config::getParam('services'), fn($element) => $element['optional']))
|
||||
->setParam('customDomainsEnabled', ($target->isKnown() && !$target->isTest()))
|
||||
->setParam('customDomainsTarget', $target->get())
|
||||
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Settings')
|
||||
->setParam('title', APP_NAME . ' - Settings')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -156,17 +148,53 @@ App::get('/console/webhooks')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/webhooks/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/webhooks/index.phtml');
|
||||
|
||||
$page->setParam('events', Config::getParam('events', []));
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Webhooks')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/webhooks/webhook')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('id', '', new UID(), 'Webhook unique ID.')
|
||||
->inject('layout')
|
||||
->action(function (string $id, View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/webhooks/webhook.phtml');
|
||||
|
||||
$page
|
||||
->setParam('events', Config::getParam('events', []))
|
||||
->setParam('new', false)
|
||||
;
|
||||
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Webhooks')
|
||||
->setParam('title', APP_NAME . ' - Webhooks')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/webhooks/webhook/new')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/webhooks/webhook.phtml');
|
||||
|
||||
$page
|
||||
->setParam('events', Config::getParam('events', []))
|
||||
->setParam('new', true)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Webhooks')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -175,16 +203,15 @@ App::get('/console/keys')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$scopes = array_keys(Config::getParam('scopes'));
|
||||
$page = new View(__DIR__.'/../../views/console/keys/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/keys/index.phtml');
|
||||
|
||||
$page->setParam('scopes', $scopes);
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - API Keys')
|
||||
->setParam('title', APP_NAME . ' - API Keys')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -193,13 +220,12 @@ App::get('/console/database')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/database/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/database/index.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Database')
|
||||
->setParam('title', APP_NAME . ' - Database')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -210,11 +236,9 @@ App::get('/console/database/collection')
|
|||
->param('id', '', new UID(), 'Collection unique ID.')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function ($id, $response, $layout) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (string $id, Response $response, View $layout) {
|
||||
|
||||
$logs = new View(__DIR__.'/../../views/console/comps/logs.phtml');
|
||||
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
|
|
@ -224,12 +248,12 @@ App::get('/console/database/collection')
|
|||
])
|
||||
;
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/database/collection.phtml');
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/database/collection.phtml');
|
||||
|
||||
$page->setParam('logs', $logs);
|
||||
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Database Collection')
|
||||
->setParam('title', APP_NAME . ' - Database Collection')
|
||||
->setParam('body', $page)
|
||||
;
|
||||
|
||||
|
|
@ -246,10 +270,9 @@ App::get('/console/database/document')
|
|||
->label('scope', 'console')
|
||||
->param('collection', '', new UID(), 'Collection unique ID.')
|
||||
->inject('layout')
|
||||
->action(function ($collection, $layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (string $collection, View $layout) {
|
||||
|
||||
$logs = new View(__DIR__.'/../../views/console/comps/logs.phtml');
|
||||
$logs = new View(__DIR__ . '/../../views/console/comps/logs.phtml');
|
||||
|
||||
$logs
|
||||
->setParam('interval', App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 0))
|
||||
|
|
@ -260,7 +283,7 @@ App::get('/console/database/document')
|
|||
])
|
||||
;
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/database/document.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/database/document.phtml');
|
||||
|
||||
$page
|
||||
->setParam('new', false)
|
||||
|
|
@ -269,7 +292,7 @@ App::get('/console/database/document')
|
|||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Database Document')
|
||||
->setParam('title', APP_NAME . ' - Database Document')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -279,10 +302,9 @@ App::get('/console/database/document/new')
|
|||
->label('scope', 'console')
|
||||
->param('collection', '', new UID(), 'Collection unique ID.')
|
||||
->inject('layout')
|
||||
->action(function ($collection, $layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (string $collection, View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/database/document.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/database/document.phtml');
|
||||
|
||||
$page
|
||||
->setParam('new', true)
|
||||
|
|
@ -291,7 +313,7 @@ App::get('/console/database/document/new')
|
|||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Database Document')
|
||||
->setParam('title', APP_NAME . ' - Database Document')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -300,10 +322,10 @@ App::get('/console/storage')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
$page = new View(__DIR__.'/../../views/console/storage/index.phtml');
|
||||
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/storage/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('home', App::getEnv('_APP_HOME', 0))
|
||||
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
|
||||
|
|
@ -311,19 +333,46 @@ App::get('/console/storage')
|
|||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Storage')
|
||||
->setParam('title', APP_NAME . ' - Storage')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
App::get('/console/storage/bucket')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->param('id', '', new UID(), 'Bucket unique ID.')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (string $id, Response $response, View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/console/storage/bucket.phtml');
|
||||
$page
|
||||
->setParam('home', App::getEnv('_APP_HOME', 0))
|
||||
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
|
||||
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME . ' - Storage Buckets')
|
||||
->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/users')
|
||||
->groups(['web', 'console'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/users/index.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/users/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('auth', Config::getParam('auth'))
|
||||
|
|
@ -332,7 +381,7 @@ App::get('/console/users')
|
|||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Users')
|
||||
->setParam('title', APP_NAME . ' - Users')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -341,13 +390,12 @@ App::get('/console/users/user')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/users/user.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/users/user.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - User')
|
||||
->setParam('title', APP_NAME . ' - User')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -356,13 +404,12 @@ App::get('/console/users/teams/team')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/console/users/team.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/console/users/team.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Team')
|
||||
->setParam('title', APP_NAME . ' - Team')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -372,15 +419,15 @@ App::get('/console/functions')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
$page = new View(__DIR__.'/../../views/console/functions/index.phtml');
|
||||
->action(function (View $layout) {
|
||||
$page = new View(__DIR__ . '/../../views/console/functions/index.phtml');
|
||||
|
||||
$page
|
||||
->setParam('runtimes', Config::getParam('runtimes'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Functions')
|
||||
->setParam('title', APP_NAME . ' - Functions')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -390,19 +437,19 @@ App::get('/console/functions/function')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'console')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
$page = new View(__DIR__.'/../../views/console/functions/function.phtml');
|
||||
->action(function (View $layout) {
|
||||
$page = new View(__DIR__ . '/../../views/console/functions/function.phtml');
|
||||
|
||||
$page
|
||||
->setParam('events', Config::getParam('events', []))
|
||||
->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
|
||||
->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
|
||||
->setParam('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900))
|
||||
->setParam('usageStatsEnabled',App::getEnv('_APP_USAGE_STATS','enabled') == 'enabled');
|
||||
->setParam('usageStatsEnabled', App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled');
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME.' - Function')
|
||||
->setParam('title', APP_NAME . ' - Function')
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -414,14 +461,14 @@ App::get('/console/version')
|
|||
->inject('response')
|
||||
->action(function ($response) {
|
||||
try {
|
||||
$version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost').'/v1/health/version'), true);
|
||||
|
||||
$version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost') . '/v1/health/version'), true);
|
||||
|
||||
if ($version && isset($version['version'])) {
|
||||
return $response->json(['version' => $version['version']]);
|
||||
} else {
|
||||
throw new Exception('Failed to check for a newer version', 500);
|
||||
throw new Exception('Failed to check for a newer version', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
throw new Exception('Failed to check for a newer version', 500);
|
||||
throw new Exception('Failed to check for a newer version', 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Specification\Format\OpenAPI3;
|
||||
use Appwrite\Specification\Format\Swagger2;
|
||||
use Appwrite\Specification\Specification;
|
||||
use Appwrite\Utopia\View;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
App::init(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
App::init(function (View $layout) {
|
||||
|
||||
$header = new View(__DIR__.'/../../views/home/comps/header.phtml');
|
||||
$footer = new View(__DIR__.'/../../views/home/comps/footer.phtml');
|
||||
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
|
||||
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
|
||||
|
||||
$footer
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
|
|
@ -30,9 +26,7 @@ App::init(function ($layout) {
|
|||
;
|
||||
}, ['layout'], 'home');
|
||||
|
||||
App::shutdown(function ($response, $layout) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
App::shutdown(function (Response $response, View $layout) {
|
||||
|
||||
$response->html($layout->render());
|
||||
}, ['response', 'layout'], 'home');
|
||||
|
|
@ -44,10 +38,7 @@ App::get('/')
|
|||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('project')
|
||||
->action(function ($response, $dbForConsole, $project) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
->action(function (Response $response, Database $dbForConsole, Document $project) {
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
|
|
@ -58,10 +49,10 @@ App::get('/')
|
|||
if ('console' === $project->getId() || $project->isEmpty()) {
|
||||
$whitelistRoot = App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled');
|
||||
|
||||
if($whitelistRoot !== 'disabled') {
|
||||
if ($whitelistRoot !== 'disabled') {
|
||||
$count = $dbForConsole->count('users', [], 1);
|
||||
|
||||
if($count !== 0) {
|
||||
if ($count !== 0) {
|
||||
return $response->redirect('/auth/signin');
|
||||
}
|
||||
}
|
||||
|
|
@ -75,17 +66,16 @@ App::get('/auth/signin')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/home/auth/signin.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/signin.phtml');
|
||||
|
||||
$page
|
||||
->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Sign In - '.APP_NAME)
|
||||
->setParam('title', 'Sign In - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -94,16 +84,16 @@ App::get('/auth/signup')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
$page = new View(__DIR__.'/../../views/home/auth/signup.phtml');
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/signup.phtml');
|
||||
|
||||
$page
|
||||
->setParam('root', App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Sign Up - '.APP_NAME)
|
||||
->setParam('title', 'Sign Up - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -112,17 +102,16 @@ App::get('/auth/recovery')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/home/auth/recovery.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/recovery.phtml');
|
||||
|
||||
$page
|
||||
->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST'))))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Password Recovery - '.APP_NAME)
|
||||
->setParam('title', 'Password Recovery - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -131,13 +120,12 @@ App::get('/auth/confirm')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/home/auth/confirm.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/confirm.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Account Confirmation - '.APP_NAME)
|
||||
->setParam('title', 'Account Confirmation - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -146,13 +134,12 @@ App::get('/auth/join')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/home/auth/join.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/join.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Invitation - '.APP_NAME)
|
||||
->setParam('title', 'Invitation - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -161,13 +148,12 @@ App::get('/auth/recovery/reset')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/home/auth/recovery/reset.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/recovery/reset.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Password Reset - '.APP_NAME)
|
||||
->setParam('title', 'Password Reset - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -176,10 +162,9 @@ App::get('/auth/oauth2/success')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/oauth2.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
|
|
@ -194,10 +179,9 @@ App::get('/auth/magic-url')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/home/auth/magicURL.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/magicURL.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
|
|
@ -212,10 +196,9 @@ App::get('/auth/oauth2/failure')
|
|||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('layout')
|
||||
->action(function ($layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/home/auth/oauth2.phtml');
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
|
|
@ -231,17 +214,16 @@ App::get('/error/:code')
|
|||
->label('scope', 'home')
|
||||
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
|
||||
->inject('layout')
|
||||
->action(function ($code, $layout) {
|
||||
/** @var Appwrite\Utopia\View $layout */
|
||||
->action(function (int $code, View $layout) {
|
||||
|
||||
$page = new View(__DIR__.'/../../views/error.phtml');
|
||||
$page = new View(__DIR__ . '/../../views/error.phtml');
|
||||
|
||||
$page
|
||||
->setParam('code', $code)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', 'Error'.' - '.APP_NAME)
|
||||
->setParam('title', 'Error' . ' - ' . APP_NAME)
|
||||
->setParam('body', $page);
|
||||
});
|
||||
|
||||
|
|
@ -250,8 +232,7 @@ App::get('/versions')
|
|||
->groups(['web', 'home'])
|
||||
->label('scope', 'public')
|
||||
->inject('response')
|
||||
->action(function ($response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
->action(function (Response $response) {
|
||||
|
||||
$platforms = Config::getParam('platforms');
|
||||
|
||||
|
|
@ -259,15 +240,15 @@ App::get('/versions')
|
|||
'server' => APP_VERSION_STABLE,
|
||||
];
|
||||
|
||||
foreach($platforms as $platform) {
|
||||
foreach ($platforms as $platform) {
|
||||
$languages = $platform['languages'] ?? [];
|
||||
|
||||
foreach ($languages as $key => $language) {
|
||||
if(isset($language['dev']) && $language['dev']) {
|
||||
if (isset($language['dev']) && $language['dev']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(isset($language['enabled']) && !$language['enabled']) {
|
||||
if (isset($language['enabled']) && !$language['enabled']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
app/db/DBIP/dbip-country-lite-2022-03.mmdb
Normal file
|
|
@ -1,90 +0,0 @@
|
|||
CREATE DATABASE IF NOT EXISTS `appwrite` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
|
||||
|
||||
USE `appwrite`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `template.abuse.abuse` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`_key` varchar(255) NOT NULL,
|
||||
`_time` int(11) NOT NULL,
|
||||
`_count` int(11) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique1` (`_key`,`_time`),
|
||||
KEY `index1` (`_key`,`_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `template.audit.audit` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`userId` varchar(45) NOT NULL,
|
||||
`event` varchar(45) NOT NULL,
|
||||
`resource` varchar(45) DEFAULT NULL,
|
||||
`userAgent` text NOT NULL,
|
||||
`ip` varchar(45) NOT NULL,
|
||||
`location` varchar(45) DEFAULT NULL,
|
||||
`time` datetime NOT NULL,
|
||||
`data` longtext DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||
KEY `index_1` (`userId`),
|
||||
KEY `index_2` (`event`),
|
||||
KEY `index_3` (`resource`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `template.database.documents` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Unique ID for each node',
|
||||
`uid` varchar(45) DEFAULT NULL,
|
||||
`status` int(11) NOT NULL DEFAULT 0,
|
||||
`createdAt` datetime DEFAULT NULL,
|
||||
`updatedAt` datetime DEFAULT NULL,
|
||||
`signature` varchar(32) NOT NULL,
|
||||
`revision` varchar(45) NOT NULL,
|
||||
`permissions` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `id_UNIQUE` (`id`),
|
||||
UNIQUE KEY `index2` (`uid`),
|
||||
KEY `index3` (`signature`,`uid`,`revision`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `template.database.properties` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
|
||||
`documentUid` varchar(45) NOT NULL COMMENT 'Unique UID foreign key',
|
||||
`documentRevision` varchar(45) NOT NULL,
|
||||
`key` varchar(32) NOT NULL COMMENT 'Property key name',
|
||||
`value` text NOT NULL COMMENT 'Value of property',
|
||||
`primitive` varchar(32) NOT NULL COMMENT 'Primitive type of property value',
|
||||
`array` tinyint(4) NOT NULL DEFAULT 0,
|
||||
`order` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `index1` (`documentUid`),
|
||||
KEY `index2` (`key`,`value`(5)),
|
||||
FULLTEXT KEY `index3` (`value`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `template.database.relationships` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`revision` varchar(45) NOT NULL,
|
||||
`start` varchar(45) NOT NULL COMMENT 'Unique UID foreign key',
|
||||
`end` varchar(45) NOT NULL COMMENT 'Unique UID foreign key',
|
||||
`key` varchar(256) NOT NULL,
|
||||
`path` int(11) NOT NULL DEFAULT 0,
|
||||
`array` tinyint(4) NOT NULL DEFAULT 0,
|
||||
`order` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `relationships_start_nodes_id_idx` (`start`),
|
||||
KEY `relationships_end_nodes_id_idx` (`end`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `template.database.unique` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`key` varchar(128) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `index1` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
/* Default App */
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `app_console.database.documents` LIKE `template.database.documents`;
|
||||
CREATE TABLE IF NOT EXISTS `app_console.database.properties` LIKE `template.database.properties`;
|
||||
CREATE TABLE IF NOT EXISTS `app_console.database.relationships` LIKE `template.database.relationships`;
|
||||
CREATE TABLE IF NOT EXISTS `app_console.database.unique` LIKE `template.database.unique`;
|
||||
CREATE TABLE IF NOT EXISTS `app_console.audit.audit` LIKE `template.audit.audit`;
|
||||
CREATE TABLE IF NOT EXISTS `app_console.abuse.abuse` LIKE `template.abuse.abuse`;
|
||||
749
app/executor.php
Normal file
|
|
@ -0,0 +1,749 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
use Swoole\ConnectionPool;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Swoole\Http\Server;
|
||||
use Swoole\Process;
|
||||
use Swoole\Runtime;
|
||||
use Swoole\Timer;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Orchestration\Adapter\DockerCLI;
|
||||
use Utopia\Orchestration\Orchestration;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
use Utopia\Storage\Device\DOSpaces;
|
||||
use Utopia\Storage\Device\Linode;
|
||||
use Utopia\Storage\Device\Wasabi;
|
||||
use Utopia\Storage\Device\S3;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Swoole\Request;
|
||||
use Utopia\Swoole\Response;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
|
||||
Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
|
||||
|
||||
/** Constants */
|
||||
const MAINTENANCE_INTERVAL = 3600; // 3600 seconds = 1 hour
|
||||
|
||||
/**
|
||||
* Create a Swoole table to store runtime information
|
||||
*/
|
||||
$activeRuntimes = new Swoole\Table(1024);
|
||||
$activeRuntimes->column('id', Swoole\Table::TYPE_STRING, 256);
|
||||
$activeRuntimes->column('created', Swoole\Table::TYPE_INT, 8);
|
||||
$activeRuntimes->column('updated', Swoole\Table::TYPE_INT, 8);
|
||||
$activeRuntimes->column('name', Swoole\Table::TYPE_STRING, 128);
|
||||
$activeRuntimes->column('status', Swoole\Table::TYPE_STRING, 128);
|
||||
$activeRuntimes->column('key', Swoole\Table::TYPE_STRING, 256);
|
||||
$activeRuntimes->create();
|
||||
|
||||
/**
|
||||
* Create orchestration pool
|
||||
*/
|
||||
$orchestrationPool = new ConnectionPool(function () {
|
||||
$dockerUser = App::getEnv('DOCKERHUB_PULL_USERNAME', null);
|
||||
$dockerPass = App::getEnv('DOCKERHUB_PULL_PASSWORD', null);
|
||||
$orchestration = new Orchestration(new DockerCLI($dockerUser, $dockerPass));
|
||||
return $orchestration;
|
||||
}, 10);
|
||||
|
||||
|
||||
/**
|
||||
* Create logger instance
|
||||
*/
|
||||
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
|
||||
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
|
||||
$logger = null;
|
||||
|
||||
if (!empty($providerName) && !empty($providerConfig) && Logger::hasProvider($providerName)) {
|
||||
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
|
||||
$adapter = new $classname($providerConfig);
|
||||
$logger = new Logger($adapter);
|
||||
}
|
||||
|
||||
function logError(Throwable $error, string $action, Utopia\Route $route = null)
|
||||
{
|
||||
global $logger;
|
||||
|
||||
if ($logger) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace("executor");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
if ($route) {
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
}
|
||||
|
||||
$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('Executor log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $error->getMessage());
|
||||
Console::error('[Error] File: ' . $error->getFile());
|
||||
Console::error('[Error] Line: ' . $error->getLine());
|
||||
}
|
||||
|
||||
function getStorageDevice($root): Device
|
||||
{
|
||||
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
|
||||
case Storage::DEVICE_LOCAL:
|
||||
default:
|
||||
return new Local($root);
|
||||
case Storage::DEVICE_S3:
|
||||
$s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
|
||||
$s3SecretKey = App::getEnv('_APP_STORAGE_S3_SECRET', '');
|
||||
$s3Region = App::getEnv('_APP_STORAGE_S3_REGION', '');
|
||||
$s3Bucket = App::getEnv('_APP_STORAGE_S3_BUCKET', '');
|
||||
$s3Acl = 'private';
|
||||
return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
|
||||
case Storage::DEVICE_DO_SPACES:
|
||||
$doSpacesAccessKey = App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
|
||||
$doSpacesSecretKey = App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
|
||||
$doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
|
||||
$doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
|
||||
$doSpacesAcl = 'private';
|
||||
return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
|
||||
case Storage::DEVICE_BACKBLAZE:
|
||||
$backblazeAccessKey = App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
|
||||
$backblazeSecretKey = App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
|
||||
$backblazeRegion = App::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
|
||||
$backblazeBucket = App::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
|
||||
$backblazeAcl = 'private';
|
||||
return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
|
||||
case Storage::DEVICE_LINODE:
|
||||
$linodeAccessKey = App::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
|
||||
$linodeSecretKey = App::getEnv('_APP_STORAGE_LINODE_SECRET', '');
|
||||
$linodeRegion = App::getEnv('_APP_STORAGE_LINODE_REGION', '');
|
||||
$linodeBucket = App::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
|
||||
$linodeAcl = 'private';
|
||||
return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
|
||||
case Storage::DEVICE_WASABI:
|
||||
$wasabiAccessKey = App::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
|
||||
$wasabiSecretKey = App::getEnv('_APP_STORAGE_WASABI_SECRET', '');
|
||||
$wasabiRegion = App::getEnv('_APP_STORAGE_WASABI_REGION', '');
|
||||
$wasabiBucket = App::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
|
||||
$wasabiAcl = 'private';
|
||||
return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
|
||||
}
|
||||
}
|
||||
|
||||
App::post('/v1/runtimes')
|
||||
->desc("Create a new runtime server")
|
||||
->param('runtimeId', '', new Text(64), 'Unique runtime ID.')
|
||||
->param('source', '', new Text(0), 'Path to source files.')
|
||||
->param('destination', '', new Text(0), 'Destination folder to store build files into.', true)
|
||||
->param('vars', [], new Assoc(), 'Environment Variables required for the build.')
|
||||
->param('commands', [], new ArrayList(new Text(1024), 100), 'Commands required to build the container. Maximum of 100 commands are allowed, each 1024 characters long.')
|
||||
->param('runtime', '', new Text(128), 'Runtime for the cloud function.')
|
||||
->param('baseImage', '', new Text(128), 'Base image name of the runtime.')
|
||||
->param('entrypoint', '', new Text(256), 'Entrypoint of the code file.', true)
|
||||
->param('remove', false, new Boolean(), 'Remove a runtime after execution.')
|
||||
->param('workdir', '', new Text(256), 'Working directory.', true)
|
||||
->inject('orchestrationPool')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
->action(function (string $runtimeId, string $source, string $destination, array $vars, array $commands, string $runtime, string $baseImage, string $entrypoint, bool $remove, string $workdir, $orchestrationPool, $activeRuntimes, Response $response) {
|
||||
if ($activeRuntimes->exists($runtimeId)) {
|
||||
throw new Exception('Runtime already exists.', 409);
|
||||
}
|
||||
|
||||
$container = [];
|
||||
$containerId = '';
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$startTime = \time();
|
||||
$endTime = 0;
|
||||
$orchestration = $orchestrationPool->get();
|
||||
|
||||
try {
|
||||
Console::info('Building container : ' . $runtimeId);
|
||||
|
||||
/**
|
||||
* Temporary file paths in the executor
|
||||
*/
|
||||
$tmpSource = "/tmp/$runtimeId/src/code.tar.gz";
|
||||
$tmpBuild = "/tmp/$runtimeId/builds/code.tar.gz";
|
||||
|
||||
/**
|
||||
* Copy code files from source to a temporary location on the executor
|
||||
*/
|
||||
$sourceDevice = getStorageDevice("/");
|
||||
$localDevice = new Local();
|
||||
$buffer = $sourceDevice->read($source);
|
||||
if (!$localDevice->write($tmpSource, $buffer)) {
|
||||
throw new Exception('Failed to copy source code to temporary directory', 500);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the mount folder
|
||||
*/
|
||||
if (!\file_exists(\dirname($tmpBuild))) {
|
||||
if (!@\mkdir(\dirname($tmpBuild), 0755, true)) {
|
||||
throw new Exception("Failed to create temporary directory", 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create container
|
||||
*/
|
||||
$secret = \bin2hex(\random_bytes(16));
|
||||
$vars = \array_merge($vars, [
|
||||
'INTERNAL_RUNTIME_KEY' => $secret,
|
||||
'INTERNAL_RUNTIME_ENTRYPOINT' => $entrypoint,
|
||||
]);
|
||||
$vars = array_map(fn ($v) => strval($v), $vars);
|
||||
$orchestration
|
||||
->setCpus((int) App::getEnv('_APP_FUNCTIONS_CPUS', 0))
|
||||
->setMemory((int) App::getEnv('_APP_FUNCTIONS_MEMORY', 0))
|
||||
->setSwap((int) App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 0));
|
||||
|
||||
/** Keep the container alive if we have commands to be executed */
|
||||
$entrypoint = !empty($commands) ? [
|
||||
'tail',
|
||||
'-f',
|
||||
'/dev/null'
|
||||
] : [];
|
||||
|
||||
$containerId = $orchestration->run(
|
||||
image: $baseImage,
|
||||
name: $runtimeId,
|
||||
hostname: $runtimeId,
|
||||
vars: $vars,
|
||||
command: $entrypoint,
|
||||
labels: [
|
||||
'openruntimes-id' => $runtimeId,
|
||||
'openruntimes-type' => 'runtime',
|
||||
'openruntimes-created' => strval($startTime),
|
||||
'openruntimes-runtime' => $runtime,
|
||||
],
|
||||
workdir: $workdir,
|
||||
volumes: [
|
||||
\dirname($tmpSource) . ':/tmp:rw',
|
||||
\dirname($tmpBuild) . ':/usr/code:rw'
|
||||
]
|
||||
);
|
||||
|
||||
if (empty($containerId)) {
|
||||
throw new Exception('Failed to create build container', 500);
|
||||
}
|
||||
|
||||
$orchestration->networkConnect($runtimeId, App::getEnv('OPEN_RUNTIMES_NETWORK', 'appwrite_runtimes'));
|
||||
|
||||
/**
|
||||
* Execute any commands if they were provided
|
||||
*/
|
||||
if (!empty($commands)) {
|
||||
$status = $orchestration->execute(
|
||||
name: $runtimeId,
|
||||
command: $commands,
|
||||
stdout: $stdout,
|
||||
stderr: $stderr,
|
||||
timeout: App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900)
|
||||
);
|
||||
|
||||
if (!$status) {
|
||||
throw new Exception('Failed to build dependenices ' . $stderr, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move built code to expected build directory
|
||||
*/
|
||||
if (!empty($destination)) {
|
||||
// Check if the build was successful by checking if file exists
|
||||
if (!\file_exists($tmpBuild)) {
|
||||
throw new Exception('Something went wrong during the build process', 500);
|
||||
}
|
||||
|
||||
$destinationDevice = getStorageDevice($destination);
|
||||
$outputPath = $destinationDevice->getPath(\uniqid() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
||||
|
||||
$buffer = $localDevice->read($tmpBuild);
|
||||
if (!$destinationDevice->write($outputPath, $buffer, $localDevice->getFileMimeType($tmpBuild))) {
|
||||
throw new Exception('Failed to move built code to storage', 500);
|
||||
};
|
||||
|
||||
$container['outputPath'] = $outputPath;
|
||||
}
|
||||
|
||||
if (empty($stdout)) {
|
||||
$stdout = 'Build Successful!';
|
||||
}
|
||||
|
||||
$endTime = \time();
|
||||
$container = array_merge($container, [
|
||||
'status' => 'ready',
|
||||
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
|
||||
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
|
||||
'startTime' => $startTime,
|
||||
'endTime' => $endTime,
|
||||
'duration' => $endTime - $startTime,
|
||||
]);
|
||||
|
||||
if (!$remove) {
|
||||
$activeRuntimes->set($runtimeId, [
|
||||
'id' => $containerId,
|
||||
'name' => $runtimeId,
|
||||
'created' => $startTime,
|
||||
'updated' => $endTime,
|
||||
'status' => 'Up ' . \round($endTime - $startTime, 2) . 's',
|
||||
'key' => $secret,
|
||||
]);
|
||||
}
|
||||
|
||||
Console::success('Build Stage completed in ' . ($endTime - $startTime) . ' seconds');
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Build failed: ' . $th->getMessage() . $stdout);
|
||||
throw new Exception($th->getMessage() . $stdout, 500);
|
||||
} finally {
|
||||
// Container cleanup
|
||||
if ($remove) {
|
||||
if (!empty($containerId)) {
|
||||
// If container properly created
|
||||
$orchestration->remove($containerId, true);
|
||||
} else {
|
||||
// If whole creation failed, but container might have been initialized
|
||||
try {
|
||||
// Try to remove with contaier name instead of ID
|
||||
$orchestration->remove($runtimeId, true);
|
||||
} catch (Throwable $th) {
|
||||
// If fails, means initialization also failed.
|
||||
// Contianer is not there, no need to remove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Release orchestration back to pool, we are done with it
|
||||
$orchestrationPool->put($orchestration);
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->json($container);
|
||||
});
|
||||
|
||||
|
||||
App::get('/v1/runtimes')
|
||||
->desc("List currently active runtimes")
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
->action(function ($activeRuntimes, Response $response) {
|
||||
$runtimes = [];
|
||||
|
||||
foreach ($activeRuntimes as $runtime) {
|
||||
$runtimes[] = $runtime;
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($runtimes);
|
||||
});
|
||||
|
||||
App::get('/v1/runtimes/:runtimeId')
|
||||
->desc("Get a runtime by its ID")
|
||||
->param('runtimeId', '', new Text(64), 'Runtime unique ID.')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
->action(function ($runtimeId, $activeRuntimes, Response $response) {
|
||||
|
||||
if (!$activeRuntimes->exists($runtimeId)) {
|
||||
throw new Exception('Runtime not found', 404);
|
||||
}
|
||||
|
||||
$runtime = $activeRuntimes->get($runtimeId);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($runtime);
|
||||
});
|
||||
|
||||
App::delete('/v1/runtimes/:runtimeId')
|
||||
->desc('Delete a runtime')
|
||||
->param('runtimeId', '', new Text(64), 'Runtime unique ID.', false)
|
||||
->inject('orchestrationPool')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
->action(function (string $runtimeId, $orchestrationPool, $activeRuntimes, Response $response) {
|
||||
|
||||
if (!$activeRuntimes->exists($runtimeId)) {
|
||||
throw new Exception('Runtime not found', 404);
|
||||
}
|
||||
|
||||
Console::info('Deleting runtime: ' . $runtimeId);
|
||||
|
||||
try {
|
||||
$orchestration = $orchestrationPool->get();
|
||||
$orchestration->remove($runtimeId, true);
|
||||
$activeRuntimes->del($runtimeId);
|
||||
Console::success('Removed runtime container: ' . $runtimeId);
|
||||
} finally {
|
||||
$orchestrationPool->put($orchestration);
|
||||
}
|
||||
|
||||
// Remove all the build containers with that same ID
|
||||
// TODO:: Delete build containers
|
||||
// foreach ($buildIds as $buildId) {
|
||||
// try {
|
||||
// Console::info('Deleting build container : ' . $buildId);
|
||||
// $status = $orchestration->remove('build-' . $buildId, true);
|
||||
// } catch (Throwable $th) {
|
||||
// Console::error($th->getMessage());
|
||||
// }
|
||||
// }
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->send();
|
||||
});
|
||||
|
||||
|
||||
App::post('/v1/execution')
|
||||
->desc('Create an execution')
|
||||
->param('runtimeId', '', new Text(64), 'The runtimeID to execute.')
|
||||
->param('vars', [], new Assoc(), 'Environment variables required for the build.')
|
||||
->param('data', '{}', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
|
||||
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
->action(
|
||||
function (string $runtimeId, array $vars, string $data, $timeout, $activeRuntimes, Response $response) {
|
||||
|
||||
if (!$activeRuntimes->exists($runtimeId)) {
|
||||
throw new Exception('Runtime not found. Please create the runtime.', 404);
|
||||
}
|
||||
|
||||
$runtime = $activeRuntimes->get($runtimeId);
|
||||
$secret = $runtime['key'];
|
||||
if (empty($secret)) {
|
||||
throw new Exception('Runtime secret not found. Please re-create the runtime.', 500);
|
||||
}
|
||||
|
||||
Console::info('Executing Runtime: ' . $runtimeId);
|
||||
|
||||
$execution = [];
|
||||
$executionStart = \microtime(true);
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$statusCode = 0;
|
||||
$errNo = -1;
|
||||
$executorResponse = '';
|
||||
|
||||
$timeout ??= (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900);
|
||||
|
||||
$ch = \curl_init();
|
||||
$body = \json_encode([
|
||||
'env' => $vars,
|
||||
'payload' => $data,
|
||||
'timeout' => $timeout
|
||||
]);
|
||||
\curl_setopt($ch, CURLOPT_URL, "http://" . $runtimeId . ":3000/");
|
||||
\curl_setopt($ch, CURLOPT_POST, true);
|
||||
\curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
\curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
||||
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||
|
||||
\curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . \strlen($body),
|
||||
'x-internal-challenge: ' . $secret,
|
||||
'host: null'
|
||||
]);
|
||||
|
||||
$executorResponse = \curl_exec($ch);
|
||||
|
||||
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
$error = \curl_error($ch);
|
||||
|
||||
$errNo = \curl_errno($ch);
|
||||
|
||||
\curl_close($ch);
|
||||
|
||||
switch (true) {
|
||||
/** No Error. */
|
||||
case $errNo === 0:
|
||||
break;
|
||||
/** Runtime not ready for requests yet. 111 is the swoole error code for Connection Refused - see https://openswoole.com/docs/swoole-error-code */
|
||||
case $errNo === 111:
|
||||
throw new Exception('An internal curl error has occurred within the executor! Error Msg: ' . $error, 406);
|
||||
/** Any other CURL error */
|
||||
default:
|
||||
throw new Exception('An internal curl error has occurred within the executor! Error Msg: ' . $error, 500);
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case $statusCode >= 500:
|
||||
$stderr = $executorResponse ?? 'Internal Runtime error.';
|
||||
break;
|
||||
case $statusCode >= 100:
|
||||
$stdout = $executorResponse;
|
||||
break;
|
||||
default:
|
||||
$stderr = $executorResponse ?? 'Execution failed.';
|
||||
break;
|
||||
}
|
||||
|
||||
$executionEnd = \microtime(true);
|
||||
$executionTime = ($executionEnd - $executionStart);
|
||||
$functionStatus = ($statusCode >= 500) ? 'failed' : 'completed';
|
||||
|
||||
Console::success('Function executed in ' . $executionTime . ' seconds, status: ' . $functionStatus);
|
||||
|
||||
$execution = [
|
||||
'status' => $functionStatus,
|
||||
'statusCode' => $statusCode,
|
||||
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
|
||||
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
|
||||
'time' => $executionTime,
|
||||
];
|
||||
|
||||
/** Update swoole table */
|
||||
$runtime['updated'] = \time();
|
||||
$activeRuntimes->set($runtimeId, $runtime);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($execution);
|
||||
}
|
||||
);
|
||||
|
||||
App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
|
||||
|
||||
$http = new Server("0.0.0.0", 80);
|
||||
|
||||
/** Set Resources */
|
||||
App::setResource('orchestrationPool', fn() => $orchestrationPool);
|
||||
App::setResource('activeRuntimes', fn() => $activeRuntimes);
|
||||
|
||||
/** Set callbacks */
|
||||
App::error(function ($utopia, $error, $request, $response) {
|
||||
$route = $utopia->match($request);
|
||||
logError($error, "httpError", $route);
|
||||
|
||||
switch ($error->getCode()) {
|
||||
case 400: // Error allowed publicly
|
||||
case 401: // Error allowed publicly
|
||||
case 402: // Error allowed publicly
|
||||
case 403: // Error allowed publicly
|
||||
case 404: // Error allowed publicly
|
||||
case 406: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 425: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
case 503: // Error allowed publicly
|
||||
$code = $error->getCode();
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
}
|
||||
|
||||
$output = [
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'file' => $error->getFile(),
|
||||
'line' => $error->getLine(),
|
||||
'trace' => $error->getTrace(),
|
||||
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
|
||||
];
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->setStatusCode($code);
|
||||
|
||||
$response->json($output);
|
||||
}, ['utopia', 'error', 'request', 'response']);
|
||||
|
||||
App::init(function ($request, $response) {
|
||||
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
|
||||
if (empty($secretKey)) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
|
||||
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
}, ['request', 'response']);
|
||||
|
||||
|
||||
$http->on('start', function ($http) {
|
||||
global $orchestrationPool;
|
||||
global $activeRuntimes;
|
||||
|
||||
/**
|
||||
* Warmup: make sure images are ready to run fast 🚀
|
||||
*/
|
||||
$runtimes = new Runtimes('v1');
|
||||
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
|
||||
$runtimes = $runtimes->getAll(true, $allowList);
|
||||
foreach ($runtimes as $runtime) {
|
||||
go(function () use ($runtime, $orchestrationPool) {
|
||||
try {
|
||||
$orchestration = $orchestrationPool->get();
|
||||
Console::info('Warming up ' . $runtime['name'] . ' ' . $runtime['version'] . ' environment...');
|
||||
$response = $orchestration->pull($runtime['image']);
|
||||
if ($response) {
|
||||
Console::success("Successfully Warmed up {$runtime['name']} {$runtime['version']}!");
|
||||
} else {
|
||||
Console::warning("Failed to Warmup {$runtime['name']} {$runtime['version']}!");
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
} finally {
|
||||
$orchestrationPool->put($orchestration);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove residual runtimes
|
||||
*/
|
||||
Console::info('Removing orphan runtimes...');
|
||||
try {
|
||||
$orchestration = $orchestrationPool->get();
|
||||
$orphans = $orchestration->list(['label' => 'openruntimes-type=runtime']);
|
||||
} finally {
|
||||
$orchestrationPool->put($orchestration);
|
||||
}
|
||||
|
||||
foreach ($orphans as $runtime) {
|
||||
go(function () use ($runtime, $orchestrationPool) {
|
||||
try {
|
||||
$orchestration = $orchestrationPool->get();
|
||||
$orchestration->remove($runtime->getName(), true);
|
||||
Console::success("Successfully removed {$runtime->getName()}");
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Orphan runtime deletion failed: ' . $th->getMessage());
|
||||
} finally {
|
||||
$orchestrationPool->put($orchestration);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register handlers for shutdown
|
||||
*/
|
||||
@Process::signal(SIGINT, function () use ($http) {
|
||||
$http->shutdown();
|
||||
});
|
||||
|
||||
@Process::signal(SIGQUIT, function () use ($http) {
|
||||
$http->shutdown();
|
||||
});
|
||||
|
||||
@Process::signal(SIGKILL, function () use ($http) {
|
||||
$http->shutdown();
|
||||
});
|
||||
|
||||
@Process::signal(SIGTERM, function () use ($http) {
|
||||
$http->shutdown();
|
||||
});
|
||||
|
||||
/**
|
||||
* Run a maintenance worker every MAINTENANCE_INTERVAL seconds to remove inactive runtimes
|
||||
*/
|
||||
Timer::tick(MAINTENANCE_INTERVAL * 1000, function () use ($orchestrationPool, $activeRuntimes) {
|
||||
Console::warning("Running maintenance task ...");
|
||||
foreach ($activeRuntimes as $runtime) {
|
||||
$inactiveThreshold = \time() - App::getEnv('_APP_FUNCTIONS_INACTIVE_THRESHOLD', 60);
|
||||
if ($runtime['updated'] < $inactiveThreshold) {
|
||||
go(function () use ($runtime, $orchestrationPool, $activeRuntimes) {
|
||||
try {
|
||||
$orchestration = $orchestrationPool->get();
|
||||
$orchestration->remove($runtime['name'], true);
|
||||
$activeRuntimes->del($runtime['name']);
|
||||
Console::success("Successfully removed {$runtime['name']}");
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Inactive Runtime deletion failed: ' . $th->getMessage());
|
||||
} finally {
|
||||
$orchestrationPool->put($orchestration);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$http->on('beforeShutdown', function () {
|
||||
global $orchestrationPool;
|
||||
Console::info('Cleaning up containers before shutdown...');
|
||||
|
||||
$orchestration = $orchestrationPool->get();
|
||||
$functionsToRemove = $orchestration->list(['label' => 'openruntimes-type=runtime']);
|
||||
$orchestrationPool->put($orchestration);
|
||||
|
||||
foreach ($functionsToRemove as $container) {
|
||||
go(function () use ($orchestrationPool, $container) {
|
||||
try {
|
||||
$orchestration = $orchestrationPool->get();
|
||||
$orchestration->remove($container->getId(), true);
|
||||
Console::info('Removed container ' . $container->getName());
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to remove container: ' . $container->getName());
|
||||
} finally {
|
||||
$orchestrationPool->put($orchestration);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
|
||||
$request = new Request($swooleRequest);
|
||||
$response = new Response($swooleResponse);
|
||||
$app = new App('UTC');
|
||||
|
||||
try {
|
||||
$app->run($request, $response);
|
||||
} catch (\Throwable $th) {
|
||||
logError($th, "serverError");
|
||||
$swooleResponse->setStatusCode(500);
|
||||
$output = [
|
||||
'message' => 'Error: ' . $th->getMessage(),
|
||||
'code' => 500,
|
||||
'file' => $th->getFile(),
|
||||
'line' => $th->getLine(),
|
||||
'trace' => $th->getTrace()
|
||||
];
|
||||
$swooleResponse->end(\json_encode($output));
|
||||
}
|
||||
});
|
||||
|
||||
$http->start();
|
||||
126
app/http.php
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Process;
|
||||
|
|
@ -13,6 +13,7 @@ use Utopia\Config\Config;
|
|||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Swoole\Files;
|
||||
use Appwrite\Utopia\Request;
|
||||
|
|
@ -21,10 +22,12 @@ use Utopia\Logger\Log\User;
|
|||
|
||||
$http = new Server("0.0.0.0", App::getEnv('PORT', 80));
|
||||
|
||||
$payloadSize = max(4000000 /* 4mb */, App::getEnv('_APP_STORAGE_LIMIT', 10000000 /* 10mb */));
|
||||
$payloadSize = 6 * (1024 * 1024); // 6MB
|
||||
$workerNumber = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$http
|
||||
->set([
|
||||
'worker_num' => $workerNumber,
|
||||
'open_http2_protocol' => true,
|
||||
// 'document_root' => __DIR__.'/../public',
|
||||
// 'enable_static_handler' => true,
|
||||
|
|
@ -35,15 +38,15 @@ $http
|
|||
])
|
||||
;
|
||||
|
||||
$http->on('WorkerStart', function($server, $workerId) {
|
||||
Console::success('Worker '.++$workerId.' started successfully');
|
||||
$http->on('WorkerStart', function ($server, $workerId) {
|
||||
Console::success('Worker ' . ++$workerId . ' started successfully');
|
||||
});
|
||||
|
||||
$http->on('BeforeReload', function($server, $workerId) {
|
||||
$http->on('BeforeReload', function ($server, $workerId) {
|
||||
Console::success('Starting reload...');
|
||||
});
|
||||
|
||||
$http->on('AfterReload', function($server, $workerId) {
|
||||
$http->on('AfterReload', function ($server, $workerId) {
|
||||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
|
|
@ -54,7 +57,7 @@ include __DIR__ . '/controllers/general.php';
|
|||
$http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
$app = new App('UTC');
|
||||
|
||||
go(function() use ($register, $app) {
|
||||
go(function () use ($register, $app) {
|
||||
// wait for database to be ready
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
|
|
@ -66,10 +69,10 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
$db = $register->get('dbPool')->get();
|
||||
$redis = $register->get('redisPool')->get();
|
||||
break; // leave the do-while if successful
|
||||
} catch(\Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('Failed to connect to database: '. $e->getMessage());
|
||||
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
|
||||
}
|
||||
sleep($sleep);
|
||||
}
|
||||
|
|
@ -83,7 +86,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
Console::success('[Setup] - Server database init started...');
|
||||
$collections = Config::getParam('collections', []); /** @var array $collections */
|
||||
|
||||
if(!$dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'))) {
|
||||
if (!$dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'))) {
|
||||
$redis->flushAll();
|
||||
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
|
|
@ -98,7 +101,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
if($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForConsole);
|
||||
$audit->setup();
|
||||
}
|
||||
|
|
@ -109,10 +112,12 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
}
|
||||
|
||||
foreach ($collections as $key => $collection) {
|
||||
if(!$dbForConsole->getCollection($key)->isEmpty()) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
if (!$dbForConsole->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
|
||||
|
||||
$attributes = [];
|
||||
|
|
@ -127,6 +132,8 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -141,13 +148,69 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
}
|
||||
|
||||
$dbForConsole->createCollection($key, $attributes, $indexes);
|
||||
|
||||
}
|
||||
|
||||
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty()) {
|
||||
Console::success('[Setup] - Creating default bucket...');
|
||||
$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
|
||||
'allowedFileExtensions' => [],
|
||||
'enabled' => true,
|
||||
'encryption' => true,
|
||||
'antivirus' => true,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['role:all'],
|
||||
'search' => 'buckets Default',
|
||||
]));
|
||||
|
||||
$bucket = $dbForConsole->getDocument('buckets', 'default');
|
||||
|
||||
Console::success('[Setup] - Creating files collection for default bucket...');
|
||||
$files = $collections['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
|
||||
foreach ($files['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => $attribute['$id'],
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($files['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => $index['$id'],
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]);
|
||||
}
|
||||
|
||||
$dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Server database init completed...');
|
||||
});
|
||||
|
||||
Console::success('Server started successfully (max payload is '.number_format($payloadSize).' bytes)');
|
||||
|
||||
Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
|
||||
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
|
||||
|
||||
// listen ctrl + c
|
||||
|
|
@ -161,13 +224,13 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$request = new Request($swooleRequest);
|
||||
$response = new Response($swooleResponse);
|
||||
|
||||
if(Files::isFileLoaded($request->getURI())) {
|
||||
if (Files::isFileLoaded($request->getURI())) {
|
||||
$time = (60 * 60 * 24 * 365 * 2); // 45 days cache
|
||||
|
||||
$response
|
||||
->setContentType(Files::getFileMimeType($request->getURI()))
|
||||
->addHeader('Cache-Control', 'public, max-age='.$time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
->send(Files::getFileContents($request->getURI()))
|
||||
;
|
||||
|
||||
|
|
@ -191,11 +254,11 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$logger = $app->getResource("logger");
|
||||
if($logger) {
|
||||
if ($logger) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $app->getResource('user');
|
||||
} catch(\Throwable $_th) {
|
||||
} catch (\Throwable $_th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +267,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
|
||||
$log = new Utopia\Logger\Log();
|
||||
|
||||
if(isset($user) && !$user->isEmpty()) {
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +278,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$log->setMessage($th->getMessage());
|
||||
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('verboseType', get_class($th));
|
||||
$log->addTag('code', $th->getCode());
|
||||
// $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant
|
||||
|
|
@ -225,6 +288,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$log->addExtra('file', $th->getFile());
|
||||
$log->addExtra('line', $th->getLine());
|
||||
$log->addExtra('trace', $th->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $th->getTrace());
|
||||
$log->addExtra('roles', Authorization::$roles);
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
|
|
@ -233,18 +297,18 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
foreach($loggerBreadcrumbs as $loggerBreadcrumb) {
|
||||
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
|
||||
$log->addBreadcrumb($loggerBreadcrumb);
|
||||
}
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Log pushed with status code: '.$responseCode);
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: '.get_class($th));
|
||||
Console::error('[Error] Message: '.$th->getMessage());
|
||||
Console::error('[Error] File: '.$th->getFile());
|
||||
Console::error('[Error] Line: '.$th->getLine());
|
||||
Console::error('[Error] Type: ' . get_class($th));
|
||||
Console::error('[Error] Message: ' . $th->getMessage());
|
||||
Console::error('[Error] File: ' . $th->getFile());
|
||||
Console::error('[Error] Line: ' . $th->getLine());
|
||||
|
||||
/**
|
||||
* Reset Database connection if PDOException was thrown.
|
||||
|
|
@ -256,7 +320,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$swooleResponse->setStatusCode(500);
|
||||
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
'message' => 'Error: '. $th->getMessage(),
|
||||
'message' => 'Error: ' . $th->getMessage(),
|
||||
'code' => 500,
|
||||
'file' => $th->getFile(),
|
||||
'line' => $th->getLine(),
|
||||
|
|
@ -280,4 +344,4 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
}
|
||||
});
|
||||
|
||||
$http->start();
|
||||
$http->start();
|
||||
|
|
|
|||
566
app/init.php
|
|
@ -2,16 +2,17 @@
|
|||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
*
|
||||
* Initializes both Appwrite API entry point, queue workers, and CLI tasks.
|
||||
* Set configuration, framework resources & app constants
|
||||
*
|
||||
*
|
||||
*/
|
||||
if (\file_exists(__DIR__.'/../vendor/autoload.php')) {
|
||||
require_once __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
if (\file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
}
|
||||
|
||||
ini_set('memory_limit','512M');
|
||||
ini_set('memory_limit', '512M');
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
ini_set('default_socket_timeout', -1);
|
||||
|
|
@ -20,8 +21,13 @@ error_reporting(E_ALL);
|
|||
use Appwrite\Extend\PDO;
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
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;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
|
|
@ -49,19 +55,32 @@ use Swoole\Database\PDOPool;
|
|||
use Swoole\Database\RedisConfig;
|
||||
use Swoole\Database\RedisPool;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
use Utopia\Storage\Device\DOSpaces;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Device\S3;
|
||||
use Utopia\Storage\Device\Linode;
|
||||
use Utopia\Storage\Device\Wasabi;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
|
||||
const APP_EMAIL_SECURITY = ''; // Default security email address
|
||||
const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s';
|
||||
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
|
||||
const APP_MODE_DEFAULT = 'default';
|
||||
const APP_MODE_ADMIN = 'admin';
|
||||
const APP_PAGING_LIMIT = 12;
|
||||
const APP_LIMIT_COUNT = 5000;
|
||||
const APP_LIMIT_USERS = 10000;
|
||||
const APP_CACHE_BUSTER = 201;
|
||||
const APP_VERSION_STABLE = '0.12.3';
|
||||
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_SUBQUERY = 1000;
|
||||
const APP_CACHE_BUSTER = 305;
|
||||
const APP_VERSION_STABLE = '0.14.2';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
|
@ -71,9 +90,11 @@ const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
|
|||
const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1073741824; // 2^32 bits / 4 bits per char
|
||||
const APP_STORAGE_UPLOADS = '/storage/uploads';
|
||||
const APP_STORAGE_FUNCTIONS = '/storage/functions';
|
||||
const APP_STORAGE_BUILDS = '/storage/builds';
|
||||
const APP_STORAGE_CACHE = '/storage/cache';
|
||||
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
|
||||
const APP_STORAGE_CONFIG = '/storage/config';
|
||||
const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT`
|
||||
const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite';
|
||||
const APP_SOCIAL_TWITTER_HANDLE = 'appwrite';
|
||||
const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
|
||||
|
|
@ -83,7 +104,7 @@ const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
|
|||
const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
|
||||
const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
|
||||
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
||||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
|
|
@ -93,29 +114,37 @@ const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
|
|||
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
|
||||
const DATABASE_TYPE_DELETE_ATTRIBUTE = 'deleteAttribute';
|
||||
const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
|
||||
// Build Worker Types
|
||||
const BUILD_TYPE_DEPLOYMENT = 'deployment';
|
||||
const BUILD_TYPE_RETRY = 'retry';
|
||||
// Deletion Types
|
||||
const DELETE_TYPE_DOCUMENT = 'document';
|
||||
const DELETE_TYPE_COLLECTIONS = 'collections';
|
||||
const DELETE_TYPE_PROJECTS = 'projects';
|
||||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
|
||||
const DELETE_TYPE_USERS = 'users';
|
||||
const DELETE_TYPE_TEAMS= 'teams';
|
||||
const DELETE_TYPE_TEAMS = 'teams';
|
||||
const DELETE_TYPE_EXECUTIONS = 'executions';
|
||||
const DELETE_TYPE_AUDIT = 'audit';
|
||||
const DELETE_TYPE_ABUSE = 'abuse';
|
||||
const DELETE_TYPE_CERTIFICATES = 'certificates';
|
||||
const DELETE_TYPE_USAGE = 'usage';
|
||||
const DELETE_TYPE_REALTIME = 'realtime';
|
||||
const DELETE_TYPE_BUCKETS = 'buckets';
|
||||
// Mail Types
|
||||
const MAIL_TYPE_VERIFICATION = 'verification';
|
||||
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
|
||||
const MAIL_TYPE_RECOVERY = 'recovery';
|
||||
const MAIL_TYPE_INVITATION = 'invitation';
|
||||
const MAIL_TYPE_CERTIFICATE = 'certificate';
|
||||
// Auth Types
|
||||
const APP_AUTH_TYPE_SESSION = 'Session';
|
||||
const APP_AUTH_TYPE_JWT = 'JWT';
|
||||
const APP_AUTH_TYPE_KEY = 'Key';
|
||||
const APP_AUTH_TYPE_ADMIN = 'Admin';
|
||||
// Response related
|
||||
const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
|
||||
|
||||
$register = new Registry();
|
||||
|
||||
|
|
@ -124,81 +153,89 @@ App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
|
|||
/*
|
||||
* ENV vars
|
||||
*/
|
||||
Config::load('events', __DIR__.'/config/events.php');
|
||||
Config::load('auth', __DIR__.'/config/auth.php');
|
||||
Config::load('providers', __DIR__.'/config/providers.php');
|
||||
Config::load('platforms', __DIR__.'/config/platforms.php');
|
||||
Config::load('collections', __DIR__.'/config/collections.php');
|
||||
Config::load('runtimes', __DIR__.'/config/runtimes.php');
|
||||
Config::load('roles', __DIR__.'/config/roles.php'); // User roles and scopes
|
||||
Config::load('scopes', __DIR__.'/config/scopes.php'); // User roles and scopes
|
||||
Config::load('services', __DIR__.'/config/services.php'); // List of services
|
||||
Config::load('variables', __DIR__.'/config/variables.php'); // List of env variables
|
||||
Config::load('avatar-browsers', __DIR__.'/config/avatars/browsers.php');
|
||||
Config::load('avatar-credit-cards', __DIR__.'/config/avatars/credit-cards.php');
|
||||
Config::load('avatar-flags', __DIR__.'/config/avatars/flags.php');
|
||||
Config::load('locale-codes', __DIR__.'/config/locale/codes.php');
|
||||
Config::load('locale-currencies', __DIR__.'/config/locale/currencies.php');
|
||||
Config::load('locale-eu', __DIR__.'/config/locale/eu.php');
|
||||
Config::load('locale-languages', __DIR__.'/config/locale/languages.php');
|
||||
Config::load('locale-phones', __DIR__.'/config/locale/phones.php');
|
||||
Config::load('locale-countries', __DIR__.'/config/locale/countries.php');
|
||||
Config::load('locale-continents', __DIR__.'/config/locale/continents.php');
|
||||
Config::load('storage-logos', __DIR__.'/config/storage/logos.php');
|
||||
Config::load('storage-mimes', __DIR__.'/config/storage/mimes.php');
|
||||
Config::load('storage-inputs', __DIR__.'/config/storage/inputs.php');
|
||||
Config::load('storage-outputs', __DIR__.'/config/storage/outputs.php');
|
||||
Config::load('events', __DIR__ . '/config/events.php');
|
||||
Config::load('auth', __DIR__ . '/config/auth.php');
|
||||
Config::load('errors', __DIR__ . '/config/errors.php');
|
||||
Config::load('providers', __DIR__ . '/config/providers.php');
|
||||
Config::load('platforms', __DIR__ . '/config/platforms.php');
|
||||
Config::load('collections', __DIR__ . '/config/collections.php');
|
||||
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
|
||||
Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
|
||||
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
|
||||
Config::load('services', __DIR__ . '/config/services.php'); // List of services
|
||||
Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
|
||||
Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
|
||||
Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
|
||||
Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');
|
||||
Config::load('locale-codes', __DIR__ . '/config/locale/codes.php');
|
||||
Config::load('locale-currencies', __DIR__ . '/config/locale/currencies.php');
|
||||
Config::load('locale-eu', __DIR__ . '/config/locale/eu.php');
|
||||
Config::load('locale-languages', __DIR__ . '/config/locale/languages.php');
|
||||
Config::load('locale-phones', __DIR__ . '/config/locale/phones.php');
|
||||
Config::load('locale-countries', __DIR__ . '/config/locale/countries.php');
|
||||
Config::load('locale-continents', __DIR__ . '/config/locale/continents.php');
|
||||
Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
|
||||
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
|
||||
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
|
||||
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
|
||||
|
||||
$user = App::getEnv('_APP_REDIS_USER','');
|
||||
$pass = App::getEnv('_APP_REDIS_PASS','');
|
||||
if(!empty($user) || !empty($pass)) {
|
||||
Resque::setBackend('redis://'.$user.':'.$pass.'@'.App::getEnv('_APP_REDIS_HOST', '').':'.App::getEnv('_APP_REDIS_PORT', ''));
|
||||
$user = App::getEnv('_APP_REDIS_USER', '');
|
||||
$pass = App::getEnv('_APP_REDIS_PASS', '');
|
||||
if (!empty($user) || !empty($pass)) {
|
||||
Resque::setBackend('redis://' . $user . ':' . $pass . '@' . App::getEnv('_APP_REDIS_HOST', '') . ':' . App::getEnv('_APP_REDIS_PORT', ''));
|
||||
} else {
|
||||
Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '').':'.App::getEnv('_APP_REDIS_PORT', ''));
|
||||
Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '') . ':' . App::getEnv('_APP_REDIS_PORT', ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* New DB Filters
|
||||
*/
|
||||
Database::addFilter('casting',
|
||||
function($value) {
|
||||
return json_encode(['value' => $value]);
|
||||
Database::addFilter(
|
||||
'casting',
|
||||
function (mixed $value) {
|
||||
return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION);
|
||||
},
|
||||
function($value) {
|
||||
function (mixed $value) {
|
||||
if (is_null($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return json_decode($value, true)['value'];
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('enum',
|
||||
function($value, Document $attribute) {
|
||||
Database::addFilter(
|
||||
'enum',
|
||||
function (mixed $value, Document $attribute) {
|
||||
if ($attribute->isSet('elements')) {
|
||||
$attribute->removeAttribute('elements');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function($value, Document $attribute) {
|
||||
function (mixed $value, Document $attribute) {
|
||||
$formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
|
||||
if (isset($formatOptions['elements'])) {
|
||||
$attribute->setAttribute('elements', $formatOptions['elements']);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('range',
|
||||
function($value, Document $attribute) {
|
||||
Database::addFilter(
|
||||
'range',
|
||||
function (mixed $value, Document $attribute) {
|
||||
if ($attribute->isSet('min')) {
|
||||
$attribute->removeAttribute('min');
|
||||
}
|
||||
if ($attribute->isSet('max')) {
|
||||
$attribute->removeAttribute('max');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
function($value, Document $attribute) {
|
||||
function (mixed $value, Document $attribute) {
|
||||
$formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
|
||||
if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
|
||||
$attribute
|
||||
|
|
@ -206,87 +243,134 @@ Database::addFilter('range',
|
|||
->setAttribute('max', $formatOptions['max'])
|
||||
;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('subQueryAttributes',
|
||||
function($value) {
|
||||
Database::addFilter(
|
||||
'subQueryAttributes',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function($value, Document $document, Database $database) {
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('attributes', [
|
||||
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], $database->getAttributeLimit(), 0, []);
|
||||
], $database->getAttributeLimit());
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('subQueryIndexes',
|
||||
function($value) {
|
||||
Database::addFilter(
|
||||
'subQueryIndexes',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function($value, Document $document, Database $database) {
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('indexes', [
|
||||
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], 64, 0, []);
|
||||
], 64);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('subQueryPlatforms',
|
||||
function($value) {
|
||||
Database::addFilter(
|
||||
'subQueryPlatforms',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function($value, Document $document, Database $database) {
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('platforms', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], $database->getIndexLimit(), 0, []);
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('subQueryDomains',
|
||||
function($value) {
|
||||
Database::addFilter(
|
||||
'subQueryDomains',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function($value, Document $document, Database $database) {
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('domains', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], $database->getIndexLimit(), 0, []);
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('subQueryKeys',
|
||||
function($value) {
|
||||
Database::addFilter(
|
||||
'subQueryKeys',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function($value, Document $document, Database $database) {
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('keys', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], $database->getIndexLimit(), 0, []);
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('subQueryWebhooks',
|
||||
function($value) {
|
||||
Database::addFilter(
|
||||
'subQueryWebhooks',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function($value, Document $document, Database $database) {
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
->find('webhooks', [
|
||||
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], $database->getIndexLimit(), 0, []);
|
||||
], APP_LIMIT_SUBQUERY);
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter('encrypt',
|
||||
function($value) {
|
||||
Database::addFilter(
|
||||
'subQuerySessions',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTokens',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('tokens', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryMemberships',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('memberships', [
|
||||
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
|
||||
], APP_LIMIT_SUBQUERY));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'encrypt',
|
||||
function (mixed $value) {
|
||||
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
|
||||
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
|
||||
$tag = null;
|
||||
|
||||
return json_encode([
|
||||
'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
|
||||
'method' => OpenSSL::CIPHER_AES_128_GCM,
|
||||
|
|
@ -295,12 +379,12 @@ Database::addFilter('encrypt',
|
|||
'version' => '1',
|
||||
]);
|
||||
},
|
||||
function($value) {
|
||||
if(is_null($value)) {
|
||||
function (mixed $value) {
|
||||
if (is_null($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = json_decode($value, true);
|
||||
$key = App::getEnv('_APP_OPENSSL_KEY_V'.$value['version']);
|
||||
$key = App::getEnv('_APP_OPENSSL_KEY_V' . $value['version']);
|
||||
|
||||
return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
|
||||
}
|
||||
|
|
@ -309,30 +393,30 @@ Database::addFilter('encrypt',
|
|||
/**
|
||||
* DB Formats
|
||||
*/
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function() {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () {
|
||||
return new Email();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function($attribute) {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
|
||||
$elements = $attribute['formatOptions']['elements'];
|
||||
return new WhiteList($elements, true);
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function() {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () {
|
||||
return new IP();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function() {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () {
|
||||
return new URL();
|
||||
}, Database::VAR_STRING);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function($attribute) {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
|
||||
$min = $attribute['formatOptions']['min'] ?? -INF;
|
||||
$max = $attribute['formatOptions']['max'] ?? INF;
|
||||
return new Range($min, $max, Range::TYPE_INTEGER);
|
||||
}, Database::VAR_INTEGER);
|
||||
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function($attribute) {
|
||||
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
|
||||
$min = $attribute['formatOptions']['min'] ?? -INF;
|
||||
$max = $attribute['formatOptions']['max'] ?? INF;
|
||||
return new Range($min, $max, Range::TYPE_FLOAT);
|
||||
|
|
@ -341,30 +425,33 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function($attribute) {
|
|||
/*
|
||||
* Registry
|
||||
*/
|
||||
$register->set('logger', function () { // Register error logger
|
||||
$register->set('logger', function () {
|
||||
// Register error logger
|
||||
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
|
||||
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
|
||||
|
||||
if(empty($providerName) || empty($providerConfig)) {
|
||||
if (empty($providerName) || empty($providerConfig)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!Logger::hasProvider($providerName)) {
|
||||
throw new Exception("Logging provider not supported. Logging disabled.");
|
||||
if (!Logger::hasProvider($providerName)) {
|
||||
throw new Exception("Logging provider not supported. Logging disabled.", 500, Exception::GENERAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$classname = '\\Utopia\\Logger\\Adapter\\'.\ucfirst($providerName);
|
||||
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
|
||||
$adapter = new $classname($providerConfig);
|
||||
return new Logger($adapter);
|
||||
});
|
||||
$register->set('dbPool', function () { // Register DB connection
|
||||
$register->set('dbPool', function () {
|
||||
// Register DB connection
|
||||
$dbHost = App::getEnv('_APP_DB_HOST', '');
|
||||
$dbPort = App::getEnv('_APP_DB_PORT', '');
|
||||
$dbUser = App::getEnv('_APP_DB_USER', '');
|
||||
$dbPass = App::getEnv('_APP_DB_PASS', '');
|
||||
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
|
||||
|
||||
$pool = new PDOPool((new PDOConfig())
|
||||
$pool = new PDOPool(
|
||||
(new PDOConfig())
|
||||
->withHost($dbHost)
|
||||
->withPort($dbPort)
|
||||
->withDbName($dbScheme)
|
||||
|
|
@ -373,8 +460,9 @@ $register->set('dbPool', function () { // Register DB connection
|
|||
->withPassword($dbPass)
|
||||
->withOptions([
|
||||
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
|
||||
])
|
||||
, 64);
|
||||
]),
|
||||
64
|
||||
);
|
||||
|
||||
return $pool;
|
||||
});
|
||||
|
|
@ -386,19 +474,22 @@ $register->set('redisPool', function () {
|
|||
$redisAuth = '';
|
||||
|
||||
if ($redisUser && $redisPass) {
|
||||
$redisAuth = $redisUser.':'.$redisPass;
|
||||
$redisAuth = $redisUser . ':' . $redisPass;
|
||||
}
|
||||
|
||||
$pool = new RedisPool((new RedisConfig)
|
||||
$pool = new RedisPool(
|
||||
(new RedisConfig())
|
||||
->withHost($redisHost)
|
||||
->withPort($redisPort)
|
||||
->withAuth($redisAuth)
|
||||
->withDbIndex(0)
|
||||
, 64);
|
||||
->withDbIndex(0),
|
||||
64
|
||||
);
|
||||
|
||||
return $pool;
|
||||
});
|
||||
$register->set('influxdb', function () { // Register DB connection
|
||||
$register->set('influxdb', function () {
|
||||
// Register DB connection
|
||||
$host = App::getEnv('_APP_INFLUXDB_HOST', '');
|
||||
$port = App::getEnv('_APP_INFLUXDB_PORT', '');
|
||||
|
||||
|
|
@ -411,7 +502,8 @@ $register->set('influxdb', function () { // Register DB connection
|
|||
|
||||
return $client;
|
||||
});
|
||||
$register->set('statsd', function () { // Register DB connection
|
||||
$register->set('statsd', function () {
|
||||
// Register DB connection
|
||||
$host = App::getEnv('_APP_STATSD_HOST', 'telegraf');
|
||||
$port = App::getEnv('_APP_STATSD_PORT', 8125);
|
||||
|
||||
|
|
@ -438,7 +530,7 @@ $register->set('smtp', function () {
|
|||
$mail->SMTPAutoTLS = false;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
|
||||
$from = \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'));
|
||||
$from = \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
$email = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
|
||||
$mail->setFrom($email, $from);
|
||||
|
|
@ -449,9 +541,10 @@ $register->set('smtp', function () {
|
|||
return $mail;
|
||||
});
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__.'/db/DBIP/dbip-country-lite-2021-12.mmdb');
|
||||
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-03.mmdb');
|
||||
});
|
||||
$register->set('db', function () { // This is usually for our workers or CLI commands scope
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
$dbHost = App::getEnv('_APP_DB_HOST', '');
|
||||
$dbPort = App::getEnv('_APP_DB_PORT', '');
|
||||
$dbUser = App::getEnv('_APP_DB_USER', '');
|
||||
|
|
@ -468,7 +561,8 @@ $register->set('db', function () { // This is usually for our workers or CLI com
|
|||
|
||||
return $pdo;
|
||||
});
|
||||
$register->set('cache', function () { // This is usually for our workers or CLI commands scope
|
||||
$register->set('cache', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
$redis = new Redis();
|
||||
$redis->pconnect(App::getEnv('_APP_REDIS_HOST', ''), App::getEnv('_APP_REDIS_PORT', ''));
|
||||
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
|
@ -480,59 +574,59 @@ $register->set('cache', function () { // This is usually for our workers or CLI
|
|||
* Localization
|
||||
*/
|
||||
Locale::$exceptions = false;
|
||||
Locale::setLanguageFromJSON('af', __DIR__.'/config/locale/translations/af.json');
|
||||
Locale::setLanguageFromJSON('ar', __DIR__.'/config/locale/translations/ar.json');
|
||||
Locale::setLanguageFromJSON('as', __DIR__.'/config/locale/translations/as.json');
|
||||
Locale::setLanguageFromJSON('az', __DIR__.'/config/locale/translations/az.json');
|
||||
Locale::setLanguageFromJSON('be', __DIR__.'/config/locale/translations/be.json');
|
||||
Locale::setLanguageFromJSON('bg', __DIR__.'/config/locale/translations/bg.json');
|
||||
Locale::setLanguageFromJSON('bh', __DIR__.'/config/locale/translations/bh.json');
|
||||
Locale::setLanguageFromJSON('bn', __DIR__.'/config/locale/translations/bn.json');
|
||||
Locale::setLanguageFromJSON('bs', __DIR__.'/config/locale/translations/bs.json');
|
||||
Locale::setLanguageFromJSON('ca', __DIR__.'/config/locale/translations/ca.json');
|
||||
Locale::setLanguageFromJSON('cs', __DIR__.'/config/locale/translations/cs.json');
|
||||
Locale::setLanguageFromJSON('da', __DIR__.'/config/locale/translations/da.json');
|
||||
Locale::setLanguageFromJSON('de', __DIR__.'/config/locale/translations/de.json');
|
||||
Locale::setLanguageFromJSON('el', __DIR__.'/config/locale/translations/el.json');
|
||||
Locale::setLanguageFromJSON('en', __DIR__.'/config/locale/translations/en.json');
|
||||
Locale::setLanguageFromJSON('eo', __DIR__.'/config/locale/translations/eo.json');
|
||||
Locale::setLanguageFromJSON('es', __DIR__.'/config/locale/translations/es.json');
|
||||
Locale::setLanguageFromJSON('fa', __DIR__.'/config/locale/translations/fa.json');
|
||||
Locale::setLanguageFromJSON('fi', __DIR__.'/config/locale/translations/fi.json');
|
||||
Locale::setLanguageFromJSON('fo', __DIR__.'/config/locale/translations/fo.json');
|
||||
Locale::setLanguageFromJSON('fr', __DIR__.'/config/locale/translations/fr.json');
|
||||
Locale::setLanguageFromJSON('ga', __DIR__.'/config/locale/translations/ga.json');
|
||||
Locale::setLanguageFromJSON('gu', __DIR__.'/config/locale/translations/gu.json');
|
||||
Locale::setLanguageFromJSON('he', __DIR__.'/config/locale/translations/he.json');
|
||||
Locale::setLanguageFromJSON('hi', __DIR__.'/config/locale/translations/hi.json');
|
||||
Locale::setLanguageFromJSON('hr', __DIR__.'/config/locale/translations/hr.json');
|
||||
Locale::setLanguageFromJSON('hu', __DIR__.'/config/locale/translations/hu.json');
|
||||
Locale::setLanguageFromJSON('hy', __DIR__.'/config/locale/translations/hy.json');
|
||||
Locale::setLanguageFromJSON('id', __DIR__.'/config/locale/translations/id.json');
|
||||
Locale::setLanguageFromJSON('is', __DIR__.'/config/locale/translations/is.json');
|
||||
Locale::setLanguageFromJSON('it', __DIR__.'/config/locale/translations/it.json');
|
||||
Locale::setLanguageFromJSON('ja', __DIR__.'/config/locale/translations/ja.json');
|
||||
Locale::setLanguageFromJSON('jv', __DIR__.'/config/locale/translations/jv.json');
|
||||
Locale::setLanguageFromJSON('kn', __DIR__.'/config/locale/translations/kn.json');
|
||||
Locale::setLanguageFromJSON('km', __DIR__.'/config/locale/translations/km.json');
|
||||
Locale::setLanguageFromJSON('ko', __DIR__.'/config/locale/translations/ko.json');
|
||||
Locale::setLanguageFromJSON('la', __DIR__.'/config/locale/translations/la.json');
|
||||
Locale::setLanguageFromJSON('lb', __DIR__.'/config/locale/translations/lb.json');
|
||||
Locale::setLanguageFromJSON('lt', __DIR__.'/config/locale/translations/lt.json');
|
||||
Locale::setLanguageFromJSON('lv', __DIR__.'/config/locale/translations/lv.json');
|
||||
Locale::setLanguageFromJSON('ml', __DIR__.'/config/locale/translations/ml.json');
|
||||
Locale::setLanguageFromJSON('mr', __DIR__.'/config/locale/translations/mr.json');
|
||||
Locale::setLanguageFromJSON('ms', __DIR__.'/config/locale/translations/ms.json');
|
||||
Locale::setLanguageFromJSON('nb', __DIR__.'/config/locale/translations/nb.json');
|
||||
Locale::setLanguageFromJSON('ne', __DIR__.'/config/locale/translations/ne.json');
|
||||
Locale::setLanguageFromJSON('nl', __DIR__.'/config/locale/translations/nl.json');
|
||||
Locale::setLanguageFromJSON('nn', __DIR__.'/config/locale/translations/nn.json');
|
||||
Locale::setLanguageFromJSON('or', __DIR__.'/config/locale/translations/or.json');
|
||||
Locale::setLanguageFromJSON('pa', __DIR__.'/config/locale/translations/pa.json');
|
||||
Locale::setLanguageFromJSON('pl', __DIR__.'/config/locale/translations/pl.json');
|
||||
Locale::setLanguageFromJSON('pt-br', __DIR__.'/config/locale/translations/pt-br.json');
|
||||
Locale::setLanguageFromJSON('pt-pt', __DIR__.'/config/locale/translations/pt-pt.json');
|
||||
Locale::setLanguageFromJSON('ro', __DIR__.'/config/locale/translations/ro.json');
|
||||
Locale::setLanguageFromJSON('af', __DIR__ . '/config/locale/translations/af.json');
|
||||
Locale::setLanguageFromJSON('ar', __DIR__ . '/config/locale/translations/ar.json');
|
||||
Locale::setLanguageFromJSON('as', __DIR__ . '/config/locale/translations/as.json');
|
||||
Locale::setLanguageFromJSON('az', __DIR__ . '/config/locale/translations/az.json');
|
||||
Locale::setLanguageFromJSON('be', __DIR__ . '/config/locale/translations/be.json');
|
||||
Locale::setLanguageFromJSON('bg', __DIR__ . '/config/locale/translations/bg.json');
|
||||
Locale::setLanguageFromJSON('bh', __DIR__ . '/config/locale/translations/bh.json');
|
||||
Locale::setLanguageFromJSON('bn', __DIR__ . '/config/locale/translations/bn.json');
|
||||
Locale::setLanguageFromJSON('bs', __DIR__ . '/config/locale/translations/bs.json');
|
||||
Locale::setLanguageFromJSON('ca', __DIR__ . '/config/locale/translations/ca.json');
|
||||
Locale::setLanguageFromJSON('cs', __DIR__ . '/config/locale/translations/cs.json');
|
||||
Locale::setLanguageFromJSON('da', __DIR__ . '/config/locale/translations/da.json');
|
||||
Locale::setLanguageFromJSON('de', __DIR__ . '/config/locale/translations/de.json');
|
||||
Locale::setLanguageFromJSON('el', __DIR__ . '/config/locale/translations/el.json');
|
||||
Locale::setLanguageFromJSON('en', __DIR__ . '/config/locale/translations/en.json');
|
||||
Locale::setLanguageFromJSON('eo', __DIR__ . '/config/locale/translations/eo.json');
|
||||
Locale::setLanguageFromJSON('es', __DIR__ . '/config/locale/translations/es.json');
|
||||
Locale::setLanguageFromJSON('fa', __DIR__ . '/config/locale/translations/fa.json');
|
||||
Locale::setLanguageFromJSON('fi', __DIR__ . '/config/locale/translations/fi.json');
|
||||
Locale::setLanguageFromJSON('fo', __DIR__ . '/config/locale/translations/fo.json');
|
||||
Locale::setLanguageFromJSON('fr', __DIR__ . '/config/locale/translations/fr.json');
|
||||
Locale::setLanguageFromJSON('ga', __DIR__ . '/config/locale/translations/ga.json');
|
||||
Locale::setLanguageFromJSON('gu', __DIR__ . '/config/locale/translations/gu.json');
|
||||
Locale::setLanguageFromJSON('he', __DIR__ . '/config/locale/translations/he.json');
|
||||
Locale::setLanguageFromJSON('hi', __DIR__ . '/config/locale/translations/hi.json');
|
||||
Locale::setLanguageFromJSON('hr', __DIR__ . '/config/locale/translations/hr.json');
|
||||
Locale::setLanguageFromJSON('hu', __DIR__ . '/config/locale/translations/hu.json');
|
||||
Locale::setLanguageFromJSON('hy', __DIR__ . '/config/locale/translations/hy.json');
|
||||
Locale::setLanguageFromJSON('id', __DIR__ . '/config/locale/translations/id.json');
|
||||
Locale::setLanguageFromJSON('is', __DIR__ . '/config/locale/translations/is.json');
|
||||
Locale::setLanguageFromJSON('it', __DIR__ . '/config/locale/translations/it.json');
|
||||
Locale::setLanguageFromJSON('ja', __DIR__ . '/config/locale/translations/ja.json');
|
||||
Locale::setLanguageFromJSON('jv', __DIR__ . '/config/locale/translations/jv.json');
|
||||
Locale::setLanguageFromJSON('kn', __DIR__ . '/config/locale/translations/kn.json');
|
||||
Locale::setLanguageFromJSON('km', __DIR__ . '/config/locale/translations/km.json');
|
||||
Locale::setLanguageFromJSON('ko', __DIR__ . '/config/locale/translations/ko.json');
|
||||
Locale::setLanguageFromJSON('la', __DIR__ . '/config/locale/translations/la.json');
|
||||
Locale::setLanguageFromJSON('lb', __DIR__ . '/config/locale/translations/lb.json');
|
||||
Locale::setLanguageFromJSON('lt', __DIR__ . '/config/locale/translations/lt.json');
|
||||
Locale::setLanguageFromJSON('lv', __DIR__ . '/config/locale/translations/lv.json');
|
||||
Locale::setLanguageFromJSON('ml', __DIR__ . '/config/locale/translations/ml.json');
|
||||
Locale::setLanguageFromJSON('mr', __DIR__ . '/config/locale/translations/mr.json');
|
||||
Locale::setLanguageFromJSON('ms', __DIR__ . '/config/locale/translations/ms.json');
|
||||
Locale::setLanguageFromJSON('nb', __DIR__ . '/config/locale/translations/nb.json');
|
||||
Locale::setLanguageFromJSON('ne', __DIR__ . '/config/locale/translations/ne.json');
|
||||
Locale::setLanguageFromJSON('nl', __DIR__ . '/config/locale/translations/nl.json');
|
||||
Locale::setLanguageFromJSON('nn', __DIR__ . '/config/locale/translations/nn.json');
|
||||
Locale::setLanguageFromJSON('or', __DIR__ . '/config/locale/translations/or.json');
|
||||
Locale::setLanguageFromJSON('pa', __DIR__ . '/config/locale/translations/pa.json');
|
||||
Locale::setLanguageFromJSON('pl', __DIR__ . '/config/locale/translations/pl.json');
|
||||
Locale::setLanguageFromJSON('pt-br', __DIR__ . '/config/locale/translations/pt-br.json');
|
||||
Locale::setLanguageFromJSON('pt-pt', __DIR__ . '/config/locale/translations/pt-pt.json');
|
||||
Locale::setLanguageFromJSON('ro', __DIR__ . '/config/locale/translations/ro.json');
|
||||
Locale::setLanguageFromJSON('ru', __DIR__ . '/config/locale/translations/ru.json');
|
||||
Locale::setLanguageFromJSON('sa', __DIR__ . '/config/locale/translations/sa.json');
|
||||
Locale::setLanguageFromJSON('sd', __DIR__ . '/config/locale/translations/sd.json');
|
||||
|
|
@ -543,39 +637,41 @@ Locale::setLanguageFromJSON('sn', __DIR__ . '/config/locale/translations/sn.json
|
|||
Locale::setLanguageFromJSON('sq', __DIR__ . '/config/locale/translations/sq.json');
|
||||
Locale::setLanguageFromJSON('sv', __DIR__ . '/config/locale/translations/sv.json');
|
||||
Locale::setLanguageFromJSON('ta', __DIR__ . '/config/locale/translations/ta.json');
|
||||
Locale::setLanguageFromJSON('te', __DIR__.'/config/locale/translations/te.json');
|
||||
Locale::setLanguageFromJSON('th', __DIR__.'/config/locale/translations/th.json');
|
||||
Locale::setLanguageFromJSON('tl', __DIR__.'/config/locale/translations/tl.json');
|
||||
Locale::setLanguageFromJSON('tr', __DIR__.'/config/locale/translations/tr.json');
|
||||
Locale::setLanguageFromJSON('uk', __DIR__.'/config/locale/translations/uk.json');
|
||||
Locale::setLanguageFromJSON('ur', __DIR__.'/config/locale/translations/ur.json');
|
||||
Locale::setLanguageFromJSON('vi', __DIR__.'/config/locale/translations/vi.json');
|
||||
Locale::setLanguageFromJSON('zh-cn', __DIR__.'/config/locale/translations/zh-cn.json');
|
||||
Locale::setLanguageFromJSON('zh-tw', __DIR__.'/config/locale/translations/zh-tw.json');
|
||||
Locale::setLanguageFromJSON('te', __DIR__ . '/config/locale/translations/te.json');
|
||||
Locale::setLanguageFromJSON('th', __DIR__ . '/config/locale/translations/th.json');
|
||||
Locale::setLanguageFromJSON('tl', __DIR__ . '/config/locale/translations/tl.json');
|
||||
Locale::setLanguageFromJSON('tr', __DIR__ . '/config/locale/translations/tr.json');
|
||||
Locale::setLanguageFromJSON('uk', __DIR__ . '/config/locale/translations/uk.json');
|
||||
Locale::setLanguageFromJSON('ur', __DIR__ . '/config/locale/translations/ur.json');
|
||||
Locale::setLanguageFromJSON('vi', __DIR__ . '/config/locale/translations/vi.json');
|
||||
Locale::setLanguageFromJSON('zh-cn', __DIR__ . '/config/locale/translations/zh-cn.json');
|
||||
Locale::setLanguageFromJSON('zh-tw', __DIR__ . '/config/locale/translations/zh-tw.json');
|
||||
|
||||
\stream_context_set_default([ // Set global user agent and http settings
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'user_agent' => \sprintf(APP_USERAGENT,
|
||||
'user_agent' => \sprintf(
|
||||
APP_USERAGENT,
|
||||
App::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)),
|
||||
App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
|
||||
),
|
||||
'timeout' => 2,
|
||||
],
|
||||
]);
|
||||
|
||||
// Runtime Execution
|
||||
App::setResource('logger', function($register) {
|
||||
App::setResource('logger', function ($register) {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('loggerBreadcrumbs', function() {
|
||||
App::setResource('loggerBreadcrumbs', function () {
|
||||
return [];
|
||||
});
|
||||
|
||||
App::setResource('register', fn() => $register);
|
||||
|
||||
App::setResource('layout', function($locale) {
|
||||
$layout = new View(__DIR__.'/views/layouts/default.phtml');
|
||||
App::setResource('layout', function ($locale) {
|
||||
$layout = new View(__DIR__ . '/views/layouts/default.phtml');
|
||||
$layout->setParam('locale', $locale);
|
||||
|
||||
return $layout;
|
||||
|
|
@ -585,11 +681,11 @@ App::setResource('locale', fn() => new Locale(App::getEnv('_APP_LOCALE', 'en')))
|
|||
|
||||
// Queues
|
||||
App::setResource('events', fn() => new Event('', ''));
|
||||
App::setResource('audits', fn() => new Event(Event::AUDITS_QUEUE_NAME, Event::AUDITS_CLASS_NAME));
|
||||
App::setResource('mails', fn() => new Event(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME));
|
||||
App::setResource('deletes', fn() => new Event(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME));
|
||||
App::setResource('database', fn() => new Event(Event::DATABASE_QUEUE_NAME, Event::DATABASE_CLASS_NAME));
|
||||
App::setResource('usage', function($register) {
|
||||
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('usage', function ($register) {
|
||||
return new Stats($register->get('statsd'));
|
||||
}, ['register']);
|
||||
|
||||
|
|
@ -629,7 +725,7 @@ App::setResource('clients', function ($request, $console, $project) {
|
|||
return $clients;
|
||||
}, ['request', 'console', 'project']);
|
||||
|
||||
App::setResource('user', function($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) {
|
||||
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
|
|
@ -639,21 +735,28 @@ App::setResource('user', function($mode, $project, $console, $request, $response
|
|||
|
||||
Authorization::setDefaultStatus(true);
|
||||
|
||||
Auth::setCookieName('a_session_'.$project->getId());
|
||||
Auth::setCookieName('a_session_' . $project->getId());
|
||||
|
||||
if (APP_MODE_ADMIN === $mode) {
|
||||
Auth::setCookieName('a_session_'.$console->getId());
|
||||
Auth::setCookieName('a_session_' . $console->getId());
|
||||
}
|
||||
|
||||
$session = Auth::decodeSession(
|
||||
$request->getCookie(Auth::$cookieName, // Get sessions
|
||||
$request->getCookie(Auth::$cookieName.'_legacy', '')));// Get fallback session from old clients (no SameSite support)
|
||||
$request->getCookie(
|
||||
Auth::$cookieName, // Get sessions
|
||||
$request->getCookie(Auth::$cookieName . '_legacy', '')
|
||||
)
|
||||
);// Get fallback session from old clients (no SameSite support)
|
||||
|
||||
// Get fallback session from clients who block 3rd-party cookies
|
||||
if($response) $response->addHeader('X-Debug-Fallback', 'false');
|
||||
if ($response) {
|
||||
$response->addHeader('X-Debug-Fallback', 'false');
|
||||
}
|
||||
|
||||
if(empty($session['id']) && empty($session['secret'])) {
|
||||
if($response) $response->addHeader('X-Debug-Fallback', 'true');
|
||||
if (empty($session['id']) && empty($session['secret'])) {
|
||||
if ($response) {
|
||||
$response->addHeader('X-Debug-Fallback', 'true');
|
||||
}
|
||||
$fallback = $request->getHeader('x-fallback-cookies', '');
|
||||
$fallback = \json_decode($fallback, true);
|
||||
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
|
||||
|
|
@ -665,17 +768,17 @@ App::setResource('user', function($mode, $project, $console, $request, $response
|
|||
if (APP_MODE_ADMIN !== $mode) {
|
||||
if ($project->isEmpty()) {
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', Auth::$unique);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$user = $dbForConsole->getDocument('users', Auth::$unique);
|
||||
}
|
||||
|
||||
if ($user->isEmpty() // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)) { // Validate user has valid login token
|
||||
if (
|
||||
$user->isEmpty() // Check a document has been found in the DB
|
||||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
|
||||
) { // Validate user has valid login token
|
||||
$user = new Document(['$id' => '', '$collection' => 'users']);
|
||||
}
|
||||
|
||||
|
|
@ -695,13 +798,13 @@ App::setResource('user', function($mode, $project, $console, $request, $response
|
|||
try {
|
||||
$payload = $jwt->decode($authJWT);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception('Failed to verify JWT. '.$error->getMessage(), 401);
|
||||
throw new Exception('Failed to verify JWT. ' . $error->getMessage(), 401, Exception::USER_JWT_INVALID);
|
||||
}
|
||||
|
||||
$jwtUserId = $payload['userId'] ?? '';
|
||||
$jwtSessionId = $payload['sessionId'] ?? '';
|
||||
|
||||
if($jwtUserId && $jwtSessionId) {
|
||||
if ($jwtUserId && $jwtSessionId) {
|
||||
$user = $dbForProject->getDocument('users', $jwtUserId);
|
||||
}
|
||||
|
||||
|
|
@ -713,14 +816,14 @@ App::setResource('user', function($mode, $project, $console, $request, $response
|
|||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
|
||||
|
||||
App::setResource('project', function($dbForConsole, $request, $console) {
|
||||
App::setResource('project', function ($dbForConsole, $request, $console) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Utopia\Database\Document $console */
|
||||
|
||||
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
|
||||
|
||||
if($projectId === 'console') {
|
||||
if ($projectId === 'console') {
|
||||
return $console;
|
||||
}
|
||||
|
||||
|
|
@ -729,7 +832,7 @@ App::setResource('project', function($dbForConsole, $request, $console) {
|
|||
return $project;
|
||||
}, ['dbForConsole', 'request', 'console']);
|
||||
|
||||
App::setResource('console', function() {
|
||||
App::setResource('console', function () {
|
||||
return new Document([
|
||||
'$id' => 'console',
|
||||
'name' => 'Appwrite',
|
||||
|
|
@ -761,27 +864,88 @@ App::setResource('console', function() {
|
|||
]);
|
||||
}, []);
|
||||
|
||||
App::setResource('dbForProject', function($db, $cache, $project) {
|
||||
App::setResource('dbForProject', function ($db, $cache, $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_'.$project->getId());
|
||||
$database->setNamespace("_{$project->getId()}");
|
||||
|
||||
return $database;
|
||||
}, ['db', 'cache', 'project']);
|
||||
|
||||
App::setResource('dbForConsole', function($db, $cache) {
|
||||
App::setResource('dbForConsole', function ($db, $cache) {
|
||||
$cache = new Cache(new RedisCache($cache));
|
||||
|
||||
$database = new Database(new MariaDB($db), $cache);
|
||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$database->setNamespace('_project_console');
|
||||
$database->setNamespace('_console');
|
||||
|
||||
return $database;
|
||||
}, ['db', 'cache']);
|
||||
|
||||
App::setResource('mode', function($request) {
|
||||
|
||||
App::setResource('deviceLocal', function () {
|
||||
return new Local();
|
||||
});
|
||||
|
||||
App::setResource('deviceFiles', function ($project) {
|
||||
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
App::setResource('deviceFunctions', function ($project) {
|
||||
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
App::setResource('deviceBuilds', function ($project) {
|
||||
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
function getDevice($root): Device
|
||||
{
|
||||
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
|
||||
case Storage::DEVICE_LOCAL:
|
||||
default:
|
||||
return new Local($root);
|
||||
case Storage::DEVICE_S3:
|
||||
$s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
|
||||
$s3SecretKey = App::getEnv('_APP_STORAGE_S3_SECRET', '');
|
||||
$s3Region = App::getEnv('_APP_STORAGE_S3_REGION', '');
|
||||
$s3Bucket = App::getEnv('_APP_STORAGE_S3_BUCKET', '');
|
||||
$s3Acl = 'private';
|
||||
return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
|
||||
case Storage::DEVICE_DO_SPACES:
|
||||
$doSpacesAccessKey = App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
|
||||
$doSpacesSecretKey = App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
|
||||
$doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
|
||||
$doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
|
||||
$doSpacesAcl = 'private';
|
||||
return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
|
||||
case Storage::DEVICE_BACKBLAZE:
|
||||
$backblazeAccessKey = App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
|
||||
$backblazeSecretKey = App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
|
||||
$backblazeRegion = App::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
|
||||
$backblazeBucket = App::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
|
||||
$backblazeAcl = 'private';
|
||||
return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
|
||||
case Storage::DEVICE_LINODE:
|
||||
$linodeAccessKey = App::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
|
||||
$linodeSecretKey = App::getEnv('_APP_STORAGE_LINODE_SECRET', '');
|
||||
$linodeRegion = App::getEnv('_APP_STORAGE_LINODE_REGION', '');
|
||||
$linodeBucket = App::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
|
||||
$linodeAcl = 'private';
|
||||
return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
|
||||
case Storage::DEVICE_WASABI:
|
||||
$wasabiAccessKey = App::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
|
||||
$wasabiSecretKey = App::getEnv('_APP_STORAGE_WASABI_SECRET', '');
|
||||
$wasabiRegion = App::getEnv('_APP_STORAGE_WASABI_REGION', '');
|
||||
$wasabiBucket = App::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
|
||||
$wasabiAcl = 'private';
|
||||
return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
|
||||
}
|
||||
}
|
||||
|
||||
App::setResource('mode', function ($request) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
|
||||
/**
|
||||
|
|
@ -792,7 +956,7 @@ App::setResource('mode', function($request) {
|
|||
return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
|
||||
}, ['request']);
|
||||
|
||||
App::setResource('geodb', function($register) {
|
||||
App::setResource('geodb', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('geodb');
|
||||
}, ['register']);
|
||||
|
|
|
|||
|
|
@ -2,27 +2,28 @@
|
|||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
*
|
||||
* Inializes both Appwrite API entry point, queue workers, and CLI tasks.
|
||||
* Set configuration, framework resources, app constants
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (file_exists(__DIR__.'/../vendor/autoload.php')) {
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
}
|
||||
|
||||
use Utopia\Preloader\Preloader;
|
||||
|
||||
include __DIR__.'/controllers/general.php';
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
$preloader = new Preloader();
|
||||
|
||||
foreach ([
|
||||
foreach (
|
||||
[
|
||||
realpath(__DIR__ . '/../vendor/composer'),
|
||||
realpath(__DIR__ . '/../vendor/amphp'),
|
||||
realpath(__DIR__ . '/../vendor/felixfbecker'),
|
||||
|
|
@ -34,8 +35,9 @@ foreach ([
|
|||
realpath(__DIR__ . '/../vendor/symfony'),
|
||||
realpath(__DIR__ . '/../vendor/mongodb'),
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/websocket'), // TODO: remove workerman autoload
|
||||
] as $key => $value) {
|
||||
if($value !== false) {
|
||||
] as $key => $value
|
||||
) {
|
||||
if ($value !== false) {
|
||||
$preloader->ignore($value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
102
app/realtime.php
|
|
@ -46,16 +46,19 @@ $stats->create();
|
|||
|
||||
$containerId = uniqid();
|
||||
$statsDocument = null;
|
||||
$workerNumber = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$adapter = new Adapter\Swoole(port: App::getEnv('PORT', 80));
|
||||
$adapter->setPackageMaxLength(64000); // Default maximum Package Size (64kb)
|
||||
$adapter
|
||||
->setPackageMaxLength(64000) // Default maximum Package Size (64kb)
|
||||
->setWorkerNumber($workerNumber);
|
||||
|
||||
$server = new Server($adapter);
|
||||
|
||||
$logError = function(Throwable $error, string $action) use ($register) {
|
||||
$logError = function (Throwable $error, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if($logger) {
|
||||
if ($logger) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
|
|
@ -71,6 +74,7 @@ $logError = function(Throwable $error, string $action) use ($register) {
|
|||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
|
|
@ -78,7 +82,7 @@ $logError = function(Throwable $error, string $action) use ($register) {
|
|||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Realtime log pushed with status code: '.$responseCode);
|
||||
Console::info('Realtime log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
|
|
@ -109,10 +113,10 @@ function getDatabase(Registry &$register, string $namespace)
|
|||
throw new Exception('Collection not ready');
|
||||
}
|
||||
break; // leave loop if successful
|
||||
} catch(\Exception $e) {
|
||||
} 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());
|
||||
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
|
||||
}
|
||||
sleep(DATABASE_RECONNECT_SLEEP);
|
||||
}
|
||||
|
|
@ -125,8 +129,7 @@ function getDatabase(Registry &$register, string $namespace)
|
|||
$register->get('redisPool')->put($redis);
|
||||
}
|
||||
];
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
|
||||
sleep(5); // wait for the initial database schema to be ready
|
||||
|
|
@ -137,7 +140,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
*/
|
||||
go(function () use ($register, $containerId, &$statsDocument, $logError) {
|
||||
try {
|
||||
[$database, $returnDatabase] = getDatabase($register, '_project_console');
|
||||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
$document = new Document([
|
||||
'$id' => $database->getId(),
|
||||
'$collection' => 'realtime',
|
||||
|
|
@ -147,7 +150,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
'timestamp' => time(),
|
||||
'value' => '{}'
|
||||
]);
|
||||
$statsDocument = Authorization::skip(fn() => $database->createDocument('realtime', $document));
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
} catch (\Throwable $th) {
|
||||
call_user_func($logError, $th, "createWorkerDocument");
|
||||
} finally {
|
||||
|
|
@ -159,28 +162,6 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
* Save current connections to the Database every 5 seconds.
|
||||
*/
|
||||
Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
|
||||
/** @var Document $statsDocument */
|
||||
foreach ($stats as $projectId => $value) {
|
||||
$connections = $stats->get($projectId, 'connections') ?? 0;
|
||||
$messages = $stats->get($projectId, 'messages' ?? 0);
|
||||
|
||||
$usage = new Event('v1-usage', 'UsageV1');
|
||||
$usage
|
||||
->setParam('projectId', $projectId)
|
||||
->setParam('realtimeConnections', $connections)
|
||||
->setParam('realtimeMessages', $messages)
|
||||
->setParam('networkRequestSize', 0)
|
||||
->setParam('networkResponseSize', 0);
|
||||
|
||||
$stats->set($projectId, [
|
||||
'messages' => 0,
|
||||
'connections' => 0
|
||||
]);
|
||||
|
||||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
||||
$usage->trigger();
|
||||
}
|
||||
}
|
||||
$payload = [];
|
||||
foreach ($stats as $projectId => $value) {
|
||||
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
|
||||
|
|
@ -190,13 +171,13 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
}
|
||||
|
||||
try {
|
||||
[$database, $returnDatabase] = getDatabase($register, '_project_console');
|
||||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
|
||||
$statsDocument
|
||||
->setAttribute('timestamp', time())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn() => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
} catch (\Throwable $th) {
|
||||
call_user_func($logError, $th, "updateWorkerDocument");
|
||||
} finally {
|
||||
|
|
@ -206,7 +187,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
});
|
||||
|
||||
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) {
|
||||
Console::success('Worker ' . $workerId . ' started succefully');
|
||||
Console::success('Worker ' . $workerId . ' started successfully');
|
||||
|
||||
$attempts = 0;
|
||||
$start = time();
|
||||
|
|
@ -216,14 +197,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
* Sending current connections to project channels on the console project every 5 seconds.
|
||||
*/
|
||||
if ($realtime->hasSubscriber('console', 'role:member', 'project')) {
|
||||
|
||||
[$database, $returnDatabase] = getDatabase($register, '_project_console');
|
||||
[$database, $returnDatabase] = getDatabase($register, '_console');
|
||||
|
||||
$payload = [];
|
||||
|
||||
$list = Authorization::skip(fn() => $database->find('realtime', [
|
||||
new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)])
|
||||
]));
|
||||
$list = Authorization::skip(fn () => $database->find('realtime', [
|
||||
new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)])
|
||||
]));
|
||||
|
||||
/**
|
||||
* Aggregate stats across containers.
|
||||
|
|
@ -247,7 +227,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
'project' => 'console',
|
||||
'roles' => ['team:' . $stats->get($projectId, 'teamId')],
|
||||
'data' => [
|
||||
'event' => 'stats.connections',
|
||||
'events' => ['stats.connections'],
|
||||
'channels' => ['project'],
|
||||
'timestamp' => time(),
|
||||
'payload' => [
|
||||
|
|
@ -274,7 +254,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
'project' => 'console',
|
||||
'roles' => ['role:guest'],
|
||||
'data' => [
|
||||
'event' => 'test.event',
|
||||
'events' => ['test.event'],
|
||||
'channels' => ['tests'],
|
||||
'timestamp' => time(),
|
||||
'payload' => $payload
|
||||
|
|
@ -317,19 +297,16 @@ $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]));
|
||||
} else {
|
||||
return;
|
||||
[$database, $returnDatabase] = getDatabase($register, "_{$projectId}");
|
||||
|
||||
$user = $database->getDocument('users', $userId);
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
|
||||
$realtime->subscribe($projectId, $connection, $roles, $realtime->connections[$connection]['channels']);
|
||||
|
||||
call_user_func($returnDatabase);
|
||||
}
|
||||
|
||||
[$database, $returnDatabase] = getDatabase($register, '_project_' . $projectId);
|
||||
|
||||
$user = $database->getDocument('users', $userId);
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
|
||||
$realtime->subscribe($projectId, $connection, $roles, $realtime->connections[$connection]['channels']);
|
||||
|
||||
call_user_func($returnDatabase);
|
||||
}
|
||||
|
||||
$receivers = $realtime->getSubscribers($event);
|
||||
|
|
@ -358,10 +335,9 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
Console::error('Pub/sub error: ' . $th->getMessage());
|
||||
$register->get('redisPool')->put($redis);
|
||||
$attempts++;
|
||||
sleep(DATABASE_RECONNECT_SLEEP);
|
||||
continue;
|
||||
}
|
||||
|
||||
$attempts++;
|
||||
}
|
||||
|
||||
Console::error('Failed to restart pub/sub...');
|
||||
|
|
@ -379,10 +355,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
|
||||
Console::info("Connection open (user: {$connection})");
|
||||
|
||||
App::setResource('db', fn() => $db);
|
||||
App::setResource('cache', fn() => $redis);
|
||||
App::setResource('request', fn() => $request);
|
||||
App::setResource('response', fn() => $response);
|
||||
App::setResource('db', fn () => $db);
|
||||
App::setResource('cache', fn () => $redis);
|
||||
App::setResource('request', fn () => $request);
|
||||
App::setResource('response', fn () => $response);
|
||||
|
||||
try {
|
||||
/** @var \Utopia\Database\Document $user */
|
||||
|
|
@ -397,7 +373,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_' . $project->getId());
|
||||
$database->setNamespace("_{$project->getId()}");
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
|
|
@ -504,7 +480,7 @@ $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('_project_' . $realtime->connections[$connection]['projectId']);
|
||||
$database->setNamespace("_{$realtime->connections[$connection]['projectId']}");
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
|
|
@ -530,7 +506,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
|||
}
|
||||
|
||||
switch ($message['type']) {
|
||||
/**
|
||||
/**
|
||||
* This type is used to authenticate.
|
||||
*/
|
||||
case 'authentication':
|
||||
|
|
|
|||
|
|
@ -19,46 +19,41 @@ $cli
|
|||
/ \ ) __/ ) __/\ /\ / ) / )( )( ) _) _ )(( O )
|
||||
\_/\_/(__) (__) (_/\_)(__\_)(__) (__) (____)(_)(__)\__/ ");
|
||||
|
||||
Console::log("\n".'👩⚕️ Running '.APP_NAME.' Doctor for version '.App::getEnv('_APP_VERSION', 'UNKNOWN').' ...'."\n");
|
||||
Console::log("\n" . '👩⚕️ Running ' . APP_NAME . ' Doctor for version ' . App::getEnv('_APP_VERSION', 'UNKNOWN') . ' ...' . "\n");
|
||||
|
||||
Console::log('Checking for production best practices...');
|
||||
|
||||
$domain = new Domain(App::getEnv('_APP_DOMAIN'));
|
||||
|
||||
if(!$domain->isKnown() || $domain->isTest()) {
|
||||
Console::log('🔴 Hostname has no public suffix ('.$domain->get().')');
|
||||
}
|
||||
else {
|
||||
Console::log('🟢 Hostname has a public suffix ('.$domain->get().')');
|
||||
if (!$domain->isKnown() || $domain->isTest()) {
|
||||
Console::log('🔴 Hostname has no public suffix (' . $domain->get() . ')');
|
||||
} else {
|
||||
Console::log('🟢 Hostname has a public suffix (' . $domain->get() . ')');
|
||||
}
|
||||
|
||||
$domain = new Domain(App::getEnv('_APP_DOMAIN_TARGET'));
|
||||
|
||||
if(!$domain->isKnown() || $domain->isTest()) {
|
||||
Console::log('🔴 CNAME target has no public suffix ('.$domain->get().')');
|
||||
}
|
||||
else {
|
||||
Console::log('🟢 CNAME target has a public suffix ('.$domain->get().')');
|
||||
if (!$domain->isKnown() || $domain->isTest()) {
|
||||
Console::log('🔴 CNAME target has no public suffix (' . $domain->get() . ')');
|
||||
} else {
|
||||
Console::log('🟢 CNAME target has a public suffix (' . $domain->get() . ')');
|
||||
}
|
||||
|
||||
if(App::getEnv('_APP_OPENSSL_KEY_V1') === 'your-secret-key' || empty(App::getEnv('_APP_OPENSSL_KEY_V1'))) {
|
||||
if (App::getEnv('_APP_OPENSSL_KEY_V1') === 'your-secret-key' || empty(App::getEnv('_APP_OPENSSL_KEY_V1'))) {
|
||||
Console::log('🔴 Not using a unique secret key for encryption');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Console::log('🟢 Using a unique secret key for encryption');
|
||||
}
|
||||
|
||||
if(App::getEnv('_APP_ENV', 'development') !== 'production') {
|
||||
if (App::getEnv('_APP_ENV', 'development') !== 'production') {
|
||||
Console::log('🔴 App environment is set for development');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Console::log('🟢 App environment is set for production');
|
||||
}
|
||||
|
||||
if('enabled' !== App::getEnv('_APP_OPTIONS_ABUSE', 'disabled')) {
|
||||
if ('enabled' !== App::getEnv('_APP_OPTIONS_ABUSE', 'disabled')) {
|
||||
Console::log('🔴 Abuse protection is disabled');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Console::log('🟢 Abuse protection is enabled');
|
||||
}
|
||||
|
||||
|
|
@ -66,20 +61,19 @@ $cli
|
|||
$authWhitelistEmails = App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null);
|
||||
$authWhitelistIPs = App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null);
|
||||
|
||||
if(empty($authWhitelistRoot)
|
||||
if (
|
||||
empty($authWhitelistRoot)
|
||||
&& empty($authWhitelistEmails)
|
||||
&& empty($authWhitelistIPs)
|
||||
) {
|
||||
Console::log('🔴 Console access limits are disabled');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Console::log('🟢 Console access limits are enabled');
|
||||
}
|
||||
|
||||
if('enabled' !== App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled')) {
|
||||
if ('enabled' !== App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled')) {
|
||||
Console::log('🔴 HTTPS force option is disabled');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Console::log('🟢 HTTPS force option is enabled');
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +81,7 @@ $cli
|
|||
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
|
||||
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
|
||||
|
||||
if(empty($providerName) || empty($providerConfig) || !Logger::hasProvider($providerName)) {
|
||||
if (empty($providerName) || empty($providerConfig) || !Logger::hasProvider($providerName)) {
|
||||
Console::log('🔴 Logging adapter is disabled');
|
||||
} else {
|
||||
Console::log('🟢 Logging adapter is enabled (' . $providerName . ')');
|
||||
|
|
@ -96,7 +90,7 @@ $cli
|
|||
\sleep(0.2);
|
||||
|
||||
try {
|
||||
Console::log("\n".'Checking connectivity...');
|
||||
Console::log("\n" . 'Checking connectivity...');
|
||||
} catch (\Throwable $th) {
|
||||
//throw $th;
|
||||
}
|
||||
|
|
@ -122,15 +116,16 @@ $cli
|
|||
Console::error('Cache............disconnected 👎');
|
||||
}
|
||||
|
||||
if(App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled
|
||||
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled
|
||||
try {
|
||||
$antivirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
|
||||
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310));
|
||||
$antivirus = new Network(
|
||||
App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
|
||||
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310)
|
||||
);
|
||||
|
||||
if((@$antivirus->ping())) {
|
||||
if ((@$antivirus->ping())) {
|
||||
Console::success('Antivirus...........connected 👍');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Console::error('Antivirus........disconnected 👎');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
|
|
@ -155,7 +150,7 @@ $cli
|
|||
$host = App::getEnv('_APP_STATSD_HOST', 'telegraf');
|
||||
$port = App::getEnv('_APP_STATSD_PORT', 8125);
|
||||
|
||||
if($fp = @\fsockopen('udp://'.$host, $port, $errCode, $errStr, 2)){
|
||||
if ($fp = @\fsockopen('udp://' . $host, $port, $errCode, $errStr, 2)) {
|
||||
Console::success('StatsD..............connected 👍');
|
||||
\fclose($fp);
|
||||
} else {
|
||||
|
|
@ -165,7 +160,7 @@ $cli
|
|||
$host = App::getEnv('_APP_INFLUXDB_HOST', '');
|
||||
$port = App::getEnv('_APP_INFLUXDB_PORT', '');
|
||||
|
||||
if($fp = @\fsockopen($host, $port, $errCode, $errStr, 2)){
|
||||
if ($fp = @\fsockopen($host, $port, $errCode, $errStr, 2)) {
|
||||
Console::success('InfluxDB............connected 👍');
|
||||
\fclose($fp);
|
||||
} else {
|
||||
|
|
@ -177,26 +172,26 @@ $cli
|
|||
Console::log('');
|
||||
Console::log('Checking volumes...');
|
||||
|
||||
foreach ([
|
||||
foreach (
|
||||
[
|
||||
'Uploads' => APP_STORAGE_UPLOADS,
|
||||
'Cache' => APP_STORAGE_CACHE,
|
||||
'Config' => APP_STORAGE_CONFIG,
|
||||
'Certs' => APP_STORAGE_CERTIFICATES
|
||||
] as $key => $volume) {
|
||||
] as $key => $volume
|
||||
) {
|
||||
$device = new Local($volume);
|
||||
|
||||
if (\is_readable($device->getRoot())) {
|
||||
Console::success('🟢 '.$key.' Volume is readable');
|
||||
}
|
||||
else {
|
||||
Console::error('🔴 '.$key.' Volume is unreadable');
|
||||
Console::success('🟢 ' . $key . ' Volume is readable');
|
||||
} else {
|
||||
Console::error('🔴 ' . $key . ' Volume is unreadable');
|
||||
}
|
||||
|
||||
if (\is_writable($device->getRoot())) {
|
||||
Console::success('🟢 '.$key.' Volume is writeable');
|
||||
}
|
||||
else {
|
||||
Console::error('🔴 '.$key.' Volume is unwriteable');
|
||||
Console::success('🟢 ' . $key . ' Volume is writeable');
|
||||
} else {
|
||||
Console::error('🔴 ' . $key . ' Volume is unwriteable');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -205,44 +200,44 @@ $cli
|
|||
Console::log('');
|
||||
Console::log('Checking disk space usage...');
|
||||
|
||||
foreach ([
|
||||
foreach (
|
||||
[
|
||||
'Uploads' => APP_STORAGE_UPLOADS,
|
||||
'Cache' => APP_STORAGE_CACHE,
|
||||
'Config' => APP_STORAGE_CONFIG,
|
||||
'Certs' => APP_STORAGE_CERTIFICATES
|
||||
] as $key => $volume) {
|
||||
] as $key => $volume
|
||||
) {
|
||||
$device = new Local($volume);
|
||||
|
||||
$percentage = (($device->getPartitionTotalSpace() - $device->getPartitionFreeSpace())
|
||||
/ $device->getPartitionTotalSpace()) * 100;
|
||||
|
||||
$message = $key.' Volume has '.Storage::human($device->getPartitionFreeSpace()) . ' free space ('.\round($percentage, 2).'% used)';
|
||||
$message = $key . ' Volume has ' . Storage::human($device->getPartitionFreeSpace()) . ' free space (' . \round($percentage, 2) . '% used)';
|
||||
|
||||
if ($percentage < 80) {
|
||||
Console::success('🟢 ' . $message);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Console::error('🔴 ' . $message);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if(App::isProduction()) {
|
||||
if (App::isProduction()) {
|
||||
Console::log('');
|
||||
$version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost').'/v1/health/version'), true);
|
||||
$version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost') . '/v1/health/version'), true);
|
||||
|
||||
if ($version && isset($version['version'])) {
|
||||
if(\version_compare($version['version'], App::getEnv('_APP_VERSION', 'UNKNOWN')) === 0) {
|
||||
Console::info('You are running the latest version of '.APP_NAME.'! 🥳');
|
||||
}
|
||||
else {
|
||||
Console::info('A new version ('.$version['version'].') is available! 🥳'."\n");
|
||||
if (\version_compare($version['version'], App::getEnv('_APP_VERSION', 'UNKNOWN')) === 0) {
|
||||
Console::info('You are running the latest version of ' . APP_NAME . '! 🥳');
|
||||
} else {
|
||||
Console::info('A new version (' . $version['version'] . ') is available! 🥳' . "\n");
|
||||
}
|
||||
} else {
|
||||
Console::error('Failed to check for a newer version'."\n");
|
||||
Console::error('Failed to check for a newer version' . "\n");
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to check for a newer version'."\n");
|
||||
Console::error('Failed to check for a newer version' . "\n");
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ $cli
|
|||
->param('httpsPort', '', new Text(4), 'Server HTTPS port', true)
|
||||
->param('organization', 'appwrite', new Text(0), 'Docker Registry organization', true)
|
||||
->param('image', 'appwrite', new Text(0), 'Main appwrite docker image', true)
|
||||
->param('interactive','Y', new Text(1), 'Run an interactive session', true)
|
||||
->param('interactive', 'Y', new Text(1), 'Run an interactive session', true)
|
||||
->action(function ($httpPort, $httpsPort, $organization, $image, $interactive) {
|
||||
/**
|
||||
* 1. Start - DONE
|
||||
|
|
@ -31,7 +31,7 @@ $cli
|
|||
* 2.4 Ask for all required vars not given as CLI args and if in interactive mode
|
||||
* Otherwise, just use default vars. - DONE
|
||||
* 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
|
||||
* 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
|
||||
* 6. Run data migration
|
||||
|
|
@ -48,8 +48,8 @@ $cli
|
|||
*/
|
||||
$analytics = new GoogleAnalytics('UA-26264668-9', uniqid('server.', true));
|
||||
|
||||
foreach($config as $category) {
|
||||
foreach($category['variables'] ?? [] as $var) {
|
||||
foreach ($config as $category) {
|
||||
foreach ($category['variables'] ?? [] as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
}
|
||||
|
|
@ -59,17 +59,17 @@ $cli
|
|||
// Create directory with write permissions
|
||||
if (null !== $path && !\file_exists(\dirname($path))) {
|
||||
if (!@\mkdir(\dirname($path), 0755, true)) {
|
||||
Console::error('Can\'t create directory '.\dirname($path));
|
||||
Console::error('Can\'t create directory ' . \dirname($path));
|
||||
Console::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$data = @file_get_contents($path.'/docker-compose.yml');
|
||||
$data = @file_get_contents($path . '/docker-compose.yml');
|
||||
|
||||
if($data !== false) {
|
||||
if ($data !== false) {
|
||||
$time = \time();
|
||||
Console::info('Compose file found, creating backup: docker-compose.yml.'.$time.'.backup');
|
||||
file_put_contents($path.'/docker-compose.yml.'.$time.'.backup',$data);
|
||||
Console::info('Compose file found, creating backup: docker-compose.yml.' . $time . '.backup');
|
||||
file_put_contents($path . '/docker-compose.yml.' . $time . '.backup', $data);
|
||||
$compose = new Compose($data);
|
||||
$appwrite = $compose->getService('appwrite');
|
||||
$oldVersion = ($appwrite) ? $appwrite->getImageVersion() : null;
|
||||
|
|
@ -83,33 +83,39 @@ $cli
|
|||
Console::warning('Traefik not found. Falling back to default ports.');
|
||||
}
|
||||
|
||||
if($oldVersion) {
|
||||
foreach($compose->getServices() as $service) { // Fetch all env vars from previous compose file
|
||||
if(!$service) {
|
||||
if ($oldVersion) {
|
||||
foreach ($compose->getServices() as $service) { // Fetch all env vars from previous compose file
|
||||
if (!$service) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$env = $service->getEnvironment()->list();
|
||||
|
||||
foreach ($env as $key => $value) {
|
||||
foreach($vars as &$var) {
|
||||
if($var['name'] === $key) {
|
||||
if (is_null($value)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($vars as &$var) {
|
||||
if ($var['name'] === $key) {
|
||||
$var['default'] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = @file_get_contents($path.'/.env');
|
||||
$data = @file_get_contents($path . '/.env');
|
||||
|
||||
if($data !== false) { // Fetch all env vars from previous .env file
|
||||
Console::info('Env file found, creating backup: .env.'.$time.'.backup');
|
||||
file_put_contents($path.'/.env.'.$time.'.backup',$data);
|
||||
if ($data !== false) { // Fetch all env vars from previous .env file
|
||||
Console::info('Env file found, creating backup: .env.' . $time . '.backup');
|
||||
file_put_contents($path . '/.env.' . $time . '.backup', $data);
|
||||
$env = new Env($data);
|
||||
|
||||
foreach ($env->list() as $key => $value) {
|
||||
foreach($vars as &$var) {
|
||||
if($var['name'] === $key) {
|
||||
if (is_null($value)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($vars as &$var) {
|
||||
if ($var['name'] === $key) {
|
||||
$var['default'] = $value;
|
||||
}
|
||||
}
|
||||
|
|
@ -117,60 +123,60 @@ $cli
|
|||
}
|
||||
|
||||
foreach ($ports as $key => $value) {
|
||||
if($value === $defaultHTTPPort) {
|
||||
if ($value === $defaultHTTPPort) {
|
||||
$defaultHTTPPort = $key;
|
||||
}
|
||||
|
||||
if($value === $defaultHTTPSPort) {
|
||||
if ($value === $defaultHTTPSPort) {
|
||||
$defaultHTTPSPort = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($httpPort)) {
|
||||
$httpPort = Console::confirm('Choose your server HTTP port: (default: '.$defaultHTTPPort.')');
|
||||
if (empty($httpPort)) {
|
||||
$httpPort = Console::confirm('Choose your server HTTP port: (default: ' . $defaultHTTPPort . ')');
|
||||
$httpPort = ($httpPort) ? $httpPort : $defaultHTTPPort;
|
||||
}
|
||||
|
||||
if(empty($httpsPort)) {
|
||||
$httpsPort = Console::confirm('Choose your server HTTPS port: (default: '.$defaultHTTPSPort.')');
|
||||
if (empty($httpsPort)) {
|
||||
$httpsPort = Console::confirm('Choose your server HTTPS port: (default: ' . $defaultHTTPSPort . ')');
|
||||
$httpsPort = ($httpsPort) ? $httpsPort : $defaultHTTPSPort;
|
||||
}
|
||||
|
||||
$input = [];
|
||||
|
||||
foreach($vars as $key => $var) {
|
||||
if(!empty($var['filter']) && ($interactive !== 'Y' || !Console::isInteractive())) {
|
||||
if($data && $var['default'] !== null) {
|
||||
foreach ($vars as $key => $var) {
|
||||
if (!empty($var['filter']) && ($interactive !== 'Y' || !Console::isInteractive())) {
|
||||
if ($data && $var['default'] !== null) {
|
||||
$input[$var['name']] = $var['default'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if($var['filter'] === 'token') {
|
||||
if ($var['filter'] === 'token') {
|
||||
$input[$var['name']] = Auth::tokenGenerator();
|
||||
continue;
|
||||
}
|
||||
|
||||
if($var['filter'] === 'password') {
|
||||
if ($var['filter'] === 'password') {
|
||||
$input[$var['name']] = Auth::passwordGenerator();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(!$var['required'] || !Console::isInteractive() || $interactive !== 'Y') {
|
||||
if (!$var['required'] || !Console::isInteractive() || $interactive !== 'Y') {
|
||||
$input[$var['name']] = $var['default'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$input[$var['name']] = Console::confirm($var['question'].' (default: \''.$var['default'].'\')');
|
||||
$input[$var['name']] = Console::confirm($var['question'] . ' (default: \'' . $var['default'] . '\')');
|
||||
|
||||
if(empty($input[$var['name']])) {
|
||||
if (empty($input[$var['name']])) {
|
||||
$input[$var['name']] = $var['default'];
|
||||
}
|
||||
}
|
||||
|
||||
$templateForCompose = new View(__DIR__.'/../views/install/compose.phtml');
|
||||
$templateForEnv = new View(__DIR__.'/../views/install/env.phtml');
|
||||
$templateForCompose = new View(__DIR__ . '/../views/install/compose.phtml');
|
||||
$templateForEnv = new View(__DIR__ . '/../views/install/env.phtml');
|
||||
|
||||
$templateForCompose
|
||||
->setParam('httpPort', $httpPort)
|
||||
|
|
@ -184,16 +190,16 @@ $cli
|
|||
->setParam('vars', $input)
|
||||
;
|
||||
|
||||
if(!file_put_contents($path.'/docker-compose.yml', $templateForCompose->render(false))) {
|
||||
if (!file_put_contents($path . '/docker-compose.yml', $templateForCompose->render(false))) {
|
||||
$message = 'Failed to save Docker Compose file';
|
||||
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE.' - '.$message);
|
||||
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message);
|
||||
Console::error($message);
|
||||
Console::exit(1);
|
||||
}
|
||||
|
||||
if(!file_put_contents($path.'/.env', $templateForEnv->render(false))) {
|
||||
if (!file_put_contents($path . '/.env', $templateForEnv->render(false))) {
|
||||
$message = 'Failed to save environment variables file';
|
||||
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE.' - '.$message);
|
||||
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message);
|
||||
Console::error($message);
|
||||
Console::exit(1);
|
||||
}
|
||||
|
|
@ -203,8 +209,8 @@ $cli
|
|||
$stderr = '';
|
||||
|
||||
foreach ($input as $key => $value) {
|
||||
if($value) {
|
||||
$env .= $key.'='.\escapeshellarg($value).' ';
|
||||
if ($value) {
|
||||
$env .= $key . '=' . \escapeshellarg($value) . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -214,13 +220,13 @@ $cli
|
|||
|
||||
if ($exit !== 0) {
|
||||
$message = 'Failed to install Appwrite dockers';
|
||||
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE.' - '.$message);
|
||||
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message);
|
||||
Console::error($message);
|
||||
Console::error($stderr);
|
||||
Console::exit($exit);
|
||||
} else {
|
||||
$message = 'Appwrite installed successfully';
|
||||
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE.' - '.$message);
|
||||
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message);
|
||||
Console::success($message);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,57 +1,121 @@
|
|||
<?php
|
||||
|
||||
global $cli;
|
||||
global $register;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
function getConsoleDB(): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
$attempts = 0;
|
||||
|
||||
do {
|
||||
try {
|
||||
$attempts++;
|
||||
$cache = new Cache(new RedisCache($register->get('cache')));
|
||||
$database = new Database(new MariaDB($register->get('db')), $cache);
|
||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$database->setNamespace('_console'); // Main DB
|
||||
|
||||
if (!$database->exists($database->getDefaultDatabase(), 'certificates')) {
|
||||
throw new \Exception('Console project not ready');
|
||||
}
|
||||
|
||||
break; // leave loop if successful
|
||||
} catch (\Exception $e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= 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;
|
||||
}
|
||||
|
||||
$cli
|
||||
->task('maintenance')
|
||||
->desc('Schedules maintenance tasks and publishes them to resque')
|
||||
->action(function () {
|
||||
Console::title('Maintenance V1');
|
||||
Console::success(APP_NAME.' maintenance process v1 has started');
|
||||
Console::success(APP_NAME . ' maintenance process v1 has started');
|
||||
|
||||
function notifyDeleteExecutionLogs(int $interval)
|
||||
{
|
||||
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
|
||||
'type' => DELETE_TYPE_EXECUTIONS,
|
||||
'timestamp' => time() - $interval
|
||||
]);
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_EXECUTIONS)
|
||||
->setTimestamp(time() - $interval)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function notifyDeleteAbuseLogs(int $interval)
|
||||
function notifyDeleteAbuseLogs(int $interval)
|
||||
{
|
||||
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
|
||||
'type' => DELETE_TYPE_ABUSE,
|
||||
'timestamp' => time() - $interval
|
||||
]);
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_ABUSE)
|
||||
->setTimestamp(time() - $interval)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function notifyDeleteAuditLogs(int $interval)
|
||||
function notifyDeleteAuditLogs(int $interval)
|
||||
{
|
||||
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
|
||||
'type' => DELETE_TYPE_AUDIT,
|
||||
'timestamp' => time() - $interval
|
||||
]);
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_AUDIT)
|
||||
->setTimestamp(time() - $interval)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function notifyDeleteUsageStats(int $interval30m, int $interval1d)
|
||||
function notifyDeleteUsageStats(int $interval30m, int $interval1d)
|
||||
{
|
||||
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
|
||||
'type' => DELETE_TYPE_USAGE,
|
||||
'timestamp1d' => time() - $interval1d,
|
||||
'timestamp30m' => time() - $interval30m,
|
||||
]);
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_USAGE)
|
||||
->setTimestamp1d(time() - $interval1d)
|
||||
->setTimestamp30m(time() - $interval30m)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function notifyDeleteConnections()
|
||||
function notifyDeleteConnections()
|
||||
{
|
||||
Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [
|
||||
'type' => DELETE_TYPE_REALTIME,
|
||||
'timestamp' => time() - 60
|
||||
]);
|
||||
(new Delete())
|
||||
->setType(DELETE_TYPE_REALTIME)
|
||||
->setTimestamp(time() - 60)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
function renewCertificates($dbForConsole)
|
||||
{
|
||||
$time = date('d-m-Y H:i:s', time());
|
||||
$certificates = $dbForConsole->find('certificates', [
|
||||
new Query('attempts', Query::TYPE_LESSEREQUAL, [5]), // Maximum 5 attempts
|
||||
new Query('renewDate', Query::TYPE_LESSEREQUAL, [\time()]) // includes 60 days cooldown (we have 30 days to renew)
|
||||
], 200); // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
|
||||
|
||||
|
||||
if (\count($certificates) > 0) {
|
||||
Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
|
||||
|
||||
$event = new Certificate();
|
||||
foreach ($certificates as $certificate) {
|
||||
$event
|
||||
->setDomain(new Document([
|
||||
'domain' => $certificate->getAttribute('domain')
|
||||
]))
|
||||
->trigger();
|
||||
}
|
||||
} else {
|
||||
Console::info("[{$time}] No certificates for renewal.");
|
||||
}
|
||||
}
|
||||
|
||||
// # of days in seconds (1 day = 86400s)
|
||||
|
|
@ -59,16 +123,19 @@ $cli
|
|||
$executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600');
|
||||
$auditLogRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', '1209600');
|
||||
$abuseLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', '86400');
|
||||
$usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600');//36 hours
|
||||
$usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600'); //36 hours
|
||||
$usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days
|
||||
|
||||
Console::loop(function() use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) {
|
||||
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) {
|
||||
$database = getConsoleDB();
|
||||
|
||||
$time = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$time}] Notifying deletes workers every {$interval} seconds");
|
||||
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
|
||||
notifyDeleteExecutionLogs($executionLogsRetention);
|
||||
notifyDeleteAbuseLogs($abuseLogsRetention);
|
||||
notifyDeleteAuditLogs($auditLogRetention);
|
||||
notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d);
|
||||
notifyDeleteConnections();
|
||||
renewCertificates($database);
|
||||
}, $interval);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -46,7 +46,13 @@ $cli
|
|||
$offset = 0;
|
||||
$projects = [$console];
|
||||
$count = 0;
|
||||
$totalProjects = $consoleDB->count('projects') + 1;
|
||||
|
||||
try {
|
||||
$totalProjects = $consoleDB->count('projects') + 1;
|
||||
} catch (\Throwable $th) {
|
||||
$consoleDB->setNamespace('_console');
|
||||
$totalProjects = $consoleDB->count('projects') + 1;
|
||||
}
|
||||
|
||||
$class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version];
|
||||
$migration = new $class();
|
||||
|
|
|
|||
|
|
@ -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', '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', 'latest'])) {
|
||||
throw new Exception('Unknown version given');
|
||||
}
|
||||
|
||||
|
|
@ -84,17 +84,24 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
break;
|
||||
case 'cli':
|
||||
$config = new CLI();
|
||||
$config->setComposerVendor('appwrite');
|
||||
$config->setComposerPackage('cli');
|
||||
$config->setNPMPackage('appwrite-cli');
|
||||
$config->setExecutableName('appwrite');
|
||||
$config->setLogo("
|
||||
$config->setLogo(json_encode("
|
||||
_ _ _ ___ __ _____
|
||||
/_\ _ __ _ ____ ___ __(_) |_ ___ / __\ / / \_ \
|
||||
//_\\| '_ \| '_ \ \ /\ / / '__| | __/ _ \ / / / / / /\/
|
||||
//_\\\| '_ \| '_ \ \ /\ / / '__| | __/ _ \ / / / / / /\/
|
||||
/ _ \ |_) | |_) \ V V /| | | | || __/ / /___/ /___/\/ /_
|
||||
\_/ \_/ .__/| .__/ \_/\_/ |_| |_|\__\___| \____/\____/\____/
|
||||
|_| |_|
|
||||
");
|
||||
|_| |_|
|
||||
|
||||
"));
|
||||
$config->setLogoUnescaped("
|
||||
_ _ _ ___ __ _____
|
||||
/_\ _ __ _ ____ ___ __(_) |_ ___ / __\ / / \_ \
|
||||
//_\\\| '_ \| '_ \ \ /\ / / '__| | __/ _ \ / / / / / /\/
|
||||
/ _ \ |_) | |_) \ V V /| | | | || __/ / /___/ /___/\/ /_
|
||||
\_/ \_/ .__/| .__/ \_/\_/ |_| |_|\__\___| \____/\____/\____/
|
||||
|_| |_| ");
|
||||
break;
|
||||
case 'php':
|
||||
$config = new PHP();
|
||||
|
|
@ -189,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.12.0',
|
||||
'X-Appwrite-Response-Format' => '0.14.0',
|
||||
]);
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ $cli
|
|||
$response = new Response(new HttpResponse());
|
||||
$mocks = ($mode === 'mocks');
|
||||
|
||||
App::setResource('request', fn () => new Request);
|
||||
App::setResource('request', fn () => new Request());
|
||||
App::setResource('response', fn () => $response);
|
||||
App::setResource('db', fn () => $db);
|
||||
App::setResource('cache', fn () => $redis);
|
||||
|
|
@ -125,7 +125,6 @@ $cli
|
|||
|
||||
foreach (['swagger2', 'open-api3'] as $format) {
|
||||
foreach ($platforms as $platform) {
|
||||
|
||||
$routes = [];
|
||||
$models = [];
|
||||
$services = [];
|
||||
|
|
|
|||
|
|
@ -2,22 +2,23 @@
|
|||
|
||||
global $cli;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Validator\Hostname;
|
||||
|
||||
$cli
|
||||
->task('ssl')
|
||||
->desc('Validate server certificates')
|
||||
->action(function () {
|
||||
$domain = App::getEnv('_APP_DOMAIN', '');
|
||||
->param('domain', App::getEnv('_APP_DOMAIN', ''), new Hostname(), 'Domain to generate certificate for. If empty, main domain will be used.', true)
|
||||
->action(function ($domain) {
|
||||
Console::success('Scheduling a job to issue a TLS certificate for domain: ' . $domain);
|
||||
|
||||
Console::log('Issue a TLS certificate for master domain ('.$domain.') in 30 seconds.
|
||||
Make sure your domain points to your server or restart to try again.');
|
||||
|
||||
ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [
|
||||
'document' => [],
|
||||
'domain' => $domain,
|
||||
'validateTarget' => false,
|
||||
'validateCNAME' => false,
|
||||
]);
|
||||
});
|
||||
(new Certificate())
|
||||
->setDomain(new Document([
|
||||
'domain' => $domain
|
||||
]))
|
||||
->setSkipRenewCheck(true)
|
||||
->trigger();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,6 +37,14 @@ use Utopia\Database\Validator\Authorization;
|
|||
*
|
||||
* 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
|
||||
|
|
@ -61,7 +69,9 @@ use Utopia\Database\Validator\Authorization;
|
|||
* 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
|
||||
|
|
@ -142,6 +152,30 @@ $cli
|
|||
'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',
|
||||
|
|
@ -202,6 +236,8 @@ $cli
|
|||
$max = 10;
|
||||
$sleep = 1;
|
||||
|
||||
$db = null;
|
||||
$redis = null;
|
||||
do { // connect to db
|
||||
try {
|
||||
$attempts++;
|
||||
|
|
@ -223,7 +259,7 @@ $cli
|
|||
$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('_project_console');
|
||||
$dbForConsole->setNamespace('_console');
|
||||
|
||||
$latestTime = [];
|
||||
|
||||
|
|
@ -241,47 +277,47 @@ $cli
|
|||
* @var InfluxDB\Client $client
|
||||
*/
|
||||
$client = $register->get('influxdb');
|
||||
if ($client) {
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
$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);
|
||||
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
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
} 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);
|
||||
// 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
|
||||
$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 = '';
|
||||
}
|
||||
$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)";
|
||||
$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();
|
||||
|
|
@ -289,7 +325,7 @@ $cli
|
|||
$projectId = $point['projectId'];
|
||||
|
||||
if (!empty($projectId) && $projectId !== 'console') {
|
||||
$dbForProject->setNamespace('_project_' . $projectId);
|
||||
$dbForProject->setNamespace('_' . $projectId);
|
||||
$metricUpdated = $metric;
|
||||
|
||||
if (!empty($groupBy)) {
|
||||
|
|
@ -325,118 +361,369 @@ $cli
|
|||
$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
|
||||
$iterations++;
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate MariaDB every 15 minutes
|
||||
* Some of the queries here might contain full-table scans.
|
||||
*/
|
||||
if ($iterations % 30 === 0) { // Every 15 minutes aggregate number of objects in database
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Aggregating database counters.");
|
||||
|
||||
$latestProject = null;
|
||||
$latestProject = null;
|
||||
do { // Loop over all the projects
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
|
||||
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);
|
||||
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());
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
if (empty($projects)) {
|
||||
continue;
|
||||
sleep($sleep);
|
||||
}
|
||||
} while ($attempts < $max);
|
||||
|
||||
$latestProject = $projects[array_key_last($projects)];
|
||||
if (empty($projects)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$projectId = $project->getId();
|
||||
$latestProject = $projects[array_key_last($projects)];
|
||||
|
||||
// Get total storage
|
||||
$dbForProject->setNamespace('_project_' . $projectId);
|
||||
$storageTotal = $dbForProject->sum('files', 'sizeOriginal') + $dbForProject->sum('tags', 'size');
|
||||
foreach ($projects as $project) {
|
||||
$projectId = $project->getId();
|
||||
|
||||
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
|
||||
$id = \md5($time . '_30m_storage.total'); //Construct unique id for each metric using time, period and metric
|
||||
$document = $dbForProject->getDocument('stats', $id);
|
||||
// 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.total',
|
||||
'value' => $storageTotal,
|
||||
'metric' => 'storage.deployments.total',
|
||||
'value' => $deploymentsTotal,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $storageTotal)
|
||||
$document->setAttribute('value', $deploymentsTotal)
|
||||
);
|
||||
}
|
||||
|
||||
$time = (int) (floor(time() / 86400) * 86400); // Time rounded to nearest day
|
||||
$id = \md5($time . '_1d_storage.total'); //Construct unique id for each metric using time, period and metric
|
||||
$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.total',
|
||||
'value' => $storageTotal,
|
||||
'metric' => 'storage.deployments.total',
|
||||
'value' => $deploymentsTotal,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $storageTotal)
|
||||
$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' => 'internal',
|
||||
],
|
||||
'collections' => [
|
||||
'metricPrefix' => 'database',
|
||||
'namespace' => 'internal',
|
||||
'subCollections' => [ // Some collections, like collections and later buckets have child collections that need counting
|
||||
'documents' => [
|
||||
'namespace' => 'external',
|
||||
],
|
||||
|
||||
$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' => '',
|
||||
],
|
||||
],
|
||||
'files' => [
|
||||
'metricPrefix' => 'storage',
|
||||
'namespace' => 'internal',
|
||||
],
|
||||
];
|
||||
],
|
||||
'buckets' => [
|
||||
'metricPrefix' => 'storage',
|
||||
'namespace' => '',
|
||||
'subCollections' => [
|
||||
'files' => [
|
||||
'namespace' => '',
|
||||
'collectionPrefix' => 'bucket_',
|
||||
'total' => [
|
||||
'field' => 'sizeOriginal'
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($collections as $collection => $options) {
|
||||
try {
|
||||
$dbForProject->setNamespace("_project_{$projectId}");
|
||||
$count = $dbForProject->count($collection);
|
||||
$metricPrefix = $options['metricPrefix'] ?? '';
|
||||
$metric = empty($metricPrefix) ? "{$collection}.count" : "{$metricPrefix}.{$collection}.count";
|
||||
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
|
||||
|
|
@ -478,86 +765,9 @@ $cli
|
|||
);
|
||||
}
|
||||
|
||||
$subCollections = $options['subCollections'] ?? [];
|
||||
|
||||
if (empty($subCollections)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$latestParent = null;
|
||||
$subCollectionCounts = []; //total project level count of sub collections
|
||||
|
||||
do { // Loop over all the parent collection document for each sub collection
|
||||
$dbForProject->setNamespace("_project_{$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("_project_{$projectId}");
|
||||
$count = $dbForProject->count($parent->getId());
|
||||
|
||||
$subCollectionCounts[$subCollection] = ($subCollectionCounts[$subCollection] ?? 0) + $count; // Project level counts for sub collections like database.documents.count
|
||||
|
||||
$dbForProject->setNamespace("_project_{$projectId}");
|
||||
|
||||
$metric = empty($metricPrefix) ? "{$collection}.{$parent->getId()}.{$subCollection}.count" : "{$metricPrefix}.{$collection}.{$parent->getId()}.{$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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!empty($parents));
|
||||
|
||||
/**
|
||||
* Inserting project level counts for sub collections like database.documents.count
|
||||
*/
|
||||
foreach ($subCollectionCounts as $subCollection => $count) {
|
||||
$dbForProject->setNamespace("_project_{$projectId}");
|
||||
|
||||
$metric = empty($metricPrefix) ? "{$subCollection}.count" : "{$metricPrefix}.{$subCollection}.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);
|
||||
|
|
@ -567,14 +777,14 @@ $cli
|
|||
'time' => $time,
|
||||
'period' => '30m',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'value' => $count + $deploymentsTotal,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
$document->setAttribute('value', $count + $deploymentsTotal)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -587,24 +797,25 @@ $cli
|
|||
'time' => $time,
|
||||
'period' => '1d',
|
||||
'metric' => $metric,
|
||||
'value' => $count,
|
||||
'value' => $count + $deploymentsTotal,
|
||||
'type' => 1,
|
||||
]));
|
||||
} else {
|
||||
$dbForProject->updateDocument(
|
||||
'stats',
|
||||
$document->getId(),
|
||||
$document->setAttribute('value', $count)
|
||||
$document->setAttribute('value', $count + $deploymentsTotal)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Console::warning("Failed to save database counters data for project {$collection}: {$e->getMessage()}");
|
||||
}
|
||||
} catch (\Exception$e) {
|
||||
Console::warning("Failed to save database counters data for project {$collection}: {$e->getMessage()}");
|
||||
Console::warning($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
} while (!empty($projects));
|
||||
}
|
||||
}
|
||||
} while (!empty($projects));
|
||||
|
||||
$iterations++;
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ $cli
|
|||
$config = Config::getParam('variables', []);
|
||||
$vars = [];
|
||||
|
||||
foreach($config as $category) {
|
||||
foreach($category['variables'] ?? [] as $var) {
|
||||
foreach ($config as $category) {
|
||||
foreach ($category['variables'] ?? [] as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($vars as $key => $value) {
|
||||
Console::log('- '.$value['name'].'='.App::getEnv($value['name'], ''));
|
||||
Console::log('- ' . $value['name'] . '=' . App::getEnv($value['name'], ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -251,8 +251,10 @@
|
|||
</span>
|
||||
|
||||
<div class="pull-start margin-end avatar-container">
|
||||
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=60&height=60&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" />
|
||||
|
||||
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" data-ls-if="{{session.clientCode|lowercase}} !== 'cli'"/>
|
||||
|
||||
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src=/images/clients/terminal.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{session.clientName}},alt={{session.clientName}}" class="avatar" loading="lazy" width="60" height="60" data-ls-if="{{session.clientCode|lowercase}} === 'cli'" >
|
||||
|
||||
<div data-ls-if="{{session.provider}} !== 'email'" class="corner">
|
||||
<img data-ls-attrs="src=/images/users/{{session.provider}}.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{session.provider}},alt={{session.provider}}" class="avatar xs" loading="lazy" width="30" height="30" />
|
||||
</div>
|
||||
|
|
@ -317,7 +319,10 @@
|
|||
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
|
||||
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
|
||||
<td data-title="Client: ">
|
||||
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
|
||||
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" data-ls-if="{{log.clientCode|lowercase}} !== 'cli'"/>
|
||||
|
||||
<img onerror="this.onerror=null;this.className='avatar hide'" data-ls-attrs="src=/images/clients/terminal.png?buster=<?php echo APP_CACHE_BUSTER; ?>,title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" data-ls-if="{{log.clientCode|lowercase}} === 'cli'" />
|
||||
|
||||
<span data-ls-if="(!{{log.clientName}})">Unknown</span>
|
||||
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></span>
|
||||
</td>
|
||||
|
|
@ -340,10 +345,10 @@
|
|||
data-name="securityLogs"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="offset">
|
||||
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-sum="{{securityLogs.sum}}" 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="{{securityLogs.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}} / {{securityLogs.sum|pageTotal}}"></span>
|
||||
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{securityLogs.total|pageTotal}}"></span>
|
||||
|
||||
<form
|
||||
data-service="account.getLogs"
|
||||
|
|
@ -354,7 +359,7 @@
|
|||
data-name="securityLogs"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="offset">
|
||||
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{securityLogs.sum}}" 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="{{securityLogs.total}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||