Merge branch 'main' of https://github.com/appwrite/appwrite into chore-sync-main-1.5.x
This commit is contained in:
commit
3ade88897c
2
.env
2
.env
|
@ -91,7 +91,7 @@ _APP_GRAPHQL_MAX_DEPTH=3
|
|||
_APP_DOCKER_HUB_USERNAME=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
_APP_VCS_GITHUB_APP_NAME=
|
||||
_APP_VCS_GITHUB_PRIVATE_KEY=""
|
||||
_APP_VCS_GITHUB_PRIVATE_KEY=disabled
|
||||
_APP_VCS_GITHUB_APP_ID=
|
||||
_APP_VCS_GITHUB_CLIENT_ID=
|
||||
_APP_VCS_GITHUB_CLIENT_SECRET=
|
||||
|
|
146
.github/workflows/tests.yml
vendored
146
.github/workflows/tests.yml
vendored
|
@ -4,57 +4,119 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
IMAGE: appwrite-dev
|
||||
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Unit & E2E
|
||||
setup:
|
||||
name: Setup & Build Appwrite Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build Appwrite
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: ${{ env.IMAGE }}
|
||||
load: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
|
||||
build-args: |
|
||||
DEBUG=false
|
||||
TESTING=true
|
||||
VERSION=dev
|
||||
|
||||
- name: Cache Docker Image
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
|
||||
unit_test:
|
||||
name: Unit Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
# Fetch submodules
|
||||
submodules: recursive
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
# This is a separate action that sets up buildx runner
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
|
||||
- name: Build Appwrite
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: appwrite-dev
|
||||
load: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
DEBUG=false
|
||||
TESTING=true
|
||||
VERSION=dev
|
||||
- name: Doctor
|
||||
run: docker compose exec -T appwrite doctor
|
||||
|
||||
- name: Start Appwrite
|
||||
run: |
|
||||
docker compose up -d
|
||||
sleep 30
|
||||
- name: Environment Variables
|
||||
run: docker compose exec -T appwrite vars
|
||||
|
||||
- name: Doctor
|
||||
run: |
|
||||
docker compose logs appwrite
|
||||
docker compose exec -T appwrite doctor
|
||||
- name: Run Unit Tests
|
||||
run: docker compose exec appwrite test /usr/src/code/tests/unit
|
||||
|
||||
- name: Environment Variables
|
||||
run: docker compose exec -T appwrite vars
|
||||
e2e_test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
service:
|
||||
[
|
||||
Account,
|
||||
Avatars,
|
||||
Console,
|
||||
Databases,
|
||||
Functions,
|
||||
GraphQL,
|
||||
Health,
|
||||
Locale,
|
||||
Projects,
|
||||
Realtime,
|
||||
Storage,
|
||||
Teams,
|
||||
Users,
|
||||
Webhooks,
|
||||
VCS,
|
||||
]
|
||||
|
||||
- name: Run Tests
|
||||
run: docker compose exec -T appwrite test --debug
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
|
||||
- name: Run ${{matrix.service}} Tests
|
||||
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,4 +1,4 @@
|
|||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = 3.2.3
|
||||
branch = 3.2.5
|
||||
|
|
|
@ -8,7 +8,7 @@ tasks:
|
|||
command: |
|
||||
docker run --rm --interactive --tty \
|
||||
--volume $PWD:/app \
|
||||
composer update \
|
||||
composer install \
|
||||
--ignore-platform-reqs \
|
||||
--optimize-autoloader \
|
||||
--no-plugins \
|
||||
|
|
32
CHANGES.md
32
CHANGES.md
|
@ -1,3 +1,35 @@
|
|||
# Version 1.4.9
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fix 400 error on function domain execution in [#7059](https://github.com/appwrite/appwrite/pull/7059)
|
||||
|
||||
# Version 1.4.8
|
||||
|
||||
## Notable changes
|
||||
|
||||
* Fix certificate emails and add support for variables in email template subject in [#6495](https://github.com/appwrite/appwrite/)pull/6495
|
||||
* Bump console to version 3.2.5 in [#7027](https://github.com/appwrite/appwrite/pull/7027)
|
||||
* Bump utopia database and storage versions in [#7002](https://github.com/appwrite/appwrite/pull/7002)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixes cookie headers not being passed properly by router in [#7024](https://github.com/appwrite/appwrite/pull/7024)
|
||||
* Fix permission problem in deletes worker in [#7013](https://github.com/appwrite/appwrite/pull/7013)
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
* Improve error handling in the realtime service in [#6998](https://github.com/appwrite/appwrite/pull/6998)
|
||||
* Update the error code for unsupported protocol in [#7006](https://github.com/appwrite/appwrite/pull/7006)
|
||||
* Improve CI tests by executing them in parallel in [#6198](https://github.com/appwrite/appwrite/pull/6198)
|
||||
* Update README.md to add links to orchestration tools in [#7011](https://github.com/appwrite/appwrite/pull/7011)
|
||||
* Update gitpod setup to install instead of update dependencies in [#6938](https://github.com/appwrite/appwrite/pull/6938)
|
||||
* Remove analytics from install script in [#7017](https://github.com/appwrite/appwrite/pull/7017)
|
||||
* Improve database logging in [#7003](https://github.com/appwrite/appwrite/pull/7003)
|
||||
* Add VCS tests in [#6894](https://github.com/appwrite/appwrite/pull/6894)
|
||||
* Improve error messages in [#6487](https://github.com/appwrite/appwrite/pull/6487)
|
||||
* Add command to delete orphaned projects in [#7015](https://github.com/appwrite/appwrite/pull/7015)
|
||||
|
||||
# Version 1.4.7
|
||||
|
||||
## Fixes
|
||||
|
|
|
@ -101,6 +101,7 @@ RUN chmod +x /usr/local/bin/hamster && \
|
|||
chmod +x /usr/local/bin/volume-sync && \
|
||||
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
|
||||
chmod +x /usr/local/bin/patch-delete-project-collections && \
|
||||
chmod +x /usr/local/bin/delete-orphaned-projects && \
|
||||
chmod +x /usr/local/bin/clear-card-cache && \
|
||||
chmod +x /usr/local/bin/calc-users-stats && \
|
||||
chmod +x /usr/local/bin/calc-tier-stats
|
||||
|
|
|
@ -66,7 +66,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.4.7
|
||||
appwrite/appwrite:1.4.9
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
@ -78,7 +78,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.4.7
|
||||
appwrite/appwrite:1.4.9
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
@ -88,7 +88,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.4.7
|
||||
appwrite/appwrite:1.4.9
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
|
|
@ -65,7 +65,7 @@ Table of Contents:
|
|||
|
||||
## Installation
|
||||
|
||||
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as Kubernetes, Docker Swarm, or Rancher.
|
||||
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as [Kubernetes](https://kubernetes.io/docs/home/), [Docker Swarm](https://docs.docker.com/engine/swarm/), or [Rancher](https://rancher.com/docs/).
|
||||
|
||||
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
|
||||
|
||||
|
@ -76,7 +76,7 @@ docker run -it --rm \
|
|||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.4.7
|
||||
appwrite/appwrite:1.4.9
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
@ -88,7 +88,7 @@ docker run -it --rm ^
|
|||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.4.7
|
||||
appwrite/appwrite:1.4.9
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
@ -98,7 +98,7 @@ docker run -it --rm `
|
|||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.4.7
|
||||
appwrite/appwrite:1.4.9
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
11
app/cli.php
11
app/cli.php
|
@ -61,7 +61,11 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
|||
->getResource();
|
||||
|
||||
$dbForConsole = new Database($dbAdapter, $cache);
|
||||
$dbForConsole->setNamespace('_console');
|
||||
|
||||
$dbForConsole
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
|
||||
// Ensure tables exist
|
||||
$collections = Config::getParam('collections', [])['console'];
|
||||
|
@ -111,7 +115,10 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
|||
|
||||
$databases[$databaseName] = $database;
|
||||
|
||||
$database->setNamespace('_' . $project->getInternalId());
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
};
|
||||
|
|
|
@ -86,7 +86,7 @@ return [
|
|||
Exception::GENERAL_PROTOCOL_UNSUPPORTED => [
|
||||
'name' => Exception::GENERAL_PROTOCOL_UNSUPPORTED,
|
||||
'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
|
||||
'code' => 500,
|
||||
'code' => 426,
|
||||
],
|
||||
Exception::GENERAL_CODES_DISABLED => [
|
||||
'name' => Exception::GENERAL_CODES_DISABLED,
|
||||
|
@ -365,7 +365,7 @@ return [
|
|||
],
|
||||
Exception::STORAGE_BUCKET_ALREADY_EXISTS => [
|
||||
'name' => Exception::STORAGE_BUCKET_ALREADY_EXISTS,
|
||||
'description' => 'A storage bucket with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'A storage bucket with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::STORAGE_BUCKET_NOT_FOUND => [
|
||||
|
@ -489,7 +489,7 @@ return [
|
|||
],
|
||||
Exception::COLLECTION_ALREADY_EXISTS => [
|
||||
'name' => Exception::COLLECTION_ALREADY_EXISTS,
|
||||
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::COLLECTION_LIMIT_EXCEEDED => [
|
||||
|
@ -521,7 +521,7 @@ return [
|
|||
],
|
||||
Exception::DOCUMENT_ALREADY_EXISTS => [
|
||||
'name' => Exception::DOCUMENT_ALREADY_EXISTS,
|
||||
'description' => 'Document with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_UPDATE_CONFLICT => [
|
||||
|
@ -563,7 +563,7 @@ return [
|
|||
],
|
||||
Exception::ATTRIBUTE_ALREADY_EXISTS => [
|
||||
'name' => Exception::ATTRIBUTE_ALREADY_EXISTS,
|
||||
'description' => 'Attribute with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
|
||||
|
@ -595,7 +595,7 @@ return [
|
|||
],
|
||||
Exception::INDEX_ALREADY_EXISTS => [
|
||||
'name' => Exception::INDEX_ALREADY_EXISTS,
|
||||
'description' => 'Index with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'Index with the requested key already exists. Try again with a different key.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::INDEX_INVALID => [
|
||||
|
@ -612,7 +612,7 @@ return [
|
|||
],
|
||||
Exception::PROJECT_ALREADY_EXISTS => [
|
||||
'name' => Exception::PROJECT_ALREADY_EXISTS,
|
||||
'description' => 'Project with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
|
||||
'description' => 'Project with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::PROJECT_UNKNOWN => [
|
||||
|
@ -743,6 +743,28 @@ return [
|
|||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Realtime */
|
||||
Exception::REALTIME_MESSAGE_FORMAT_INVALID => [
|
||||
'name' => Exception::REALTIME_MESSAGE_FORMAT_INVALID,
|
||||
'description' => 'Message format is not valid.',
|
||||
'code' => 1003,
|
||||
],
|
||||
Exception::REALTIME_POLICY_VIOLATION => [
|
||||
'name' => Exception::REALTIME_POLICY_VIOLATION,
|
||||
'description' => 'Policy violation.',
|
||||
'code' => 1008,
|
||||
],
|
||||
Exception::REALTIME_TOO_MANY_MESSAGES => [
|
||||
'name' => Exception::REALTIME_TOO_MANY_MESSAGES,
|
||||
'description' => 'Too many messages.',
|
||||
'code' => 1013,
|
||||
],
|
||||
Exception::MIGRATION_PROVIDER_ERROR => [
|
||||
'name' => Exception::MIGRATION_PROVIDER_ERROR,
|
||||
'description' => 'An error occurred on the provider\'s side. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Provider Errors */
|
||||
Exception::PROVIDER_NOT_FOUND => [
|
||||
'name' => Exception::PROVIDER_NOT_FOUND,
|
||||
|
@ -804,5 +826,6 @@ return [
|
|||
'name' => Exception::MESSAGE_ALREADY_SCHEDULED,
|
||||
'description' => 'Message with the requested ID has already been scheduled for delivery.',
|
||||
'code' => 400,
|
||||
]
|
||||
],
|
||||
|
||||
];
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
Subproject commit e9657389879c8d76a9b3a0d3486c1d86f43c3bb9
|
||||
Subproject commit 9810ce85812ca26c95b7d35196848c92e8ba813d
|
|
@ -1000,7 +1000,12 @@ App::post('/v1/account/sessions/magic-url')
|
|||
$customTemplate = $project->getAttribute('templates', [])['email.magicSession-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message->setParam('{{body}}', $body);
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.magicSession.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.magicSession.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.magicSession.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.magicSession.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
|
@ -1050,16 +1055,7 @@ App::post('/v1/account/sessions/magic-url')
|
|||
}
|
||||
|
||||
$emailVariables = [
|
||||
'subject' => $subject,
|
||||
'hello' => $locale->getText("emails.magicSession.hello"),
|
||||
'body' => $body,
|
||||
'footer' => $locale->getText("emails.magicSession.footer"),
|
||||
'thanks' => $locale->getText("emails.magicSession.thanks"),
|
||||
'signature' => $locale->getText("emails.magicSession.signature"),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'bg-body' => '#f7f7f7',
|
||||
'bg-content' => '#ffffff',
|
||||
'text-content' => '#000000',
|
||||
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
|
||||
'user' => '',
|
||||
'team' => '',
|
||||
|
@ -2483,7 +2479,12 @@ App::post('/v1/account/recovery')
|
|||
$customTemplate = $project->getAttribute('templates', [])['email.recovery-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message->setParam('{{body}}', $body);
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.recovery.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.recovery.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.recovery.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
|
@ -2533,16 +2534,7 @@ App::post('/v1/account/recovery')
|
|||
}
|
||||
|
||||
$emailVariables = [
|
||||
'subject' => $subject,
|
||||
'hello' => $locale->getText("emails.recovery.hello"),
|
||||
'body' => $body,
|
||||
'footer' => $locale->getText("emails.recovery.footer"),
|
||||
'thanks' => $locale->getText("emails.recovery.thanks"),
|
||||
'signature' => $locale->getText("emails.recovery.signature"),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'bg-body' => '#f7f7f7',
|
||||
'bg-content' => '#ffffff',
|
||||
'text-content' => '#000000',
|
||||
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
|
||||
'user' => $profile->getAttribute('name'),
|
||||
'team' => '',
|
||||
|
@ -2550,7 +2542,6 @@ App::post('/v1/account/recovery')
|
|||
'redirect' => $url
|
||||
];
|
||||
|
||||
|
||||
$queueForMails
|
||||
->setRecipient($profile->getAttribute('email', ''))
|
||||
->setName($profile->getAttribute('name'))
|
||||
|
@ -2735,7 +2726,12 @@ App::post('/v1/account/verification')
|
|||
$customTemplate = $project->getAttribute('templates', [])['email.verification-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message->setParam('{{body}}', $body);
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.verification.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.verification.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.verification.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.verification.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
|
@ -2785,16 +2781,7 @@ App::post('/v1/account/verification')
|
|||
}
|
||||
|
||||
$emailVariables = [
|
||||
'subject' => $subject,
|
||||
'hello' => $locale->getText("emails.verification.hello"),
|
||||
'body' => $body,
|
||||
'footer' => $locale->getText("emails.verification.footer"),
|
||||
'thanks' => $locale->getText("emails.verification.thanks"),
|
||||
'signature' => $locale->getText("emails.verification.signature"),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'bg-body' => '#f7f7f7',
|
||||
'bg-content' => '#ffffff',
|
||||
'text-content' => '#000000',
|
||||
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
|
||||
'user' => $user->getAttribute('name'),
|
||||
'team' => '',
|
||||
|
|
|
@ -46,6 +46,7 @@ use Utopia\Validator\Boolean;
|
|||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
include_once __DIR__ . '/../shared/api.php';
|
||||
|
||||
|
@ -58,7 +59,14 @@ $redeployVcs = function (Request $request, Document $function, Document $project
|
|||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$providerRepositoryId = $function->getAttribute('providerRepositoryId', '');
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$providerBranch = $function->getAttribute('providerBranch', 'main');
|
||||
$authorUrl = "https://github.com/$owner";
|
||||
$repositoryUrl = "https://github.com/$owner/$repositoryName";
|
||||
|
@ -287,9 +295,8 @@ App::post('/v1/functions')
|
|||
$ruleModel = new Rule();
|
||||
$ruleCreate =
|
||||
$queueForEvents
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
;
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME);
|
||||
|
||||
$ruleCreate
|
||||
->setProject($project)
|
||||
|
@ -870,8 +877,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
|||
->setContentType('application/gzip')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"')
|
||||
;
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
|
||||
$size = $deviceFunctions->getFileSize($path);
|
||||
$rangeHeader = $request->getHeader('range');
|
||||
|
@ -899,7 +905,6 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
|||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
$response->addHeader('Content-Length', $deviceFunctions->getFileSize($path));
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceFunctions->read(
|
||||
|
|
|
@ -87,22 +87,18 @@ App::get('/v1/health/db')
|
|||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
} else {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
$failure[] = $database;
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
'status' => 'fail',
|
||||
'ping' => \round((\microtime(true) - $checkStart) / 1000)
|
||||
]);
|
||||
$failure[] = $database;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($failure)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failure));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'statuses' => $output,
|
||||
'total' => count($output),
|
||||
|
@ -536,10 +532,10 @@ App::get('/v1/health/storage/local')
|
|||
|
||||
foreach (
|
||||
[
|
||||
'Uploads' => APP_STORAGE_UPLOADS,
|
||||
'Cache' => APP_STORAGE_CACHE,
|
||||
'Config' => APP_STORAGE_CONFIG,
|
||||
'Certs' => APP_STORAGE_CERTIFICATES
|
||||
'Uploads' => APP_STORAGE_UPLOADS,
|
||||
'Cache' => APP_STORAGE_CACHE,
|
||||
'Config' => APP_STORAGE_CONFIG,
|
||||
'Certs' => APP_STORAGE_CERTIFICATES
|
||||
] as $key => $volume
|
||||
) {
|
||||
$device = new Local($volume);
|
||||
|
@ -601,7 +597,7 @@ App::get('/v1/health/anti-virus')
|
|||
});
|
||||
|
||||
App::get('/v1/health/stats') // Currently only used internally
|
||||
->desc('Get system stats')
|
||||
->desc('Get system stats')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'root')
|
||||
// ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
|
|
|
@ -208,6 +208,16 @@ App::post('/v1/migrations/firebase')
|
|||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$serviceAccountData = json_decode($serviceAccount, true);
|
||||
|
||||
if (empty($serviceAccountData)) {
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
|
||||
}
|
||||
|
||||
if (!isset($serviceAccountData['project_id']) || !isset($serviceAccountData['client_email']) || !isset($serviceAccountData['private_key'])) {
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
|
||||
}
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
|
@ -449,15 +459,26 @@ App::get('/v1/migrations/appwrite/report')
|
|||
->inject('project')
|
||||
->inject('user')
|
||||
->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) {
|
||||
try {
|
||||
$appwrite = new Appwrite($projectID, $endpoint, $key);
|
||||
$appwrite = new Appwrite($projectID, $endpoint, $key);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($appwrite->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||
try {
|
||||
$report = $appwrite->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
|
||||
case 429:
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
|
||||
case 500:
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/report')
|
||||
|
@ -475,15 +496,36 @@ App::get('/v1/migrations/firebase/report')
|
|||
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
|
||||
->inject('response')
|
||||
->action(function (array $resources, string $serviceAccount, Response $response) {
|
||||
try {
|
||||
$firebase = new Firebase(json_decode($serviceAccount, true));
|
||||
$serviceAccount = json_decode($serviceAccount, true);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($firebase->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
if (empty($serviceAccount)) {
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
|
||||
}
|
||||
|
||||
if (!isset($serviceAccount['project_id']) || !isset($serviceAccount['client_email']) || !isset($serviceAccount['private_key'])) {
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
|
||||
}
|
||||
|
||||
$firebase = new Firebase($serviceAccount);
|
||||
|
||||
try {
|
||||
$report = $firebase->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
|
||||
case 429:
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
|
||||
case 500:
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/report/oauth')
|
||||
|
@ -869,15 +911,26 @@ App::get('/v1/migrations/supabase/report')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response) {
|
||||
try {
|
||||
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
|
||||
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($supabase->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
try {
|
||||
$report = $supabase->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
|
||||
case 429:
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
|
||||
case 500:
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/nhost/report')
|
||||
|
@ -901,15 +954,26 @@ App::get('/v1/migrations/nhost/report')
|
|||
->param('port', 5432, new Integer(true), 'Source\'s Database Port.', true)
|
||||
->inject('response')
|
||||
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response) {
|
||||
try {
|
||||
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
|
||||
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($nhost->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
try {
|
||||
$report = $nhost->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
switch ($e->getCode()) {
|
||||
case 401:
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
|
||||
case 429:
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
|
||||
case 500:
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::patch('/v1/migrations/:migrationId')
|
||||
|
|
|
@ -100,11 +100,6 @@ App::post('/v1/projects')
|
|||
$backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '02:00'];
|
||||
|
||||
$databases = Config::getParam('pools-database', []);
|
||||
$databaseSelfHosted = 'database_db_fra1_self_hosted_0_0';
|
||||
$selfHostedIndex = array_search($databaseSelfHosted, $databases);
|
||||
if ($selfHostedIndex !== false) {
|
||||
unset($databases[$selfHostedIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove databases from the list that are currently undergoing an backup
|
||||
|
@ -175,16 +170,6 @@ App::post('/v1/projects')
|
|||
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update database with self-managed db every $mod projects
|
||||
*/
|
||||
$mod = 20;
|
||||
if ($project->getInternalId() % $mod === 0 && $selfHostedIndex !== false) {
|
||||
$database = $databaseSelfHosted;
|
||||
$project->setAttribute('database', $database);
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project);
|
||||
}
|
||||
|
||||
$dbForProject = new Database($pools->get($database)->pop()->getResource(), $cache);
|
||||
$dbForProject->setNamespace("_{$project->getInternalId()}");
|
||||
$dbForProject->create();
|
||||
|
@ -1718,7 +1703,6 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
|||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
|
||||
->setParam('{{user}}', '')
|
||||
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
|
||||
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'))
|
||||
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
|
||||
|
|
|
@ -1109,7 +1109,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
|||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
$response->addHeader('Content-Length', $deviceFiles->getFileSize($path));
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceFiles->read(
|
||||
|
@ -1262,7 +1261,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
|||
|
||||
$size = $deviceFiles->getFileSize($path);
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
$response->addHeader('Content-Length', $deviceFiles->getFileSize($path));
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceFiles->read(
|
||||
|
|
|
@ -555,7 +555,12 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$customTemplate = $project->getAttribute('templates', [])['email.invitation-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message->setParam('{{body}}', $body);
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
|
@ -606,16 +611,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
|
||||
$emailVariables = [
|
||||
'owner' => $user->getAttribute('name'),
|
||||
'subject' => $subject,
|
||||
'hello' => $locale->getText("emails.invitation.hello"),
|
||||
'body' => $body,
|
||||
'footer' => $locale->getText("emails.invitation.footer"),
|
||||
'thanks' => $locale->getText("emails.invitation.thanks"),
|
||||
'signature' => $locale->getText("emails.invitation.signature"),
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'bg-body' => '#f7f7f7',
|
||||
'bg-content' => '#ffffff',
|
||||
'text-content' => '#000000',
|
||||
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
|
||||
'user' => $user->getAttribute('name'),
|
||||
'team' => $team->getAttribute('name'),
|
||||
|
|
|
@ -22,7 +22,6 @@ use Utopia\Database\Helpers\Permission;
|
|||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Detector\Adapter\Bun;
|
||||
use Utopia\Detector\Adapter\CPP;
|
||||
use Utopia\Detector\Adapter\Dart;
|
||||
|
@ -36,6 +35,7 @@ use Utopia\Detector\Adapter\Ruby;
|
|||
use Utopia\Detector\Adapter\Swift;
|
||||
use Utopia\Detector\Detector;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
|
@ -66,7 +66,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
}
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
|
@ -154,7 +161,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
$message = 'Authorization required for external contributor.';
|
||||
|
||||
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'failure', $message, $authorizeUrl, $name);
|
||||
continue;
|
||||
|
@ -206,7 +220,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
$message = 'Starting...';
|
||||
|
||||
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
|
||||
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/functions/function-$functionId";
|
||||
|
@ -273,7 +294,7 @@ App::get('/v1/vcs/github/callback')
|
|||
->label('scope', 'public')
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->param('installation_id', '', new Text(256, 0), 'GitHub installation ID', true)
|
||||
->param('setup_action', '', new Text(256, 0), 'GitHub setup actuon type', true)
|
||||
->param('setup_action', '', new Text(256, 0), 'GitHub setup action type', true)
|
||||
->param('state', '', new Text(2048), 'GitHub state. Contains info sent when starting authorization flow.', true)
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
|
||||
->inject('gitHub')
|
||||
|
@ -458,9 +479,12 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
|||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
@ -720,9 +744,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
|||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
@ -766,9 +793,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
|||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
@ -1090,7 +1120,14 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
|||
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
$pullRequestResponse = $github->getPullRequest($owner, $repositoryName, $providerPullRequestId);
|
||||
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
|
||||
|
|
|
@ -117,12 +117,14 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
|
|||
$path .= '?' . $query;
|
||||
}
|
||||
|
||||
$requestHeaders = $request->getHeaders();
|
||||
|
||||
$body = \json_encode([
|
||||
'async' => false,
|
||||
'body' => $swooleRequest->getContent() ?? '',
|
||||
'method' => $swooleRequest->server['request_method'],
|
||||
'path' => $path,
|
||||
'headers' => $swooleRequest->header
|
||||
'headers' => $requestHeaders
|
||||
]);
|
||||
|
||||
$headers = [
|
||||
|
|
|
@ -9,506 +9,13 @@ use Appwrite\Utopia\Request;
|
|||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Storage\Validator\File;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
App::get('/v1/mock/tests/foo')
|
||||
->desc('Get Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'get')
|
||||
->label('sdk.description', 'Mock a get request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/foo')
|
||||
->desc('Post Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'post')
|
||||
->label('sdk.description', 'Mock a post request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::patch('/v1/mock/tests/foo')
|
||||
->desc('Patch Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'patch')
|
||||
->label('sdk.description', 'Mock a patch request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::put('/v1/mock/tests/foo')
|
||||
->desc('Put Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'put')
|
||||
->label('sdk.description', 'Mock a put request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::delete('/v1/mock/tests/foo')
|
||||
->desc('Delete Foo')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'foo')
|
||||
->label('sdk.method', 'delete')
|
||||
->label('sdk.description', 'Mock a delete request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($x, $y, $z) {
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/bar')
|
||||
->desc('Get Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'get')
|
||||
->label('sdk.description', 'Mock a get request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/bar')
|
||||
->desc('Post Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'post')
|
||||
->label('sdk.description', 'Mock a post request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.offline.model', '/mock/tests/bar')
|
||||
->label('sdk.offline.key', '{required}')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::patch('/v1/mock/tests/bar')
|
||||
->desc('Patch Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'patch')
|
||||
->label('sdk.description', 'Mock a patch request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::put('/v1/mock/tests/bar')
|
||||
->desc('Put Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'put')
|
||||
->label('sdk.description', 'Mock a put request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::delete('/v1/mock/tests/bar')
|
||||
->desc('Delete Bar')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'bar')
|
||||
->label('sdk.method', 'delete')
|
||||
->label('sdk.description', 'Mock a delete request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('default', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->action(function ($required, $default, $z) {
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/headers')
|
||||
->desc('Get headers')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'headers')
|
||||
->label('sdk.description', 'Return headers from the request')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$res = [
|
||||
'x-sdk-name' => $request->getHeader('x-sdk-name'),
|
||||
'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
|
||||
'x-sdk-language' => $request->getHeader('x-sdk-language'),
|
||||
'x-sdk-version' => $request->getHeader('x-sdk-version'),
|
||||
];
|
||||
$res = array_map(function ($key, $value) {
|
||||
return $key . ': ' . $value;
|
||||
}, array_keys($res), $res);
|
||||
$res = implode("; ", $res);
|
||||
|
||||
$response->dynamic(new Document(['result' => $res]), Response::MODEL_MOCK);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/download')
|
||||
->desc('Download File')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'download')
|
||||
->label('sdk.methodType', 'location')
|
||||
->label('sdk.description', 'Mock a file download request.')
|
||||
->label('sdk.response.type', '*/*')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response
|
||||
->setContentType('text/plain')
|
||||
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->send("GET:/v1/mock/tests/general/download:passed")
|
||||
;
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/upload')
|
||||
->desc('Upload File')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'upload')
|
||||
->label('sdk.description', 'Mock a file upload request.')
|
||||
->label('sdk.request.type', 'multipart/form-data')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->param('x', '', new Text(100), 'Sample string param')
|
||||
->param('y', '', new Integer(true), 'Sample numeric param')
|
||||
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
|
||||
->param('file', [], new File(), 'Sample file param', skipValidation: true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (string $x, int $y, array $z, mixed $file, Request $request, Response $response) {
|
||||
|
||||
$file = $request->getFiles('file');
|
||||
|
||||
$contentRange = $request->getHeader('content-range');
|
||||
|
||||
$chunkSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
if (!empty($contentRange)) {
|
||||
$start = $request->getContentRangeStart();
|
||||
$end = $request->getContentRangeEnd();
|
||||
$size = $request->getContentRangeSize();
|
||||
$id = $request->getHeader('x-appwrite-id', '');
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
if (is_null($start) || is_null($end) || is_null($size) || $end >= $size) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
|
||||
}
|
||||
|
||||
if ($start > $end || $end > $size) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
|
||||
}
|
||||
|
||||
if ($start === 0 && !empty($id)) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'First chunked request cannot have id header');
|
||||
}
|
||||
|
||||
if ($start !== 0 && $id !== 'newfileid') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'All chunked request must have id header (except first)');
|
||||
}
|
||||
|
||||
if ($end !== $size - 1 && $end - $start + 1 !== $chunkSize) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB (except last chunk)');
|
||||
}
|
||||
|
||||
if ($end !== $size - 1 && $file['size'] !== $chunkSize) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong chunk size');
|
||||
}
|
||||
|
||||
if ($file['size'] > $chunkSize) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB or less');
|
||||
}
|
||||
|
||||
if ($end !== $size - 1) {
|
||||
$response->json([
|
||||
'$id' => ID::custom('newfileid'),
|
||||
'chunksTotal' => (int) ceil($size / ($end + 1 - $start)),
|
||||
'chunksUploaded' => ceil($start / $chunkSize) + 1
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'][0] : $file['tmp_name'];
|
||||
$file['name'] = (\is_array($file['name'])) ? $file['name'][0] : $file['name'];
|
||||
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
|
||||
|
||||
if ($file['name'] !== 'file.png') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file name');
|
||||
}
|
||||
|
||||
if ($file['size'] !== 38756) {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file size');
|
||||
}
|
||||
|
||||
if (\md5(\file_get_contents($file['tmp_name'])) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file uploaded');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/redirect')
|
||||
->desc('Redirect')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'redirect')
|
||||
->label('sdk.description', 'Mock a redirect request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->redirect('/v1/mock/tests/general/redirect/done');
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/redirect/done')
|
||||
->desc('Redirected')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'redirected')
|
||||
->label('sdk.description', 'Mock a redirected request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/set-cookie')
|
||||
->desc('Set Cookie')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'setCookie')
|
||||
->label('sdk.description', 'Mock a set cookie request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->action(function (Response $response, Request $request) {
|
||||
|
||||
$response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', $request->getHostname(), true, true);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/get-cookie')
|
||||
->desc('Get Cookie')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'getCookie')
|
||||
->label('sdk.description', 'Mock a cookie response.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->action(function (Request $request) {
|
||||
|
||||
if ($request->getCookie('cookieName', '') !== 'cookieValue') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Missing cookie value');
|
||||
}
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/empty')
|
||||
->desc('Empty Response')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'empty')
|
||||
->label('sdk.description', 'Mock an empty response.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/nullable')
|
||||
->desc('Nullable Test')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'nullable')
|
||||
->label('sdk.description', 'Mock a nullable parameter.')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('nullable', '', new Nullable(new Text(100)), 'Sample string param')
|
||||
->param('optional', '', new Text(100), 'Sample string param', true)
|
||||
->action(function (string $required, string $nullable, ?string $optional) {
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/enum')
|
||||
->desc('Enum Test')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'enum')
|
||||
->label('sdk.description', 'Mock an enum parameter.')
|
||||
->label('sdk.mock', true)
|
||||
->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample enum param')
|
||||
->action(function (string $mockType) {
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/400-error')
|
||||
->desc('400 Error')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'error400')
|
||||
->label('sdk.description', 'Mock a 400 failed request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_BAD_REQUEST)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ERROR)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Mock 400 error');
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/500-error')
|
||||
->desc('500 Error')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'error500')
|
||||
->label('sdk.description', 'Mock a 500 failed request.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_INTERNAL_SERVER_ERROR)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ERROR)
|
||||
->label('sdk.mock', true)
|
||||
->action(function () {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Mock 500 error', 500);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/502-error')
|
||||
->desc('502 Error')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'error502')
|
||||
->label('sdk.description', 'Mock a 502 bad gateway.')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_BAD_GATEWAY)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_TEXT)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('sdk.mock', true)
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
|
||||
$response
|
||||
->setStatusCode(502)
|
||||
->text('This is a text error')
|
||||
;
|
||||
});
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2')
|
||||
->desc('OAuth Login')
|
||||
|
@ -646,6 +153,66 @@ App::patch('/v1/mock/functions-v2')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/mock/github/callback')
|
||||
->desc('Create installation document using GitHub installation id')
|
||||
->groups(['mock', 'api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('providerInstallationId', '', new UID(), 'GitHub installation ID')
|
||||
->param('projectId', '', new UID(), 'Project ID of the project where app is to be installed')
|
||||
->inject('gitHub')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) {
|
||||
$isDevelopment = App::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
$error = 'Project with the ID from state could not be found.';
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
|
||||
}
|
||||
|
||||
if (!empty($providerInstallationId)) {
|
||||
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
|
||||
$projectInternalId = $project->getInternalId();
|
||||
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$installation = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'providerInstallationId' => $providerInstallationId,
|
||||
'projectId' => $projectId,
|
||||
'projectInternalId' => $projectInternalId,
|
||||
'provider' => 'github',
|
||||
'organization' => $owner,
|
||||
'personal' => false
|
||||
]);
|
||||
|
||||
$installation = $dbForConsole->createDocument('installations', $installation);
|
||||
}
|
||||
|
||||
$response->json([
|
||||
'installationId' => $installation->getId(),
|
||||
]);
|
||||
});
|
||||
|
||||
App::shutdown()
|
||||
->groups(['mock'])
|
||||
->inject('utopia')
|
||||
|
|
15
app/init.php
15
app/init.php
|
@ -105,8 +105,8 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
|
|||
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 = 514;
|
||||
const APP_VERSION_STABLE = '1.4.7';
|
||||
const APP_CACHE_BUSTER = 515;
|
||||
const APP_VERSION_STABLE = '1.4.9';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
@ -1080,6 +1080,9 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
}
|
||||
}
|
||||
|
||||
$dbForProject->setMetadata('user', $user->getId());
|
||||
$dbForConsole->setMetadata('user', $user->getId());
|
||||
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
|
||||
|
||||
|
@ -1153,6 +1156,8 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
|
|||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
return $database;
|
||||
|
@ -1169,6 +1174,8 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
|
|||
|
||||
$database
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
return $database;
|
||||
|
@ -1189,6 +1196,8 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
|||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
return $database;
|
||||
|
@ -1205,6 +1214,8 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
|||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
return $database;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
|
@ -20,7 +22,6 @@ use Utopia\Database\DateTime;
|
|||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
|
@ -28,6 +29,9 @@ use Utopia\Database\Database;
|
|||
use Utopia\WebSocket\Server;
|
||||
use Utopia\WebSocket\Adapter;
|
||||
|
||||
/**
|
||||
* @var \Utopia\Registry\Registry $register
|
||||
*/
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
@ -47,7 +51,10 @@ function getConsoleDB(): Database
|
|||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
|
||||
$database->setNamespace('_console');
|
||||
$database
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', '_console');
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
@ -70,7 +77,11 @@ function getProjectDB(Document $project): Database
|
|||
;
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
$database->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
$database
|
||||
->setNamespace('_' . $project->getInternalId())
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
@ -122,12 +133,12 @@ $server = new Server($adapter);
|
|||
$logError = function (Throwable $error, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger) {
|
||||
if ($logger && !$error instanceof Exception) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace("realtime");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setServer(gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
@ -182,7 +193,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
break;
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable) {
|
||||
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
|
||||
sleep(DATABASE_RECONNECT_SLEEP);
|
||||
}
|
||||
|
@ -210,7 +221,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
|||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable $th) {
|
||||
call_user_func($logError, $th, "updateWorkerDocument");
|
||||
} finally {
|
||||
$register->get('pools')->reclaim();
|
||||
|
@ -362,7 +373,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
$stats->incr($event['project'], 'messages', $num);
|
||||
}
|
||||
});
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable $th) {
|
||||
call_user_func($logError, $th, "pubSubConnection");
|
||||
|
||||
Console::error('Pub/sub error: ' . $th->getMessage());
|
||||
|
@ -389,19 +400,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
App::setResource('response', fn() => $response);
|
||||
|
||||
try {
|
||||
/** @var \Utopia\Database\Document $project */
|
||||
/** @var Document $project */
|
||||
$project = $app->getResource('project');
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
*/
|
||||
if (empty($project->getId())) {
|
||||
throw new Exception('Missing or unknown project ID', 1008);
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID');
|
||||
}
|
||||
|
||||
$dbForProject = getProjectDB($project);
|
||||
$console = $app->getResource('console'); /** @var \Utopia\Database\Document $console */
|
||||
$user = $app->getResource('user'); /** @var \Utopia\Database\Document $user */
|
||||
$console = $app->getResource('console'); /** @var Document $console */
|
||||
$user = $app->getResource('user'); /** @var Document $user */
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
|
@ -416,7 +427,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
$abuse = new Abuse($timeLimit);
|
||||
|
||||
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
|
||||
throw new Exception('Too many requests', 1013);
|
||||
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many requests');
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -425,10 +436,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
* Skip this check for non-web platforms which are not required to send an origin header.
|
||||
*/
|
||||
$origin = $request->getOrigin();
|
||||
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
|
||||
$originValidator = new Origin(array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
|
||||
|
||||
if (!$originValidator->isValid($origin) && $project->getId() !== 'console') {
|
||||
throw new Exception($originValidator->getDescription(), 1008);
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
}
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
|
@ -439,7 +450,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
* Channels Check
|
||||
*/
|
||||
if (empty($channels)) {
|
||||
throw new Exception('Missing channels', 1008);
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
|
||||
}
|
||||
|
||||
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
|
||||
|
@ -460,7 +471,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
]);
|
||||
$stats->incr($project->getId(), 'connections');
|
||||
$stats->incr($project->getId(), 'connectionsTotal');
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable $th) {
|
||||
call_user_func($logError, $th, "initServer");
|
||||
|
||||
$response = [
|
||||
|
@ -486,7 +497,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
|
||||
try {
|
||||
$app = new App('UTC');
|
||||
$response = new Response(new SwooleResponse());
|
||||
$projectId = $realtime->connections[$connection]['projectId'];
|
||||
$database = getConsoleDB();
|
||||
|
@ -494,6 +504,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
|||
if ($projectId !== 'console') {
|
||||
$project = Authorization::skip(fn() => $database->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
} else {
|
||||
$project = null;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -510,22 +522,22 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
|||
$abuse = new Abuse($timeLimit);
|
||||
|
||||
if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
|
||||
throw new Exception('Too many messages', 1013);
|
||||
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many messages.');
|
||||
}
|
||||
|
||||
$message = json_decode($message, true);
|
||||
|
||||
if (is_null($message) || (!array_key_exists('type', $message) && !array_key_exists('data', $message))) {
|
||||
throw new Exception('Message format is not valid.', 1003);
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message format is not valid.');
|
||||
}
|
||||
|
||||
switch ($message['type']) {
|
||||
/**
|
||||
/**
|
||||
* This type is used to authenticate.
|
||||
*/
|
||||
case 'authentication':
|
||||
if (!array_key_exists('session', $message['data'])) {
|
||||
throw new Exception('Payload is not valid.', 1003);
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
}
|
||||
|
||||
$session = Auth::decodeSession($message['data']['session']);
|
||||
|
@ -540,7 +552,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
|||
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration) // Validate user has valid login token
|
||||
) {
|
||||
// cookie not valid
|
||||
throw new Exception('Session is not valid.', 1003);
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
|
||||
}
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
|
@ -560,9 +572,9 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
|||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Message type is not valid.', 1003);
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
} catch (Throwable $th) {
|
||||
$response = [
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
|
|
|
@ -35,7 +35,6 @@ use Utopia\Queue\Connection;
|
|||
Authorization::disable();
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
|
||||
Server::setResource('register', fn () => $register);
|
||||
|
||||
Server::setResource('dbForConsole', function (Cache $cache, Registry $register) {
|
||||
|
|
3
bin/delete-orphaned-projects
Normal file
3
bin/delete-orphaned-projects
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php delete-orphaned-projects $@
|
|
@ -64,9 +64,9 @@
|
|||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.5.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.17.*",
|
||||
"utopia-php/storage": "0.18.*",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
"utopia-php/vcs": "0.5.*",
|
||||
"utopia-php/vcs": "0.6.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
"matomo/device-detector": "6.1.*",
|
||||
"dragonmantank/cron-expression": "3.3.2",
|
||||
|
|
38
composer.lock
generated
38
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "2faed5529f24984975ab6a85194c966e",
|
||||
"content-hash": "9afc62ce9c6ba587b9c028e11494e026",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
@ -2318,16 +2318,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "0.3.5",
|
||||
"version": "0.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83"
|
||||
"reference": "f78273b38bade23db5866e5c7cb5f55427ba82af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83",
|
||||
"reference": "b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/f78273b38bade23db5866e5c7cb5f55427ba82af",
|
||||
"reference": "f78273b38bade23db5866e5c7cb5f55427ba82af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2360,9 +2360,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/0.3.5"
|
||||
"source": "https://github.com/utopia-php/migration/tree/0.3.6"
|
||||
},
|
||||
"time": "2023-09-25T16:51:47+00:00"
|
||||
"time": "2023-11-02T15:13:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
|
@ -2742,16 +2742,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/storage",
|
||||
"version": "0.17.0",
|
||||
"version": "0.18.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/storage.git",
|
||||
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee"
|
||||
"reference": "983e6dee137012f9f57f126d3c79aab54e4e8824"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/efec5376c02d3d8330f1beb1469e6d6e313e21ee",
|
||||
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/983e6dee137012f9f57f126d3c79aab54e4e8824",
|
||||
"reference": "983e6dee137012f9f57f126d3c79aab54e4e8824",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2791,9 +2791,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/storage/issues",
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.17.0"
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.18.1"
|
||||
},
|
||||
"time": "2023-08-21T11:28:36+00:00"
|
||||
"time": "2023-10-24T14:44:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/swoole",
|
||||
|
@ -2904,16 +2904,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/vcs",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/vcs.git",
|
||||
"reference": "47144f272030b7ed1b05471f2cb3aabeb8cb831c"
|
||||
"reference": "f135291b87cb45335fc6608722e7f89894bc33ee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/47144f272030b7ed1b05471f2cb3aabeb8cb831c",
|
||||
"reference": "47144f272030b7ed1b05471f2cb3aabeb8cb831c",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/f135291b87cb45335fc6608722e7f89894bc33ee",
|
||||
"reference": "f135291b87cb45335fc6608722e7f89894bc33ee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2947,9 +2947,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/vcs/issues",
|
||||
"source": "https://github.com/utopia-php/vcs/tree/0.5.0"
|
||||
"source": "https://github.com/utopia-php/vcs/tree/0.6.2"
|
||||
},
|
||||
"time": "2023-09-13T19:05:52+00:00"
|
||||
"time": "2023-11-08T15:36:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/websocket",
|
||||
|
|
|
@ -225,6 +225,12 @@ class Exception extends \Exception
|
|||
public const MIGRATION_NOT_FOUND = 'migration_not_found';
|
||||
public const MIGRATION_ALREADY_EXISTS = 'migration_already_exists';
|
||||
public const MIGRATION_IN_PROGRESS = 'migration_in_progress';
|
||||
public const MIGRATION_PROVIDER_ERROR = 'migration_provider_error';
|
||||
|
||||
/** Realtime */
|
||||
public const REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid';
|
||||
public const REALTIME_TOO_MANY_MESSAGES = 'realtime_too_many_messages';
|
||||
public const REALTIME_POLICY_VIOLATION = 'realtime_policy_violation';
|
||||
|
||||
/** Provider */
|
||||
public const PROVIDER_NOT_FOUND = 'provider_not_found';
|
||||
|
@ -245,8 +251,8 @@ class Exception extends \Exception
|
|||
public const MESSAGE_ALREADY_SENT = 'message_already_sent';
|
||||
public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled';
|
||||
|
||||
protected $type = '';
|
||||
protected $errors = [];
|
||||
protected string $type = '';
|
||||
protected array $errors = [];
|
||||
|
||||
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
|
||||
{
|
||||
|
|
|
@ -72,6 +72,8 @@ abstract class Migration
|
|||
'1.4.5' => 'V19',
|
||||
'1.4.6' => 'V19',
|
||||
'1.4.7' => 'V19',
|
||||
'1.4.8' => 'V19',
|
||||
'1.4.9' => 'V19',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,6 +18,7 @@ use Appwrite\Platform\Tasks\Version;
|
|||
use Appwrite\Platform\Tasks\VolumeSync;
|
||||
use Appwrite\Platform\Tasks\CalcTierStats;
|
||||
use Appwrite\Platform\Tasks\Upgrade;
|
||||
use Appwrite\Platform\Tasks\DeleteOrphanedProjects;
|
||||
|
||||
class Tasks extends Service
|
||||
{
|
||||
|
@ -40,6 +41,8 @@ class Tasks extends Service
|
|||
->addAction(VolumeSync::getName(), new VolumeSync())
|
||||
->addAction(Specs::getName(), new Specs())
|
||||
->addAction(CalcTierStats::getName(), new CalcTierStats())
|
||||
->addAction(DeleteOrphanedProjects::getName(), new DeleteOrphanedProjects())
|
||||
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
115
src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php
Normal file
115
src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
class DeleteOrphanedProjects extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'delete-orphaned-projects';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this
|
||||
->desc('Get stats for projects')
|
||||
->inject('pools')
|
||||
->inject('cache')
|
||||
->inject('dbForConsole')
|
||||
->inject('register')
|
||||
->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
|
||||
$this->action($pools, $cache, $dbForConsole, $register);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
|
||||
{
|
||||
|
||||
Console::title('Delete orphaned projects V1');
|
||||
Console::success(APP_NAME . ' Delete orphaned projects started');
|
||||
|
||||
/** @var array $collections */
|
||||
$collectionsConfig = Config::getParam('collections', [])['projects'] ?? [];
|
||||
|
||||
/* Initialise new Utopia app */
|
||||
$app = new App('UTC');
|
||||
$console = $app->getResource('console');
|
||||
$projects = [$console];
|
||||
|
||||
/** Database connections */
|
||||
$totalProjects = $dbForConsole->count('projects');
|
||||
Console::success("Found a total of: {$totalProjects} projects");
|
||||
|
||||
$orphans = 0;
|
||||
$count = 0;
|
||||
$limit = 30;
|
||||
$sum = 30;
|
||||
$offset = 0;
|
||||
while (!empty($projects)) {
|
||||
foreach ($projects as $project) {
|
||||
|
||||
/**
|
||||
* Skip user projects with id 'console'
|
||||
*/
|
||||
if ($project->getId() === 'console') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = $project->getAttribute('database');
|
||||
$adapter = $pools
|
||||
->get($db)
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setDefaultDatabase('appwrite');
|
||||
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||
$collectionsCreated = $dbForProject->count(Database::METADATA);
|
||||
$message = ' (' . $collectionsCreated . ') collections where found on project (' . $project->getId() . '))';
|
||||
if ($collectionsCreated < (count($collectionsConfig) + 2)) {
|
||||
Console::error($message);
|
||||
$orphans++;
|
||||
} else {
|
||||
Console::log($message);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
//$dbForConsole->deleteDocument('projects', $project->getId());
|
||||
//Console::success('Deleting project (' . $project->getId() . ')');
|
||||
Console::error(' (0) collections where found for project (' . $project->getId() . ')');
|
||||
$orphans++;
|
||||
} finally {
|
||||
$pools
|
||||
->get($db)
|
||||
->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
$sum = \count($projects);
|
||||
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]);
|
||||
|
||||
$offset = $offset + $limit;
|
||||
$count = $count + $sum;
|
||||
}
|
||||
|
||||
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans . ' orphans');
|
||||
}
|
||||
}
|
|
@ -6,9 +6,6 @@ use Appwrite\Auth\Auth;
|
|||
use Appwrite\Docker\Compose;
|
||||
use Appwrite\Docker\Env;
|
||||
use Appwrite\Utopia\View;
|
||||
use Utopia\Analytics\Adapter;
|
||||
use Utopia\Analytics\Adapter\GoogleAnalytics;
|
||||
use Utopia\Analytics\Event;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Validator\Text;
|
||||
|
@ -43,12 +40,6 @@ class Install extends Action
|
|||
/** @var array<string, array<string, string>> $vars array whre key is variable name and value is variable */
|
||||
$vars = [];
|
||||
|
||||
/**
|
||||
* We are using a random value every execution for identification.
|
||||
* This allows us to collect information without invading the privacy of our users.
|
||||
*/
|
||||
$analytics = new GoogleAnalytics('UA-26264668-9', uniqid('server.', true));
|
||||
|
||||
foreach ($config as $category) {
|
||||
foreach ($category['variables'] ?? [] as $var) {
|
||||
$vars[$var['name']] = $var;
|
||||
|
@ -82,7 +73,7 @@ class Install extends Action
|
|||
file_put_contents($this->path . '/docker-compose.yml.' . $time . '.backup', $data);
|
||||
$compose = new Compose($data);
|
||||
$appwrite = $compose->getService('appwrite');
|
||||
$oldVersion = ($appwrite) ? $appwrite->getImageVersion() : null;
|
||||
$oldVersion = $appwrite?->getImageVersion();
|
||||
try {
|
||||
$ports = $compose->getService('traefik')->getPorts();
|
||||
} catch (\Throwable $th) {
|
||||
|
@ -209,14 +200,12 @@ class Install extends Action
|
|||
|
||||
if (!file_put_contents($this->path . '/docker-compose.yml', $templateForCompose->render(false))) {
|
||||
$message = 'Failed to save Docker Compose file';
|
||||
$this->sendEvent($analytics, $message);
|
||||
Console::error($message);
|
||||
Console::exit(1);
|
||||
}
|
||||
|
||||
if (!file_put_contents($this->path . '/.env', $templateForEnv->render(false))) {
|
||||
$message = 'Failed to save environment variables file';
|
||||
$this->sendEvent($analytics, $message);
|
||||
Console::error($message);
|
||||
Console::exit(1);
|
||||
}
|
||||
|
@ -237,29 +226,12 @@ class Install extends Action
|
|||
|
||||
if ($exit !== 0) {
|
||||
$message = 'Failed to install Appwrite dockers';
|
||||
$this->sendEvent($analytics, $message);
|
||||
Console::error($message);
|
||||
Console::error($stderr);
|
||||
Console::exit($exit);
|
||||
} else {
|
||||
$message = 'Appwrite installed successfully';
|
||||
$this->sendEvent($analytics, $message);
|
||||
Console::success($message);
|
||||
}
|
||||
}
|
||||
|
||||
private function sendEvent(Adapter $analytics, string $message): void
|
||||
{
|
||||
$event = new Event();
|
||||
$event->setName(APP_VERSION_STABLE);
|
||||
$event->setValue($message);
|
||||
$event->setUrl('http://localhost/');
|
||||
$event->setProps([
|
||||
'category' => 'install/server',
|
||||
'action' => 'install',
|
||||
]);
|
||||
$event->setType('install/server');
|
||||
|
||||
$analytics->createEvent($event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -426,29 +426,31 @@ class Certificates extends Action
|
|||
$locale->setDefault('en');
|
||||
}
|
||||
|
||||
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
|
||||
|
||||
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
|
||||
$body
|
||||
->setParam('{{domain}}', $domain)
|
||||
->setParam('{{error}}', $errorMessage)
|
||||
->setParam('{{attempt}}', $attempt)
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{body}}', $locale->getText("emails.certificate.body"))
|
||||
->setParam('{{redirect}}', 'https://' . $domain)
|
||||
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.certificate.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.certificate.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"))
|
||||
->setParam('{{project}}', 'Console')
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"));
|
||||
$body = $message->render();
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'domain' => $domain,
|
||||
'error' => '<br><pre>' . $errorMessage . '</pre>',
|
||||
'attempt' => $attempt,
|
||||
'project' => 'Console',
|
||||
'redirect' => 'https://' . $domain,
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setRecipient(App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'))
|
||||
->setBody($body->render())
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setVariables($emailVariables)
|
||||
->setName('Appwrite Administrator')
|
||||
->trigger();
|
||||
}
|
||||
|
|
|
@ -58,13 +58,21 @@ class Mails extends Action
|
|||
$subject = $payload['subject'];
|
||||
$variables = $payload['variables'];
|
||||
$name = $payload['name'];
|
||||
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
|
||||
$body = $payload['body'];
|
||||
|
||||
$bodyTemplate = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
|
||||
$bodyTemplate->setParam('{{body}}', $body);
|
||||
foreach ($variables as $key => $value) {
|
||||
$body->setParam('{{' . $key . '}}', $value);
|
||||
$bodyTemplate->setParam('{{' . $key . '}}', $value);
|
||||
}
|
||||
$body = $bodyTemplate->render();
|
||||
|
||||
$body = $body->render();
|
||||
$subjectTemplate = Template::fromString($subject);
|
||||
foreach ($variables as $key => $value) {
|
||||
$subjectTemplate->setParam('{{' . $key . '}}', $value);
|
||||
}
|
||||
// render() will return the subject in <p> tags, so use strip_tags() to remove them
|
||||
$subject = \strip_tags($subjectTemplate->render());
|
||||
|
||||
/** @var PHPMailer $mail */
|
||||
$mail = empty($smtp)
|
||||
|
|
|
@ -95,4 +95,46 @@ class Request extends UtopiaRequest
|
|||
{
|
||||
return self::$route != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers
|
||||
*
|
||||
* Method for getting all HTTP header parameters, including cookies.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
$headers = $this->generateHeaders();
|
||||
|
||||
if (empty($this->swoole->cookie)) {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
$cookieHeaders = [];
|
||||
foreach ($this->swoole->cookie as $key => $value) {
|
||||
$cookieHeaders[] = "{$key}={$value}";
|
||||
}
|
||||
|
||||
if (!empty($cookieHeaders)) {
|
||||
$headers['cookie'] = \implode('; ', $cookieHeaders);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header
|
||||
*
|
||||
* Method for querying HTTP header parameters. If $key is not found $default value will be returned.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $default
|
||||
* @return string
|
||||
*/
|
||||
public function getHeader(string $key, string $default = ''): string
|
||||
{
|
||||
$headers = $this->getHeaders();
|
||||
return $headers[$key] ?? $default;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,8 @@ trait ProjectCustom
|
|||
'locale.read',
|
||||
'avatars.read',
|
||||
'health.read',
|
||||
'rules.read',
|
||||
'rules.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'providers.read',
|
||||
|
@ -90,7 +92,7 @@ trait ProjectCustom
|
|||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read'
|
||||
'subscribers.read',
|
||||
],
|
||||
]);
|
||||
|
||||
|
|
|
@ -1344,4 +1344,192 @@ class FunctionsCustomServerTest extends Scope
|
|||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCookieExecution()
|
||||
{
|
||||
$timeout = 5;
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz";
|
||||
$this->packageCode('php-cookie');
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test PHP Cookie executions',
|
||||
'runtime' => 'php-8.0',
|
||||
'entrypoint' => 'index.php',
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||
|
||||
// Poll until deployment is built
|
||||
while (true) {
|
||||
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
if (
|
||||
$deployment['headers']['status-code'] >= 400
|
||||
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
\sleep(1);
|
||||
}
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
|
||||
// Wait a little for activation to finish
|
||||
sleep(5);
|
||||
|
||||
$cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5';
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'async' => false,
|
||||
'headers' => [
|
||||
'cookie' => $cookie
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $execution['headers']['status-code']);
|
||||
$this->assertEquals('completed', $execution['body']['status']);
|
||||
$this->assertEquals(200, $execution['body']['responseStatusCode']);
|
||||
$this->assertEquals($cookie, $execution['body']['responseBody']);
|
||||
|
||||
// Cleanup : Delete function
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testFunctionsDomain()
|
||||
{
|
||||
$timeout = 5;
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz";
|
||||
$this->packageCode('php-cookie');
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test PHP Cookie executions',
|
||||
'runtime' => 'php-8.0',
|
||||
'entrypoint' => 'index.php',
|
||||
'timeout' => $timeout,
|
||||
'execute' => ['any']
|
||||
]);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [ 'equal("resourceId", "' . $functionId . '")', 'equal("resourceType", "function")' ]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(1, $rules['body']['total']);
|
||||
$this->assertCount(1, $rules['body']['rules']);
|
||||
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
|
||||
|
||||
$domain = $rules['body']['rules'][0]['domain'];
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||
|
||||
// Poll until deployment is built
|
||||
while (true) {
|
||||
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
if (
|
||||
$deployment['headers']['status-code'] >= 400
|
||||
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
\sleep(1);
|
||||
}
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
|
||||
// Wait a little for activation to finish
|
||||
sleep(5);
|
||||
|
||||
$cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => $cookie
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($cookie, $response['body']);
|
||||
|
||||
// Cleanup : Delete function
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Utopia\CLI\Console;
|
||||
|
||||
trait Base
|
||||
{
|
||||
// Databases
|
||||
|
@ -2456,4 +2458,13 @@ trait Base
|
|||
|
||||
throw new \InvalidArgumentException('Invalid query type');
|
||||
}
|
||||
|
||||
// Function-related methods
|
||||
protected string $stdout = '';
|
||||
protected string $stderr = '';
|
||||
|
||||
protected function packageCode($folder)
|
||||
{
|
||||
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,11 @@ class FunctionsClientTest extends Scope
|
|||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php/code.tar.gz";
|
||||
|
||||
$folder = 'php';
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
|
||||
$this->packageCode($folder);
|
||||
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
|
|
|
@ -81,7 +81,11 @@ class FunctionsServerTest extends Scope
|
|||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php/code.tar.gz";
|
||||
|
||||
$folder = 'php';
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
|
||||
$this->packageCode($folder);
|
||||
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
|
|
17
tests/e2e/Services/VCS/VCSBase.php
Normal file
17
tests/e2e/Services/VCS/VCSBase.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\VCS;
|
||||
|
||||
use Utopia\App;
|
||||
|
||||
trait VCSBase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY') === 'disabled') {
|
||||
$this->markTestSkipped('VCS is not enabled.');
|
||||
}
|
||||
}
|
||||
}
|
319
tests/e2e/Services/VCS/VCSConsoleClientTest.php
Normal file
319
tests/e2e/Services/VCS/VCSConsoleClientTest.php
Normal file
|
@ -0,0 +1,319 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\VCS;
|
||||
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\Cache\Adapter\None;
|
||||
use Utopia\Cache\Cache;
|
||||
|
||||
class VCSConsoleClientTest extends Scope
|
||||
{
|
||||
use VCSBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
|
||||
public string $providerInstallationId = '42954928';
|
||||
public string $providerRepositoryId = '705764267';
|
||||
public string $providerRepositoryId2 = '708688544';
|
||||
|
||||
public function testGitHubAuthorize(): string
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/mock/github/callback', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'providerInstallationId' => $this->providerInstallationId,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($response['body']['installationId']);
|
||||
$installationId = $response['body']['installationId'];
|
||||
return $installationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testGetInstallation(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$installation = $this->client->call(Client::METHOD_GET, '/vcs/installations/' . $installationId, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $installation['headers']['status-code']);
|
||||
$this->assertEquals('github', $installation['body']['provider']);
|
||||
$this->assertEquals('appwrite-test', $installation['body']['organization']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testDetectRuntime(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/detection', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $runtime['headers']['status-code']);
|
||||
$this->assertEquals($runtime['body']['runtime'], 'ruby-3.1');
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId/detection', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $runtime['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testListRepositories(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $repositories['headers']['status-code']);
|
||||
$this->assertEquals($repositories['body']['total'], 3);
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['name'], 'function1.4');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['provider'], 'github');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][1]['name'], 'appwrite');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][2]['name'], 'ruby-starter');
|
||||
|
||||
|
||||
$searchedRepositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => 'func'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $searchedRepositories['headers']['status-code']);
|
||||
$this->assertEquals($searchedRepositories['body']['total'], 1);
|
||||
$this->assertEquals($searchedRepositories['body']['providerRepositories'][0]['name'], 'function1.4');
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/randomInstallationId/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $repositories['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testGetRepository(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $repository['headers']['status-code']);
|
||||
$this->assertEquals($repository['body']['name'], 'ruby-starter');
|
||||
$this->assertEquals($repository['body']['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repository['body']['private'], false);
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId2, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $repository['headers']['status-code']);
|
||||
$this->assertEquals($repository['body']['name'], 'function1.4');
|
||||
$this->assertEquals($repository['body']['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repository['body']['private'], true);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $repository['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testListRepositoryBranches(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $repositoryBranches['headers']['status-code']);
|
||||
$this->assertEquals($repositoryBranches['body']['total'], 2);
|
||||
$this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'main');
|
||||
$this->assertEquals($repositoryBranches['body']['branches'][1]['name'], 'test');
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId/branches', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $repositoryBranches['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testCreateFunctionUsingVCS(string $installationId): array
|
||||
{
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => [Role::user($this->getUser()['$id'])->toString()],
|
||||
'runtime' => 'php-8.0',
|
||||
'entrypoint' => 'index.php',
|
||||
'events' => [
|
||||
'users.*.create',
|
||||
'users.*.delete',
|
||||
],
|
||||
'schedule' => '0 0 1 1 *',
|
||||
'timeout' => 10,
|
||||
'installationId' => $installationId,
|
||||
'providerRepositoryId' => $this->providerRepositoryId,
|
||||
'providerBranch' => 'main',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertEquals('Test', $function['body']['name']);
|
||||
$this->assertEquals('php-8.0', $function['body']['runtime']);
|
||||
$this->assertEquals('index.php', $function['body']['entrypoint']);
|
||||
$this->assertEquals('705764267', $function['body']['providerRepositoryId']);
|
||||
$this->assertEquals('main', $function['body']['providerBranch']);
|
||||
|
||||
return [
|
||||
'installationId' => $installationId,
|
||||
'functionId' => $function['body']['$id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunctionUsingVCS
|
||||
*/
|
||||
public function testUpdateFunctionUsingVCS(array $data): string
|
||||
{
|
||||
$function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => [Role::user($this->getUser()['$id'])->toString()],
|
||||
'runtime' => 'php-8.0',
|
||||
'entrypoint' => 'index.php',
|
||||
'events' => [
|
||||
'users.*.create',
|
||||
'users.*.delete',
|
||||
],
|
||||
'schedule' => '0 0 1 1 *',
|
||||
'timeout' => 10,
|
||||
'installationId' => $data['installationId'],
|
||||
'providerRepositoryId' => $this->providerRepositoryId2,
|
||||
'providerBranch' => 'main',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertEquals('Test', $function['body']['name']);
|
||||
$this->assertEquals('php-8.0', $function['body']['runtime']);
|
||||
$this->assertEquals('index.php', $function['body']['entrypoint']);
|
||||
$this->assertEquals('708688544', $function['body']['providerRepositoryId']);
|
||||
$this->assertEquals('main', $function['body']['providerBranch']);
|
||||
|
||||
return $function['body']['$id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testCreateRepository(string $installationId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$github = new GitHub(new Cache(new None()));
|
||||
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($this->providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'test-repo-1',
|
||||
'private' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals('test-repo-1', $repository['body']['name']);
|
||||
$this->assertEquals('appwrite-test', $repository['body']['organization']);
|
||||
$this->assertEquals('github', $repository['body']['provider']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$repository = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'test-repo-1',
|
||||
'private' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $repository['headers']['status-code']);
|
||||
$this->assertEquals('Provider Error: Repository creation failed. name already exists on this account', $repository['body']['message']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$result = $github->deleteRepository('appwrite-test', 'test-repo-1');
|
||||
$this->assertEquals($result, true);
|
||||
}
|
||||
}
|
24
tests/e2e/Services/VCS/VCSCustomClientTest.php
Normal file
24
tests/e2e/Services/VCS/VCSCustomClientTest.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\VCS;
|
||||
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Client;
|
||||
|
||||
class VCSCustomClientTest extends Scope
|
||||
{
|
||||
use VCSBase;
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
|
||||
public function testGetInstallation(string $installationId = 'randomString'): void
|
||||
{
|
||||
$installation = $this->client->call(Client::METHOD_GET, '/vcs/installations/' . $installationId, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(401, $installation['headers']['status-code']);
|
||||
}
|
||||
}
|
25
tests/e2e/Services/VCS/VCSCustomServerTest.php
Normal file
25
tests/e2e/Services/VCS/VCSCustomServerTest.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\VCS;
|
||||
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Tests\E2E\Client;
|
||||
|
||||
class VCSCustomServerTest extends Scope
|
||||
{
|
||||
use VCSBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
public function testGetInstallation(string $installationId = 'randomString'): void
|
||||
{
|
||||
$installation = $this->client->call(Client::METHOD_GET, '/vcs/installations/' . $installationId, array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(401, $installation['headers']['status-code']);
|
||||
}
|
||||
}
|
5
tests/resources/functions/php-cookie/index.php
Normal file
5
tests/resources/functions/php-cookie/index.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
return function ($context) {
|
||||
return $context->res->send($context->req->headers['cookie'] ?? '');
|
||||
};
|
Loading…
Reference in a new issue