Compare commits

..
Author SHA1 Message Date
loks0n f8b9807eb5 bench: init 2024-07-15 10:41:24 +01:00
5163 changed files with 3706 additions and 449714 deletions
+3 -4
View File
@@ -9,8 +9,7 @@ _APP_CONSOLE_WHITELIST_IPS=
_APP_CONSOLE_COUNTRIES_DENYLIST=AQ
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io
_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_EMAIL_SECURITY=security@appwrite.io
_APP_EMAIL_CERTIFICATES=certificates@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
@@ -69,8 +68,8 @@ _APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_FUNCTIONS_CPUS=8
_APP_FUNCTIONS_MEMORY=8192
_APP_FUNCTIONS_CPUS=1
_APP_FUNCTIONS_MEMORY=1024
_APP_FUNCTIONS_INACTIVE_THRESHOLD=600
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=600
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
-47
View File
@@ -1,47 +0,0 @@
name: Nightly Security Scan
on:
schedule:
- cron: '0 0 * * *' # 12am UTC daily runtime
workflow_dispatch:
jobs:
scan-image:
name: Scan Docker Image
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build the Docker image
run: docker build . -t appwrite_image:latest
- name: Run Trivy vulnerability scanner on image
uses: aquasecurity/trivy-action@0.20.0
with:
image-ref: 'appwrite_image:latest'
format: 'sarif'
output: 'trivy-image-results.sarif'
ignore-unfixed: 'false'
severity: 'CRITICAL,HIGH'
- name: Upload Docker Image Scan Results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-image-results.sarif'
scan-code:
name: Scan Code
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner on filesystem
uses: aquasecurity/trivy-action@0.20.0
with:
scan-type: 'fs'
format: 'sarif'
output: 'trivy-fs-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Code Scan Results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-fs-results.sarif'
-105
View File
@@ -1,105 +0,0 @@
name: PR Security Scan
on:
pull_request_target:
types: [opened, synchronize, reopened]
jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
submodules: 'recursive'
- name: Build the Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
load: true
tags: pr_image:${{ github.sha }}
- name: Run Trivy vulnerability scanner on image
uses: aquasecurity/trivy-action@0.20.0
with:
image-ref: 'pr_image:${{ github.sha }}'
format: 'json'
output: 'trivy-image-results.json'
severity: 'CRITICAL,HIGH'
- name: Run Trivy vulnerability scanner on source code
uses: aquasecurity/trivy-action@0.20.0
with:
scan-type: 'fs'
scan-ref: '.'
format: 'json'
output: 'trivy-fs-results.json'
severity: 'CRITICAL,HIGH'
- name: Process Trivy scan results
id: process-results
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let commentBody = '## Security Scan Results for PR\n\n';
function processResults(results, title) {
let sectionBody = `### ${title}\n\n`;
if (results.Results && results.Results.some(result => result.Vulnerabilities && result.Vulnerabilities.length > 0)) {
sectionBody += '| Package | Version | Vulnerability | Severity |\n';
sectionBody += '|---------|---------|----------------|----------|\n';
const uniqueVulns = new Set();
results.Results.forEach(result => {
if (result.Vulnerabilities) {
result.Vulnerabilities.forEach(vuln => {
const vulnKey = `${vuln.PkgName}-${vuln.InstalledVersion}-${vuln.VulnerabilityID}`;
if (!uniqueVulns.has(vulnKey)) {
uniqueVulns.add(vulnKey);
sectionBody += `| ${vuln.PkgName} | ${vuln.InstalledVersion} | [${vuln.VulnerabilityID}](https://nvd.nist.gov/vuln/detail/${vuln.VulnerabilityID}) | ${vuln.Severity} |\n`;
}
});
}
});
} else {
sectionBody += '🎉 No vulnerabilities found!\n';
}
return sectionBody;
}
try {
const imageResults = JSON.parse(fs.readFileSync('trivy-image-results.json', 'utf8'));
const fsResults = JSON.parse(fs.readFileSync('trivy-fs-results.json', 'utf8'));
commentBody += processResults(imageResults, "Docker Image Scan Results");
commentBody += '\n';
commentBody += processResults(fsResults, "Source Code Scan Results");
} catch (error) {
commentBody += `There was an error while running the security scan: ${error.message}\n`;
commentBody += 'Please contact the core team for assistance.';
}
core.setOutput('comment-body', commentBody);
- name: Find Comment
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Security Scan Results for PR
- name: Create or update comment
uses: peter-evans/create-or-update-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.fc.outputs.comment-id }}
body: ${{ steps.process-results.outputs.comment-body }}
edit-mode: replace
+13 -94
View File
@@ -16,22 +16,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
- name: Build Appwrite
uses: docker/build-push-action@v6
uses: docker/build-push-action@v3
with:
context: .
push: false
tags: ${{ env.IMAGE }}
load: true
cache-from: type=gha,scope=appwrite
cache-to: type=gha,mode=max,scope=appwrite
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
build-args: |
DEBUG=false
@@ -39,11 +39,9 @@ jobs:
VERSION=dev
- name: Cache Docker Image
uses: actions/cache@v4
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
restore-keys: |
appwrite-dev-
path: /tmp/${{ env.IMAGE }}.tar
unit_test:
@@ -53,10 +51,10 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Load Cache
uses: actions/cache@v4
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -83,10 +81,10 @@ jobs:
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Load Cache
uses: actions/cache@v4
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -115,7 +113,6 @@ jobs:
Console,
Databases,
Functions,
FunctionsSchedule,
GraphQL,
Health,
Locale,
@@ -131,10 +128,10 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Load Cache
uses: actions/cache@v4
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -144,88 +141,10 @@ jobs:
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 30
sleep 25
- name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
- name: Run ${{matrix.service}} Shared Tables Tests
run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
benchmarking:
name: Benchmark
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Load and Start Appwrite
run: |
sed -i 's/traefik/localhost/g' .env
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 10
- name: Install Oha
run: |
echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list
sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg
sudo apt update
sudo apt install oha
- name: Benchmark PR
run: oha -z 180s http://localhost/v1/health/version -j > benchmark.json
- name: Cleaning
run: docker compose down -v
- name: Installing latest version
run: |
rm docker-compose.yml
rm .env
curl https://appwrite.io/install/compose -o docker-compose.yml
curl https://appwrite.io/install/env -o .env
sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env
docker compose up -d
sleep 10
- name: Benchmark Latest
run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json
- name: Prepare comment
run: |
echo '## :sparkles: Benchmark results' > benchmark.txt
echo ' ' >> benchmark.txt
echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt
echo " " >> benchmark.txt
echo " " >> benchmark.txt
echo "## :zap: Benchmark Comparison" >> benchmark.txt
echo " " >> benchmark.txt
echo "| Metric | This PR | Latest version | " >> benchmark.txt
echo "| --- | --- | --- | " >> benchmark.txt
echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt
- name: Save results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: benchmark.json
path: benchmark.json
retention-days: 7
- name: Find Comment
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Benchmark results
- name: Comment on PR
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: benchmark.txt
edit-mode: replace
-1
View File
@@ -9,7 +9,6 @@
!/.idea/php.xml
.DS_Store
.php_cs.cache
.phpactor.json
debug/
app/sdks
dev/yasd_init.php
+4
View File
@@ -0,0 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 1.6.x
-225
View File
@@ -1,228 +1,3 @@
# Version 1.6.0
## What's Changed
### Notable changes
* Allow execution filter attributes in [#7607](https://github.com/appwrite/appwrite/pull/7607)
* Add dynamic API keys for function executions in [#7512](https://github.com/appwrite/appwrite/pull/7512)
* Add metrics for successful and failed builds in [#8210](https://github.com/appwrite/appwrite/pull/8210)
* Update logging config to use a DSN approach in [#8187](https://github.com/appwrite/appwrite/pull/8187)
* Add projects.createJWT endpoint for dynamic keys in [#8213](https://github.com/appwrite/appwrite/pull/8213)
* Add users.createJWT() endpoint for local function development in [#8207](https://github.com/appwrite/appwrite/pull/8207)
* Added cancel build endpoint in [#7605](https://github.com/appwrite/appwrite/pull/7605)
* Add CLI as a function deployment type in [#8215](https://github.com/appwrite/appwrite/pull/8215)
* Add vcs.getRepositoryContents() endpoint in [#8330](https://github.com/appwrite/appwrite/pull/8330)
* Add appwrite version in function variables in [#8336](https://github.com/appwrite/appwrite/pull/8336)
* Add support for scheduled executions in [#8243](https://github.com/appwrite/appwrite/pull/8243)
* Add endpoint to delete execution in [#8337](https://github.com/appwrite/appwrite/pull/8337)
* OPR v4 support in [#8323](https://github.com/appwrite/appwrite/pull/8323)
* Mock OTP and phone numbers in [#7565](https://github.com/appwrite/appwrite/pull/7565)
* Support scheduled executions in [#8355](https://github.com/appwrite/appwrite/pull/8355)
* Add alert for new sessions in [#8315](https://github.com/appwrite/appwrite/pull/8315)
* Update delete authenticator to remove OTP Validation in [#8367](https://github.com/appwrite/appwrite/pull/8367)
* Track project last activity in [#8366](https://github.com/appwrite/appwrite/pull/8366)
* Containerize the console in [#8406](https://github.com/appwrite/appwrite/pull/8406)
* Implement MBSeconds Metric on 1.5.X in [#8385](https://github.com/appwrite/appwrite/pull/8385)
* Support JWTs without session ID in [#8420](https://github.com/appwrite/appwrite/pull/8420)
* 1.6.x sdks in [#8359](https://github.com/appwrite/appwrite/pull/8359)
* Base migration for 1.6.x in [#8417](https://github.com/appwrite/appwrite/pull/8417)
* 1.6.x migrations and filters in [#8403](https://github.com/appwrite/appwrite/pull/8403)
* Add APPWRITE_REGION in function variables in [#8394](https://github.com/appwrite/appwrite/pull/8394)
* Support dynamic keys for domain executions in [#8428](https://github.com/appwrite/appwrite/pull/8428)
* Bump DBIP to latest version in [#8467](https://github.com/appwrite/appwrite/pull/8467)
* Automatically restart function on crash in [#8473](https://github.com/appwrite/appwrite/pull/8473)
* Don't send session alerts for otp and magic-url logins in [#8459](https://github.com/appwrite/appwrite/pull/8459)
* Mark 4XX executions as successful in [#8493](https://github.com/appwrite/appwrite/pull/8493)
* Add dynamic keys in builds in [#8492](https://github.com/appwrite/appwrite/pull/8492)
* Allow deployment queries on type and size in [#8515](https://github.com/appwrite/appwrite/pull/8515)
* Add OTP email template in [#8501](https://github.com/appwrite/appwrite/pull/8501)
* Update console links in [#8523](https://github.com/appwrite/appwrite/pull/8523)
* Add multipart support in [#8477](https://github.com/appwrite/appwrite/pull/8477)
* Separate deployment sizes in [#8556](https://github.com/appwrite/appwrite/pull/8556)
* Add go runtime in [#8572](https://github.com/appwrite/appwrite/pull/8572)
* Add react native platform in [#8562](https://github.com/appwrite/appwrite/pull/8562)
* Merge deployments and build storage metrics together in API in [#8443](https://github.com/appwrite/appwrite/pull/8443)
* Support string attribute resizing in [#8597](https://github.com/appwrite/appwrite/pull/8597)
* Support renaming attributes in [#8544](https://github.com/appwrite/appwrite/pull/8544)
* Add VCS vars to deployments & executions in [#8631](https://github.com/appwrite/appwrite/pull/8631)
* Function storage metrics in [#8668](https://github.com/appwrite/appwrite/pull/8668)
* External messaging usage count in [#8672](https://github.com/appwrite/appwrite/pull/8672)
### Fixes
* Fix execution duration in [#8357](https://github.com/appwrite/appwrite/pull/8357)
* Fix file size calculations in [#8432](https://github.com/appwrite/appwrite/pull/8432)
* Fix disabled function logging in [#8398](https://github.com/appwrite/appwrite/pull/8398)
* Fix function redeployments in [#8434](https://github.com/appwrite/appwrite/pull/8434)
* Add value to variables template in [#8483](https://github.com/appwrite/appwrite/pull/8483)
* Fix build size limits in [#8396](https://github.com/appwrite/appwrite/pull/8396)
* Fix deployment method name in [#8490](https://github.com/appwrite/appwrite/pull/8490)
* Fix function disconnecting from git in [#8500](https://github.com/appwrite/appwrite/pull/8500)
* Increase buckets metadata in [#8452](https://github.com/appwrite/appwrite/pull/8452)
* Fix deploy from git with space in [#8517](https://github.com/appwrite/appwrite/pull/8517)
* Fix missing build logs in [#8484](https://github.com/appwrite/appwrite/pull/8484)
* Delete team memberships synchronously in [#8217](https://github.com/appwrite/appwrite/pull/8217)
* Fix Anyof validator in specs in [#8543](https://github.com/appwrite/appwrite/pull/8543)
* Fix missing function variables in [#8554](https://github.com/appwrite/appwrite/pull/8554)
* Fix deadlock in [#8609](https://github.com/appwrite/appwrite/pull/8609)
* Fix domain execution stats in [#8608](https://github.com/appwrite/appwrite/pull/8608)
* Update console redirect to include query params in [#8619](https://github.com/appwrite/appwrite/pull/8619)
* Update abuse-key for mfa challenge endpoints in [#8649](https://github.com/appwrite/appwrite/pull/8649)
* Fix cross-project scheduler stability in [#8641](https://github.com/appwrite/appwrite/pull/8641)
* Fix vcs deployment size in [#8640](https://github.com/appwrite/appwrite/pull/8640)
* Fix logging behaviour for Functions in [#8627](https://github.com/appwrite/appwrite/pull/8627)
* Add retention env vars to deletes worker in [#8662](https://github.com/appwrite/appwrite/pull/8662)
* Fix scheduled executions data in [#8639](https://github.com/appwrite/appwrite/pull/8639)
### Miscellaneous
* Sync 1.6.x with main in [#8163](https://github.com/appwrite/appwrite/pull/8163)
* Remove build ID from rebuild deployment endpoint in [#8214](https://github.com/appwrite/appwrite/pull/8214)
* 1.6.x specs in [#8304](https://github.com/appwrite/appwrite/pull/8304)
* Sync with main in [#8295](https://github.com/appwrite/appwrite/pull/8295)
* Fix 1.6.x failing tests in [#8333](https://github.com/appwrite/appwrite/pull/8333)
* Ensure CI/CD works in [#8350](https://github.com/appwrite/appwrite/pull/8350)
* Update specs in [#8356](https://github.com/appwrite/appwrite/pull/8356)
* Sync main to 1.6.x in [#8430](https://github.com/appwrite/appwrite/pull/8430)
* Add scheduledAt in execution response model in [#8425](https://github.com/appwrite/appwrite/pull/8425)
* Move functions marketplace to appwrite in [#8427](https://github.com/appwrite/appwrite/pull/8427)
* Refactor deployment check in function tests in [#8444](https://github.com/appwrite/appwrite/pull/8444)
* Add ci/cd benchmark in [#8414](https://github.com/appwrite/appwrite/pull/8414)
* Upgrade SDK version in [#8465](https://github.com/appwrite/appwrite/pull/8465)
* Improve session alert in [#8399](https://github.com/appwrite/appwrite/pull/8399)
* Address review comments in [#8422](https://github.com/appwrite/appwrite/pull/8422)
* Add scopes to function template in [#8496](https://github.com/appwrite/appwrite/pull/8496)
* Update benchmark comment in [#8507](https://github.com/appwrite/appwrite/pull/8507)
* Add key to runtime model in [#8503](https://github.com/appwrite/appwrite/pull/8503)
* Upgrade logger in [#8497](https://github.com/appwrite/appwrite/pull/8497)
* Change default email addresses in [#8466](https://github.com/appwrite/appwrite/pull/8466)
* Improve scheduled executions in [#8412](https://github.com/appwrite/appwrite/pull/8412)
* Sync 1.5.x into main in [#8509](https://github.com/appwrite/appwrite/pull/8509)
* Sync 1.6 with main in [#8529](https://github.com/appwrite/appwrite/pull/8529)
* Fix templates CORS in [#8528](https://github.com/appwrite/appwrite/pull/8528)
* Update size to specification for variable runtimes in [#8537](https://github.com/appwrite/appwrite/pull/8537)
* Add boundary to multipart header in [#8539](https://github.com/appwrite/appwrite/pull/8539)
* Support manual templates in [#8527](https://github.com/appwrite/appwrite/pull/8527)
* Reorder runtimes in [#8540](https://github.com/appwrite/appwrite/pull/8540)
* Fix 1.6 bugs in [#8358](https://github.com/appwrite/appwrite/pull/8358)
* Add seconds precision to scheduledAt in [#8546](https://github.com/appwrite/appwrite/pull/8546)
* Update docker base image in [#8485](https://github.com/appwrite/appwrite/pull/8485)
* Update create execution return type in [#8542](https://github.com/appwrite/appwrite/pull/8542)
* Default fallback to for templateBranch in [#8547](https://github.com/appwrite/appwrite/pull/8547)
* Fix env vars functions test in [#8555](https://github.com/appwrite/appwrite/pull/8555)
* Fix session alerts in [#8550](https://github.com/appwrite/appwrite/pull/8550)
* Add runtime controls in [#8384](https://github.com/appwrite/appwrite/pull/8384)
* Revert request type to json in create execution in [#8563](https://github.com/appwrite/appwrite/pull/8563)
* Sync 1.6.x Filters and Migrations with latest in [#8553](https://github.com/appwrite/appwrite/pull/8553)
* Update sdks in [#8551](https://github.com/appwrite/appwrite/pull/8551)
* Update Docs in [#8567](https://github.com/appwrite/appwrite/pull/8567)
* Headers validator benchmark in [#8561](https://github.com/appwrite/appwrite/pull/8561)
* Fix go version in [#8571](https://github.com/appwrite/appwrite/pull/8571)
* Update dependencies in [#8574](https://github.com/appwrite/appwrite/pull/8574)
* Upgrade console in [#8575](https://github.com/appwrite/appwrite/pull/8575)
* 1.6.x logging test in [#8580](https://github.com/appwrite/appwrite/pull/8580)
* Bump console sdk in [#8581](https://github.com/appwrite/appwrite/pull/8581)
* Update sdks in [#8582](https://github.com/appwrite/appwrite/pull/8582)
* Add changelogs for dart and flutter in [#8587](https://github.com/appwrite/appwrite/pull/8587)
* Add payload validator in [#8594](https://github.com/appwrite/appwrite/pull/8594)
* Update geodb in [#8615](https://github.com/appwrite/appwrite/pull/8615)
* Update createdeployment methodtype to upload in [#8616](https://github.com/appwrite/appwrite/pull/8616)
* Remove tenant in document filter in [#8624](https://github.com/appwrite/appwrite/pull/8624)
* Improve mail datetime format in [#8628](https://github.com/appwrite/appwrite/pull/8628)
* Fix router function execution logging in [#8625](https://github.com/appwrite/appwrite/pull/8625)
* Add Functions templates async test in [#8622](https://github.com/appwrite/appwrite/pull/8622)
* Update console in [#8629](https://github.com/appwrite/appwrite/pull/8629)
* 1.6.1 in [#8630](https://github.com/appwrite/appwrite/pull/8630)
* Update version in [#8646](https://github.com/appwrite/appwrite/pull/8646)
* Phone auth metric rename in [#8648](https://github.com/appwrite/appwrite/pull/8648)
* Pretty print specs in [#8643](https://github.com/appwrite/appwrite/pull/8643)
* Fix messaging metrics in [#8674](https://github.com/appwrite/appwrite/pull/8674)
* Bump console to 5.0.6 in [#8585](https://github.com/appwrite/appwrite/pull/8585)
# Version 1.5.10
## What's Changed
### Notable changes
* Bump console to version 4.3.30 in [#8520](https://github.com/appwrite/appwrite/pull/8520)
### Fixes
* Fix migration stuck at "Starting Data Migration [...]" in [#8519](https://github.com/appwrite/appwrite/pull/8519)
# Version 1.5.9
## What's Changed
### Notable changes
* Add Darija (Moroccan Arabic) translation file in [7501](https://github.com/appwrite/appwrite/pull/7501)
* Bump console to version 4.3.29 in [8504](https://github.com/appwrite/appwrite/pull/8504)
### Fixes
* Fix domain check in [8472](https://github.com/appwrite/appwrite/pull/8472)
* Fix "API must be called in the coroutine" in [8495](https://github.com/appwrite/appwrite/pull/8495)
* Bump executor version from 0.5.5 to 0.5.7 in [8502](https://github.com/appwrite/appwrite/pull/8502)
### Miscellaneous
* Add profiler for debugging in [8397](https://github.com/appwrite/appwrite/pull/8397)
* Document APIs that don't support redirects in [8233](https://github.com/appwrite/appwrite/pull/8233)
# Version 1.5.8
## What's Changed
### Notable changes
* Support Twilio messaging service SID in [8222](https://github.com/appwrite/appwrite/pull/8222)
* Improve cache performance in [8230](https://github.com/appwrite/appwrite/pull/8230)
* Add hk in translations in [8179](https://github.com/appwrite/appwrite/pull/8179)
* Update pwd abuse in [8255](https://github.com/appwrite/appwrite/pull/8255)
* Remove detailed trace in [8374](https://github.com/appwrite/appwrite/pull/8374)
* Remove relationship attributes from realtime event payloads in [8381](https://github.com/appwrite/appwrite/pull/8381)
* Sanitize URLs in emails in [8415](https://github.com/appwrite/appwrite/pull/8415)
* Bump console to version 4.3.27 in [8482](https://github.com/appwrite/appwrite/pull/8482)
### Fixes
* Ensure usage is counted for errors in [8120](https://github.com/appwrite/appwrite/pull/8120)
* Fix MFA for OAuth2 only accounts in [8245](https://github.com/appwrite/appwrite/pull/8245)
* Delete Expired Targets Per Project in [8239](https://github.com/appwrite/appwrite/pull/8239)
* Don't set the target field if the existing target document is false in [8236](https://github.com/appwrite/appwrite/pull/8236)
* Disable validation for project DBs during migration in [8298](https://github.com/appwrite/appwrite/pull/8298)
* Add `default` to Collection Attributes in Migration in [8271](https://github.com/appwrite/appwrite/pull/8271)
* Fix Create bucket endpoint validator for maximum file size in [8275](https://github.com/appwrite/appwrite/pull/8275)
* Disable validation for subquery to prevent error in [8297](https://github.com/appwrite/appwrite/pull/8297)
* Fix 'Missing required attribute "expire"' on `users.createSession()` in [8308](https://github.com/appwrite/appwrite/pull/8308)
* Fix certificate emails in [8292](https://github.com/appwrite/appwrite/pull/8292)
* Fix browser-cached deleted file in [8264](https://github.com/appwrite/appwrite/pull/8264)
* Fix migration of firebase users [8377](https://github.com/appwrite/appwrite/pull/8377)
* Fix `path` for vcs function deployments in [8408](https://github.com/appwrite/appwrite/pull/8408)
* Fix calculations in [8431](https://github.com/appwrite/appwrite/pull/8431)
* Fix bugs with migrations in [8442](https://github.com/appwrite/appwrite/pull/8442)
* Fix queueForUsage not triggering for domain executions in [8463](https://github.com/appwrite/appwrite/pull/8463)
* Fix realtime permission change in [8416](https://github.com/appwrite/appwrite/pull/8416)
### Miscellaneous
* Bump base image from 0.9.0 to 0.9.1 in [8238](https://github.com/appwrite/appwrite/pull/8238)
* Use latest Platform and add Core module in [7936](https://github.com/appwrite/appwrite/pull/7936)
* Add Test to Validate Headers aren't Overridden in [8228](https://github.com/appwrite/appwrite/pull/8228)
* Fix hyperlink in storage docs in [8269](https://github.com/appwrite/appwrite/pull/8269)
* Update cache & database in [8285](https://github.com/appwrite/appwrite/pull/8285)
* Fix flaky certificate test in [8316](https://github.com/appwrite/appwrite/pull/8316)
* Fix flaky function test in [8317](https://github.com/appwrite/appwrite/pull/8317)
* Update account API reference in [8305](https://github.com/appwrite/appwrite/pull/8305)
* Update functions API reference in [8346](https://github.com/appwrite/appwrite/pull/8346)
* Implement deploymentsStorage metric for projects API in [8258](https://github.com/appwrite/appwrite/pull/8258)
* Add new audit events in [8424](https://github.com/appwrite/appwrite/pull/8424)
* Move mbSeconds into 1.5.x in [8449](https://github.com/appwrite/appwrite/pull/8449)
* Clean projects cache while migrating in [8395](https://github.com/appwrite/appwrite/pull/8395)
* Use git tags for function template in [8445](https://github.com/appwrite/appwrite/pull/8445)
# Version 1.5.7
## What's Changed
-21
View File
@@ -319,13 +319,10 @@ These are the current metrics we collect usage stats for:
| users | Total number of users per project|
| executions | Total number of executions per project |
| databases | Total number of databases per project |
| databases.storage | Total amount of storage used by all databases per project (in bytes) |
| collections | Total number of collections per project |
| {databaseInternalId}.collections | Total number of collections per database|
| {databaseInternalId}.storage | Sum of database storage (in bytes) |
| documents | Total number of documents per project |
| {databaseInternalId}.{collectionInternalId}.documents | Total number of documents per collection |
| {databaseInternalId}.{collectionInternalId}.storage | Sum of database storage used by the collection (in bytes) |
| buckets | Total number of buckets per project |
| files | Total number of files per project |
| {bucketInternalId}.files.storage | Sum of files.storage per bucket (in bytes) |
@@ -500,18 +497,6 @@ If you are in PHP Storm you don't need any plugin. Below are the settings requir
2. If needed edit the **dev/xdebug.ini** file to your needs.
3. Launch your Appwrite instance while your debugger is listening for connections.
## Profiling
Appwrite uses XDebug [Profiler](https://xdebug.org/docs/profiler) for generating **CacheGrind** files. The generated file would be located in each of the `appwrite` containers inside the `/tmp/xdebug` folder.
To disable the profiler while debugging remove the `,profiler` mode from the `xdebug.ini` file
```diff
zend_extension=xdebug
[xdebug]
-xdebug.mode=develop,debug,profile
+xdebug.mode=develop,debug
```
### VS Code Launch Configuration
```json
@@ -556,12 +541,6 @@ To run end-2-end tests for a specific service use:
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
```
To run one specific test:
```bash
docker compose exec appwrite vendor/bin/phpunit --filter [FunctionName]
```
## 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.
+21 -6
View File
@@ -1,4 +1,4 @@
FROM composer:2.0 AS composer
FROM composer:2.0 as composer
ARG TESTING=false
ENV TESTING=$TESTING
@@ -12,7 +12,24 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.9.3 AS final
FROM --platform=$BUILDPLATFORM node:20.11.0-alpine3.19 as node
COPY app/console /usr/local/src/console
WORKDIR /usr/local/src/console
ARG VITE_GA_PROJECT
ARG VITE_CONSOLE_MODE
ARG VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
ENV VITE_GA_PROJECT=$VITE_GA_PROJECT
ENV VITE_CONSOLE_MODE=$VITE_CONSOLE_MODE
ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
RUN npm ci
RUN npm run build
FROM appwrite/base:0.9.1 as final
LABEL maintainer="team@appwrite.io"
@@ -28,11 +45,10 @@ RUN \
apk add boost boost-dev; \
fi
RUN apk add libwebp
WORKDIR /usr/src/code
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
COPY --from=node /usr/local/src/console/build /usr/src/code/console
# Add Source Code
COPY ./app /usr/src/code/app
@@ -93,10 +109,9 @@ RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
# Enable Extensions
RUN if [ "$DEBUG" == "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi
RUN if [ "$DEBUG" == "true" ]; then mkdir -p /tmp/xdebug; fi
RUN if [ "$DEBUG" = "false" ]; then rm -rf /usr/src/code/dev; fi
RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so; fi
EXPOSE 80
CMD [ "php", "app/http.php" ]
CMD [ "php", "app/http.php" ]
+3 -3
View File
@@ -67,7 +67,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.6.0
appwrite/appwrite:1.5.7
```
### Windows
@@ -79,7 +79,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.6.0
appwrite/appwrite:1.5.7
```
#### PowerShell
@@ -89,7 +89,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.6.0
appwrite/appwrite:1.5.7
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+4 -10
View File
@@ -1,4 +1,4 @@
> Appwrite Init has concluded! You can check out all the latest announcements [on our Init website](https://appwrite.io/init) 🚀
> Our Appwrite Init event has concluded. You can check out all the new and upcoming features [on our Init website](https://appwrite.io/init) 🚀
<br />
<p align="center">
@@ -75,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.6.0
appwrite/appwrite:1.5.7
```
### Windows
@@ -87,7 +87,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.6.0
appwrite/appwrite:1.5.7
```
#### PowerShell
@@ -97,7 +97,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.6.0
appwrite/appwrite:1.5.7
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
@@ -134,12 +134,6 @@ Choose from one of the providers below:
<br /><sub><b>Akamai Compute</b></sub></a>
</a>
</td>
<td align="center" width="100" height="100">
<a href="https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6">
<img width="50" height="39" src="public/images/integrations/aws-logo.svg" alt="AWS Logo" />
<br /><sub><b>AWS Marketplace</b></sub></a>
</a>
</td>
</tr>
</table>
Binary file not shown.
Binary file not shown.
+4 -13
View File
@@ -1,12 +1,12 @@
<?php
require_once __DIR__ . '/init.php';
require_once __DIR__ . '/controllers/general.php';
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Func;
use Appwrite\Platform\Appwrite;
use Appwrite\Runtimes\Runtimes;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\CLI;
@@ -23,12 +23,6 @@ use Utopia\Queue\Connection;
use Utopia\Registry\Registry;
use Utopia\System\System;
// overwriting runtimes to be architectur agnostic for CLI
Config::setParam('runtimes', (new Runtimes('v4'))->getAll(supported: false));
// require controllers after overwriting runtimes
require_once __DIR__ . '/controllers/general.php';
Authorization::disable();
CLI::setResource('register', fn () => $register);
@@ -191,18 +185,15 @@ CLI::setResource('logError', function (Registry $register) {
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
$responseCode = $logger->addLog($log);
Console::info('Usage stats log pushed with status code: ' . $responseCode);
}
Console::warning("Failed: {$error->getMessage()}");
+5 -104
View File
@@ -2406,17 +2406,6 @@ $projectCollections = array_merge([
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('originalId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'default' => null,
'array' => false,
],
],
'indexes' => [
[
@@ -3066,25 +3055,14 @@ $projectCollections = array_merge([
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('specification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_FUNCTION_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'$id' => ID::custom('scopes'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => [],
'required' => true,
'default' => null,
'array' => true,
'filters' => [],
],
@@ -3870,39 +3848,6 @@ $projectCollections = array_merge([
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('scheduledAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('scheduleInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('scheduleId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
@@ -4120,24 +4065,13 @@ $projectCollections = array_merge([
'$id' => ID::custom('source'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 8192, // reduce size
'size' => 8192,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('destination'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false, // make true after patch script
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('credentials'),
'type' => Database::VAR_STRING,
@@ -4160,28 +4094,6 @@ $projectCollections = array_merge([
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('statusCounters'),
'type' => Database::VAR_STRING,
@@ -4431,17 +4343,6 @@ $consoleCollections = array_merge([
'array' => false,
'filters' => [],
],
[
'$id' => 'accessedAt',
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('services'),
'type' => Database::VAR_STRING,
@@ -4732,7 +4633,7 @@ $consoleCollections = array_merge([
'$id' => ID::custom('type'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'size' => 16,
'signed' => true,
'required' => false,
'default' => null,
@@ -5827,7 +5728,7 @@ $bucketCollections = [
'$id' => ID::custom('metadata'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 75000, // https://tools.ietf.org/html/rfc4288#section-4.2
'size' => 16384, // https://tools.ietf.org/html/rfc4288#section-4.2
'signed' => true,
'required' => false,
'default' => null,
-10
View File
@@ -529,11 +529,6 @@ return [
'description' => 'Synchronous function execution timed out. Use asynchronous execution instead, or ensure the execution duration doesn\'t exceed 30 seconds.',
'code' => 408,
],
Exception::FUNCTION_TEMPLATE_NOT_FOUND => [
'name' => Exception::FUNCTION_TEMPLATE_NOT_FOUND,
'description' => 'Function Template with the requested ID could not be found.',
'code' => 404,
],
/** Builds */
Exception::BUILD_NOT_FOUND => [
@@ -699,11 +694,6 @@ return [
'description' => 'The relationship value is invalid.',
'code' => 400,
],
Exception::ATTRIBUTE_INVALID_RESIZE => [
'name' => Exception::ATTRIBUTE_INVALID_RESIZE,
'description' => "Existing data is too large for new size, truncate your existing data then try again.",
'code' => 400,
],
/** Indexes */
Exception::INDEX_NOT_FOUND => [
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -6,9 +6,7 @@ return [
'magicSession',
'recovery',
'invitation',
'mfaChallenge',
'sessionAlert',
'otpSession'
'mfaChallenge'
],
'sms' => [
'verification',
@@ -4,9 +4,7 @@
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
<tr>
<td align="center" style="border-radius: 8px; background-color: #ffffff;">
<p style="text-align: start; font-size: 14px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 32px; border: 1px solid #EDEDF0; display: inline-block; word-break: break-word;">
{{error}}
</p>
<p style="text-align: start; font-size: 14px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 32px; border: 1px solid #EDEDF0; display: inline-block; word-break: break-word;">{{error}}</p>
</td>
</tr>
</table>
@@ -11,4 +11,4 @@
<p>{{footer}}</p>
<p style="margin-bottom: 0px;">{{thanks}}</p>
<p style="margin-top: 0px;">{{signature}}</p>
<p style="margin-top: 0px;">{{signature}}</p>
-238
View File
@@ -1,238 +0,0 @@
{
"settings.inspire": "\"الفن ديال الحكمة هو الفن ديال أنك تعرف أش تنخّل.\"",
"settings.locale": "ar-ma",
"settings.direction": "rtl",
"emails.sender": "فرقة %s",
"emails.verification.subject": "التيْقان ديال الحساب",
"emails.verification.hello": "السلام {{user}}",
"emails.verification.body": "تبّع هاد الوصلة باش تيقّن لادريسة تاع ليميل ديالك.",
"emails.verification.footer": "إلا ماشي نتا اللي طلبتي تيقّن هاد لادريسة تاع ليميل، ممكن تنخّل هاد البرية.",
"emails.verification.thanks": "شكرا",
"emails.verification.signature": "فرقة {{project}}",
"emails.magicSession.subject": "تكونيكطا",
"emails.magicSession.hello": "السلام,",
"emails.magicSession.body": "تبّع هاد الوصلة باش تتكونيكطا.",
"emails.magicSession.footer": "إلا ماشي نتا اللي طلبتي تتكونيكطا بهاد ليميل، ممكن تنخّل هاد البرية.",
"emails.magicSession.thanks": "شكرا",
"emails.magicSession.signature": "فرقة {{project}}",
"emails.recovery.subject": "تبدال كلمة السر",
"emails.recovery.hello": "السلام {{user}}",
"emails.recovery.body": "تبّع هاد الوصلة باش تبدّل كلمة السر تاع {{project}}.",
"emails.recovery.footer": "إلا ماشي نتا اللي طلبتي تبدّل كلمة السر، ممكن تنخّل هاد البرية.",
"emails.recovery.thanks": "شكرا",
"emails.recovery.signature": "فرقة {{project}}",
"emails.invitation.subject": "عراضة ل فرقة %s ف %s",
"emails.invitation.hello": "السلام",
"emails.invitation.body": "هاد البرية تصيفطات ليك حيت {{owner}} بغى يعرض عليك تولّي عضو ف فرقة {{team}} عند {{project}}.",
"emails.invitation.footer": "إلا كنتي ما مسوّقش, ممكن تنخّل هاد البرية.",
"emails.invitation.thanks": "شكرا",
"emails.invitation.signature": "فرقة {{project}}",
"emails.certificate.subject": "السرتافيكة فشلات ل %s",
"emails.certificate.hello": "السلام",
"emails.certificate.body": "السرتافيكة ديال الضومين ديالك '{{domain}}' ما قدّاتش تجينيرا. هادي هي المحاولة نمرة {{attempt}}, السبب ديال هاد الفشل هو: {{error}}",
"emails.certificate.footer": "السرتافيكة الفايتة ديالك غاتبقى مزيانة لمدة 30 يوم من عند أول فشل. كانشجعوك بزاف أنك تبقشش فهاد الموضوع, وا إلّا الضومين ديالك ما غايبقاش خدّام فيه الـ SSL.",
"emails.certificate.thanks": "شكرا",
"emails.certificate.signature": "فرقة {{project}}",
"locale.country.unknown": "ما معروفش",
"countries.af": "أفغانستان",
"countries.ao": "أنڭولا",
"countries.al": "ألبانيا",
"countries.ad": "أندورا",
"countries.ae": "الإمارات العربية المتّاحدة",
"countries.ar": "الأرجنتين",
"countries.am": "أرمينيا",
"countries.ag": "أنتيڭوا وبربودا",
"countries.au": "ؤسطراليا",
"countries.at": "النامسا",
"countries.az": "أديربيجان",
"countries.bi": "بوروندي",
"countries.be": "بلجيكا",
"countries.bj": "بينين",
"countries.bf": "بوركينا فاصو",
"countries.bd": "بنڭلاديش",
"countries.bg": "بلڭاريا",
"countries.bh": "البحرين",
"countries.bs": "دزيرات البهاما",
"countries.ba": "البوسنة ؤ الهرسك",
"countries.by": "بيلاروسيا",
"countries.bz": "بيليز",
"countries.bo": "بوليڤيا",
"countries.br": "البرازيل",
"countries.bb": "باربادوس",
"countries.bn": "بروناي",
"countries.bt": "بوتان",
"countries.bw": "بوتسوانا",
"countries.cf": "جمهورية إفريقيا الوسطانية",
"countries.ca": "كانادا",
"countries.ch": "سويسرا",
"countries.cl": "تشيلي",
"countries.cn": "الشينوا",
"countries.ci": "ساحل العاج",
"countries.cm": "الكاميرون",
"countries.cd": "جمهورية الكونڭو الديمقراطية",
"countries.cg": "جمهورية الكونڭو",
"countries.co": "كولومبيا",
"countries.km": "دزيرات القومور",
"countries.cv": "الراس الخضر",
"countries.cr": "كوسطاريكا",
"countries.cu": "كوبا",
"countries.cy": "قوبروص",
"countries.cz": "التشيك",
"countries.de": "ألمانيا",
"countries.dj": "دجيبوتي",
"countries.dm": "ضومينيكا",
"countries.dk": "الدنمارك",
"countries.do": "جمهورية الضومينيكان",
"countries.dz": "الدزاير",
"countries.ec": "إكوادور",
"countries.eg": "مصر",
"countries.er": "إريتريا",
"countries.es": "سبانيا",
"countries.ee": "إسطونيا",
"countries.et": "إتيوپيا",
"countries.fi": "فينلاندا",
"countries.fj": "فيدجي",
"countries.fr": "فرانسا",
"countries.fm": "ميكرونيزيا",
"countries.ga": "الڭابون",
"countries.gb": "المملكة المتّاحدة",
"countries.ge": "تجورجيا",
"countries.gh": "غانا",
"countries.gn": "غينيا",
"countries.gm": "ڭامبيا",
"countries.gw": "غينيا بيساو",
"countries.gq": "غينيا الستوائية",
"countries.gr": "اليونان",
"countries.gd": "ڭرينادا",
"countries.gt": "ڭواتيمالا",
"countries.gy": "ڭيانا",
"countries.hn": "هوندوراس",
"countries.hr": "كرواتيا",
"countries.ht": "هايتي",
"countries.hu": "الماجر",
"countries.id": "إندونيسيا",
"countries.in": "الهند",
"countries.ie": "إرلاندا",
"countries.ir": "إران",
"countries.iq": "العراق",
"countries.is": "إسلاندا",
"countries.il": "إسرائيل",
"countries.it": "الطاليان",
"countries.jm": "جامايكا",
"countries.jo": "الأردن",
"countries.jp": "الجاپون",
"countries.kz": "كازاخستان",
"countries.ke": "كينيا",
"countries.kg": "قيرغيزستان",
"countries.kh": "كمبوديا",
"countries.ki": "كيريباتي",
"countries.kn": "سانت كيتس ؤ نيفيس",
"countries.kr": "كوريا الجنوبية",
"countries.kw": "الكويت",
"countries.la": "لاوس",
"countries.lb": "لبنان",
"countries.lr": "ليبيريا",
"countries.ly": "ليبيا",
"countries.lc": "سانت لوسيا",
"countries.li": "ليختنشتاين",
"countries.lk": "سري لانكا",
"countries.ls": "ليسوتو",
"countries.lt": "ليتوانيا",
"countries.lu": "لوكسمبورڭ",
"countries.lv": "لاتفيا",
"countries.ma": "المغريب",
"countries.mc": "موناكو",
"countries.md": "مولضوڤا",
"countries.mg": "ماداغشقار",
"countries.mv": "دزيرات المالديڤ",
"countries.mx": "الميكسيك",
"countries.mh": "دزيرات مارشال",
"countries.mk": "مقدونيا",
"countries.ml": "مالي",
"countries.mt": "مالطا",
"countries.mm": "ميانمار",
"countries.me": "مونطينيڭرو",
"countries.mn": "منغوليا",
"countries.mz": "الموزمبيق",
"countries.mr": "موريتانيا",
"countries.mu": "موريشيوس",
"countries.mw": "مالاوي",
"countries.my": "ماليزيا",
"countries.na": "ناميبيا",
"countries.ne": "النيجر",
"countries.ng": "نيجيريا",
"countries.ni": "نيكاراڭوا",
"countries.nl": "هولاندا",
"countries.no": "النرويج",
"countries.np": "نيپال",
"countries.nr": "ناورو",
"countries.nz": "نيوزيلاندا",
"countries.om": "عمّان",
"countries.pk": "پاكيستان",
"countries.pa": "پاناما",
"countries.pe": "الپيرو",
"countries.ph": "الفيليپين",
"countries.pw": "پالاو",
"countries.pg": "پاپوا غينيا الجديدة",
"countries.pl": "پولاندا",
"countries.kp": "كوريا الشمالية",
"countries.pt": "البرطقيز",
"countries.py": "الپاراڭواي",
"countries.qa": "قطر",
"countries.ro": "رومانيا",
"countries.ru": "روسيا",
"countries.rw": "روّاندا",
"countries.sa": "المملكة العربية السعودية",
"countries.sd": "السودان",
"countries.sn": "السينيڭال",
"countries.sg": "سنغافورة",
"countries.sb": "دزيرات سليمان",
"countries.sl": "صييراليون",
"countries.sv": "السالڤاضور",
"countries.sm": "سان مارينو",
"countries.so": "الصومال",
"countries.rs": "صيربيا",
"countries.ss": "جنوب السودان",
"countries.st": "صاو طومي ؤ پرينسيپي",
"countries.sr": "سورينام",
"countries.sk": "صلوڤاكيا",
"countries.si": "صلوڤينيا",
"countries.se": "السويد",
"countries.sz": "سوازيلاند",
"countries.sc": "السيشيل",
"countries.sy": "سوريا",
"countries.td": "تشاد",
"countries.tg": "الطوڭو",
"countries.th": "الطايلوند",
"countries.tj": "طادجيكيستان",
"countries.tm": "تركمانيستان",
"countries.tl": "تيمور الشرقية",
"countries.to": "تونڭا",
"countries.tt": "ترينيداد ؤ طوباڭو",
"countries.tn": "تونس",
"countries.tr": "توركيا",
"countries.tv": "توڤالو",
"countries.tz": "طنزانيا",
"countries.ug": "ؤڭاندا",
"countries.ua": "ؤكرانيا",
"countries.uy": "ؤروڭواي",
"countries.us": "ميريكان",
"countries.uz": "ؤزباكيستان",
"countries.va": "مدينة الڤاتيكان",
"countries.vc": "سانت ڤانسون ؤ دزيرات ڭرينادين",
"countries.ve": "ڤينيزويلا",
"countries.vn": "ڤيطنام",
"countries.vu": "ڤانواتو",
"countries.ws": "ساموا",
"countries.ye": "اليمن",
"countries.za": "جنوب إفريقيا",
"countries.zm": "زامبيا",
"countries.zw": "زيمبابوي",
"continents.af": "أفريقيا",
"continents.an": "القارة القطبية الجنوبية",
"continents.as": "أسيا",
"continents.eu": "ؤروپا",
"continents.na": "ميريكان الشمالية",
"continents.oc": "ؤقيانوسيا",
"continents.sa": "ميريكان الجنوبية"
}
+3 -9
View File
@@ -18,13 +18,13 @@
"emails.magicSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.",
"emails.magicSession.thanks": "Thanks,",
"emails.magicSession.signature": "{{project}} team",
"emails.sessionAlert.subject": "Security alert: new session on your {{project}} account",
"emails.sessionAlert.subject": "New session alert for {{project}}",
"emails.sessionAlert.hello":"Hello {{user}}",
"emails.sessionAlert.body": "A new session has been created on your {{b}}{{project}}{{/b}} account, {{b}}on {{date}}, {{year}} at {{time}} UTC{{/b}}.\nHere are the details of the new session: ",
"emails.sessionAlert.body": "We're writing to inform you that a new session has been initiated on your {{b}}{{project}}{{/b}} account, on {{b}}{{dateTime}}{{/b}}. \nHere are the details of the new session: ",
"emails.sessionAlert.listDevice": "Device: {{b}}{{device}}{{/b}}",
"emails.sessionAlert.listIpAddress": "IP Address: {{b}}{{ipAddress}}{{/b}}",
"emails.sessionAlert.listCountry": "Country: {{b}}{{country}}{{/b}}",
"emails.sessionAlert.footer": "If this was you, there's nothing more you need to do.\nIf you didn't initiate this session or suspect any unauthorized activity, please secure your account.",
"emails.sessionAlert.footer": "If you didn't request the sign in, you can safely ignore this email. If you suspect unauthorized activity, please secure your account immediately.",
"emails.sessionAlert.thanks": "Thanks,",
"emails.sessionAlert.signature": "{{project}} team",
"emails.otpSession.subject": "OTP for {{project}} Login",
@@ -52,12 +52,6 @@
"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 will 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",
"sms.verification.body": "{{secret}}",
"locale.country.unknown": "Unknown",
"countries.af": "Afghanistan",
+42 -20
View File
@@ -1,5 +1,9 @@
<?php
const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
return [
APP_PLATFORM_CLIENT => [
'key' => APP_PLATFORM_CLIENT,
@@ -11,7 +15,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '16.0.0',
'version' => '15.0.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -59,7 +63,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '13.0.0',
'version' => '12.0.4',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -77,7 +81,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '7.0.0',
'version' => '6.0.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -112,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '6.0.0',
'version' => '5.1.1',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -134,7 +138,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.5.0',
'version' => '0.4.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -199,7 +203,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '1.2.1',
'version' => '0.6.3',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,
@@ -210,14 +214,14 @@ return [
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
'gitBranch' => 'dev',
'gitBranch' => 'main',
'gitRepoName' => 'sdk-for-console',
'gitUserName' => 'appwrite',
],
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '6.0.0',
'version' => '5.0.5',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -245,7 +249,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '14.1.0',
'version' => '13.0.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -263,7 +267,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '12.1.0',
'version' => '11.0.0',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@@ -281,7 +285,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '12.0.0',
'version' => '11.0.2',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -299,7 +303,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '6.1.0',
'version' => '5.0.3',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -317,7 +321,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '12.1.0',
'version' => '11.0.2',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -335,10 +339,10 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => '0.2.0',
'version' => '4.0.1',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => 'https://github.com/appwrite/sdk-for-go',
'enabled' => true,
'package' => '',
'enabled' => false,
'beta' => true,
'dev' => false,
'hidden' => false,
@@ -350,10 +354,28 @@ return [
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
],
[
'key' => 'java',
'name' => 'Java',
'version' => '4.0.2',
'url' => 'https://github.com/appwrite/sdk-for-java',
'package' => '',
'enabled' => false,
'beta' => true,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'prism' => 'java',
'source' => \realpath(__DIR__ . '/../sdks/server-java'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-java.git',
'gitRepoName' => 'sdk-for-java',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
],
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.10.0',
'version' => '0.8.2',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -371,7 +393,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '12.1.0',
'version' => '11.0.3',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -389,7 +411,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '6.1.0',
'version' => '5.0.2',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -411,7 +433,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '6.1.0',
'version' => '5.0.2',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+2 -2
View File
@@ -17,6 +17,7 @@ $member = [
'files.read',
'files.write',
'projects.read',
'projects.write',
'locale.read',
'avatars.read',
'execution.read',
@@ -48,7 +49,6 @@ $admins = [
'collections.write',
'platforms.read',
'platforms.write',
'projects.write',
'keys.read',
'keys.write',
'webhooks.read',
@@ -75,7 +75,7 @@ $admins = [
'topics.write',
'topics.read',
'subscribers.write',
'subscribers.read',
'subscribers.read'
];
return [
-51
View File
@@ -1,51 +0,0 @@
<?php
use Appwrite\Functions\Specification;
return [
Specification::S_05VCPU_512MB => [
'slug' => Specification::S_05VCPU_512MB,
'memory' => 512,
'cpus' => 0.5
],
Specification::S_1VCPU_512MB => [
'slug' => Specification::S_1VCPU_512MB,
'memory' => 512,
'cpus' => 1
],
Specification::S_1VCPU_1GB => [
'slug' => Specification::S_1VCPU_1GB,
'memory' => 1024,
'cpus' => 1
],
Specification::S_2VCPU_2GB => [
'slug' => Specification::S_2VCPU_2GB,
'memory' => 2048,
'cpus' => 2
],
Specification::S_2VCPU_4GB => [
'slug' => Specification::S_2VCPU_4GB,
'memory' => 4096,
'cpus' => 2
],
Specification::S_4VCPU_4GB => [
'slug' => Specification::S_4VCPU_4GB,
'memory' => 4096,
'cpus' => 4
],
Specification::S_4VCPU_8GB => [
'slug' => Specification::S_4VCPU_8GB,
'memory' => 8192,
'cpus' => 4
],
Specification::S_8VCPU_4GB => [
'slug' => Specification::S_8VCPU_4GB,
'memory' => 4096,
'cpus' => 8
],
Specification::S_8VCPU_8GB => [
'slug' => Specification::S_8VCPU_8GB,
'memory' => 8192,
'cpus' => 8
]
];
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
@@ -6,8 +6,6 @@ return [
'image/gif',
'image/png',
'image/webp',
// 'image/heic',
'image/avif',
// Video Files
'video/mp4',
-3
View File
@@ -6,7 +6,4 @@ return [ // Accepted outputs files
'gif' => 'image/gif',
'png' => 'image/png',
'webp' => 'image/webp',
// 'heic' => 'image/heic',
// 'heics' => 'image/heic',
'avif' => 'image/avif'
];
+3 -21
View File
@@ -144,17 +144,8 @@ return [
],
[
'name' => '_APP_SYSTEM_EMAIL_ADDRESS',
'description' => 'This is the sender email address that will appear on email messages sent to developers from the Appwrite console. The default value is \'noreply@appwrite.io\'. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users\' SPAM folders.',
'description' => 'This is the sender email address that will appear on email messages sent to developers from the Appwrite console. The default value is \'team@appwrite.io\'. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users\' SPAM folders.',
'introduction' => '0.7.0',
'default' => 'noreply@appwrite.io',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_SYSTEM_TEAM_EMAIL',
'description' => 'This is the sender email address that will appear in the generated specs. The default value is \'team@appwrite.io\'.',
'introduction' => '1.6.0',
'default' => 'team@appwrite.io',
'required' => false,
'question' => '',
@@ -193,7 +184,7 @@ return [
'introduction' => '1.5.1',
'default' => '',
'required' => true,
'question' => 'Enter an email that will be used when registering for SSL certificates',
'question' => '',
'filter' => ''
],
[
@@ -720,22 +711,13 @@ return [
'variables' => [
[
'name' => '_APP_FUNCTIONS_SIZE_LIMIT',
'description' => 'The maximum size of a function in bytes. The default value is 30MB.',
'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_BUILD_SIZE_LIMIT',
'description' => 'The maximum size of a built deployment in bytes. The default value is 2,000,000,000 (2GB), and the maximum value is 4,294,967,295 (4.2GB).',
'introduction' => '1.6.0',
'default' => '2000000000',
'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. This is the global limit, timeout for individual functions are configured in the function\'s settings or in appwrite.json.',
Submodule
+1
Submodule app/console added at f978cecfaf
+46 -45
View File
@@ -55,8 +55,8 @@ use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
$oauthDefaultSuccess = '/console/auth/oauth2/success';
$oauthDefaultFailure = '/console/auth/oauth2/failure';
$oauthDefaultSuccess = '/auth/oauth2/success';
$oauthDefaultFailure = '/auth/oauth2/failure';
function sendSessionAlert(Locale $locale, Document $user, Document $project, Document $session, Mail $queueForMails)
{
@@ -124,9 +124,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
'date' => (new \DateTime())->format('F j'),
'year' => (new \DateTime())->format('YYYY'),
'time' => (new \DateTime())->format('H:i:s'),
'dateTime' => DateTime::format(new \DateTime(), 'Y-m-d H:i:s'),
'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'),
'device' => $session->getAttribute('clientName'),
@@ -206,6 +204,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->purgeCachedDocument('users', $user->getId());
Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
$dbForProject->purgeCachedDocument('users', $user->getId());
@@ -224,21 +223,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
}
$isAllowedTokenType = match ($verifiedToken->getAttribute('type')) {
Auth::TOKEN_TYPE_MAGIC_URL,
Auth::TOKEN_TYPE_EMAIL => false,
default => true
};
$hasUserEmail = $user->getAttribute('email', false) !== false;
$isSessionAlertsEnabled = $project->getAttribute('auths', [])['sessionAlerts'] ?? false;
$isNotFirstSession = $dbForProject->count('sessions', [
Query::equal('userId', [$user->getId()]),
]) !== 1;
if ($isAllowedTokenType && $hasUserEmail && $isSessionAlertsEnabled && $isNotFirstSession) {
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
}
@@ -394,7 +379,7 @@ App::post('/v1/account')
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
if ($existingTarget) {
if($existingTarget) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
}
}
@@ -919,11 +904,7 @@ App::post('/v1/account/sessions/email')
;
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
if ($dbForProject->count('sessions', [
Query::equal('userId', [$user->getId()]),
]) !== 1) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
}
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
}
$response->dynamic($session, Response::MODEL_SESSION);
@@ -1786,10 +1767,10 @@ App::post('/v1/account/tokens/magic-url')
->inject('queueForEvents')
->inject('queueForMails')
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
$url = htmlentities($url);
if ($phrase === true) {
$phrase = Phrase::generate();
@@ -1879,7 +1860,7 @@ App::post('/v1/account/tokens/magic-url')
$dbForProject->purgeCachedDocument('users', $user->getId());
if (empty($url)) {
$url = $request->getProtocol() . '://' . $request->getHostname() . '/console/auth/magic-url';
$url = $request->getProtocol() . '://' . $request->getHostname() . '/auth/magic-url';
}
$url = Template::parseURL($url);
@@ -2986,7 +2967,6 @@ App::post('/v1/account/recovery')
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
$url = htmlentities($url);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
@@ -3250,7 +3230,6 @@ App::post('/v1/account/verification')
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
$url = htmlentities($url);
if ($user->getAttribute('emailVerification')) {
throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED);
}
@@ -3572,7 +3551,7 @@ App::post('/v1/account/verification/phone')
});
App::put('/v1/account/verification/phone')
->desc('Update phone verification (confirmation)')
->desc('Create phone verification (confirmation)')
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'users.[userId].verification.[tokenId].update')
@@ -3683,7 +3662,7 @@ App::patch('/v1/account/mfa')
});
App::get('/v1/account/mfa/factors')
->desc('List factors')
->desc('List Factors')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@@ -3715,7 +3694,7 @@ App::get('/v1/account/mfa/factors')
});
App::post('/v1/account/mfa/authenticators/:type')
->desc('Create authenticator')
->desc('Add Authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@@ -3787,7 +3766,7 @@ App::post('/v1/account/mfa/authenticators/:type')
});
App::put('/v1/account/mfa/authenticators/:type')
->desc('Verify authenticator')
->desc('Verify Authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@@ -3852,7 +3831,7 @@ App::put('/v1/account/mfa/authenticators/:type')
});
App::post('/v1/account/mfa/recovery-codes')
->desc('Create MFA recovery codes')
->desc('Create MFA Recovery Codes')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@@ -3894,7 +3873,7 @@ App::post('/v1/account/mfa/recovery-codes')
});
App::patch('/v1/account/mfa/recovery-codes')
->desc('Regenerate MFA recovery codes')
->desc('Regenerate MFA Recovery Codes')
->groups(['api', 'account', 'mfaProtected'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@@ -3935,7 +3914,7 @@ App::patch('/v1/account/mfa/recovery-codes')
});
App::get('/v1/account/mfa/recovery-codes')
->desc('Get MFA recovery codes')
->desc('Get MFA Recovery Codes')
->groups(['api', 'account', 'mfaProtected'])
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@@ -3965,8 +3944,8 @@ App::get('/v1/account/mfa/recovery-codes')
});
App::delete('/v1/account/mfa/authenticators/:type')
->desc('Delete authenticator')
->groups(['api', 'account', 'mfaProtected'])
->desc('Delete Authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].delete.mfa')
->label('scope', 'account')
->label('audits.event', 'user.update')
@@ -3979,11 +3958,12 @@ App::delete('/v1/account/mfa/authenticators/:type')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
->param('otp', '', new Text(256), 'Valid verification token.')
->inject('response')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $type, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
->action(function (string $type, string $otp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
$authenticator = (match ($type) {
Type::TOTP => TOTP::getAuthenticatorFromUser($user),
@@ -3994,6 +3974,27 @@ App::delete('/v1/account/mfa/authenticators/:type')
throw new Exception(Exception::USER_AUTHENTICATOR_NOT_FOUND);
}
$success = (match ($type) {
Type::TOTP => Challenge\TOTP::verify($user, $otp),
default => false
});
if (!$success) {
$mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []);
if (in_array($otp, $mfaRecoveryCodes)) {
$mfaRecoveryCodes = array_diff($mfaRecoveryCodes, [$otp]);
$mfaRecoveryCodes = array_values($mfaRecoveryCodes);
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
$dbForProject->updateDocument('users', $user->getId(), $user);
$success = true;
}
}
if (!$success) {
throw new Exception(Exception::USER_INVALID_TOKEN);
}
$dbForProject->deleteDocument('authenticators', $authenticator->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
@@ -4003,7 +4004,7 @@ App::delete('/v1/account/mfa/authenticators/:type')
});
App::post('/v1/account/mfa/challenge')
->desc('Create MFA challenge')
->desc('Create 2FA Challenge')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('event', 'users.[userId].challenges.[challengeId].create')
@@ -4018,7 +4019,7 @@ App::post('/v1/account/mfa/challenge')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MFA_CHALLENGE)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{userId}')
->label('abuse-key', 'url:{url},token:{param-token}')
->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.')
->inject('response')
->inject('dbForProject')
@@ -4191,7 +4192,7 @@ App::post('/v1/account/mfa/challenge')
});
App::put('/v1/account/mfa/challenge')
->desc('Create MFA challenge (confirmation)')
->desc('Create MFA Challenge (confirmation)')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('event', 'users.[userId].sessions.[sessionId].create')
@@ -4205,7 +4206,7 @@ App::put('/v1/account/mfa/challenge')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_SESSION)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},challengeId:{param-challengeId}')
->label('abuse-key', 'userId:{param-userId}')
->param('challengeId', '', new Text(256), 'ID of the challenge.')
->param('otp', '', new Text(256), 'Valid verification token.')
->inject('project')
@@ -4452,7 +4453,7 @@ App::delete('/v1/account/targets/:targetId/push')
$response->noContent();
});
App::get('/v1/account/identities')
->desc('List identities')
->desc('List Identities')
->groups(['api', 'account'])
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
+3 -3
View File
@@ -550,7 +550,7 @@ App::get('/v1/avatars/initials')
});
App::get('/v1/cards/cloud')
->desc('Get front Of Cloud Card')
->desc('Get Front Of Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
@@ -757,7 +757,7 @@ App::get('/v1/cards/cloud')
});
App::get('/v1/cards/cloud-back')
->desc('Get back Of Cloud Card')
->desc('Get Back Of Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
@@ -835,7 +835,7 @@ App::get('/v1/cards/cloud-back')
});
App::get('/v1/cards/cloud-og')
->desc('Get OG image From Cloud Card')
->desc('Get OG Image From Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
+1 -1
View File
@@ -57,7 +57,7 @@ App::get('/v1/console/variables')
});
App::post('/v1/console/assistant')
->desc('Ask query')
->desc('Ask Query')
->groups(['api', 'assistant'])
->label('scope', 'assistant.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
+42 -156
View File
@@ -5,7 +5,6 @@ use Appwrite\Detector\Detector;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Utopia\Database\Validator\CustomId;
@@ -21,15 +20,12 @@ use Utopia\Audit\Audit;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception as DatabaseException;
use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Conflict as ConflictException;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Limit as LimitException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Exception\Restricted as RestrictedException;
use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Exception\Truncate as TruncateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@@ -232,15 +228,13 @@ function updateAttribute(
Database $dbForProject,
Event $queueForEvents,
string $type,
int $size = null,
string $filter = null,
string|bool|int|float $default = null,
bool $required = null,
int|float $min = null,
int|float $max = null,
array $elements = null,
array $options = [],
string $newKey = null,
array $options = []
): Document {
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
@@ -286,10 +280,6 @@ function updateAttribute(
->setAttribute('default', $default)
->setAttribute('required', $required);
if (!empty($size)) {
$attribute->setAttribute('size', $size);
}
$formatOptions = $attribute->getAttribute('formatOptions');
switch ($attribute->getAttribute('format')) {
@@ -355,7 +345,6 @@ function updateAttribute(
$dbForProject->updateRelationship(
collection: $collectionId,
id: $key,
newKey: $newKey,
onDelete: $primaryDocumentOptions['onDelete'],
);
@@ -363,52 +352,22 @@ function updateAttribute(
$relatedCollection = $dbForProject->getDocument('database_' . $db->getInternalId(), $primaryDocumentOptions['relatedCollection']);
$relatedAttribute = $dbForProject->getDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $primaryDocumentOptions['twoWayKey']);
if (!empty($newKey) && $newKey !== $key) {
$options['twoWayKey'] = $newKey;
}
$relatedOptions = \array_merge($relatedAttribute->getAttribute('options'), $options);
$relatedAttribute->setAttribute('options', $relatedOptions);
$dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $primaryDocumentOptions['twoWayKey'], $relatedAttribute);
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
}
} else {
try {
$dbForProject->updateAttribute(
collection: $collectionId,
id: $key,
size: $size,
required: $required,
default: $default,
formatOptions: $options ?? null,
newKey: $newKey ?? null
);
} catch (TruncateException) {
throw new Exception(Exception::ATTRIBUTE_INVALID_RESIZE);
}
}
if (!empty($newKey) && $key !== $newKey) {
// Delete attribute and recreate since we can't modify IDs
$original = clone $attribute;
$dbForProject->deleteDocument('attributes', $attribute->getId());
$attribute
->setAttribute('$id', ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $newKey))
->setAttribute('key', $newKey);
try {
$attribute = $dbForProject->createDocument('attributes', $attribute);
} catch (DatabaseException|PDOException) {
$attribute = $dbForProject->createDocument('attributes', $original);
}
} else {
$attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute);
$dbForProject->updateAttribute(
collection: $collectionId,
id: $key,
required: $required,
default: $default,
formatOptions: $options ?? null
);
}
$attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute);
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collection->getId());
$queueForEvents
@@ -453,8 +412,7 @@ App::post('/v1/databases')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('queueForUsage')
->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $queueForEvents, Usage $queueForUsage) {
->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $queueForEvents) {
$databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId;
@@ -504,7 +462,6 @@ App::post('/v1/databases')
}
$queueForEvents->setParam('databaseId', $database->getId());
$queueForUsage->addMetric(str_replace(['{databaseInternalId}'], [$database->getInternalId()], METRIC_DATABASE_ID_STORAGE), 1); // per database
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -736,8 +693,7 @@ App::delete('/v1/databases/:databaseId')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->inject('queueForUsage')
->action(function (string $databaseId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Usage $queueForUsage) {
->action(function (string $databaseId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
$database = $dbForProject->getDocument('databases', $databaseId);
@@ -760,9 +716,6 @@ App::delete('/v1/databases/:databaseId')
->setParam('databaseId', $database->getId())
->setPayload($response->output($database, Response::MODEL_DATABASE));
$queueForUsage
->addMetric(METRIC_DATABASES_STORAGE, 1); // Global, deletion forces full recalculation
$response->noContent();
});
@@ -1199,7 +1152,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
'filters' => $filters,
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
@@ -1907,12 +1859,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('size', null, new Integer(), 'Maximum size of the string attribute.', true)
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?int $size, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
@@ -1921,10 +1871,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_STRING,
size: $size,
default: $default,
required: $required,
newKey: $newKey
required: $required
);
$response
@@ -1950,11 +1898,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Email()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
@@ -1964,8 +1911,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email
type: Database::VAR_STRING,
filter: APP_DATABASE_ATTRIBUTE_EMAIL,
default: $default,
required: $required,
newKey: $newKey
required: $required
);
$response
@@ -1992,11 +1938,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/
->param('elements', null, new ArrayList(new Text(DATABASE::LENGTH_KEY), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . DATABASE::LENGTH_KEY . ' characters long.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Text(0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
@@ -2007,8 +1952,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/
filter: APP_DATABASE_ATTRIBUTE_ENUM,
default: $default,
required: $required,
elements: $elements,
newKey: $newKey
elements: $elements
);
$response
@@ -2034,11 +1978,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:k
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new IP()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
@@ -2048,8 +1991,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:k
type: Database::VAR_STRING,
filter: APP_DATABASE_ATTRIBUTE_IP,
default: $default,
required: $required,
newKey: $newKey
required: $required
);
$response
@@ -2075,11 +2017,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new URL()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
@@ -2089,8 +2030,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:
type: Database::VAR_STRING,
filter: APP_DATABASE_ATTRIBUTE_URL,
default: $default,
required: $required,
newKey: $newKey
required: $required
);
$response
@@ -2118,11 +2058,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
->param('min', null, new Integer(), 'Minimum value to enforce on new documents')
->param('max', null, new Integer(), 'Maximum value to enforce on new documents')
->param('default', null, new Nullable(new Integer()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
@@ -2133,8 +2072,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
default: $default,
required: $required,
min: $min,
max: $max,
newKey: $newKey
max: $max
);
$formatOptions = $attribute->getAttribute('formatOptions', []);
@@ -2169,11 +2107,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float
->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents')
->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents')
->param('default', null, new Nullable(new FloatValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
@@ -2184,8 +2121,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float
default: $default,
required: $required,
min: $min,
max: $max,
newKey: $newKey
max: $max
);
$formatOptions = $attribute->getAttribute('formatOptions', []);
@@ -2218,11 +2154,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Boolean()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
@@ -2231,8 +2166,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole
queueForEvents: $queueForEvents,
type: Database::VAR_BOOLEAN,
default: $default,
required: $required,
newKey: $newKey
required: $required
);
$response
@@ -2258,11 +2192,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new DatetimeValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
@@ -2271,8 +2204,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet
queueForEvents: $queueForEvents,
type: Database::VAR_DATETIME,
default: $default,
required: $required,
newKey: $newKey
required: $required
);
$response
@@ -2297,7 +2229,6 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('onDelete', null, new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL], true), 'Constraints option', true)
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -2306,7 +2237,6 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
string $collectionId,
string $key,
?string $onDelete,
?string $newKey,
Response $response,
Database $dbForProject,
Event $queueForEvents
@@ -2321,8 +2251,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
required: false,
options: [
'onDelete' => $onDelete
],
newKey: $newKey
]
);
$options = $attribute->getAttribute('options', []);
@@ -2357,8 +2286,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->inject('queueForUsage')
->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Usage $queueForUsage) {
->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
@@ -2443,9 +2371,6 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
->setContext('database', $db)
->setPayload($response->output($attribute, $model));
$queueForUsage
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$db->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
$response->noContent();
});
@@ -2509,7 +2434,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
'required' => true,
'array' => false,
'default' => null,
'size' => Database::LENGTH_KEY
'size' => 36
];
$oldAttributes[] = [
@@ -2821,9 +2746,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('mode')
->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode) {
->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode) {
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
@@ -3019,29 +2943,17 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$processDocument($collection, $document);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->getPayload(), sensitive: $relationships);
;
$queueForUsage
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
});
App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
@@ -3612,23 +3524,15 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$processDocument($collection, $document);
$response->dynamic($document, Response::MODEL_DOCUMENT);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->getPayload(), sensitive: $relationships);
;
$response->dynamic($document, Response::MODEL_DOCUMENT);
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
@@ -3658,9 +3562,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->inject('dbForProject')
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('mode')
->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Usage $queueForUsage, string $mode) {
->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, string $mode) {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -3725,14 +3628,6 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$processDocument($collection, $document);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
$queueForDeletes
->setType(DELETE_TYPE_AUDIT)
->setDocument($document);
@@ -3743,10 +3638,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships);
$queueForUsage
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
->setPayload($response->output($document, Response::MODEL_DOCUMENT));
$response->noContent();
});
@@ -3773,7 +3665,6 @@ App::get('/v1/databases/usage')
METRIC_DATABASES,
METRIC_COLLECTIONS,
METRIC_DOCUMENTS,
METRIC_DATABASES_STORAGE
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
@@ -3824,11 +3715,9 @@ App::get('/v1/databases/usage')
'databasesTotal' => $usage[$metrics[0]]['total'],
'collectionsTotal' => $usage[$metrics[1]]['total'],
'documentsTotal' => $usage[$metrics[2]]['total'],
'storageTotal' => $usage[$metrics[3]]['total'],
'databases' => $usage[$metrics[0]]['data'],
'collections' => $usage[$metrics[1]]['data'],
'documents' => $usage[$metrics[2]]['data'],
'storage' => $usage[$metrics[3]]['data'],
]), Response::MODEL_USAGE_DATABASES);
});
@@ -3860,7 +3749,6 @@ App::get('/v1/databases/:databaseId/usage')
$metrics = [
str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_COLLECTIONS),
str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_DOCUMENTS),
str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE)
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
@@ -3911,10 +3799,8 @@ App::get('/v1/databases/:databaseId/usage')
'range' => $range,
'collectionsTotal' => $usage[$metrics[0]]['total'],
'documentsTotal' => $usage[$metrics[1]]['total'],
'storageTotal' => $usage[$metrics[2]]['total'],
'collections' => $usage[$metrics[0]]['data'],
'documents' => $usage[$metrics[1]]['data'],
'storage' => $usage[$metrics[2]]['data'],
]), Response::MODEL_USAGE_DATABASE);
});
+42 -298
View File
@@ -10,9 +10,6 @@ use Appwrite\Event\Usage;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\Validator\Headers;
use Appwrite\Functions\Validator\Payload;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Tasks\ScheduleExecutions;
use Appwrite\Task\Validator\Cron;
@@ -47,11 +44,9 @@ use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\AnyOf;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -167,13 +162,7 @@ App::post('/v1/functions')
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true)
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the function template.', true)
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])
->param('templateBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function template.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -183,7 +172,7 @@ App::post('/v1/functions')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
@@ -198,12 +187,12 @@ App::post('/v1/functions')
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateVersion)
&& !empty($templateBranch)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('version', $templateVersion);
->setAttribute('branch', $templateBranch);
}
$installation = $dbForConsole->getDocument('installations', $installationId);
@@ -244,7 +233,6 @@ App::post('/v1/functions')
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'specification' => $specification
]));
$schedule = Authorization::skip(
@@ -293,34 +281,9 @@ App::post('/v1/functions')
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
// Redeploy vcs logic
if (!empty($providerRepositoryId)) {
// Deploy VCS
$redeployVcs($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
} elseif (!$template->isEmpty()) {
// Deploy non-VCS from template
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint', ''),
'commands' => $function->getAttribute('commands', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint', '')]),
'activate' => true,
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
}
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
@@ -468,13 +431,13 @@ App::get('/v1/functions/runtimes')
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
$allowed = [];
foreach ($runtimes as $id => $runtime) {
if (!empty($allowList) && !\in_array($id, $allowList)) {
foreach ($runtimes as $key => $runtime) {
if (!empty($allowList) && !\in_array($key, $allowList)) {
continue;
}
$runtime['$id'] = $id;
$allowed[] = $runtime;
$runtimes[$key]['$id'] = $key;
$allowed[] = $runtimes[$key];
}
$response->dynamic(new Document([
@@ -483,42 +446,6 @@ App::get('/v1/functions/runtimes')
]), Response::MODEL_RUNTIME_LIST);
});
App::get('/v1/functions/specifications')
->groups(['api', 'functions'])
->desc('List available function runtime specifications')
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listSpecifications')
->label('sdk.description', '/docs/references/functions/list-specifications.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SPECIFICATION_LIST)
->inject('response')
->inject('plan')
->action(function (Response $response, array $plan) {
$allRuntimeSpecs = Config::getParam('runtime-specifications', []);
$runtimeSpecs = [];
foreach ($allRuntimeSpecs as $spec) {
$spec['enabled'] = true;
if (array_key_exists('runtimeSpecifications', $plan)) {
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
}
// Only add specs that are within the limits set by environment variables
if ($spec['cpus'] <= System::getEnv('_APP_FUNCTIONS_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_FUNCTIONS_MEMORY', 512)) {
$runtimeSpecs[] = $spec;
}
}
$response->dynamic(new Document([
'specifications' => $runtimeSpecs,
'total' => count($runtimeSpecs)
]), Response::MODEL_SPECIFICATION_LIST);
});
App::get('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Get function')
@@ -576,8 +503,6 @@ App::get('/v1/functions/:functionId/usage')
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS)
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
@@ -640,10 +565,6 @@ App::get('/v1/functions/:functionId/usage')
'buildsTime' => $usage[$metrics[4]]['data'],
'executions' => $usage[$metrics[5]]['data'],
'executionsTime' => $usage[$metrics[6]]['data'],
'buildsMbSecondsTotal' => $usage[$metrics[7]]['total'],
'buildsMbSeconds' => $usage[$metrics[7]]['data'],
'executionsMbSeconds' => $usage[$metrics[8]]['data'],
'executionsMbSecondsTotal' => $usage[$metrics[8]]['total']
]), Response::MODEL_USAGE_FUNCTION);
});
@@ -674,8 +595,6 @@ App::get('/v1/functions/usage')
METRIC_BUILDS_COMPUTE,
METRIC_EXECUTIONS,
METRIC_EXECUTIONS_COMPUTE,
METRIC_BUILDS_MB_SECONDS,
METRIC_EXECUTIONS_MB_SECONDS,
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
@@ -739,10 +658,6 @@ App::get('/v1/functions/usage')
'buildsTime' => $usage[$metrics[5]]['data'],
'executions' => $usage[$metrics[6]]['data'],
'executionsTime' => $usage[$metrics[7]]['data'],
'buildsMbSecondsTotal' => $usage[$metrics[8]]['total'],
'buildsMbSeconds' => $usage[$metrics[8]]['data'],
'executionsMbSeconds' => $usage[$metrics[9]]['data'],
'executionsMbSecondsTotal' => $usage[$metrics[9]]['total'],
]), Response::MODEL_USAGE_FUNCTIONS);
});
@@ -773,16 +688,10 @@ App::put('/v1/functions/:functionId')
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Controle System) deployment.', true)
->param('providerRepositoryId', null, new Nullable(new Text(128, 0)), 'Repository ID of the repo linked to the function', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -791,8 +700,9 @@ App::put('/v1/functions/:functionId')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@@ -828,8 +738,8 @@ App::put('/v1/functions/:functionId')
$isConnected = !empty($function->getAttribute('providerRepositoryId', ''));
// Git disconnect logic. Disconnecting only when providerRepositoryId is empty, allowing for continue updates without disconnecting git
if ($isConnected && ($providerRepositoryId !== null && empty($providerRepositoryId))) {
// Git disconnect logic
if ($isConnected && empty($providerRepositoryId)) {
$repositories = $dbForConsole->find('repositories', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('resourceInternalId', [$function->getInternalId()]),
@@ -890,21 +800,6 @@ App::put('/v1/functions/:functionId')
$live = false;
}
$spec = Config::getParam('runtime-specifications')[$specification] ?? [];
// Enforce Cold Start if spec limits change.
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
try {
$executor->deleteRuntime($project->getId(), $function->getAttribute('deployment'));
} catch (\Throwable $th) {
// Don't throw if the deployment doesn't exist
if ($th->getCode() !== 404) {
throw $th;
}
}
}
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
'execute' => $execute,
'name' => $name,
@@ -926,7 +821,6 @@ App::put('/v1/functions/:functionId')
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'specification' => $specification,
'search' => implode(' ', [$functionId, $name, $runtime]),
])));
@@ -950,12 +844,12 @@ App::put('/v1/functions/:functionId')
App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->groups(['api', 'functions'])
->desc('Download deployment')
->desc('Download Deployment')
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getDeploymentDownload')
->label('sdk.description', '/docs/references/functions/get-deployment-download.md')
->label('sdk.method', 'downloadDeployment')
->label('sdk.description', '/docs/references/functions/download-deployment.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
@@ -1035,7 +929,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
App::patch('/v1/functions/:functionId/deployments/:deploymentId')
->groups(['api', 'functions'])
->desc('Update deployment')
->desc('Update function deployment')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
@@ -1152,7 +1046,6 @@ App::post('/v1/functions/:functionId/deployments')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createDeployment')
->label('sdk.methodType', 'upload')
->label('sdk.description', '/docs/references/functions/create-deployment.md')
->label('sdk.packaging', true)
->label('sdk.request.type', 'multipart/form-data')
@@ -1172,9 +1065,9 @@ App::post('/v1/functions/:functionId/deployments')
->inject('deviceForFunctions')
->inject('deviceForLocal')
->inject('queueForBuilds')
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, mixed $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds) {
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, bool $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds) {
$activate = \strval($activate) === 'true' || \strval($activate) === '1';
$activate = filter_var($activate, FILTER_VALIDATE_BOOLEAN);
$function = $dbForProject->getDocument('functions', $functionId);
@@ -1272,6 +1165,7 @@ App::post('/v1/functions/:functionId/deployments')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
}
$activate = (bool) filter_var($activate, FILTER_VALIDATE_BOOLEAN);
$type = $request->getHeader('x-sdk-language') === 'cli' ? 'cli' : 'manual';
if ($chunksUploaded === $chunks) {
@@ -1428,8 +1322,7 @@ App::get('/v1/functions/:functionId/deployments')
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
$result->setAttribute('buildSize', $build->getAttribute('size', 0));
$result->setAttribute('size', $result->getAttribute('size', 0));
$result->setAttribute('size', $result->getAttribute('size', 0) + $build->getAttribute('size', 0));
}
$response->dynamic(new Document([
@@ -1475,8 +1368,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId')
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
$deployment->setAttribute('buildSize', $build->getAttribute('size', 0));
$deployment->setAttribute('size', $deployment->getAttribute('size', 0));
$deployment->setAttribute('size', $deployment->getAttribute('size', 0) + $build->getAttribute('size', 0));
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
});
@@ -1567,8 +1459,7 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions) {
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@@ -1580,23 +1471,13 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$path = $deployment->getAttribute('path');
if (empty($path) || !$deviceForFunctions->exists($path)) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$deploymentId = ID::unique();
$destination = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$deviceForFunctions->transfer($path, $destination, $deviceForFunctions);
$deployment->removeAttribute('$internalId');
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
'$internalId' => '',
'$id' => $deploymentId,
'buildId' => '',
'buildInternalId' => '',
'path' => $destination,
'entrypoint' => $function->getAttribute('entrypoint'),
'commands' => $function->getAttribute('commands', ''),
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
@@ -1684,17 +1565,8 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
]));
}
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
try {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
$executor->deleteRuntime($project->getId(), $deploymentId . "-build");
} catch (\Throwable $th) {
// Don't throw if the deployment doesn't exist
if ($th->getCode() !== 404) {
throw $th;
}
}
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
$deleteBuild = $executor->deleteRuntime($project->getId(), $deploymentId . "-build");
$queueForEvents
->setParam('functionId', $function->getId())
@@ -1713,18 +1585,16 @@ App::post('/v1/functions/:functionId/executions')
->label('sdk.method', 'createExecution')
->label('sdk.description', '/docs/references/functions/create-execution.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_MULTIPART)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION)
->label('sdk.request.type', Response::CONTENT_TYPE_JSON)
->param('functionId', '', new UID(), 'Function ID.')
->param('body', '', new Payload(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('async', false, new Boolean(true), 'Execute code in the background. Default value is false.', true)
->param('body', '', new Text(0, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true)
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
->param('scheduledAt', null, new DatetimeValidator(true, DateTimeValidator::PRECISION_MINUTES, 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
->param('headers', [], new Assoc(), 'HTTP headers of execution. Defaults to empty.', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('response')
->inject('request')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
@@ -1733,36 +1603,12 @@ App::post('/v1/functions/:functionId/executions')
->inject('queueForUsage')
->inject('queueForFunctions')
->inject('geodb')
->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) {
$async = \strval($async) === 'true' || \strval($async) === '1';
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, ?string $scheduledAt, Response $response, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) {
if (!$async && !is_null($scheduledAt)) {
if(!$async && !is_null($scheduledAt)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Scheduled executions must run asynchronously. Set scheduledAt to a future date, or set async to true.');
}
/**
* @var array<string, mixed> $headers
*/
$assocParams = ['headers'];
foreach ($assocParams as $assocParam) {
if (!empty('headers') && !is_array($$assocParam)) {
$$assocParam = \json_decode($$assocParam, true);
}
}
$booleanParams = ['async'];
foreach ($booleanParams as $booleamParam) {
if (!empty($$booleamParam) && !is_bool($$booleamParam)) {
$$booleamParam = $$booleamParam === "true" ? true : false;
}
}
// 'headers' validator
$validator = new Headers();
if (!$validator->isValid($headers)) {
throw new Exception($validator->getDescription(), 400);
}
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -1774,7 +1620,6 @@ App::post('/v1/functions/:functionId/executions')
$version = $function->getAttribute('version', 'v2');
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)];
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
@@ -1869,7 +1714,7 @@ App::post('/v1/functions/:functionId/executions')
$status = $async ? 'waiting' : 'processing';
if (!is_null($scheduledAt)) {
if(!is_null($scheduledAt)) {
$status = 'scheduled';
}
@@ -1899,8 +1744,9 @@ App::post('/v1/functions/:functionId/executions')
->setContext('function', $function);
if ($async) {
if (is_null($scheduledAt)) {
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
if(is_null($scheduledAt)) {
$queueForFunctions
->setType('http')
->setExecution($execution)
@@ -1921,10 +1767,10 @@ App::post('/v1/functions/:functionId/executions')
'path' => $path,
'method' => $method,
'body' => $body,
'userId' => $user->getId()
'jwt' => $jwt,
];
$schedule = $dbForConsole->createDocument('schedules', new Document([
$dbForConsole->createDocument('schedules', new Document([
'region' => System::getEnv('_APP_REGION', 'default'),
'resourceType' => ScheduleExecutions::getSupportedResource(),
'resourceId' => $execution->getId(),
@@ -1935,13 +1781,6 @@ App::post('/v1/functions/:functionId/executions')
'data' => $data,
'active' => true,
]));
$execution = $execution
->setAttribute('scheduleId', $schedule->getId())
->setAttribute('scheduleInternalId', $schedule->getInternalId())
->setAttribute('scheduledAt', $scheduledAt);
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
return $response
@@ -1986,23 +1825,7 @@ App::post('/v1/functions/:functionId/executions')
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_FUNCTION_CPUS' => $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
'APPWRITE_FUNCTION_MEMORY' => $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
'APPWRITE_VERSION' => APP_VERSION_STABLE,
'APPWRITE_REGION' => $project->getAttribute('region'),
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
'APPWRITE_VCS_REPOSITORY_ID' => $deployment->getAttribute('providerRepositoryId', ''),
'APPWRITE_VCS_REPOSITORY_NAME' => $deployment->getAttribute('providerRepositoryName', ''),
'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment->getAttribute('providerRepositoryOwner', ''),
'APPWRITE_VCS_REPOSITORY_URL' => $deployment->getAttribute('providerRepositoryUrl', ''),
'APPWRITE_VCS_REPOSITORY_BRANCH' => $deployment->getAttribute('providerBranch', ''),
'APPWRITE_VCS_REPOSITORY_BRANCH_URL' => $deployment->getAttribute('providerBranchUrl', ''),
'APPWRITE_VCS_COMMIT_HASH' => $deployment->getAttribute('providerCommitHash', ''),
'APPWRITE_VCS_COMMIT_MESSAGE' => $deployment->getAttribute('providerCommitMessage', ''),
'APPWRITE_VCS_COMMIT_URL' => $deployment->getAttribute('providerCommitUrl', ''),
'APPWRITE_VCS_COMMIT_AUTHOR_NAME' => $deployment->getAttribute('providerCommitAuthor', ''),
'APPWRITE_VCS_COMMIT_AUTHOR_URL' => $deployment->getAttribute('providerCommitAuthorUrl', ''),
'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment->getAttribute('providerRootDirectory', ''),
'APPWRITE_VERSION' => APP_VERSION_STABLE
]);
/** Execute function */
@@ -2025,8 +1848,6 @@ App::post('/v1/functions/:functionId/executions')
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
@@ -2039,7 +1860,7 @@ App::post('/v1/functions/:functionId/executions')
}
/** Update execution status */
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
$execution->setAttribute('status', $status);
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
@@ -2065,8 +1886,6 @@ App::post('/v1/functions/:functionId/executions')
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
;
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
@@ -2089,17 +1908,6 @@ App::post('/v1/functions/:functionId/executions')
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
$execution->setAttribute('responseHeaders', $headers);
$acceptTypes = \explode(', ', $request->getHeader('accept'));
foreach ($acceptTypes as $acceptType) {
if (\str_starts_with($acceptType, 'application/json') || \str_starts_with($acceptType, 'application/*')) {
$response->setContentType(Response::CONTENT_TYPE_JSON);
break;
} elseif (\str_starts_with($acceptType, 'multipart/form-data') || \str_starts_with($acceptType, 'multipart/*')) {
$response->setContentType(Response::CONTENT_TYPE_MULTIPART);
break;
}
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($execution, Response::MODEL_EXECUTION);
@@ -2541,67 +2349,3 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
$response->noContent();
});
App::get('/v1/functions/templates')
->groups(['api'])
->desc('List function templates')
->label('scope', 'public')
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listTemplates')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.description', '/docs/references/functions/list-templates.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST)
->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true)
->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true)
->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true)
->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true)
->inject('response')
->action(function (array $runtimes, array $usecases, int $limit, int $offset, Response $response) {
$templates = Config::getParam('function-templates', []);
if (!empty($runtimes)) {
$templates = \array_filter($templates, function ($template) use ($runtimes) {
return \count(\array_intersect($runtimes, \array_column($template['runtimes'], 'name'))) > 0;
});
}
if (!empty($usecases)) {
$templates = \array_filter($templates, function ($template) use ($usecases) {
return \count(\array_intersect($usecases, $template['useCases'])) > 0;
});
}
$responseTemplates = \array_slice($templates, $offset, $limit);
$response->dynamic(new Document([
'templates' => $responseTemplates,
'total' => \count($responseTemplates),
]), Response::MODEL_TEMPLATE_FUNCTION_LIST);
});
App::get('/v1/functions/templates/:templateId')
->desc('Get function template')
->label('scope', 'public')
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getTemplate')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.description', '/docs/references/functions/get-template.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION)
->param('templateId', '', new Text(128), 'Template ID.')
->inject('response')
->action(function (string $templateId, Response $response) {
$templates = Config::getParam('function-templates', []);
$template = array_shift(\array_filter($templates, function ($template) use ($templateId) {
return $template['id'] === $templateId;
}));
if (empty($template)) {
throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND);
}
$response->dynamic(new Document($template), Response::MODEL_TEMPLATE_FUNCTION);
});
+1 -1
View File
@@ -69,7 +69,7 @@ App::get('/v1/locale')
});
App::get('/v1/locale/codes')
->desc('List locale codes')
->desc('List Locale Codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
+15 -20
View File
@@ -33,7 +33,7 @@ include_once __DIR__ . '/../shared/api.php';
App::post('/v1/migrations/appwrite')
->groups(['api', 'migrations'])
->desc('Migrate Appwrite data')
->desc('Migrate Appwrite Data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -60,7 +60,6 @@ App::post('/v1/migrations/appwrite')
'status' => 'pending',
'stage' => 'init',
'source' => Appwrite::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'endpoint' => $endpoint,
'projectId' => $projectId,
@@ -88,7 +87,7 @@ App::post('/v1/migrations/appwrite')
App::post('/v1/migrations/firebase/oauth')
->groups(['api', 'migrations'])
->desc('Migrate Firebase data (OAuth)')
->desc('Migrate Firebase Data (OAuth)')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -165,7 +164,6 @@ App::post('/v1/migrations/firebase/oauth')
'status' => 'pending',
'stage' => 'init',
'source' => Firebase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'serviceAccount' => json_encode($serviceAccount),
],
@@ -191,7 +189,7 @@ App::post('/v1/migrations/firebase/oauth')
App::post('/v1/migrations/firebase')
->groups(['api', 'migrations'])
->desc('Migrate Firebase data (Service Account)')
->desc('Migrate Firebase Data (Service Account)')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -226,7 +224,6 @@ App::post('/v1/migrations/firebase')
'status' => 'pending',
'stage' => 'init',
'source' => Firebase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'serviceAccount' => $serviceAccount,
],
@@ -252,7 +249,7 @@ App::post('/v1/migrations/firebase')
App::post('/v1/migrations/supabase')
->groups(['api', 'migrations'])
->desc('Migrate Supabase data')
->desc('Migrate Supabase Data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -282,7 +279,6 @@ App::post('/v1/migrations/supabase')
'status' => 'pending',
'stage' => 'init',
'source' => Supabase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'endpoint' => $endpoint,
'apiKey' => $apiKey,
@@ -313,7 +309,7 @@ App::post('/v1/migrations/supabase')
App::post('/v1/migrations/nhost')
->groups(['api', 'migrations'])
->desc('Migrate NHost data')
->desc('Migrate NHost Data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -344,7 +340,6 @@ App::post('/v1/migrations/nhost')
'status' => 'pending',
'stage' => 'init',
'source' => NHost::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'subdomain' => $subdomain,
'region' => $region,
@@ -376,7 +371,7 @@ App::post('/v1/migrations/nhost')
App::get('/v1/migrations')
->groups(['api', 'migrations'])
->desc('List migrations')
->desc('List Migrations')
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -429,7 +424,7 @@ App::get('/v1/migrations')
App::get('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Get migration')
->desc('Get Migration')
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -453,7 +448,7 @@ App::get('/v1/migrations/:migrationId')
App::get('/v1/migrations/appwrite/report')
->groups(['api', 'migrations'])
->desc('Generate a report on Appwrite data')
->desc('Generate a report on Appwrite Data')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -495,7 +490,7 @@ App::get('/v1/migrations/appwrite/report')
App::get('/v1/migrations/firebase/report')
->groups(['api', 'migrations'])
->desc('Generate a report on Firebase data')
->desc('Generate a report on Firebase Data')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -542,7 +537,7 @@ App::get('/v1/migrations/firebase/report')
App::get('/v1/migrations/firebase/report/oauth')
->groups(['api', 'migrations'])
->desc('Generate a report on Firebase data using OAuth')
->desc('Generate a report on Firebase Data using OAuth')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -632,7 +627,7 @@ App::get('/v1/migrations/firebase/report/oauth')
});
App::get('/v1/migrations/firebase/connect')
->desc('Authorize with Firebase')
->desc('Authorize with firebase')
->groups(['api', 'migrations'])
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -786,7 +781,7 @@ App::get('/v1/migrations/firebase/redirect')
});
App::get('/v1/migrations/firebase/projects')
->desc('List Firebase projects')
->desc('List Firebase Projects')
->groups(['api', 'migrations'])
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -875,7 +870,7 @@ App::get('/v1/migrations/firebase/projects')
});
App::get('/v1/migrations/firebase/deauthorize')
->desc('Revoke Appwrite\'s authorization to access Firebase projects')
->desc('Revoke Appwrite\'s authorization to access Firebase Projects')
->groups(['api', 'migrations'])
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -990,7 +985,7 @@ App::get('/v1/migrations/nhost/report')
App::patch('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Retry migration')
->desc('Retry Migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].retry')
->label('audits.event', 'migration.retry')
@@ -1035,7 +1030,7 @@ App::patch('/v1/migrations/:migrationId')
App::delete('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Delete migration')
->desc('Delete Migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].delete')
->label('audits.event', 'migrationId.delete')
+8 -133
View File
@@ -40,26 +40,18 @@ App::get('/v1/project/usage')
$metrics = [
'total' => [
METRIC_EXECUTIONS,
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS,
METRIC_DOCUMENTS,
METRIC_DATABASES,
METRIC_USERS,
METRIC_BUCKETS,
METRIC_FILES_STORAGE,
METRIC_DATABASES_STORAGE,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS_STORAGE
METRIC_FILES_STORAGE
],
'period' => [
METRIC_NETWORK_REQUESTS,
METRIC_NETWORK_INBOUND,
METRIC_NETWORK_OUTBOUND,
METRIC_USERS,
METRIC_EXECUTIONS,
METRIC_DATABASES_STORAGE,
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS
METRIC_EXECUTIONS
]
];
@@ -136,38 +128,6 @@ App::get('/v1/project/usage')
];
}, $dbForProject->find('functions'));
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('functions'));
$buildsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('functions'));
$bucketsBreakdown = array_map(function ($bucket) use ($dbForProject) {
$id = $bucket->getId();
$name = $bucket->getAttribute('name');
@@ -184,79 +144,6 @@ App::get('/v1/project/usage')
];
}, $dbForProject->find('buckets'));
$databasesStorageBreakdown = array_map(function ($database) use ($dbForProject) {
$id = $database->getId();
$name = $database->getAttribute('name');
$metric = str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('databases'));
$functionsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$deploymentMetric = str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE);
$deploymentValue = $dbForProject->findOne('stats', [
Query::equal('metric', [$deploymentMetric]),
Query::equal('period', ['inf'])
]);
$buildMetric = str_replace(['{functionInternalId}'], [$function->getInternalId()], METRIC_FUNCTION_ID_BUILDS_STORAGE);
$buildValue = $dbForProject->findOne('stats', [
Query::equal('metric', [$buildMetric]),
Query::equal('period', ['inf'])
]);
$value = ($buildValue['value'] ?? 0) + ($deploymentValue['value'] ?? 0);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value,
];
}, $dbForProject->find('functions'));
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('functions'));
$buildsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS);
$value = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('functions'));
// merge network inbound + outbound
$projectBandwidth = [];
foreach ($usage[METRIC_NETWORK_INBOUND] as $item) {
@@ -284,32 +171,20 @@ App::get('/v1/project/usage')
'users' => ($usage[METRIC_USERS]),
'executions' => ($usage[METRIC_EXECUTIONS]),
'executionsTotal' => $total[METRIC_EXECUTIONS],
'executionsMbSecondsTotal' => $total[METRIC_EXECUTIONS_MB_SECONDS],
'buildsMbSecondsTotal' => $total[METRIC_BUILDS_MB_SECONDS],
'documentsTotal' => $total[METRIC_DOCUMENTS],
'databasesTotal' => $total[METRIC_DATABASES],
'databasesStorageTotal' => $total[METRIC_DATABASES_STORAGE],
'usersTotal' => $total[METRIC_USERS],
'bucketsTotal' => $total[METRIC_BUCKETS],
'filesStorageTotal' => $total[METRIC_FILES_STORAGE],
'functionsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE] + $total[METRIC_BUILDS_STORAGE],
'buildsStorageTotal' => $total[METRIC_BUILDS_STORAGE],
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
'executionsBreakdown' => $executionsBreakdown,
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
'bucketsBreakdown' => $bucketsBreakdown,
'databasesStorageBreakdown' => $databasesStorageBreakdown,
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
'functionsStorageBreakdown' => $functionsStorageBreakdown,
'bucketsBreakdown' => $bucketsBreakdown
]), Response::MODEL_USAGE_PROJECT);
});
// Variables
App::post('/v1/project/variables')
->desc('Create variable')
->desc('Create Variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('audits.event', 'variable.create')
@@ -364,7 +239,7 @@ App::post('/v1/project/variables')
});
App::get('/v1/project/variables')
->desc('List variables')
->desc('List Variables')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -389,7 +264,7 @@ App::get('/v1/project/variables')
});
App::get('/v1/project/variables/:variableId')
->desc('Get variable')
->desc('Get Variable')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -413,7 +288,7 @@ App::get('/v1/project/variables/:variableId')
});
App::put('/v1/project/variables/:variableId')
->desc('Update variable')
->desc('Update Variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -459,7 +334,7 @@ App::put('/v1/project/variables/:variableId')
});
App::delete('/v1/project/variables/:variableId')
->desc('Delete variable')
->desc('Delete Variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
+13 -28
View File
@@ -16,13 +16,12 @@ use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\Abuse\Adapters\Database\TimeLimit;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Query as QueryException;
@@ -59,7 +58,6 @@ App::init()
App::post('/v1/projects')
->desc('Create project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.create')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
@@ -174,7 +172,6 @@ App::post('/v1/projects')
'webhooks' => null,
'keys' => null,
'auths' => $auths,
'accessedAt' => DateTime::now(),
'search' => implode(' ', [$projectId, $name]),
'database' => $dsn,
]));
@@ -877,14 +874,6 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers')
->inject('dbForConsole')
->action(function (string $projectId, array $numbers, Response $response, Database $dbForConsole) {
$uniqueNumbers = [];
foreach ($numbers as $number) {
if (isset($uniqueNumbers[$number['phone']])) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Duplicate phone numbers are not allowed.');
}
$uniqueNumbers[$number['phone']] = $number['otp'];
}
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -903,7 +892,6 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers')
App::delete('/v1/projects/:projectId')
->desc('Delete project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.delete')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
@@ -923,7 +911,6 @@ App::delete('/v1/projects/:projectId')
}
$queueForDeletes
->setProject($project)
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($project);
@@ -1200,7 +1187,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
App::post('/v1/projects/:projectId/keys')
->desc('Create key')
->groups(['api', 'projects'])
->label('scope', 'keys.write')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createKey')
@@ -1250,7 +1237,7 @@ App::post('/v1/projects/:projectId/keys')
App::get('/v1/projects/:projectId/keys')
->desc('List keys')
->groups(['api', 'projects'])
->label('scope', 'keys.read')
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listKeys')
@@ -1282,7 +1269,7 @@ App::get('/v1/projects/:projectId/keys')
App::get('/v1/projects/:projectId/keys/:keyId')
->desc('Get key')
->groups(['api', 'projects'])
->label('scope', 'keys.read')
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getKey')
@@ -1316,7 +1303,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
App::put('/v1/projects/:projectId/keys/:keyId')
->desc('Update key')
->groups(['api', 'projects'])
->label('scope', 'keys.write')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateKey')
@@ -1362,7 +1349,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
App::delete('/v1/projects/:projectId/keys/:keyId')
->desc('Delete key')
->groups(['api', 'projects'])
->label('scope', 'keys.write')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteKey')
@@ -1436,8 +1423,7 @@ App::post('/v1/projects/:projectId/jwts')
App::post('/v1/projects/:projectId/platforms')
->desc('Create platform')
->groups(['api', 'projects'])
->label('audits.event', 'platforms.create')
->label('scope', 'platforms.write')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createPlatform')
@@ -1445,7 +1431,7 @@ App::post('/v1/projects/:projectId/platforms')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PLATFORM)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_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, Origin::CLIENT_TYPE_REACT_NATIVE_IOS, Origin::CLIENT_TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_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)
@@ -1487,7 +1473,7 @@ App::post('/v1/projects/:projectId/platforms')
App::get('/v1/projects/:projectId/platforms')
->desc('List platforms')
->groups(['api', 'projects'])
->label('scope', 'platforms.read')
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listPlatforms')
@@ -1519,7 +1505,7 @@ App::get('/v1/projects/:projectId/platforms')
App::get('/v1/projects/:projectId/platforms/:platformId')
->desc('Get platform')
->groups(['api', 'projects'])
->label('scope', 'platforms.read')
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getPlatform')
@@ -1553,7 +1539,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
App::put('/v1/projects/:projectId/platforms/:platformId')
->desc('Update platform')
->groups(['api', 'projects'])
->label('scope', 'platforms.write')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updatePlatform')
@@ -1600,8 +1586,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
App::delete('/v1/projects/:projectId/platforms/:platformId')
->desc('Delete platform')
->groups(['api', 'projects'])
->label('audits.event', 'platforms.delete')
->label('scope', 'platforms.write')
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deletePlatform')
@@ -1745,7 +1730,7 @@ App::post('/v1/projects/:projectId/smtp/tests')
->param('port', 587, new Integer(), 'SMTP server port', true)
->param('username', '', new Text(0, 0), 'SMTP server username', true)
->param('password', '', new Text(0, 0), 'SMTP server password', true)
->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true)
->param('secure', '', new WhiteList(['tls'], true), 'Does SMTP server use secure connection', true)
->inject('response')
->inject('dbForConsole')
->inject('queueForMails')
+6 -6
View File
@@ -23,7 +23,7 @@ use Utopia\Validator\WhiteList;
App::post('/v1/proxy/rules')
->groups(['api', 'proxy'])
->desc('Create rule')
->desc('Create Rule')
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].create')
->label('audits.event', 'rule.create')
@@ -51,7 +51,7 @@ App::post('/v1/proxy/rules')
}
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if ($functionsDomain != '' && str_ends_with($domain, $functionsDomain)) {
if (str_ends_with($domain, $functionsDomain)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions domain or it\'s subdomain to specific resource. Please use different domain.');
}
@@ -149,7 +149,7 @@ App::post('/v1/proxy/rules')
App::get('/v1/proxy/rules')
->groups(['api', 'proxy'])
->desc('List rules')
->desc('List Rules')
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
@@ -212,7 +212,7 @@ App::get('/v1/proxy/rules')
App::get('/v1/proxy/rules/:ruleId')
->groups(['api', 'proxy'])
->desc('Get rule')
->desc('Get Rule')
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
@@ -241,7 +241,7 @@ App::get('/v1/proxy/rules/:ruleId')
App::delete('/v1/proxy/rules/:ruleId')
->groups(['api', 'proxy'])
->desc('Delete rule')
->desc('Delete Rule')
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].delete')
->label('audits.event', 'rules.delete')
@@ -277,7 +277,7 @@ App::delete('/v1/proxy/rules/:ruleId')
});
App::patch('/v1/proxy/rules/:ruleId/verification')
->desc('Update rule verification status')
->desc('Update Rule Verification Status')
->groups(['api', 'proxy'])
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].update')
+7 -3
View File
@@ -63,7 +63,7 @@ App::post('/v1/storage/buckets')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
@@ -240,7 +240,7 @@ App::put('/v1/storage/buckets/:bucketId')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
@@ -889,6 +889,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support
$output = 'jpg';
}
$inputs = Config::getParam('storage-inputs');
$outputs = Config::getParam('storage-outputs');
$fileLogos = Config::getParam('storage-logos');
@@ -1545,7 +1549,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
});
App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->desc('Delete file')
->desc('Delete File')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'buckets.[bucketId].files.[fileId].delete')
+7 -41
View File
@@ -10,7 +10,6 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Platform\Workers\Deletes;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
@@ -43,7 +42,6 @@ use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Host;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
App::post('/v1/teams')
->desc('Create team')
@@ -340,12 +338,10 @@ App::delete('/v1/teams/:teamId')
->label('sdk.response.model', Response::MODEL_NONE)
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('getProjectDB')
->inject('dbForProject')
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('project')
->action(function (string $teamId, Response $response, callable $getProjectDB, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Document $project) {
->inject('queueForDeletes')
->action(function (string $teamId, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
$team = $dbForProject->getDocument('teams', $teamId);
@@ -357,14 +353,9 @@ App::delete('/v1/teams/:teamId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
}
$deletes = new Deletes();
$deletes->deleteMemberships($getProjectDB, $team, $project);
if ($project->getId() === 'console') {
$queueForDeletes
->setType(DELETE_TYPE_TEAM_PROJECTS)
->setDocument($team);
}
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($team);
$queueForEvents
->setParam('teamId', $team->getId())
@@ -395,17 +386,7 @@ App::post('/v1/teams/:teamId/memberships')
->param('email', '', new Email(), 'Email of the new team member.', true)
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], function (Document $project) {
if ($project->getId() === 'console') {
;
$roles = array_keys(Config::getParam('roles', []));
array_filter($roles, function ($role) {
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
return 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](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
->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](https://appwrite.io/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. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // 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')
@@ -420,7 +401,6 @@ App::post('/v1/teams/:teamId/memberships')
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$url = htmlentities($url);
if (empty($url)) {
if (!$isAPIKey && !$isPrivilegedUser) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'URL is required');
@@ -688,7 +668,6 @@ App::post('/v1/teams/:teamId/memberships')
}
$queueForEvents
->setParam('userId', $invitee->getId())
->setParam('teamId', $team->getId())
->setParam('membershipId', $membership->getId())
;
@@ -879,17 +858,7 @@ 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', [], function (Document $project) {
if ($project->getId() === 'console') {
;
$roles = array_keys(Config::getParam('roles', []));
array_filter($roles, function ($role) {
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
return 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.', false, ['project'])
->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')
@@ -932,7 +901,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
$dbForProject->purgeCachedDocument('users', $profile->getId());
$queueForEvents
->setParam('userId', $profile->getId())
->setParam('teamId', $team->getId())
->setParam('membershipId', $membership->getId());
@@ -1058,7 +1026,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$queueForEvents
->setParam('userId', $user->getId())
->setParam('teamId', $team->getId())
->setParam('membershipId', $membership->getId())
;
@@ -1140,7 +1107,6 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
}
$queueForEvents
->setParam('userId', $user->getId())
->setParam('teamId', $team->getId())
->setParam('membershipId', $membership->getId())
->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP))
+21 -17
View File
@@ -140,7 +140,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
if ($existingTarget) {
if($existingTarget) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
}
}
@@ -164,7 +164,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]),
]);
if ($existingTarget) {
if($existingTarget) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
}
}
@@ -452,7 +452,7 @@ App::post('/v1/users/scrypt-modified')
});
App::post('/v1/users/:userId/targets')
->desc('Create user target')
->desc('Create User Target')
->groups(['api', 'users'])
->label('audits.event', 'target.create')
->label('audits.resource', 'target/response.$id')
@@ -647,7 +647,7 @@ App::get('/v1/users/:userId/prefs')
});
App::get('/v1/users/:userId/targets/:targetId')
->desc('Get user target')
->desc('Get User Target')
->groups(['api', 'users'])
->label('scope', 'targets.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
@@ -848,7 +848,7 @@ App::get('/v1/users/:userId/logs')
});
App::get('/v1/users/:userId/targets')
->desc('List user targets')
->desc('List User Targets')
->groups(['api', 'users'])
->label('scope', 'targets.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
@@ -903,7 +903,7 @@ App::get('/v1/users/:userId/targets')
});
App::get('/v1/users/identities')
->desc('List identities')
->desc('List Identities')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
@@ -1425,7 +1425,7 @@ App::patch('/v1/users/:userId/prefs')
});
App::patch('/v1/users/:userId/targets/:targetId')
->desc('Update user target')
->desc('Update User target')
->groups(['api', 'users'])
->label('audits.event', 'target.update')
->label('audits.resource', 'target/{response.$id}')
@@ -1557,7 +1557,7 @@ App::patch('/v1/users/:userId/mfa')
});
App::get('/v1/users/:userId/mfa/factors')
->desc('List factors')
->desc('List Factors')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
@@ -1590,7 +1590,7 @@ App::get('/v1/users/:userId/mfa/factors')
});
App::get('/v1/users/:userId/mfa/recovery-codes')
->desc('Get MFA recovery codes')
->desc('Get MFA Recovery Codes')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
@@ -1625,7 +1625,7 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
});
App::patch('/v1/users/:userId/mfa/recovery-codes')
->desc('Create MFA recovery codes')
->desc('Create MFA Recovery Codes')
->groups(['api', 'users'])
->label('event', 'users.[userId].create.mfa.recovery-codes')
->label('scope', 'users.write')
@@ -1671,7 +1671,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
});
App::put('/v1/users/:userId/mfa/recovery-codes')
->desc('Regenerate MFA recovery codes')
->desc('Regenerate MFA Recovery Codes')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa.recovery-codes')
->label('scope', 'users.write')
@@ -1716,7 +1716,7 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
});
App::delete('/v1/users/:userId/mfa/authenticators/:type')
->desc('Delete authenticator')
->desc('Delete Authenticator')
->groups(['api', 'users'])
->label('event', 'users.[userId].delete.mfa')
->label('scope', 'users.write')
@@ -1786,7 +1786,7 @@ App::post('/v1/users/:userId/sessions')
throw new Exception(Exception::USER_NOT_FOUND);
}
$secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION);
$secret = Auth::codeGenerator();
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
@@ -1803,7 +1803,6 @@ App::post('/v1/users/:userId/sessions')
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
'expire' => $expire,
],
$detector->getOS(),
$detector->getClient(),
@@ -1815,6 +1814,7 @@ App::post('/v1/users/:userId/sessions')
$session = $dbForProject->createDocument('sessions', $session);
$session
->setAttribute('secret', $secret)
->setAttribute('expire', $expire)
->setAttribute('countryName', $countryName);
$queueForEvents
@@ -2109,7 +2109,7 @@ App::post('/v1/users/:userId/jwts')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_JWT)
->param('userId', '', new UID(), 'User ID.')
->param('sessionId', '', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true)
->param('sessionId', 'recent', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true)
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForProject')
@@ -2124,7 +2124,7 @@ App::post('/v1/users/:userId/jwts')
$sessions = $user->getAttribute('sessions', []);
$session = new Document();
if ($sessionId === 'recent') {
if($sessionId === 'recent') {
// Get most recent
$session = \count($sessions) > 0 ? $sessions[\count($sessions) - 1] : new Document();
} else {
@@ -2137,13 +2137,17 @@ App::post('/v1/users/:userId/jwts')
}
}
if ($session->isEmpty()) {
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic(new Document(['jwt' => $jwt->encode([
'userId' => $user->getId(),
'sessionId' => $session->isEmpty() ? '' : $session->getId()
'sessionId' => $session->getId()
])]), Response::MODEL_JWT);
});
+8 -8
View File
@@ -96,7 +96,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$commentStatus = $isAuthorized ? 'waiting' : 'failed';
$authorizeUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
$authorizeUrl = $request->getProtocol() . '://' . $request->getHostname() . "/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
$action = $isAuthorized ? ['type' => 'logs'] : ['type' => 'authorize', 'url' => $authorizeUrl];
@@ -263,7 +263,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
};
App::get('/v1/vcs/github/authorize')
->desc('Install GitHub app')
->desc('Install GitHub App')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
@@ -305,7 +305,7 @@ App::get('/v1/vcs/github/authorize')
});
App::get('/v1/vcs/github/callback')
->desc('Capture installation and authorization from GitHub app')
->desc('Capture installation and authorization from GitHub App')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -509,7 +509,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
$vcsContents = [];
foreach ($contents as $content) {
$isDirectory = false;
if ($content['type'] === GitHub::CONTENTS_DIRECTORY) {
if($content['type'] === GitHub::CONTENTS_DIRECTORY) {
$isDirectory = true;
}
@@ -598,7 +598,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
});
App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
->desc('List repositories')
->desc('List Repositories')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
@@ -843,7 +843,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
});
App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
->desc('List repository branches')
->desc('List Repository Branches')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
@@ -892,7 +892,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
});
App::post('/v1/vcs/github/events')
->desc('Create event')
->desc('Create Event')
->groups(['api', 'vcs'])
->label('scope', 'public')
->inject('gitHub')
@@ -1120,7 +1120,7 @@ App::get('/v1/vcs/installations/:installationId')
});
App::delete('/v1/vcs/installations/:installationId')
->desc('Delete installation')
->desc('Delete Installation')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
->label('sdk.namespace', 'vcs')
+18 -84
View File
@@ -2,7 +2,6 @@
require_once __DIR__ . '/../init.php';
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Event\Certificate;
use Appwrite\Event\Event;
@@ -12,11 +11,9 @@ use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
use Appwrite\Utopia\Response\Filters\V18 as ResponseV18;
use Appwrite\Utopia\View;
use Executor\Executor;
use MaxMind\Db\Reader;
@@ -32,7 +29,6 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Domains\Domain;
use Utopia\DSN\DSN;
use Utopia\Locale\Locale;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Logger\Logger;
@@ -97,9 +93,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$type = $route->getAttribute('resourceType');
if ($type === 'function') {
$utopia->getRoute()?->label('sdk.namespace', 'functions');
$utopia->getRoute()?->label('sdk.method', 'createExecution');
if (System::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
if ($request->getMethod() !== Request::METHOD_GET) {
@@ -137,7 +130,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$version = $function->getAttribute('version', 'v2');
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)];
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
@@ -171,15 +163,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"');
}
$jwtExpiry = $function->getAttribute('timeout', 900);
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
$apiKey = $jwtObj->encode([
'projectId' => $project->getId(),
'scopes' => $function->getAttribute('scopes', [])
]);
$headers = \array_merge([], $requestHeaders);
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-id'] = '';
$headers['x-appwrite-user-jwt'] = '';
@@ -258,36 +242,15 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$endpoint = $protocol . '://' . $hostname . "/v1";
// Appwrite vars
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_FUNCTION_CPUS' => $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
'APPWRITE_FUNCTION_MEMORY' => $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
'APPWRITE_VERSION' => APP_VERSION_STABLE,
'APPWRITE_REGION' => $project->getAttribute('region'),
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
'APPWRITE_VCS_REPOSITORY_ID' => $deployment->getAttribute('providerRepositoryId', ''),
'APPWRITE_VCS_REPOSITORY_NAME' => $deployment->getAttribute('providerRepositoryName', ''),
'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment->getAttribute('providerRepositoryOwner', ''),
'APPWRITE_VCS_REPOSITORY_URL' => $deployment->getAttribute('providerRepositoryUrl', ''),
'APPWRITE_VCS_REPOSITORY_BRANCH' => $deployment->getAttribute('providerBranch', ''),
'APPWRITE_VCS_REPOSITORY_BRANCH_URL' => $deployment->getAttribute('providerBranchUrl', ''),
'APPWRITE_VCS_COMMIT_HASH' => $deployment->getAttribute('providerCommitHash', ''),
'APPWRITE_VCS_COMMIT_MESSAGE' => $deployment->getAttribute('providerCommitMessage', ''),
'APPWRITE_VCS_COMMIT_URL' => $deployment->getAttribute('providerCommitUrl', ''),
'APPWRITE_VCS_COMMIT_AUTHOR_NAME' => $deployment->getAttribute('providerCommitAuthor', ''),
'APPWRITE_VCS_COMMIT_AUTHOR_URL' => $deployment->getAttribute('providerCommitAuthorUrl', ''),
'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment->getAttribute('providerRootDirectory', ''),
'APPWRITE_VERSION' => APP_VERSION_STABLE
]);
/** Execute function */
@@ -310,8 +273,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
@@ -324,7 +285,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
}
/** Update execution status */
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
$execution->setAttribute('status', $status);
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
@@ -346,27 +307,17 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
throw $th;
}
} finally {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
}
$queueForUsage
->addMetric(METRIC_NETWORK_REQUESTS, 1)
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
->addMetric(METRIC_EXECUTIONS, 1)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->setProject($project)
->trigger()
;
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
}
$execution->setAttribute('logs', '');
@@ -483,9 +434,6 @@ App::init()
if (version_compare($requestFormat, '1.5.0', '<')) {
$request->addFilter(new RequestV17());
}
if (version_compare($requestFormat, '1.6.0', '<')) {
$request->addFilter(new RequestV18());
}
}
$domain = $request->getHostname();
@@ -602,9 +550,6 @@ App::init()
if (version_compare($responseFormat, '1.5.0', '<')) {
$response->addFilter(new ResponseV17());
}
if (version_compare($responseFormat, '1.6.0', '<')) {
$response->addFilter(new ResponseV18());
}
if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) {
$response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks");
}
@@ -777,24 +722,16 @@ App::error()
$providerName = System::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', '');
$providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', '');
try {
$loggingProvider = new DSN($providerConfig ?? '');
$providerName = $loggingProvider->getScheme();
if (!empty($providerName) && $providerName === 'sentry') {
$key = $loggingProvider->getPassword();
$projectId = $loggingProvider->getUser() ?? '';
$host = 'https://' . $loggingProvider->getHost();
$adapter = new Sentry($projectId, $key, $host);
$logger = new Logger($adapter);
$logger->setSample(0.04);
$publish = true;
} else {
throw new \Exception('Invalid experimental logging provider');
if (!(empty($providerName) || empty($providerConfig))) {
if (!Logger::hasProvider($providerName)) {
throw new Exception("Logging provider not supported. Logging is disabled");
}
} catch (\Throwable $th) {
Console::warning('Failed to initialize logging provider: ' . $th->getMessage());
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
$adapter = new $classname($providerConfig);
$logger = new Logger($adapter);
$logger->setSample(0.04);
$publish = true;
}
}
@@ -855,6 +792,7 @@ App::error()
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
@@ -863,12 +801,8 @@ App::error()
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
}
/** Wrap all exceptions inside Appwrite\Extend\Exception */
+1 -1
View File
@@ -158,7 +158,7 @@ App::patch('/v1/mock/functions-v2')
App::post('/v1/mock/api-key-unprefixed')
->desc('Create API Key (without standard prefix)')
->groups(['mock', 'api', 'projects'])
->label('scope', 'public')
->label('scope', 'projects.write')
->label('docs', false)
->param('projectId', '', new UID(), 'Project ID.')
->inject('response')
+43 -66
View File
@@ -18,7 +18,7 @@ use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\Database\TimeLimit;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
@@ -95,7 +95,7 @@ $databaseListener = function (string $event, Document $document, Document $proje
$databaseInternalId = $parts[1] ?? 0;
$queueForUsage
->addMetric(METRIC_COLLECTIONS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value)
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value) // per database
;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
@@ -160,22 +160,42 @@ App::init()
->inject('session')
->inject('servers')
->inject('mode')
->inject('team')
->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) {
->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode) {
$route = $utopia->getRoute();
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
/** Default role */
$roles = Config::getParam('roles', []);
/**
* ACL Check
*/
$role = ($user->isEmpty())
? Role::guests()->toString()
: Role::users()->toString();
/** Allowed Scopes for the role */
$scopes = $roles[$role]['scopes'];
// Add user roles
$memberships = $user->find('teamId', $project->getAttribute('teamId'), 'memberships');
if ($memberships) {
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
switch ($memberRole) {
case 'owner':
$role = Auth::USER_ROLE_OWNER;
break;
case 'admin':
$role = Auth::USER_ROLE_ADMIN;
break;
case 'developer':
$role = Auth::USER_ROLE_DEVELOPER;
break;
}
}
}
$roles = Config::getParam('roles', []);
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
$apiKey = $request->getHeader('x-appwrite-key', '');
@@ -186,15 +206,14 @@ App::init()
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Remove after migration
if (!\str_contains($apiKey, '_')) {
if(!\str_contains($apiKey, '_')) {
$keyType = API_KEY_STANDARD;
$authKey = $apiKey;
} else {
[ $keyType, $authKey ] = \explode('_', $apiKey, 2);
}
if ($keyType === API_KEY_DYNAMIC) {
if($keyType === API_KEY_DYNAMIC) {
// Dynamic key
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
@@ -224,7 +243,7 @@ App::init()
Authorization::setRole(Auth::USER_ROLE_APPS);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
}
} elseif ($keyType === API_KEY_STANDARD) {
} elseif($keyType === API_KEY_STANDARD) {
// No underline means no prefix. Backwards compatibility.
// Regular key
@@ -251,7 +270,7 @@ App::init()
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
$accessedAt = $key->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) {
$key->setAttribute('accessedAt', DateTime::now());
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->purgeCachedDocument('projects', $project->getId());
@@ -274,38 +293,13 @@ App::init()
}
}
}
// Admin User Authentication
elseif (($project->getId() === 'console' && !$team->isEmpty() && !$user->isEmpty()) || ($project->getId() !== 'console' && !$user->isEmpty() && $mode === APP_MODE_ADMIN)) {
$teamId = $team->getId();
$adminRoles = [];
$memberships = $user->getAttribute('memberships', []);
foreach ($memberships as $membership) {
if ($membership->getAttribute('confirm', false) === true && $membership->getAttribute('teamId') === $teamId) {
$adminRoles = $membership->getAttribute('roles', []);
break;
}
}
if (empty($adminRoles)) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$scopes = []; // reset scope if admin
foreach ($adminRoles as $role) {
$scopes = \array_merge($scopes, $roles[$role]['scopes']);
}
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
}
$scopes = \array_unique($scopes);
Authorization::setRole($role);
foreach (Auth::getRoles($user) as $authRole) {
Authorization::setRole($authRole);
}
/** Do not allow access to disabled services */
$service = $route->getLabel('sdk.namespace', '');
if (!empty($service)) {
if (
@@ -316,14 +310,14 @@ App::init()
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
}
}
/** Do now allow access if scope is not allowed */
$scope = $route->getLabel('scope', 'none');
if (!\in_array($scope, $scopes)) {
if ($project->isEmpty()) { // Check if permission is denied because project is missing
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')');
}
/** Do not allow access to blocked accounts */
if (false === $user->getAttribute('status')) { // Account is blocked
throw new Exception(Exception::USER_BLOCKED);
}
@@ -513,12 +507,7 @@ App::init()
->setContentType($cacheLog->getAttribute('mimeType'))
->send($data);
} else {
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Pragma', 'no-cache')
->addHeader('Expires', 0)
->addHeader('X-Appwrite-Cache', 'miss')
;
$response->addHeader('X-Appwrite-Cache', 'miss');
}
}
});
@@ -607,11 +596,10 @@ App::shutdown()
/**
* Trigger functions.
*/
if (!$queueForEvents->isPaused()) {
$queueForFunctions
->from($queueForEvents)
->trigger();
}
$queueForFunctions
->from($queueForEvents)
->trigger();
/**
* Trigger webhooks.
*/
@@ -772,23 +760,12 @@ App::shutdown()
->trigger();
}
/**
* Update project last activity
*/
if (!$project->isEmpty() && $project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project));
}
}
/**
* Update user last activity
*/
if (!$user->isEmpty()) {
$accessedAt = $user->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCCESS)) > $accessedAt) {
$user->setAttribute('accessedAt', DateTime::now());
if (APP_MODE_ADMIN !== $mode) {
+42 -10
View File
@@ -16,7 +16,8 @@ App::init()
;
});
App::get('/')
App::get('/console/*')
->alias('/')
->alias('auth/*')
->alias('/invite')
->alias('/login')
@@ -30,14 +31,45 @@ App::get('/')
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$url = parse_url($request->getURI());
$target = "/console{$url['path']}";
$params = $request->getParams();
if (!empty($params)) {
$target .= "?" . \http_build_query($params);
$fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
// Card SSR
if (\str_starts_with($request->getURI(), '/card')) {
$urlCunks = \explode('/', $request->getURI());
$userId = $urlCunks[\count($urlCunks) - 1] ?? '';
$domain = $request->getProtocol() . '://' . $request->getHostname();
if (!empty($userId)) {
$ogImageUrl = $domain . '/v1/cards/cloud-og?userId=' . $userId;
} else {
$ogImageUrl = $domain . '/v1/cards/cloud-og?mock=normal';
}
$ogTags = [
'<title>Appwrite Cloud Card</title>',
'<meta name="description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
'<meta property="og:url" content="' . $domain . $request->getURI() . '">',
'<meta name="og:image:type" content="image/png">',
'<meta name="og:image:width" content="1008">',
'<meta name="og:image:height" content="1008">',
'<meta property="og:type" content="website">',
'<meta property="og:title" content="Appwrite Cloud Card">',
'<meta property="og:description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
'<meta property="og:image" content="' . $ogImageUrl . '">',
'<meta name="twitter:card" content="summary_large_image">',
'<meta property="twitter:domain" content="' . $request->getHostname() . '">',
'<meta property="twitter:url" content="' . $domain . $request->getURI() . '">',
'<meta name="twitter:title" content="Appwrite Cloud Card">',
'<meta name="twitter:image:type" content="image/png">',
'<meta name="twitter:image:width" content="1008">',
'<meta name="twitter:image:height" content="1008">',
'<meta name="twitter:description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
'<meta name="twitter:image" content="' . $ogImageUrl . '">',
];
$fallback = \str_replace('<!-- {{CLOUD_OG}} -->', \implode('', $ogTags), $fallback);
}
if ($url['fragment'] ?? false) {
$target .= "#{$url['fragment']}";
}
$response->redirect($target);
$response->html($fallback);
});
+6 -7
View File
@@ -9,7 +9,7 @@ use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
use Swoole\Http\Server;
use Swoole\Process;
use Utopia\Abuse\Adapters\Database\TimeLimit;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
@@ -57,6 +57,8 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
Files::load(__DIR__ . '/../console');
include __DIR__ . '/controllers/general.php';
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
@@ -290,6 +292,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::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
@@ -298,12 +301,8 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
}
Console::error('[Error] Type: ' . get_class($th));
+34 -105
View File
@@ -33,7 +33,6 @@ use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Specification;
use Appwrite\GraphQL\Promises\Adapter\Swoole;
use Appwrite\GraphQL\Schema;
use Appwrite\Hooks\Hooks;
@@ -41,7 +40,6 @@ use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Origin;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\URL\URL as AppwriteURL;
use Appwrite\Utopia\Request;
use MaxMind\Db\Reader;
use PHPMailer\PHPMailer\PHPMailer;
use Swoole\Database\PDOProxy;
@@ -115,12 +113,11 @@ const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4318;
const APP_VERSION_STABLE = '1.6.0';
const APP_CACHE_BUSTER = 443;
const APP_VERSION_STABLE = '1.5.7';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -149,12 +146,6 @@ const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
const APP_HOSTNAME_INTERNAL = 'appwrite';
const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_05VCPU_512MB;
const APP_FUNCTION_CPUS_DEFAULT = 0.5;
const APP_FUNCTION_MEMORY_DEFAULT = 512;
const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
@@ -180,7 +171,7 @@ const DELETE_TYPE_PROJECTS = 'projects';
const DELETE_TYPE_FUNCTIONS = 'functions';
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
const DELETE_TYPE_USERS = 'users';
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
const DELETE_TYPE_TEAMS = 'teams';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
@@ -227,28 +218,15 @@ const API_KEY_DYNAMIC = 'dynamic';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
const METRIC_AUTH_METHOD_PHONE = 'auth.method.phone';
const METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE = METRIC_AUTH_METHOD_PHONE . '.{countryCode}';
const METRIC_MESSAGES = 'messages';
const METRIC_MESSAGES_SENT = METRIC_MESSAGES . '.sent';
const METRIC_MESSAGES_FAILED = METRIC_MESSAGES . '.failed';
const METRIC_MESSAGES_TYPE = METRIC_MESSAGES . '.{type}';
const METRIC_MESSAGES_TYPE_SENT = METRIC_MESSAGES . '.{type}.sent';
const METRIC_MESSAGES_TYPE_FAILED = METRIC_MESSAGES . '.{type}.failed';
const METRIC_MESSAGES_TYPE_PROVIDER = METRIC_MESSAGES . '.{type}.{provider}';
const METRIC_MESSAGES_TYPE_PROVIDER_SENT = METRIC_MESSAGES . '.{type}.{provider}.sent';
const METRIC_MESSAGES_TYPE_PROVIDER_FAILED = METRIC_MESSAGES . '.{type}.{provider}.failed';
const METRIC_MESSAGES = 'messages';
const METRIC_MESSAGES_COUNTRY_CODE = '{countryCode}.messages';
const METRIC_SESSIONS = 'sessions';
const METRIC_DATABASES = 'databases';
const METRIC_COLLECTIONS = 'collections';
const METRIC_DATABASES_STORAGE = 'databases.storage';
const METRIC_DATABASE_ID_COLLECTIONS = '{databaseInternalId}.collections';
const METRIC_DATABASE_ID_STORAGE = '{databaseInternalId}.databases.storage';
const METRIC_DOCUMENTS = 'documents';
const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage';
const METRIC_BUCKETS = 'buckets';
const METRIC_FILES = 'files';
const METRIC_FILES_STORAGE = 'files.storage';
@@ -264,7 +242,6 @@ const METRIC_BUILDS_STORAGE = 'builds.storage';
const METRIC_BUILDS_COMPUTE = 'builds.compute';
const METRIC_BUILDS_COMPUTE_SUCCESS = 'builds.compute.success';
const METRIC_BUILDS_COMPUTE_FAILED = 'builds.compute.failed';
const METRIC_BUILDS_MB_SECONDS = 'builds.mbSeconds';
const METRIC_FUNCTION_ID_BUILDS = '{functionInternalId}.builds';
const METRIC_FUNCTION_ID_BUILDS_SUCCESS = '{functionInternalId}.builds.success';
const METRIC_FUNCTION_ID_BUILDS_FAILED = '{functionInternalId}.builds.failed';
@@ -274,13 +251,10 @@ const METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS = '{functionInternalId}.builds.
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED = '{functionInternalId}.builds.compute.failed';
const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
const METRIC_FUNCTION_ID_BUILDS_MB_SECONDS = '{functionInternalId}.builds.mbSeconds';
const METRIC_EXECUTIONS = 'executions';
const METRIC_EXECUTIONS_COMPUTE = 'executions.compute';
const METRIC_EXECUTIONS_MB_SECONDS = 'executions.mbSeconds';
const METRIC_FUNCTION_ID_EXECUTIONS = '{functionInternalId}.executions';
const METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE = '{functionInternalId}.executions.compute';
const METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS = '{functionInternalId}.executions.mbSeconds';
const METRIC_NETWORK_REQUESTS = 'network.requests';
const METRIC_NETWORK_INBOUND = 'network.inbound';
const METRIC_NETWORK_OUTBOUND = 'network.outbound';
@@ -328,8 +302,6 @@ 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('runtime-specifications', __DIR__ . '/config/runtimes/specifications.php');
Config::load('function-templates', __DIR__ . '/config/function-templates.php');
/**
* New DB Filters
@@ -649,9 +621,9 @@ Database::addFilter(
])
));
if (\count($targetIds) > 0) {
return $database->skipValidation(fn () => $database->find('targets', [
return $database->find('targets', [
Query::equal('$internalId', $targetIds)
]));
]);
}
return [];
}
@@ -772,13 +744,12 @@ $register->set('logger', function () {
$providerName = $loggingProvider->getScheme();
$providerConfig = match ($providerName) {
'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => 'https://' . $loggingProvider->getHost()],
'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
'logowl' => ['ticket' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
default => ['key' => $loggingProvider->getHost()],
};
} catch (Throwable $th) {
} catch (Throwable) {
// Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables
Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage());
$configChunks = \explode(";", $providerConfig);
$providerConfig = match ($providerName) {
@@ -796,22 +767,13 @@ $register->set('logger', function () {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
}
try {
$adapter = match ($providerName) {
'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
'raygun' => new Raygun($providerConfig['key']),
'appsignal' => new AppSignal($providerConfig['key']),
default => null
};
} catch (Throwable $th) {
$adapter = null;
}
if ($adapter === null) {
Console::error("Logging provider not supported. Logging is disabled");
return;
}
$adapter = match ($providerName) {
'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
'raygun' => new Raygun($providerConfig['key']),
'appsignal' => new AppSignal($providerConfig['key']),
default => throw new Exception('Provider "' . $providerName . '" not supported.')
};
return new Logger($adapter);
});
@@ -1035,7 +997,7 @@ $register->set('smtp', function () {
return $mail;
});
$register->set('geodb', function () {
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-09.mmdb');
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-02.mmdb');
});
$register->set('passwordsDictionary', function () {
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
@@ -1259,13 +1221,13 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$user = new Document([]);
}
// if (APP_MODE_ADMIN === $mode) {
// if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
// Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
// } else {
// $user = new Document([]);
// }
// }
if (APP_MODE_ADMIN === $mode) {
if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
} else {
$user = new Document([]);
}
}
$authJWT = $request->getHeader('x-appwrite-jwt', '');
@@ -1279,15 +1241,14 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
}
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
$jwtSessionId = $payload['sessionId'] ?? '';
if ($jwtUserId && $jwtSessionId) {
$user = $dbForProject->getDocument('users', $jwtUserId);
}
$jwtSessionId = $payload['sessionId'] ?? '';
if (!empty($jwtSessionId)) {
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
$user = new Document([]);
}
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
$user = new Document([]);
}
}
@@ -1342,7 +1303,7 @@ App::setResource('console', function () {
'$collection' => ID::custom('projects'),
'description' => 'Appwrite core engine',
'logo' => '',
'teamId' => null,
'teamId' => -1,
'webhooks' => [],
'keys' => [],
'platforms' => [
@@ -1523,9 +1484,9 @@ App::setResource('deviceForBuilds', function ($project) {
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
}, ['project']);
function getDevice(string $root, string $connection = ''): Device
function getDevice($root): Device
{
$connection = !empty($connection) ? $connection : System::getEnv('_APP_CONNECTIONS_STORAGE', '');
$connection = System::getEnv('_APP_CONNECTIONS_STORAGE', '');
if (!empty($connection)) {
$acl = 'private';
@@ -1770,35 +1731,3 @@ App::setResource('requestTimestamp', function ($request) {
App::setResource('plan', function (array $plan = []) {
return [];
});
App::setResource('team', function (Document $project, Database $dbForConsole, App $utopia, Request $request) {
$teamInternalId = '';
if ($project->getId() !== 'console') {
$teamInternalId = $project->getAttribute('teamInternalId', '');
} else {
$route = $utopia->match($request);
$path = $route->getPath();
if (str_starts_with($path, '/v1/projects/:projectId')) {
$uri = $request->getURI();
$pid = explode('/', $uri)[3];
$p = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $pid));
$teamInternalId = $p->getAttribute('teamInternalId', '');
} elseif ($path === '/v1/projects') {
$teamId = $request->getParam('teamId', '');
$team = Authorization::skip(fn () => $dbForConsole->getDocument('teams', $teamId));
return $team;
}
}
$team = Authorization::skip(function () use ($dbForConsole, $teamInternalId) {
return $dbForConsole->findOne('teams', [
Query::equal('$internalId', [$teamInternalId]),
]);
});
if (!$team) {
$team = new Document([]);
}
return $team;
}, ['project', 'dbForConsole', 'utopia', 'request']);
+9 -21
View File
@@ -13,7 +13,7 @@ use Swoole\Runtime;
use Swoole\Table;
use Swoole\Timer;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\Database\TimeLimit;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\App;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -40,7 +40,7 @@ require_once __DIR__ . '/init.php';
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
// Allows overriding
if (!function_exists('getConsoleDB')) {
if (!function_exists("getConsoleDB")) {
function getConsoleDB(): Database
{
global $register;
@@ -66,7 +66,7 @@ if (!function_exists('getConsoleDB')) {
}
// Allows overriding
if (!function_exists('getProjectDB')) {
if (!function_exists("getProjectDB")) {
function getProjectDB(Document $project): Database
{
global $register;
@@ -113,7 +113,7 @@ if (!function_exists('getProjectDB')) {
}
// Allows overriding
if (!function_exists('getCache')) {
if (!function_exists("getCache")) {
function getCache(): Cache
{
global $register;
@@ -135,14 +135,7 @@ if (!function_exists('getCache')) {
}
}
if (!function_exists('getRealtime')) {
function getRealtime(): Realtime
{
return new Realtime();
}
}
$realtime = getRealtime();
$realtime = new Realtime();
/**
* Table for statistics across all workers.
@@ -185,18 +178,15 @@ $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);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
$responseCode = $logger->addLog($log);
Console::info('Realtime log pushed with status code: ' . $responseCode);
}
Console::error('[Error] Type: ' . get_class($error));
@@ -391,10 +381,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$user = $database->getDocument('users', $userId);
$roles = Auth::getRoles($user);
$channels = $realtime->connections[$connection]['channels'];
$realtime->unsubscribe($connection);
$realtime->subscribe($projectId, $connection, $roles, $channels);
$realtime->subscribe($projectId, $connection, $roles, $realtime->connections[$connection]['channels']);
$register->get('pools')->reclaim();
}
+1 -31
View File
@@ -163,28 +163,6 @@ $image = $this->getParam('image', '');
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:5.0.12
restart: unless-stopped
networks:
- appwrite
labels:
- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite_console.loadbalancer.server.port=80"
#ws
- traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`)
- traefik.http.routers.appwrite_console_http.service=appwrite_console
# wss
- traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`)
- traefik.http.routers.appwrite_console_https.service=appwrite_console
- traefik.http.routers.appwrite_console_https.tls=true
appwrite-realtime:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: realtime
@@ -336,9 +314,6 @@ $image = $this->getParam('image', '');
- _APP_LOGGING_CONFIG
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_EXECUTION
appwrite-worker-databases:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@@ -478,8 +453,6 @@ $image = $this->getParam('image', '');
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -531,8 +504,6 @@ $image = $this->getParam('image', '');
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_CONFIG
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
appwrite-worker-messaging:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@@ -681,7 +652,6 @@ $image = $this->getParam('image', '');
entrypoint: worker-usage-dump
<<: *x-logging
container_name: appwrite-worker-usage-dump
restart: unless-stopped
networks:
- appwrite
depends_on:
@@ -795,7 +765,7 @@ $image = $this->getParam('image', '');
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.6.11
image: executor:benchmark
networks:
- appwrite
- runtimes
+4 -7
View File
@@ -58,7 +58,7 @@ Server::setResource('project', function (Message $message, Database $dbForConsol
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
if ($project->getId() === 'console' || $project->isEmpty() || ! empty($project->getInternalId())) {
if ($project->getId() === 'console') {
return $project;
}
@@ -341,17 +341,14 @@ $worker
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::getRoles());
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
$responseCode = $logger->addLog($log);
Console::info('Usage stats log pushed with status code: ' . $responseCode);
}
Console::error('[Error] Type: ' . get_class($error));
-162
View File
@@ -1,162 +0,0 @@
x-logging: &x-logging
logging:
driver: "json-file"
options:
max-file: "5"
max-size: "10m"
services:
traefik:
command:
- --log.level=DEBUG
- --api.insecure=true
ports:
- 9500:8080
appwrite:
ports:
- 9501:80
volumes:
- ./phpunit.xml:/usr/src/code/phpunit.xml
- ./tests:/usr/src/code/tests
- ./app:/usr/src/code/app
- ./docs:/usr/src/code/docs
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/src/code/dev
appwrite-realtime:
ports:
- 9505:80
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-audits:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-webhooks:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-deletes:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-databases:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-builds:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-certificates:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-functions:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-mails:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-messaging:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-migrations:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-usage:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-worker-usage-dump:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-task-maintenance:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-task-scheduler-functions:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-task-scheduler-executions:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
appwrite-task-scheduler-messages:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
mariadb:
ports:
- "3306:3306"
redis:
ports:
- "6379:6379"
# Dev Tools Start ------------------------------------------------------------------------------------------
#
# The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack
#
# Here is a description of the different tools and why are we using them:
#
# MailCatcher - An SMTP server. Catches all system emails and displays them in a nice UI.
# RequestCatcher - An HTTP server. Catches all system https calls and displays them using a simple HTTP API. Used to debug & tests webhooks and HTTP tasks
# Redis Insight - A nice UI for exploring Redis data
# Adminer - A nice UI for exploring MariaDB data
# GraphQl Explorer - A nice UI for exploring GraphQL API
maildev: # used mainly for dev tests
image: appwrite/mailcatcher:1.0.0
container_name: appwrite-mailcatcher
<<: *x-logging
ports:
- "9503:1080"
networks:
- appwrite
request-catcher: # used mainly for dev tests
image: appwrite/requestcatcher:1.0.0
container_name: appwrite-requestcatcher
<<: *x-logging
ports:
- "9504:5000"
networks:
- appwrite
adminer:
image: adminer
container_name: appwrite-adminer
<<: *x-logging
restart: always
ports:
- 9506:8080
networks:
- appwrite
redis-insight:
image: redis/redisinsight:latest
restart: unless-stopped
networks:
- appwrite
environment:
- REDIS_HOSTS=redis
ports:
- "8081:5540"
graphql-explorer:
container_name: appwrite-graphql-explorer
image: appwrite/altair:0.3.0
restart: unless-stopped
networks:
- appwrite
ports:
- "9509:3000"
environment:
- SERVER_URL=http://localhost/v1/graphql
-888
View File
@@ -1,888 +0,0 @@
x-logging: &x-logging
logging:
driver: "json-file"
options:
max-file: "5"
max-size: "10m"
services:
traefik:
image: traefik:2.11
container_name: appwrite-traefik
<<: *x-logging
command:
- --providers.file.directory=/storage/config
- --providers.file.watch=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --entrypoints.appwrite_web.address=:80
- --entrypoints.appwrite_websecure.address=:443
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-config:/storage/config:ro
- appwrite-certificates:/storage/certificates:ro
depends_on:
- appwrite
networks:
- gateway
- appwrite
appwrite:
image: appwrite-dev
build:
context: .
args:
DEBUG: false
TESTING: true
VERSION: dev
container_name: appwrite
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_api.loadbalancer.server.port=80
#http
- traefik.http.routers.appwrite_api_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_api_http.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_http.service=appwrite_api
# https
- traefik.http.routers.appwrite_api_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_api_https.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_https.service=appwrite_api
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- appwrite-functions:/storage/functions:rw
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_LOCALE
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_SESSION_ALERTS
- _APP_CONSOLE_WHITELIST_IPS
- _APP_CONSOLE_HOSTNAMES
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_EMAIL_SECURITY
- _APP_SYSTEM_RESPONSE_FORMAT
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_USAGE_STATS
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
- _APP_STORAGE_ANTIVIRUS
- _APP_STORAGE_ANTIVIRUS_HOST
- _APP_STORAGE_ANTIVIRUS_PORT
- _APP_STORAGE_DEVICE
- _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_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_RUNTIMES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_LOGGING_CONFIG
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_DELAY
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
- _APP_VCS_GITHUB_APP_ID
- _APP_VCS_GITHUB_WEBHOOK_SECRET
- _APP_VCS_GITHUB_CLIENT_SECRET
- _APP_VCS_GITHUB_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:5.0.12
restart: unless-stopped
networks:
- appwrite
labels:
- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite_console.loadbalancer.server.port=80"
#ws
- traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`)
- traefik.http.routers.appwrite_console_http.service=appwrite_console
# wss
- traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`)
- traefik.http.routers.appwrite_console_https.service=appwrite_console
- traefik.http.routers.appwrite_console_https.tls=true
appwrite-realtime:
image: appwrite-dev
entrypoint: realtime
container_name: appwrite-realtime
<<: *x-logging
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
- "traefik.docker.network=appwrite"
- "traefik.http.services.appwrite_realtime.loadbalancer.server.port=80"
#ws
- traefik.http.routers.appwrite_realtime_ws.entrypoints=appwrite_web
- traefik.http.routers.appwrite_realtime_ws.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_ws.service=appwrite_realtime
# wss
- traefik.http.routers.appwrite_realtime_wss.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_realtime_wss.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_wss.service=appwrite_realtime
- traefik.http.routers.appwrite_realtime_wss.tls=true
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
appwrite-worker-audits:
image: appwrite-dev
entrypoint: worker-audits
<<: *x-logging
container_name: appwrite-worker-audits
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
appwrite-worker-webhooks:
image: appwrite-dev
entrypoint: worker-webhooks
<<: *x-logging
container_name: appwrite-worker-webhooks
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_CONFIG
appwrite-worker-deletes:
image: appwrite-dev
entrypoint: worker-deletes
<<: *x-logging
container_name: appwrite-worker-deletes
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_STORAGE_DEVICE
- _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_LOGGING_CONFIG
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_EXECUTION
appwrite-worker-databases:
image: appwrite-dev
entrypoint: worker-databases
<<: *x-logging
container_name: appwrite-worker-databases
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
appwrite-worker-builds:
image: appwrite-dev
entrypoint: worker-builds
<<: *x-logging
container_name: appwrite-worker-builds
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
- _APP_VCS_GITHUB_APP_ID
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_STORAGE_DEVICE
- _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
appwrite-worker-certificates:
image: appwrite-dev
entrypoint: worker-certificates
<<: *x-logging
container_name: appwrite-worker-certificates
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
appwrite-worker-functions:
image: appwrite-dev
entrypoint: worker-functions
<<: *x-logging
container_name: appwrite-worker-functions
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
- openruntimes-executor
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_USAGE_STATS
- _APP_DOCKER_HUB_USERNAME
- _APP_DOCKER_HUB_PASSWORD
- _APP_LOGGING_CONFIG
appwrite-worker-mails:
image: appwrite-dev
entrypoint: worker-mails
<<: *x-logging
container_name: appwrite-worker-mails
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_CONFIG
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
appwrite-worker-messaging:
image: appwrite-dev
entrypoint: worker-messaging
<<: *x-logging
container_name: appwrite-worker-messaging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-uploads:/storage/uploads:rw
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
- _APP_SMS_FROM
- _APP_SMS_PROVIDER
- _APP_STORAGE_DEVICE
- _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
appwrite-worker-migrations:
image: appwrite-dev
entrypoint: worker-migrations
<<: *x-logging
container_name: appwrite-worker-migrations
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_EMAIL_SECURITY
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
appwrite-task-maintenance:
image: appwrite-dev
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-task-maintenance
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_DOMAIN_FUNCTIONS
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
appwrite-worker-usage:
image: appwrite-dev
entrypoint: worker-usage
container_name: appwrite-worker-usage
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-worker-usage-dump:
image: appwrite-dev
entrypoint: worker-usage-dump
<<: *x-logging
container_name: appwrite-worker-usage-dump
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-task-scheduler-functions:
image: appwrite-dev
entrypoint: schedule-functions
container_name: appwrite-task-scheduler-functions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-task-scheduler-executions:
image: appwrite-dev
entrypoint: schedule-executions
container_name: appwrite-task-scheduler-executions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-task-scheduler-messages:
image: appwrite-dev
entrypoint: schedule-messages
container_name: appwrite-task-scheduler-messages
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-assistant:
image: appwrite/assistant:0.4.0
container_name: appwrite-assistant
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
openruntimes-executor:
container_name: openruntimes-executor
hostname: exc1
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.6.11
networks:
- appwrite
- runtimes
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-builds:/storage/builds:rw
- appwrite-functions:/storage/functions:rw
# Host mount nessessary to share files between executor and runtimes.
# It's not possible to share mount file between 2 containers without host mount (copying is too slow)
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_FUNCTIONS_MAINTENANCE_INTERVAL
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
- OPR_EXECUTOR_ENV=$_APP_ENV
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_S3_SECRET=$_APP_STORAGE_S3_SECRET
- OPR_EXECUTOR_STORAGE_S3_REGION=$_APP_STORAGE_S3_REGION
- OPR_EXECUTOR_STORAGE_S3_BUCKET=$_APP_STORAGE_S3_BUCKET
- OPR_EXECUTOR_STORAGE_DO_SPACES_ACCESS_KEY=$_APP_STORAGE_DO_SPACES_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_DO_SPACES_SECRET=$_APP_STORAGE_DO_SPACES_SECRET
- OPR_EXECUTOR_STORAGE_DO_SPACES_REGION=$_APP_STORAGE_DO_SPACES_REGION
- OPR_EXECUTOR_STORAGE_DO_SPACES_BUCKET=$_APP_STORAGE_DO_SPACES_BUCKET
- OPR_EXECUTOR_STORAGE_BACKBLAZE_ACCESS_KEY=$_APP_STORAGE_BACKBLAZE_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_BACKBLAZE_SECRET=$_APP_STORAGE_BACKBLAZE_SECRET
- OPR_EXECUTOR_STORAGE_BACKBLAZE_REGION=$_APP_STORAGE_BACKBLAZE_REGION
- OPR_EXECUTOR_STORAGE_BACKBLAZE_BUCKET=$_APP_STORAGE_BACKBLAZE_BUCKET
- OPR_EXECUTOR_STORAGE_LINODE_ACCESS_KEY=$_APP_STORAGE_LINODE_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_LINODE_SECRET=$_APP_STORAGE_LINODE_SECRET
- OPR_EXECUTOR_STORAGE_LINODE_REGION=$_APP_STORAGE_LINODE_REGION
- OPR_EXECUTOR_STORAGE_LINODE_BUCKET=$_APP_STORAGE_LINODE_BUCKET
- OPR_EXECUTOR_STORAGE_WASABI_ACCESS_KEY=$_APP_STORAGE_WASABI_ACCESS_KEY
- OPR_EXECUTOR_STORAGE_WASABI_SECRET=$_APP_STORAGE_WASABI_SECRET
- OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mariadb:/var/lib/mysql:rw
environment:
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
command: "mysqld --innodb-flush-method=fsync"
redis:
image: redis:7.2.4-alpine
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
networks:
- appwrite
volumes:
- appwrite-redis:/data:rw
networks:
gateway:
name: gateway
appwrite:
name: appwrite
runtimes:
name: runtimes
volumes:
appwrite-mariadb:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-config:
+12 -14
View File
@@ -11,10 +11,9 @@
}
],
"scripts": {
"test": "vendor/bin/phpunit",
"test": "vendor/bin/phpunit --filter testBenchmark",
"lint": "vendor/bin/pint --test",
"format": "vendor/bin/pint",
"bench": "vendor/bin/phpbench run --report=benchmark"
"format": "vendor/bin/pint"
},
"autoload": {
"psr-4": {
@@ -43,24 +42,24 @@
"ext-openssl": "*",
"ext-zlib": "*",
"ext-sockets": "*",
"appwrite/php-runtimes": "0.16.*",
"appwrite/php-runtimes": "0.14.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "0.43.0",
"utopia-php/abuse": "0.37.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "0.43.0",
"utopia-php/audit": "0.39.*",
"utopia-php/cache": "0.10.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.53.6",
"utopia-php/database": "0.49.*",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.2.*",
"utopia-php/image": "0.7.*",
"utopia-php/image": "0.6.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/logger": "0.5.*",
"utopia-php/messaging": "0.12.*",
"utopia-php/migration": "0.6.*",
"utopia-php/migration": "0.4.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.5.*",
@@ -70,7 +69,7 @@
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",
"utopia-php/system": "0.8.*",
"utopia-php/vcs": "0.8.*",
"utopia-php/vcs": "0.7.*",
"utopia-php/websocket": "0.1.*",
"matomo/device-detector": "6.1.*",
"dragonmantank/cron-expression": "3.3.2",
@@ -83,12 +82,11 @@
},
"require-dev": {
"ext-fileinfo": "*",
"appwrite/sdk-generator": "0.39.*",
"appwrite/sdk-generator": "0.38.*",
"phpunit/phpunit": "9.5.20",
"swoole/ide-helper": "5.1.2",
"textalk/websocket": "1.5.7",
"laravel/pint": "^1.14",
"phpbench/phpbench": "^1.2"
"laravel/pint": "^1.14"
},
"provide": {
"ext-phpiredis": "*"
Generated
+184 -1596
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -1,8 +1,6 @@
zend_extension=xdebug
[xdebug]
xdebug.mode=develop,debug,profile
xdebug.mode=develop,debug
xdebug.client_host=host.docker.internal
xdebug.start_with_request=yes
xdebug.output_dir=/tmp/xdebug
xdebug.use_compression=false
xdebug.start_with_request=yes
+1033
View File
File diff suppressed because it is too large Load Diff
@@ -8,4 +8,4 @@ X-Appwrite-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...
{
"otp": "<OTP>"
}
}
@@ -1,18 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createAnonymousSession(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
}));
@@ -1,23 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createEmailPasswordSession(
"email@example.com", // email
"password", // password
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,24 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createEmailToken(
"<USER_ID>", // userId
"email@example.com", // email
false, // phrase (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,18 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createJWT(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
}));
@@ -1,25 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createMagicURLToken(
"<USER_ID>", // userId
"email@example.com", // email
"https://example.com", // url (optional)
false, // phrase (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,23 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
import io.appwrite.enums.AuthenticatorType;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createMfaAuthenticator(
AuthenticatorType.TOTP, // type
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,23 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
import io.appwrite.enums.AuthenticationFactor;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createMfaChallenge(
AuthenticationFactor.EMAIL, // factor
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,18 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createMfaRecoveryCodes(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
}));
@@ -1,26 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
import io.appwrite.enums.OAuthProvider;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createOAuth2Session(
OAuthProvider.AMAZON, // provider
"https://example.com", // success (optional)
"https://example.com", // failure (optional)
listOf(), // scopes (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,26 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
import io.appwrite.enums.OAuthProvider;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createOAuth2Token(
OAuthProvider.AMAZON, // provider
"https://example.com", // success (optional)
"https://example.com", // failure (optional)
listOf(), // scopes (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,23 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createPhoneToken(
"<USER_ID>", // userId
"+12065550100", // phone
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,18 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createPhoneVerification(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
}));
@@ -1,24 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createPushTarget(
"<TARGET_ID>", // targetId
"<IDENTIFIER>", // identifier
"<PROVIDER_ID>", // providerId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,23 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createRecovery(
"email@example.com", // email
"https://example.com", // url
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,23 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createSession(
"<USER_ID>", // userId
"<SECRET>", // secret
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.createVerification(
"https://example.com", // url
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,25 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.create(
"<USER_ID>", // userId
"email@example.com", // email
"", // password
"<NAME>", // name (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.deleteIdentity(
"<IDENTITY_ID>", // identityId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,23 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
import io.appwrite.enums.AuthenticatorType;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.deleteMfaAuthenticator(
AuthenticatorType.TOTP, // type
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.deletePushTarget(
"<TARGET_ID>", // targetId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,22 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.deleteSession(
"<SESSION_ID>", // sessionId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -1,18 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.deleteSessions(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
}));
@@ -1,18 +0,0 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
Client client = new Client(context)
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Account account = new Account(client);
account.getMfaRecoveryCodes(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
}));

Some files were not shown because too many files have changed in this diff Show More