Compare commits

..
201 changed files with 2438 additions and 2380 deletions
+1
View File
@@ -39,6 +39,7 @@ _APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
_APP_REDIS_PASS=
_APP_REDIS_USER=
COMPOSE_PROFILES=mariadb,mongodb,postgresql
_APP_DB_ADAPTER=mongodb
_APP_DB_HOST=mongodb
_APP_DB_PORT=27017
+139 -78
View File
@@ -174,46 +174,22 @@ jobs:
outputs:
databases: ${{ steps.generate.outputs.databases }}
modes: ${{ steps.generate.outputs.modes }}
services: ${{ steps.generate.outputs.services }}
steps:
- name: Generate matrix
id: generate
uses: actions/github-script@v8
with:
script: |
const databases = [
{ name: 'MariaDB', env: '.env,compose/mariadb.env', compose: 'docker-compose.yml:compose/mariadb.yml' },
{ name: 'PostgreSQL', env: '.env,compose/postgresql.env', compose: 'docker-compose.yml:compose/postgresql.yml' },
{ name: 'MongoDB', env: '.env', compose: 'docker-compose.yml' },
];
const modes = ['dedicated', 'shared_v1', 'shared_v2'];
const allDatabases = ['MariaDB', 'PostgreSQL', 'MongoDB'];
const allModes = ['dedicated', 'shared_v1', 'shared_v2'];
const services = [
{ name: 'Account', parallel: true },
{ name: 'Avatars', parallel: true, runner: 'blacksmith-4vcpu-ubuntu-2404' },
{ name: 'Databases', runner: 'blacksmith-4vcpu-ubuntu-2404' },
{ name: 'TablesDB', runner: 'blacksmith-4vcpu-ubuntu-2404' },
{ name: 'Functions', runner: 'blacksmith-4vcpu-ubuntu-2404' },
{ name: 'FunctionsSchedule', parallel: true },
{ name: 'GraphQL', parallel: true },
{ name: 'Projects', parallel: true },
{ name: 'Realtime', runner: 'blacksmith-4vcpu-ubuntu-2404' },
{ name: 'Sites', parallel: true, runner: 'blacksmith-4vcpu-ubuntu-2404' },
{ name: 'Teams', parallel: true },
{ name: 'Users', parallel: true },
{ name: 'ProjectWebhooks', parallel: true },
{ name: 'Migrations', parallel: true },
{ name: 'Project', parallel: true },
];
core.setOutput('services', JSON.stringify(services));
const defaultDatabases = [databases.find(d => d.name === 'MongoDB')];
const defaultDatabases = ['MongoDB'];
const defaultModes = ['dedicated'];
const pr = context.payload.pull_request;
if (!pr) {
core.setOutput('databases', JSON.stringify(databases));
core.setOutput('modes', JSON.stringify(modes));
core.setOutput('databases', JSON.stringify(allDatabases));
core.setOutput('modes', JSON.stringify(allModes));
return;
}
@@ -234,8 +210,8 @@ jobs:
const decode = (content) => JSON.parse(Buffer.from(content, 'base64').toString());
const databaseChanged = getDbVersion(decode(base.content)) !== getDbVersion(decode(head.content));
core.setOutput('databases', JSON.stringify(databaseChanged ? databases : defaultDatabases));
core.setOutput('modes', JSON.stringify(databaseChanged ? modes : defaultModes));
core.setOutput('databases', JSON.stringify(databaseChanged ? allDatabases : defaultDatabases));
core.setOutput('modes', JSON.stringify(databaseChanged ? allModes : defaultModes));
build:
name: Build
@@ -312,29 +288,28 @@ jobs:
run: docker compose exec -T appwrite vars
- name: Run Unit Tests
timeout-minutes: 15
run: >-
docker compose exec
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/unit
uses: itznotabug/php-retry@v3
with:
max_attempts: 2
retry_wait_seconds: 60
timeout_minutes: 15
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/unit
command: >-
docker compose exec
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/unit
e2e_general:
name: Tests / E2E / ${{ matrix.database.name }} (${{ matrix.mode }}) / General
name: Tests / E2E / General
runs-on: ubuntu-latest
needs: [build, matrix]
env:
COMPOSE_FILE: ${{ matrix.database.compose }}
COMPOSE_ENV_FILES: ${{ matrix.database.env }}
needs: build
permissions:
contents: read
pull-requests: write
strategy:
fail-fast: false
matrix:
database: ${{ fromJSON(needs.matrix.outputs.databases) }}
mode: ${{ fromJSON(needs.matrix.outputs.modes) }}
steps:
- name: Checkout repository
- name: checkout
uses: actions/checkout@v6
- name: Download Docker Image
@@ -351,10 +326,6 @@ jobs:
- name: Load and Start Appwrite
timeout-minutes: 5
env:
_APP_BROWSER_HOST: http://invalid-browser/v1
_APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }}
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }}
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose pull --quiet --ignore-buildable
@@ -368,12 +339,19 @@ jobs:
sleep 1
done
- name: Run tests
timeout-minutes: 20
run: >-
docker compose exec -T
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite vendor/bin/paratest --processes $(nproc) --functional --testsuite GeneralGroup --exclude-group abuseEnabled --exclude-group screenshots
- name: Run General Tests
uses: itznotabug/php-retry@v3
with:
max_attempts: 2
retry_wait_seconds: 60
timeout_minutes: 15
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/General
command: >-
docker compose exec -T
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/e2e/General
- name: Failure Logs
if: failure()
@@ -382,12 +360,9 @@ jobs:
docker compose logs
e2e_service:
name: Tests / E2E / ${{ matrix.database.name }} (${{ matrix.mode }}) / ${{ matrix.service.name }}
runs-on: ${{ matrix.service.runner || 'ubuntu-latest' }}
name: Tests / E2E / ${{ matrix.database }} (${{ matrix.mode }}) / ${{ matrix.service }}
runs-on: ${{ matrix.runner || 'ubuntu-latest' }}
needs: [build, matrix]
env:
COMPOSE_FILE: ${{ matrix.database.compose }}
COMPOSE_ENV_FILES: ${{ matrix.database.env }}
permissions:
contents: read
pull-requests: write
@@ -396,7 +371,45 @@ jobs:
matrix:
database: ${{ fromJSON(needs.matrix.outputs.databases) }}
mode: ${{ fromJSON(needs.matrix.outputs.modes) }}
service: ${{ fromJSON(needs.matrix.outputs.services) }}
service: [
Account,
Avatars,
Console,
Databases,
TablesDB,
Functions,
FunctionsSchedule,
GraphQL,
Health,
Locale,
Projects,
Realtime,
Sites,
Proxy,
Storage,
Tokens,
Teams,
Users,
ProjectWebhooks,
Webhooks,
VCS,
Messaging,
Migrations,
Project
]
include:
- service: Databases
runner: blacksmith-4vcpu-ubuntu-2404
- service: Sites
runner: blacksmith-4vcpu-ubuntu-2404
- service: Functions
runner: blacksmith-4vcpu-ubuntu-2404
- service: Avatars
runner: blacksmith-4vcpu-ubuntu-2404
- service: Realtime
runner: blacksmith-4vcpu-ubuntu-2404
- service: TablesDB
runner: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v6
@@ -407,6 +420,25 @@ jobs:
name: ${{ env.IMAGE }}
path: /tmp
- name: Set database environment
run: |
if [ "${{ matrix.database }}" = "MariaDB" ]; then
echo "COMPOSE_PROFILES=mariadb" >> $GITHUB_ENV
echo "_APP_DB_ADAPTER=mariadb" >> $GITHUB_ENV
echo "_APP_DB_HOST=mariadb" >> $GITHUB_ENV
echo "_APP_DB_PORT=3306" >> $GITHUB_ENV
elif [ "${{ matrix.database }}" = "MongoDB" ]; then
echo "COMPOSE_PROFILES=mongodb" >> $GITHUB_ENV
echo "_APP_DB_ADAPTER=mongodb" >> $GITHUB_ENV
echo "_APP_DB_HOST=mongodb" >> $GITHUB_ENV
echo "_APP_DB_PORT=27017" >> $GITHUB_ENV
elif [ "${{ matrix.database }}" = "PostgreSQL" ]; then
echo "COMPOSE_PROFILES=postgresql" >> $GITHUB_ENV
echo "_APP_DB_ADAPTER=postgresql" >> $GITHUB_ENV
echo "_APP_DB_HOST=postgresql" >> $GITHUB_ENV
echo "_APP_DB_PORT=5432" >> $GITHUB_ENV
fi
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
@@ -433,11 +465,26 @@ jobs:
done
- name: Run tests
timeout-minutes: 20
run: |
docker compose exec -T \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) ${{ matrix.service.parallel && '--functional' || '' }} --testsuite ${{ matrix.service.name }} --exclude-group abuseEnabled --exclude-group screenshots
uses: itznotabug/php-retry@v3
with:
max_attempts: 2
retry_wait_seconds: 60
timeout_minutes: 20
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/Services/${{ matrix.service }}
command: |
SERVICE_PATH="/usr/src/code/tests/e2e/Services/${{ matrix.service }}"
# Services that rely on sequential test method execution (shared static state)
FUNCTIONAL_FLAG="--functional"
case "${{ matrix.service }}" in
Databases|TablesDB|Functions|Realtime) FUNCTIONAL_FLAG="" ;;
esac
docker compose exec -T \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite vendor/bin/paratest --processes $(nproc) $FUNCTIONAL_FLAG "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots --log-junit tests/e2e/Services/${{ matrix.service }}/junit.xml
- name: Failure Logs
if: failure()
@@ -484,11 +531,18 @@ jobs:
docker compose up -d --quiet-pull --wait
- name: Run tests
timeout-minutes: 15
run: >-
docker compose exec -T
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/e2e --group=abuseEnabled
uses: itznotabug/php-retry@v3
with:
max_attempts: 2
retry_wait_seconds: 60
timeout_minutes: 15
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e
command: >-
docker compose exec -T
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/e2e --group=abuseEnabled
- name: Failure Logs
if: failure()
@@ -542,11 +596,18 @@ jobs:
done
- name: Run tests
timeout-minutes: 15
run: >-
docker compose exec -T
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/e2e/Services/Sites --group=screenshots
uses: itznotabug/php-retry@v3
with:
max_attempts: 2
retry_wait_seconds: 60
timeout_minutes: 15
job_id: ${{ job.check_run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}
test_dir: tests/e2e/Services/Sites
command: >-
docker compose exec -T
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}"
appwrite test /usr/src/code/tests/e2e/Services/Sites --group=screenshots
- name: Failure Logs
if: failure()
+86 -99
View File
@@ -1,120 +1,107 @@
# Appwrite
# AGENTS.md
Self-hosted Backend-as-a-Service platform. Hybrid monolithic-microservice architecture built with PHP 8.3+ on Swoole, delivered as Docker containers.
Appwrite is an end-to-end backend server for web, mobile, native, and backend apps. This guide provides context and instructions for AI coding agents working on the Appwrite codebase.
## Commands
## Project Overview
| Command | Purpose |
|---------|---------|
| `docker compose up -d --force-recreate --build` | Build and start all services |
| `docker compose exec appwrite test tests/e2e/Services/[Service]` | Run E2E tests for a service |
| `docker compose exec appwrite test tests/e2e/Services/[Service] --filter=[Method]` | Run a single test method |
| `docker compose exec appwrite test tests/unit/` | Run unit tests |
| `composer format` | Auto-format code (Pint, PSR-12) |
| `composer format <file>` | Format a specific file |
| `composer lint <file>` | Check formatting of a file |
| `composer analyze` | Static analysis (PHPStan level 3) |
| `composer check` | Same as `analyze` |
Appwrite is a self-hosted Backend-as-a-Service (BaaS) platform that provides developers with a set of APIs and tools to build secure, scalable applications. The project uses a hybrid monolithic-microservice architecture built with PHP, running on Swoole for high performance.
## Stack
**Key Technologies:**
- **Backend:** PHP 8.3+, Swoole
- **Libraries:** Utopia PHP
- **Database:** MariaDB, Redis
- **Cache:** Redis
- **Queue:** Redis
- **Containers:** Docker
- PHP 8.3+, Swoole 6.x (async runtime, replaces PHP-FPM)
- Utopia PHP framework (HTTP routing, CLI, DI, queue)
- MongoDB (default), MariaDB, PostgreSQL (adapters via utopia-php/database; use `compose/mariadb.yml` or `compose/postgresql.yml` overlays to switch)
- Redis (cache, queue, pub/sub)
- Docker + Traefik (reverse proxy)
- PHPUnit 12, Pint (PSR-12), PHPStan level 3
## Development Commands
## Project layout
```bash
# Run Appwrite
docker compose up -d --force-recreate --build
- **src/Appwrite/Platform/Modules/** -- feature modules (Account, Avatars, Compute, Console, Databases, Functions, Health, Project, Projects, Proxy, Sites, Storage, Teams, Tokens, VCS, Webhooks)
- **src/Appwrite/Platform/Workers/** -- background job workers
- **src/Appwrite/Platform/Tasks/** -- CLI tasks
- **app/init.php** -- bootstrap (registers services, resources, listeners)
- **app/init/** -- configs, constants, locales, models, registers, resources, span, database filters/formats
- **bin/** -- CLI entry points: `worker-*` (14 workers), `schedule-*`, `queue-*`, plus `doctor`, `install`, `migrate`, `realtime`, `upgrade`, `ssl`, `vars`, `maintenance`, `interval`, `specs`, `sdks`, etc.
- **tests/e2e/** -- end-to-end tests per service
- **tests/unit/** -- unit tests
- **public/** -- static assets and generated SDKs
# Run specific test
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] --filter=[FunctionName]
## Module structure
Each module under `src/Appwrite/Platform/Modules/{Name}/` contains:
```
Module.php -- registers all services for the module
Services/Http.php -- registers HTTP endpoints
Services/Workers.php -- registers background workers
Services/Tasks.php -- registers CLI tasks
Http/{Service}/ -- endpoint actions (Create.php, Get.php, Update.php, Delete.php, XList.php)
Workers/ -- worker implementations
Tasks/ -- CLI task implementations
# Format code
composer format
```
HTTP endpoint nesting reflects the URL path. Sub-resources get subdirectories. For example, within the Functions module:
`Http/Deployments/Template/Create.php` -> `POST /v1/functions/:functionId/deployments/template`
## Code Style Guidelines
File names in Http directories must only be `Get.php`, `Create.php`, `Update.php`, `Delete.php`, or `XList.php`. For non-CRUD operations, model the endpoint as a property update. For example, updating a team membership status lives at `Teams/Http/Memberships/Status/Update.php` (`PATCH /v1/teams/:teamId/memberships/:membershipId/status`).
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) coding standard
- Use PSR-4 autoloading
- Strict type declarations where applicable
- Comprehensive PHPDoc comments
Register new modules in `src/Appwrite/Platform/Appwrite.php`. Detailed module guide: `src/Appwrite/Platform/AGENTS.md`.
### Naming Conventions
## Action pattern (HTTP endpoints)
#### `resourceType` Naming Rule
When a collection has a combination of `resourceType`, `resourceId`, and/or `resourceInternalId`, the value of `resourceType` MUST always be **plural** - for example: `functions`, `sites`, `deployments`.
Examples:
```php
class Create extends Action
{
public static function getName(): string { return 'createTeam'; }
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/teams')
->desc('Create team')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].create')
->label('scope', 'teams.write')
->param('teamId', '', new CustomId(), 'Team ID.')
->param('name', null, new Text(128), 'Team name.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
}
public function action(
string $teamId,
string $name,
Response $response,
Database $dbForProject,
Event $queueForEvents,
): void {
// implementation
}
}
'resourceType' => 'functions'
'resourceType' => 'sites'
'resourceType' => 'deployments'
```
Common injections: `$response`, `$request`, `$dbForProject`, `$dbForPlatform`, `$user`, `$project`, `$queueForEvents`, `$queueForMails`, `$queueForDeletes`.
## Performance Patterns
## Conventions
### Document Update Optimization
- PSR-12 formatting enforced by Pint. PSR-4 autoloading.
- `resourceType` values are always **plural**: `'functions'`, `'sites'`, `'deployments'`.
- When updating documents, pass only changed attributes as a sparse Document:
```php
// correct
$dbForProject->updateDocument('users', $user->getId(), new Document([
'name' => $name,
]));
// incorrect -- passing full document is inefficient
$user->setAttribute('name', $name);
$dbForProject->updateDocument('users', $user->getId(), $user);
```
Exceptions: migrations, `array_merge()` with `getArrayCopy()`, updates where nearly all attributes change, complex nested relationship logic requiring full document state.
- Avoid introducing dependencies outside the `utopia-php` ecosystem.
- Never hardcode credentials -- use environment variables.
- Code changes may require container restart. No central log location -- check relevant containers.
When updating documents, always pass only the changed attributes as a sparse `Document` rather than the full document. This is more efficient because `updateDocument()` internally performs `array_merge($old, $new)`.
## Cross-repo context
**Correct Pattern:**
```php
// Good: Pass only changed attributes directly
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
'name' => $name,
'email' => $email,
]));
```
Appwrite is the base server for `appwrite/cloud`. Changes to the Action pattern, module structure, DI system, or response models affect cloud. The `feat-dedicated-db` feature spans cloud, edge, and console.
**Incorrect Pattern:**
```php
$user->setAttribute('name', $name);
$user->setAttribute('email', $email);
// Bad: Passing full document is inefficient
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
```
**Exceptions:**
- Migration files (need full document updates by design)
- Cases already using `array_merge()` with `getArrayCopy()`
- Updates where almost all attributes of the document change at once (sparse update provides little benefit compared to passing the full document)
- Complex nested relationship logic where full document state is required
## Security Considerations
### Critical Security Practices
- **Never hardcode credentials** - Use environment variables
- **Rate limiting** - Respect abuse prevention mechanisms
## Dependencies
Avoid introducing new dependencies other than utopia-php.
## Adding new endpoints
When adding new endpoints, make sure to use modules and follow its patterns. Find instruction in [Modules AGENTS.md](src/Appwrite/Platform/AGENTS.md) file.
## Pull Request Guidelines
### Before Submitting
- Run `composer format`
- Update documentation if adding features
- Add/update tests for your changes
- Check that Docker build succeeds
`docs/specs/authentication.drawio.svg`
## Known Issues and Gotchas
- **Hot Reload:** Code changes require container restart in some cases
- **Logging:** There is no central place for logs, so when debugging, ensure to check all possibly relevant containers
+1 -1
View File
@@ -296,7 +296,7 @@ Appwrite stack is a combination of a variety of open-source technologies and too
### Other Technologies
- Redis - for managing cache and in-memory data (currently, we do not use Redis for persistent data).
- MongoDB - default database for storage and queries (MariaDB and PostgreSQL also supported via compose overlays).
- MariaDB - for database storage and queries.
- InfluxDB - for managing stats and time-series based data
- Statsd - for sending data over UDP protocol (using Telegraf)
- ClamAV - for validating and scanning storage files.
+1 -1
View File
@@ -64,7 +64,7 @@ return [
[
'$id' => ID::custom('database'),
'type' => Database::VAR_STRING,
'size' => 2000,
'size' => 128,
'required' => false,
'signed' => true,
'array' => false,
+24 -35
View File
@@ -250,16 +250,26 @@ return [
],
],
],
],
],
APP_SDK_PLATFORM_STATIC => [
'key' => APP_SDK_PLATFORM_STATIC,
'name' => 'Static',
'description' => 'SDK artifacts for Appwrite integrations that do not require a generated platform API specification.',
'enabled' => true,
'beta' => false,
'sdks' => [
[
'key' => 'markdown',
'name' => 'Markdown',
'version' => '0.3.0',
'url' => 'https://github.com/appwrite/sdk-for-md.git',
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'markdown',
'source' => \realpath(__DIR__ . '/../sdks/console-md'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-md.git',
'gitRepoName' => 'sdk-for-md',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/md/CHANGELOG.md'),
],
[
'key' => 'agent-skills',
'name' => 'AgentSkills',
@@ -269,10 +279,9 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'spec' => 'static',
'family' => APP_SDK_PLATFORM_STATIC,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'agent-skills',
'source' => \realpath(__DIR__ . '/../sdks/static-agent-skills'),
'source' => \realpath(__DIR__ . '/../sdks/console-agent-skills'),
'gitUrl' => 'git@github.com:appwrite/agent-skills.git',
'gitRepoName' => 'agent-skills',
'gitUserName' => 'appwrite',
@@ -289,10 +298,9 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'spec' => 'static',
'family' => APP_SDK_PLATFORM_STATIC,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'cursor-plugin',
'source' => \realpath(__DIR__ . '/../sdks/static-cursor-plugin'),
'source' => \realpath(__DIR__ . '/../sdks/console-cursor-plugin'),
'gitUrl' => 'git@github.com:appwrite/cursor-plugin.git',
'gitRepoName' => 'cursor-plugin',
'gitUserName' => 'appwrite',
@@ -486,25 +494,6 @@ return [
'gitBranch' => 'dev',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/swift/CHANGELOG.md'),
],
[
'key' => 'rust',
'name' => 'Rust',
'version' => '0.1.0',
'url' => 'https://github.com/appwrite/sdk-for-rust',
'package' => 'https://crates.io/crates/appwrite',
'enabled' => true,
'beta' => true,
'dev' => true,
'hidden' => false,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'rust',
'source' => \realpath(__DIR__ . '/../sdks/server-rust'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-rust.git',
'gitRepoName' => 'sdk-for-rust',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/rust/CHANGELOG.md'),
],
[
'key' => 'graphql',
'name' => 'GraphQL',
+2 -8
View File
@@ -28,18 +28,12 @@ use Utopia\Validator\Text;
Http::init()
->groups(['graphql'])
->inject('project')
->inject('user')
->inject('request')
->inject('response')
->inject('authorization')
->action(function (Document $project, User $user, Request $request, Response $response, Authorization $authorization) {
$response->setUser($user);
$request->setUser($user);
->action(function (Document $project, Authorization $authorization) {
if (
array_key_exists('graphql', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['graphql']
&& !($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
+3 -4
View File
@@ -24,6 +24,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Targets;
use Appwrite\Utopia\Database\Validator\Queries\Topics;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\Async\Promise;
use Utopia\Audit\Audit;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -55,8 +56,6 @@ use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use function Swoole\Coroutine\batch;
Http::post('/v1/messaging/providers/mailgun')
->desc('Create Mailgun provider')
->groups(['api', 'messaging'])
@@ -2918,7 +2917,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
$subscribers = Promise::map(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
return function () use ($subscriber, $dbForProject, $authorization) {
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
@@ -2927,7 +2926,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
->setAttribute('target', $target)
->setAttribute('userName', $user->getAttribute('name'));
};
}, $subscribers));
}, $subscribers))->await();
$response
->dynamic(new Document([
+14 -6
View File
@@ -4,6 +4,7 @@ use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Async\Promise;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
@@ -126,20 +127,19 @@ Http::get('/v1/project/usage')
};
$authorization->skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
$tasks = [];
foreach ($metrics['total'] as $metric) {
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
$result = $db->findOne('stats', [
$tasks['total_' . $metric] = fn () => $db->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$total[$metric] = $result['value'] ?? 0;
}
foreach ($metrics['period'] as $metric) {
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
$results = $db->find('stats', [
$tasks['period_' . $metric] = fn () => $db->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::greaterThanEqual('time', $firstDay),
@@ -147,9 +147,17 @@ Http::get('/v1/project/usage')
Query::limit($limit),
Query::orderDesc('time'),
]);
}
$results = Promise::map($tasks)->await();
foreach ($metrics['total'] as $metric) {
$total[$metric] = $results['total_' . $metric]['value'] ?? 0;
}
foreach ($metrics['period'] as $metric) {
$stats[$metric] = [];
foreach ($results as $result) {
foreach ($results['period_' . $metric] as $result) {
$stats[$metric][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
+15 -8
View File
@@ -28,6 +28,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Users;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\Async\Promise;
use Utopia\Audit\Audit;
use Utopia\Auth\Hash;
use Utopia\Auth\Hashes\Argon2;
@@ -2750,23 +2751,29 @@ Http::get('/v1/users/usage')
];
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $count => $metric) {
$result = $dbForProject->findOne('stats', [
$limit = $days['limit'];
$period = $days['period'];
$tasks = [];
foreach ($metrics as $metric) {
$tasks[$metric . '_total'] = fn () => $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats', [
$tasks[$metric . '_data'] = fn () => $dbForProject->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
}
$results = Promise::map($tasks)->await();
foreach ($metrics as $metric) {
$stats[$metric]['total'] = $results[$metric . '_total']['value'] ?? 0;
$stats[$metric]['data'] = [];
foreach ($results as $result) {
foreach ($results[$metric . '_data'] as $result) {
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
+1 -10
View File
@@ -1270,16 +1270,7 @@ Http::error()
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
*/
if (!$publish && $project->getId() !== 'console') {
$errorUser = new DBUser();
try {
$resolvedUser = $utopia->getResource('user');
if ($resolvedUser instanceof DBUser) {
$errorUser = $resolvedUser;
}
} catch (\Throwable) {
// User resource may not be available in error context
}
if (!$errorUser->isPrivileged($authorization->getRoles())) {
if (!DBUser::isPrivileged($authorization->getRoles())) {
$bus->dispatch(new RequestCompleted(
project: $project->getArrayCopy(),
request: $request,
+11 -14
View File
@@ -96,7 +96,7 @@ Http::init()
->inject('team')
->inject('apiKey')
->inject('authorization')
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
$route = $utopia->getRoute();
/**
@@ -419,7 +419,7 @@ Http::init()
if (
array_key_exists($namespace, $project->getAttribute('services', []))
&& ! $project->getAttribute('services', [])[$namespace]
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
&& ! (User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
}
@@ -483,10 +483,7 @@ Http::init()
->inject('telemetry')
->inject('platform')
->inject('authorization')
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Context $usage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
$response->setUser($user);
$request->setUser($user);
->action(function (Http $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Context $usage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
$route = $utopia->getRoute();
$path = $route->getMatchedPath();
@@ -499,7 +496,7 @@ Http::init()
if (
array_key_exists('rest', $project->getAttribute('apis', []))
&& ! $project->getAttribute('apis', [])['rest']
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
&& ! (User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -531,8 +528,8 @@ Http::init()
$closestLimit = null;
$roles = $authorization->getRoles();
$isPrivilegedUser = $user->isPrivileged($roles);
$isAppUser = $user->isApp($roles);
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
@@ -614,7 +611,7 @@ Http::init()
if ($useCache) {
$route = $utopia->match($request);
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && ! $user->isPrivileged($authorization->getRoles());
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && ! User::isPrivileged($authorization->getRoles());
$key = $request->cacheIdentifier();
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
@@ -633,7 +630,7 @@ Http::init()
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = ! $resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (! $bucket->getAttribute('enabled') && ! $isAppUser && ! $isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -666,7 +663,7 @@ Http::init()
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
// Do not update transformedAt if it's a console user
if (! $user->isPrivileged($authorization->getRoles())) {
if (! User::isPrivileged($authorization->getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
@@ -700,7 +697,7 @@ Http::init()
->groups(['session'])
->inject('user')
->inject('request')
->action(function (User $user, Request $request) {
->action(function (Document $user, Request $request) {
if (\str_contains($request->getURI(), 'oauth2')) {
return;
}
@@ -987,7 +984,7 @@ Http::shutdown()
}
if ($project->getId() !== 'console') {
if (! $user->isPrivileged($authorization->getRoles())) {
if (! User::isPrivileged($authorization->getRoles())) {
$bus->dispatch(new RequestCompleted(
project: $project->getArrayCopy(),
request: $request,
+3 -4
View File
@@ -36,9 +36,8 @@ Http::init()
->inject('request')
->inject('project')
->inject('geodb')
->inject('user')
->inject('authorization')
->action(function (Http $utopia, Request $request, Document $project, Reader $geodb, User $user, Authorization $authorization) {
->action(function (Http $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
if (!empty($denylist && $project->getId() === 'console')) {
$countries = explode(',', $denylist);
@@ -51,8 +50,8 @@ Http::init()
$route = $utopia->match($request);
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
$isAppUser = $user->isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
+1 -1
View File
@@ -103,7 +103,7 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
$lines = explode("\n", $data, 3);
$request = $lines[0];
if (count($lines) > 1) {
$domain = trim(explode('Host: ', $lines[1])[1] ?? '');
$domain = trim(explode('Host: ', $lines[1])[1]);
}
// Sync executions are considered risky
-1
View File
@@ -97,7 +97,6 @@ const APP_COMPUTE_DEPLOYMENT_MAX_RETENTION = 100 * 365; // 100 years
const APP_SDK_PLATFORM_SERVER = 'server';
const APP_SDK_PLATFORM_CLIENT = 'client';
const APP_SDK_PLATFORM_CONSOLE = 'console';
const APP_SDK_PLATFORM_STATIC = 'static';
const APP_VCS_GITHUB_USERNAME = 'Appwrite';
const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite';
-3
View File
@@ -432,10 +432,8 @@ Http::setResource('user', function (string $mode, Document $project, Document $c
$jwtUserId = $payload['userId'] ?? '';
if (! empty($jwtUserId)) {
if ($mode === APP_MODE_ADMIN) {
/** @var User $user */
$user = $dbForPlatform->getDocument('users', $jwtUserId);
} else {
/** @var User $user */
$user = $dbForProject->getDocument('users', $jwtUserId);
}
}
@@ -455,7 +453,6 @@ Http::setResource('user', function (string $mode, Document $project, Document $c
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
/** @var User $accountKeyUser */
$accountKeyUser = $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId));
if (! $accountKeyUser->isEmpty()) {
$key = $accountKeyUser->find(
+6 -6
View File
@@ -518,7 +518,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$project = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$database = getProjectDB($project);
/** @var User $user */
/** @var Appwrite\Utopia\Database\Documents\User $user */
$user = $database->getDocument('users', $userId);
$roles = $user->getRoles($database->getAuthorization());
@@ -642,14 +642,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID');
}
$timelimit = $app->getResource('timelimit');
$user = $app->getResource('user'); /** @var User $user */
$logUser = $user;
if (
array_key_exists('realtime', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['realtime']
&& !($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -660,6 +656,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Project is not accessible in this region. Please make sure you are using the correct endpoint');
}
$timelimit = $app->getResource('timelimit');
$user = $app->getResource('user'); /** @var User $user */
$logUser = $user;
/*
* Abuse Check
*
+1 -1
View File
@@ -13,7 +13,7 @@ $enabledDatabases = $enabledDatabases ?? ['mongodb', 'mariadb', 'postgresql'];
$isLocalInstall = $isLocalInstall ?? false;
$cardStep = ($step === 5) ? 4 : $step;
$cardStep = min(4, $step);
$stepFile = __DIR__ . "/installer/templates/steps/step-{$cardStep}.phtml";
if (!is_file($stepFile)) {
$stepFile = __DIR__ . "/installer/templates/steps/step-1.phtml";
@@ -478,10 +478,6 @@ body {
overflow: hidden;
}
.installer-page[data-upgrade='true'] .installer-step {
min-height: 0;
}
.action-shell {
display: flex;
flex-direction: column;
@@ -1812,92 +1808,3 @@ body {
gap: var(--gap-s);
}
}
.migration-option {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--gap-l);
padding: var(--space-6);
background: var(--bgcolor-neutral-default);
border-radius: var(--border-radius-m);
outline: var(--border-width-s) solid var(--border-neutral);
outline-offset: calc(var(--border-width-s) * -1);
cursor: pointer;
transition: outline-color 0.15s ease-in-out;
}
.migration-option:hover {
outline-color: var(--border-neutral-stronger);
}
.migration-option-content {
display: flex;
flex-direction: column;
gap: 2px;
}
.migration-switch {
flex-shrink: 0;
}
.migration-switch-track {
position: relative;
display: block;
width: 32px;
height: 20px;
border-radius: 10px;
background: var(--bgcolor-neutral-invert-weaker);
transition: background 0.15s ease-in-out;
}
.migration-switch-thumb {
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--bgcolor-neutral-primary);
transition: transform 0.15s ease-in-out;
}
#run-migration:checked ~ .migration-switch-track {
background: var(--bgcolor-neutral-invert-weak);
}
#run-migration:checked ~ .migration-switch-track .migration-switch-thumb {
transform: translateX(12px);
}
#run-migration:focus-visible ~ .migration-switch-track {
box-shadow: 0 0 0 var(--border-width-l) var(--border-focus);
}
.migration-hint {
display: flex;
align-items: flex-start;
gap: var(--gap-s);
padding: 0 var(--space-2);
}
.migration-hint-icon {
flex-shrink: 0;
width: 16px;
height: 16px;
color: var(--fgcolor-neutral-tertiary);
margin-top: 1px;
}
.migration-hint-icon svg {
width: 100%;
height: 100%;
}
.migration-code {
padding: 1px 4px;
border-radius: var(--border-radius-xs, 4px);
background: var(--bgcolor-neutral-secondary);
font-family: monospace;
font-size: inherit;
}
+6 -13
View File
@@ -12,7 +12,7 @@
const { validateInstallRequest } = window.InstallerStepsProgress || {};
const isUpgrade = document.body?.dataset.upgrade === 'true';
const stepFlow = isUpgrade ? [1, 6, 4, 5] : [1, 2, 3, 4, 5];
const stepFlow = isUpgrade ? [1, 4, 5] : [1, 2, 3, 4, 5];
const cardSteps = stepFlow.filter((step) => step !== 5);
const normalizeStep = (step) => {
@@ -53,7 +53,7 @@
let pendingStep = null;
let pendingPushState = false;
const clampStep = (step) => Math.max(1, Math.min(6, step));
const clampStep = (step) => Math.max(1, Math.min(5, step));
const isInstallLocked = () => Boolean(window.InstallerSteps?.isInstallLocked?.());
const scrollToFirstError = (panel) => {
@@ -399,18 +399,11 @@
}
}
}
if (action === 'next' && String(target) === '5') {
if (typeof validateInstallRequest === 'function') {
const isValid = await validateInstallRequest();
if (!isValid) {
return;
}
if (action === 'next' && String(target) === '5' && typeof validateInstallRequest === 'function') {
const isValid = await validateInstallRequest();
if (!isValid) {
return;
}
// Clear stale install data from previous runs so initStep5
// starts a fresh install instead of trying to resume.
const { clearInstallLock, clearInstallId } = window.InstallerStepsState || {};
clearInstallLock?.();
clearInstallId?.();
}
if (isInstallLocked() && Number(target) !== 5) {
requestStep(5, true);
@@ -14,7 +14,6 @@
ENV_VARS: 'env-vars',
DOCKER_CONTAINERS: 'docker-containers',
ACCOUNT_SETUP: 'account-setup',
MIGRATION: 'migration',
SSL_CERTIFICATE: 'ssl-certificate',
REDIRECT: 'redirect'
});
@@ -53,11 +52,6 @@
id: STEP_IDS.DOCKER_CONTAINERS,
inProgress: 'Restarting Docker containers...',
done: 'Docker containers restarted'
},
{
id: STEP_IDS.MIGRATION,
inProgress: 'Running database migration...',
done: 'Database migration completed'
}
] : [
{
@@ -101,7 +95,7 @@
const clampStep = (step) => {
const numeric = Number(step);
if (Number.isNaN(numeric)) return 1;
return Math.max(1, Math.min(6, numeric));
return Math.max(1, Math.min(5, numeric));
};
window.InstallerStepsContext = Object.freeze({
@@ -373,8 +373,7 @@
opensslKey: (formState?.opensslKey || '').trim(),
assistantOpenAIKey: normalizedAssistantKey,
accountEmail: normalizedAccountEmail,
accountPassword: normalizedAccountPassword,
migrate: formState?.migrate ?? false
accountPassword: normalizedAccountPassword
};
};
@@ -722,8 +721,7 @@
});
startSyncedSpinnerRotation(list);
const completeId = activeInstall?.installId || getStoredInstallId?.();
notifyInstallComplete(completeId, sessionDetails).finally(() => {
notifyInstallComplete(activeInstall?.installId, sessionDetails).finally(() => {
setTimeout(() => redirectToApp(protocol), TIMINGS?.redirectDelay ?? 0);
});
};
@@ -913,28 +911,21 @@
};
const isSnapshotTerminal = (snapshot) => {
if (!snapshot?.steps) return 'empty';
if (!snapshot?.steps) return true;
const stepEntries = Object.values(snapshot.steps);
if (stepEntries.length === 0) return 'empty';
if (stepEntries.length === 0) return true;
const hasError = stepEntries.some((s) => s.status === STATUS.ERROR);
if (hasError) return 'error';
if (hasError) return true;
const allCompleted = INSTALLATION_STEPS.every((step) => {
const detail = snapshot.steps[step.id];
return detail && detail.status === STATUS.COMPLETED;
});
if (allCompleted) return 'completed';
return false;
return allCompleted;
};
const resumeInstall = async (installId) => {
const snapshot = await fetchInstallStatus(installId);
const terminal = isSnapshotTerminal(snapshot);
if (!snapshot || terminal) {
if (terminal === 'completed') {
return 'completed';
}
return false;
}
if (!snapshot || isSnapshotTerminal(snapshot)) return false;
activeInstall = {
installId,
controller: new AbortController(),
@@ -1078,33 +1069,14 @@
startInstallStream(newInstallId);
};
const recoverToLastStep = () => {
clearInstallId?.();
clearInstallLock?.();
const url = new URL(window.location.href);
const lastStep = url.searchParams.get('step');
// Stay on the current URL so the user keeps their place;
// only navigate away if we're already on step 5 (the
// progress screen) since there's nothing to show.
if (!lastStep || String(lastStep) === '5') {
window.location.href = '/?step=1';
}
};
const lock = getInstallLock?.();
const existingInstallId = lock?.installId || getStoredInstallId?.();
if (existingInstallId) {
resumeInstall(existingInstallId).then((result) => {
if (result === 'completed') {
// Install already finished — redirect to console
// instead of bouncing back to step 1.
stopSyncedSpinnerRotation();
setUnloadGuard(false);
clearInstallLock?.();
resumeInstall(existingInstallId).then((resumed) => {
if (!resumed) {
clearInstallId?.();
startSslCheck(null);
} else if (!result) {
recoverToLastStep();
clearInstallLock?.();
window.location.href = '/?step=1';
}
});
} else {
-25
View File
@@ -329,30 +329,6 @@
}
};
const initStep6 = (root) => {
if (!root) return;
syncInstallLockFlag?.();
applyLockPayload?.();
applyBodyDefaults?.();
const checkbox = root.querySelector('#run-migration');
if (checkbox) {
if (formState.migrate !== undefined) {
checkbox.checked = formState.migrate;
} else {
formState.migrate = checkbox.checked;
}
checkbox.addEventListener('change', () => {
formState.migrate = checkbox.checked;
dispatchStateChange?.('migrate');
});
}
if (isInstallLocked?.()) {
disableControls?.(root);
}
};
const initStep = (step, container) => {
if (!container) return;
const root = container.querySelector('.step-layout') || container;
@@ -370,7 +346,6 @@
if (normalized === 3) initStep3(root);
if (normalized === 4) initStep4(root);
if (normalized === 5) Progress.initStep5?.(root);
if (normalized === 6) initStep6(root);
};
window.InstallerSteps = {
@@ -62,14 +62,12 @@ $badgeClass = $defaultSecretKey !== '' ? 'badge-success' : 'badge-warning';
<span class="badge badge-neutral typography-text-xs-400" data-review-assistant-badge>Disabled</span>
<div class="review-label typography-text-xs-400 text-neutral-tertiary">Appwrite Assistant</div>
</div>
<?php if (!$isUpgrade) { ?>
<div class="review-row">
<span class="badge <?php echo $badgeClass; ?> typography-text-xs-400" data-review-badge>
<?php echo htmlspecialchars((string) $badgeLabel, ENT_QUOTES, 'UTF-8'); ?>
</span>
<div class="review-label typography-text-xs-400 text-neutral-tertiary">Secret API key</div>
</div>
<?php } ?>
</div>
</div>
</div>
@@ -6,7 +6,7 @@ $isUpgrade = $isUpgrade ?? false;
<div class="install-panel">
<div class="install-header">
<div class="typography-text-m-400 text-neutral-primary">
<?php echo $isUpgrade ? 'Updating Appwrite…' : 'Installing Appwrite…'; ?>
<?php echo $isUpgrade ? 'Updating your app…' : 'Installing your app…'; ?>
</div>
</div>
<div class="install-list" data-install-list></div>
@@ -1,37 +0,0 @@
<?php
$isUpgrade = $isUpgrade ?? false;
?>
<div class="step-layout" data-step="6">
<div class="stack-xl">
<div class="stack-xxxs">
<h1 class="typography-title-s text-neutral-primary">Database migration</h1>
<p class="typography-text-m-400 text-neutral-secondary">
Run database migration after the update to apply schema changes.
</p>
</div>
<div class="stack-xl">
<label class="migration-option" for="run-migration">
<span class="migration-option-content">
<span class="typography-text-m-500 text-neutral-primary">Run migration automatically</span>
<span class="typography-text-xs-400 text-neutral-tertiary">Recommended when upgrading to a new version</span>
</span>
<span class="migration-switch">
<input type="checkbox" id="run-migration" name="migrate" class="sr-only" checked>
<span class="migration-switch-track" aria-hidden="true">
<span class="migration-switch-thumb"></span>
</span>
</span>
</label>
<div class="migration-hint">
<span class="migration-hint-icon">
<?php include __DIR__ . '/../../icons/info.svg'; ?>
</span>
<span class="typography-text-xs-400 text-neutral-tertiary">
To run manually later: <code class="migration-code">docker compose exec appwrite migrate</code>
</span>
</div>
</div>
</div>
</div>
-3
View File
@@ -1,3 +0,0 @@
_APP_DB_ADAPTER=mariadb
_APP_DB_HOST=mariadb
_APP_DB_PORT=3306
-36
View File
@@ -1,36 +0,0 @@
x-logging: &x-logging
logging:
driver: "json-file"
options:
max-file: "5"
max-size: "10m"
services:
mongodb:
scale: 0
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-mariadb:/var/lib/mysql:rw
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
command: "mysqld --innodb-flush-method=fsync"
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 12
volumes:
appwrite-mariadb:
-3
View File
@@ -1,3 +0,0 @@
_APP_DB_ADAPTER=postgresql
_APP_DB_HOST=postgresql
_APP_DB_PORT=5432
-3
View File
@@ -1,3 +0,0 @@
services:
mongodb:
scale: 0
+7
View File
@@ -60,6 +60,7 @@
"utopia-php/compression": "0.1.*",
"utopia-php/config": "1.*",
"utopia-php/console": "0.1.*",
"utopia-php/async": "@dev",
"utopia-php/database": "5.*",
"utopia-php/agents": "1.*",
"utopia-php/detector": "0.2.*",
@@ -111,6 +112,12 @@
"provide": {
"ext-phpiredis": "*"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/utopia-php/async.git"
}
],
"config": {
"platform": {
},
Generated
+233 -47
View File
@@ -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": "f9225f2b580de0ccb796b2fb8c881384",
"content-hash": "98a9cffeea945bf19942a259a64fdb7b",
"packages": [
{
"name": "adhocore/jwt",
@@ -1226,16 +1226,16 @@
},
{
"name": "open-telemetry/api",
"version": "1.9.0",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/api.git",
"reference": "6f8d237ce2c304ca85f31970f788e7f074d147be"
"reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/6f8d237ce2c304ca85f31970f788e7f074d147be",
"reference": "6f8d237ce2c304ca85f31970f788e7f074d147be",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/df5197c6fd0ddd8e9883b87de042d9341300e2ad",
"reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad",
"shasum": ""
},
"require": {
@@ -1292,20 +1292,20 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2026-02-25T13:24:05+00:00"
"time": "2026-01-21T04:14:03+00:00"
},
{
"name": "open-telemetry/context",
"version": "1.5.0",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/context.git",
"reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07"
"reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/context/zipball/3c414b246e0dabb7d6145404e6a5e4536ca18d07",
"reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07",
"url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf",
"reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf",
"shasum": ""
},
"require": {
@@ -1347,11 +1347,11 @@
],
"support": {
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
"docs": "https://opentelemetry.io/docs/languages/php",
"docs": "https://opentelemetry.io/docs/php",
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-10-19T06:44:33+00:00"
"time": "2025-09-19T00:05:49+00:00"
},
{
"name": "open-telemetry/exporter-otlp",
@@ -1419,16 +1419,16 @@
},
{
"name": "open-telemetry/gen-otlp-protobuf",
"version": "1.9.0",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git",
"reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9"
"reference": "673af5b06545b513466081884b47ef15a536edde"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/a229cf161d42001d64c8f21e8f678581fe1c66b9",
"reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9",
"url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/673af5b06545b513466081884b47ef15a536edde",
"reference": "673af5b06545b513466081884b47ef15a536edde",
"shasum": ""
},
"require": {
@@ -1474,30 +1474,30 @@
],
"support": {
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
"docs": "https://opentelemetry.io/docs/languages/php",
"docs": "https://opentelemetry.io/docs/php",
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-10-19T06:44:33+00:00"
"time": "2025-09-17T23:10:12+00:00"
},
{
"name": "open-telemetry/sdk",
"version": "1.14.0",
"version": "1.13.0",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/sdk.git",
"reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410"
"reference": "c76f91203bf7ef98ab3f4e0a82ca21699af185e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/6e3d0ce93e76555dd5e2f1d19443ff45b990e410",
"reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410",
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/c76f91203bf7ef98ab3f4e0a82ca21699af185e1",
"reference": "c76f91203bf7ef98ab3f4e0a82ca21699af185e1",
"shasum": ""
},
"require": {
"ext-json": "*",
"nyholm/psr7-server": "^1.1",
"open-telemetry/api": "^1.8",
"open-telemetry/api": "^1.7",
"open-telemetry/context": "^1.4",
"open-telemetry/sem-conv": "^1.0",
"php": "^8.1",
@@ -1575,7 +1575,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2026-03-21T11:50:01+00:00"
"time": "2026-01-28T11:38:11+00:00"
},
{
"name": "open-telemetry/sem-conv",
@@ -1634,6 +1634,71 @@
},
"time": "2026-01-21T04:14:03+00:00"
},
{
"name": "opis/closure",
"version": "4.5.0",
"source": {
"type": "git",
"url": "https://github.com/opis/closure.git",
"reference": "b97e42b95bb72d87507f5e2d137ceb239aea8d6b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opis/closure/zipball/b97e42b95bb72d87507f5e2d137ceb239aea8d6b",
"reference": "b97e42b95bb72d87507f5e2d137ceb239aea8d6b",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.x-dev"
}
},
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Opis\\Closure\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marius Sarca",
"email": "marius.sarca@gmail.com"
},
{
"name": "Sorin Sarca",
"email": "sarca_sorin@hotmail.com"
}
],
"description": "A library that can be used to serialize closures (anonymous functions) and arbitrary data.",
"homepage": "https://opis.io/closure",
"keywords": [
"anonymous classes",
"anonymous functions",
"closure",
"function",
"serializable",
"serialization",
"serialize"
],
"support": {
"issues": "https://github.com/opis/closure/issues",
"source": "https://github.com/opis/closure/tree/4.5.0"
},
"time": "2026-03-05T13:32:42+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v3.1.3",
@@ -3403,16 +3468,16 @@
},
{
"name": "utopia-php/agents",
"version": "1.3.0",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/agents.git",
"reference": "06064fd9fb19b77ae45a12ec7bcbc17670912c30"
"reference": "052227953678a30ecc4b5467401fcb0b2386471e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/agents/zipball/06064fd9fb19b77ae45a12ec7bcbc17670912c30",
"reference": "06064fd9fb19b77ae45a12ec7bcbc17670912c30",
"url": "https://api.github.com/repos/utopia-php/agents/zipball/052227953678a30ecc4b5467401fcb0b2386471e",
"reference": "052227953678a30ecc4b5467401fcb0b2386471e",
"shasum": ""
},
"require": {
@@ -3450,9 +3515,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/agents/issues",
"source": "https://github.com/utopia-php/agents/tree/1.3.0"
"source": "https://github.com/utopia-php/agents/tree/1.2.1"
},
"time": "2026-03-26T03:51:11+00:00"
"time": "2026-02-24T06:03:55+00:00"
},
{
"name": "utopia-php/analytics",
@@ -3500,6 +3565,125 @@
},
"time": "2026-02-09T12:46:39+00:00"
},
{
"name": "utopia-php/async",
"version": "dev-main",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/async.git",
"reference": "7a0c6957b41731a5c999382ad26a0b2fdbd19812"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/async/zipball/7a0c6957b41731a5c999382ad26a0b2fdbd19812",
"reference": "7a0c6957b41731a5c999382ad26a0b2fdbd19812",
"shasum": ""
},
"require": {
"opis/closure": "4.*",
"php": ">=8.1"
},
"require-dev": {
"amphp/amp": "3.*",
"amphp/parallel": "2.*",
"amphp/process": "^2.0",
"laravel/pint": "1.*",
"phpstan/phpstan": "2.*",
"phpunit/phpunit": "11.5.45",
"react/child-process": "0.*",
"react/event-loop": "1.*",
"swoole/ide-helper": "*"
},
"suggest": {
"amphp/amp": "Required for Amp promise adapter",
"amphp/parallel": "Required for Amp parallel adapter",
"ext-ev": "Required for ReactPHP event loop (recommended for best performance)",
"ext-parallel": "Required for parallel adapter (requires PHP ZTS build)",
"ext-sockets": "Required for Swoole Process adapter",
"ext-swoole": "Required for Swoole Thread and Process adapters (recommended for best performance)",
"react/child-process": "Required for ReactPHP parallel adapter",
"react/event-loop": "Required for ReactPHP promise and parallel adapters"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\Async\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Utopia\\Tests\\": "tests/"
}
},
"scripts": {
"test-unit": [
"vendor/bin/phpunit tests/Unit --exclude-group no-swoole"
],
"test-promise-sync": [
"vendor/bin/phpunit tests/E2e/Promise/SyncTest.php"
],
"test-promise-swoole": [
"vendor/bin/phpunit tests/E2e/Promise/Swoole"
],
"test-promise-amp": [
"vendor/bin/phpunit tests/E2e/Promise/Amp"
],
"test-promise-react": [
"vendor/bin/phpunit tests/E2e/Promise/React"
],
"test-parallel-sync": [
"vendor/bin/phpunit tests/E2e/Parallel/Sync"
],
"test-parallel-swoole-thread": [
"vendor/bin/phpunit tests/E2e/Parallel/Swoole/ThreadTest.php"
],
"test-parallel-swoole-process": [
"vendor/bin/phpunit tests/E2e/Parallel/Swoole/ProcessTest.php"
],
"test-parallel-amp": [
"vendor/bin/phpunit tests/E2e/Parallel/Amp"
],
"test-parallel-react": [
"vendor/bin/phpunit tests/E2e/Parallel/React"
],
"test-parallel-ext": [
"php -n -d extension=parallel.so -d extension=sockets.so vendor/bin/phpunit tests/E2e/Parallel/Parallel"
],
"test-e2e": [
"vendor/bin/phpunit tests/E2e --exclude-group ext-parallel"
],
"test": [
"@test-unit",
"@test-e2e",
"@test-parallel-ext"
],
"lint": [
"vendor/bin/pint"
],
"format": [
"php -d memory_limit=4G vendor/bin/pint"
],
"check": [
"vendor/bin/phpstan analyse src tests --level=max --memory-limit=4G"
]
},
"license": [
"MIT"
],
"authors": [
{
"name": "Appwrite Team",
"email": "team@appwrite.io"
}
],
"description": "High-performance concurrent + parallel library with Promise and Parallel execution support for PHP.",
"support": {
"source": "https://github.com/utopia-php/async/tree/main",
"issues": "https://github.com/utopia-php/async/issues"
},
"time": "2026-01-09T06:16:09+00:00"
},
{
"name": "utopia-php/audit",
"version": "2.2.1",
@@ -4002,16 +4186,16 @@
},
{
"name": "utopia-php/dns",
"version": "1.6.6",
"version": "1.6.5",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/dns.git",
"reference": "917901ecfe5f09a540e4f689b6cbb80b9f55035d"
"reference": "574327f0f5fabefa7048030c5634cde33ad10640"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/917901ecfe5f09a540e4f689b6cbb80b9f55035d",
"reference": "917901ecfe5f09a540e4f689b6cbb80b9f55035d",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/574327f0f5fabefa7048030c5634cde33ad10640",
"reference": "574327f0f5fabefa7048030c5634cde33ad10640",
"shasum": ""
},
"require": {
@@ -4053,9 +4237,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/dns/issues",
"source": "https://github.com/utopia-php/dns/tree/1.6.6"
"source": "https://github.com/utopia-php/dns/tree/1.6.5"
},
"time": "2026-03-27T11:13:50+00:00"
"time": "2026-02-19T16:06:46+00:00"
},
{
"name": "utopia-php/domains",
@@ -5439,16 +5623,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.14.0",
"version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "7e7e257b10a8c1384a237e7d8d73452e2108901e"
"reference": "a724aa8db52f83ea35854a004837fa5ce990b736"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/7e7e257b10a8c1384a237e7d8d73452e2108901e",
"reference": "7e7e257b10a8c1384a237e7d8d73452e2108901e",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/a724aa8db52f83ea35854a004837fa5ce990b736",
"reference": "a724aa8db52f83ea35854a004837fa5ce990b736",
"shasum": ""
},
"require": {
@@ -5484,9 +5668,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/1.14.0"
"source": "https://github.com/appwrite/sdk-generator/tree/1.12.1"
},
"time": "2026-03-26T12:50:11+00:00"
"time": "2026-03-24T05:18:43+00:00"
},
{
"name": "brianium/paratest",
@@ -6195,11 +6379,11 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.44",
"version": "2.1.42",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/4a88c083c668b2c364a425c9b3171b2d9ea5d218",
"reference": "4a88c083c668b2c364a425c9b3171b2d9ea5d218",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0",
"reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0",
"shasum": ""
},
"require": {
@@ -6244,7 +6428,7 @@
"type": "github"
}
],
"time": "2026-03-25T17:34:21+00:00"
"time": "2026-03-17T14:58:32+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -8435,7 +8619,9 @@
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {},
"stability-flags": {
"utopia-php/async": 20
},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
+1
View File
@@ -4,6 +4,7 @@
services:
appwrite-mongo-express:
profiles: ["mongodb"]
image: mongo-express
container_name: appwrite-mongo-express
networks:
+37 -13
View File
@@ -1261,27 +1261,28 @@ services:
retries: 20
start_period: 5s
postgresql:
image: appwrite/postgres:0.1.0
container_name: appwrite-postgresql
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-postgresql:/var/lib/postgresql/18/data:rw
- appwrite-mariadb:/var/lib/mysql:rw
ports:
- "5432:5432"
- "3306:3306"
environment:
- POSTGRES_DB=${_APP_DB_SCHEMA}
- POSTGRES_USER=${_APP_DB_USER}
- POSTGRES_PASSWORD=${_APP_DB_PASS}
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
command: "mysqld --innodb-flush-method=fsync"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${_APP_DB_USER} -d ${_APP_DB_SCHEMA}"]
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 10
start_period: 10s
command: "postgres"
retries: 12
mongodb:
image: mongo:8.2.5
@@ -1319,6 +1320,28 @@ services:
retries: 10
start_period: 30s
postgresql:
image: appwrite/postgres:0.1.0
container_name: appwrite-postgresql
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-postgresql:/var/lib/postgresql/18/data:rw
ports:
- "5432:5432"
environment:
- POSTGRES_DB=${_APP_DB_SCHEMA}
- POSTGRES_USER=${_APP_DB_USER}
- POSTGRES_PASSWORD=${_APP_DB_PASS}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${_APP_DB_USER} -d ${_APP_DB_SCHEMA}"]
interval: 5s
timeout: 5s
retries: 10
start_period: 10s
command: "postgres"
ollama:
image: appwrite/ollama:0.1.1
container_name: ollama
@@ -1441,9 +1464,10 @@ networks:
volumes:
appwrite-postgresql:
appwrite-mariadb:
appwrite-mongodb:
appwrite-mongodb-keyfile:
appwrite-postgresql:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
@@ -1 +0,0 @@
Create multiple operations in a single transaction.
@@ -1 +0,0 @@
Create a new transaction.
@@ -1 +0,0 @@
Delete a transaction by its unique ID.
@@ -1 +0,0 @@
Get a transaction by its unique ID.
@@ -1 +0,0 @@
List transactions across all databases.
@@ -1 +0,0 @@
Update a transaction, to either commit or roll back its operations.
@@ -1 +0,0 @@
Create a new Collection. Before using this route, you should create a new database resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console.
@@ -1 +0,0 @@
Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console.
@@ -1 +0,0 @@
Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console.
@@ -1,2 +0,0 @@
Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.
Attributes can be `key`, `fulltext`, and `unique`.
@@ -1 +0,0 @@
Create multiple operations in a single transaction.
@@ -1 +0,0 @@
Create a new transaction.
-1
View File
@@ -1 +0,0 @@
Create a new Database.
@@ -1 +0,0 @@
Decrement a specific column of a row by a given value.
@@ -1 +0,0 @@
Delete a collection by its unique ID. Only users with write permissions have access to delete this resource.
@@ -1 +0,0 @@
Delete a document by its unique ID.
@@ -1 +0,0 @@
Bulk delete documents using queries, if no queries are passed then all documents are deleted.
@@ -1 +0,0 @@
Delete an index.
@@ -1 +0,0 @@
Delete a transaction by its unique ID.
-1
View File
@@ -1 +0,0 @@
Delete a database by its unique ID. Only API keys with with databases.write scope can delete a database.
@@ -1 +0,0 @@
Get the collection activity logs list by its unique ID.
@@ -1 +0,0 @@
Get usage metrics and statistics for a collection. Returning the total number of documents. The response includes both current totals and historical data over time. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, range defaults to 30 days.
@@ -1 +0,0 @@
Get a collection by its unique ID. This endpoint response returns a JSON object with the collection metadata.
@@ -1 +0,0 @@
Get usage metrics and statistics for a database. You can view the total number of collections, documents, and storage usage. The response includes both current totals and historical data over time. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, range defaults to 30 days.
@@ -1 +0,0 @@
Get the document activity logs list by its unique ID.
@@ -1 +0,0 @@
Get a document by its unique ID. This endpoint response returns a JSON object with the document data.
-1
View File
@@ -1 +0,0 @@
Get index by ID.
-1
View File
@@ -1 +0,0 @@
Get the database activity logs list by its unique ID.
@@ -1 +0,0 @@
Get a transaction by its unique ID.
-1
View File
@@ -1 +0,0 @@
Get a database by its unique ID. This endpoint response returns a JSON object with the database metadata.
@@ -1 +0,0 @@
Increment a specific column of a row by a given value.
@@ -1 +0,0 @@
List attributes in the collection.
@@ -1 +0,0 @@
Get a list of all collections that belong to the provided databaseId. You can use the search parameter to filter your results.
@@ -1 +0,0 @@
Get a list of all the user's documents in a given collection. You can use the query params to filter your results.
@@ -1 +0,0 @@
List indexes in the collection.
@@ -1 +0,0 @@
List transactions across all databases.
-1
View File
@@ -1 +0,0 @@
List usage metrics and statistics for all databases in the project. You can view the total number of databases, collections, documents, and storage usage. The response includes both current totals and historical data over time. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, range defaults to 30 days.
-1
View File
@@ -1 +0,0 @@
Get a list of all databases from the current Appwrite project. You can use the search parameter to filter your results.
@@ -1 +0,0 @@
Update a collection by its unique ID.
@@ -1 +0,0 @@
Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated.
@@ -1 +0,0 @@
Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be updated.
@@ -1 +0,0 @@
Update a transaction, to either commit or roll back its operations.
-1
View File
@@ -1 +0,0 @@
Update a database by its unique ID.
@@ -1 +0,0 @@
Create or update a Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console.
@@ -1 +0,0 @@
Create or update Documents. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console.
+17
View File
@@ -0,0 +1,17 @@
# Change Log
## 0.3.0
* Add `bytesMax` and `bytesUsed` properties to Collection and Table documentation
* Add `queries` parameter to `listKeys` and `keyId` parameter to `createKey` documentation
* Add `dart-3.10` and `flutter-3.38` runtimes
* Fix Teams membership docs to use `string[]` instead of `Roles[]`
## 0.2.0
* Document array-based enum parameters in Markdown examples (e.g., `permissions: BrowserPermission[]`).
* Breaking change: `Output` enum has been removed; use `ImageFormat` instead.
## 0.1.0
* Initial release
-5
View File
@@ -1,5 +0,0 @@
# Change Log
## 0.1.0
* Initial release
-68
View File
@@ -1,68 +0,0 @@
## Getting Started
### Init your SDK
Initialize your SDK with your Appwrite server API endpoint and project ID which can be found on your project settings page and your new API secret Key from project's API keys section.
```rust
use appwrite::client::Client;
let client = Client::new()
.set_endpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.set_project("5df5acd0d48c2") // Your project ID
.set_key("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key
.set_self_signed(true); // Use only on dev mode with a self-signed SSL cert
```
### Make Your First Request
Once your SDK object is set, create any of the Appwrite service objects and choose any request to send. Full documentation for any service method you would like to use can be found in your SDK documentation or in the [API References](https://appwrite.io/docs) section.
```rust
use appwrite::client::Client;
use appwrite::services::users::Users;
use appwrite::id::ID;
let client = Client::new()
.set_endpoint("https://[HOSTNAME_OR_IP]/v1")
.set_project("5df5acd0d48c2")
.set_key("919c2d18fb5d4...a2ae413da83346ad2")
.set_self_signed(true);
let users = Users::new(&client);
let user = users.create(
ID::unique(),
Some("email@example.com"),
Some("+123456789"),
Some("password"),
Some("Walter O'Brien"),
).await?;
println!("{}", user.name);
println!("{}", user.email);
```
### Error Handling
The Appwrite Rust SDK returns `Result` types. You can handle errors using standard Rust error handling patterns. Below is an example.
```rust
use appwrite::error::AppwriteError;
match users.create(
ID::unique(),
Some("email@example.com"),
Some("+123456789"),
Some("password"),
Some("Walter O'Brien"),
).await {
Ok(user) => println!("{}", user.name),
Err(AppwriteError { message, code, .. }) => {
eprintln!("Error {}: {}", code, message);
}
}
```
### Learn more
You can use the following resources to learn more and get help
- 🚀 [Getting Started Tutorial](https://appwrite.io/docs/getting-started-for-server)
- 📜 [Appwrite Docs](https://appwrite.io/docs)
- 💬 [Discord Community](https://appwrite.io/discord)
+533
View File
@@ -114,6 +114,12 @@ parameters:
count: 1
path: app/controllers/mock.php
-
message: '#^Call to an undefined method Utopia\\Database\\Document\:\:getRoles\(\)\.$#'
identifier: method.notFound
count: 1
path: app/controllers/shared/api.php
-
message: '#^Variable \$register might not be defined\.$#'
identifier: variable.undefined
@@ -186,6 +192,108 @@ parameters:
count: 3
path: app/worker.php
-
message: '#^PHPDoc tag @return with type string is incompatible with native type int\.$#'
identifier: return.phpDocType
count: 1
path: src/Appwrite/Auth/OAuth2.php
-
message: '#^PHPDoc tag @param references unknown parameter\: \$token$#'
identifier: parameter.notFound
count: 1
path: src/Appwrite/Auth/OAuth2/Disqus.php
-
message: '#^PHPDoc tag @param references unknown parameter\: \$value$#'
identifier: parameter.notFound
count: 1
path: src/Appwrite/Auth/Validator/PersonalData.php
-
message: '#^PHPDoc tag @param has invalid value \(DeviceDetector\)\: Unexpected token "\\n ", expected variable at offset 32 on line 2$#'
identifier: phpDoc.parseError
count: 1
path: src/Appwrite/Detector/Detector.php
-
message: '#^PHPDoc tag @param has invalid value \(string\)\: Unexpected token "\\n ", expected variable at offset 24 on line 2$#'
identifier: phpDoc.parseError
count: 1
path: src/Appwrite/Detector/Detector.php
-
message: '#^PHPDoc tag @var above a method has no effect\.$#'
identifier: varTag.misplaced
count: 1
path: src/Appwrite/Docker/Compose.php
-
message: '#^PHPDoc tag @var above a method has no effect\.$#'
identifier: varTag.misplaced
count: 1
path: src/Appwrite/Docker/Compose/Service.php
-
message: '#^PHPDoc tag @var above a method has no effect\.$#'
identifier: varTag.misplaced
count: 1
path: src/Appwrite/Docker/Env.php
-
message: '#^PHPDoc tag @param has invalid value \(int port\)\: Unexpected token "port", expected variable at offset 50 on line 4$#'
identifier: phpDoc.parseError
count: 1
path: src/Appwrite/Event/Mail.php
-
message: '#^PHPDoc tag @param references unknown parameter\: \$password$#'
identifier: parameter.notFound
count: 1
path: src/Appwrite/Event/Mail.php
-
message: '#^PHPDoc tag @return with type string is incompatible with native type Appwrite\\Event\\Mail\.$#'
identifier: return.phpDocType
count: 1
path: src/Appwrite/Event/Mail.php
-
message: '#^Method Appwrite\\Event\\Message\\Usage\:\:fromArray\(\) should return static\(Appwrite\\Event\\Message\\Usage\) but returns Appwrite\\Event\\Message\\Usage\.$#'
identifier: return.type
count: 1
path: src/Appwrite/Event/Message/Usage.php
-
message: '#^PHPDoc tag @param references unknown parameter\: \$message$#'
identifier: parameter.notFound
count: 1
path: src/Appwrite/Event/Messaging.php
-
message: '#^PHPDoc tag @return with type string is incompatible with native type Utopia\\Database\\Document\.$#'
identifier: return.phpDocType
count: 1
path: src/Appwrite/Event/Messaging.php
-
message: '#^Method Appwrite\\Functions\\EventProcessor\:\:getFunctionsEvents\(\) should return array\<string, bool\> but returns array\<int\<0, max\>\>\.$#'
identifier: return.type
count: 1
path: src/Appwrite/Functions/EventProcessor.php
-
message: '#^Method Appwrite\\Functions\\EventProcessor\:\:getWebhooksEvents\(\) should return array\<string, bool\> but returns array\<int\<0, max\>\>\.$#'
identifier: return.type
count: 1
path: src/Appwrite/Functions/EventProcessor.php
-
message: '#^Variable \$hostname on left side of \?\? always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Functions/EventProcessor.php
-
message: '#^Anonymous function has an unused use \$context\.$#'
identifier: closure.unusedUse
@@ -258,6 +366,30 @@ parameters:
count: 1
path: src/Appwrite/GraphQL/Types/Mapper.php
-
message: '#^Unsafe access to private property Appwrite\\GraphQL\\Types\\Mapper\:\:\$models through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: src/Appwrite/GraphQL/Types/Mapper.php
-
message: '#^Unsafe call to private method Appwrite\\GraphQL\\Types\\Mapper\:\:getColumnImplementation\(\) through static\:\:\.$#'
identifier: staticClassAccess.privateMethod
count: 2
path: src/Appwrite/GraphQL/Types/Mapper.php
-
message: '#^Unsafe call to private method Appwrite\\GraphQL\\Types\\Mapper\:\:getHashOptionsImplementation\(\) through static\:\:\.$#'
identifier: staticClassAccess.privateMethod
count: 1
path: src/Appwrite/GraphQL/Types/Mapper.php
-
message: '#^Unsafe call to private method Appwrite\\GraphQL\\Types\\Mapper\:\:getUnionImplementation\(\) through static\:\:\.$#'
identifier: staticClassAccess.privateMethod
count: 1
path: src/Appwrite/GraphQL/Types/Mapper.php
-
message: '#^Call to an undefined method Appwrite\\Migration\\Version\\V15\:\:documentsIterator\(\)\.$#'
identifier: method.notFound
@@ -324,6 +456,12 @@ parameters:
count: 1
path: src/Appwrite/OpenSSL/OpenSSL.php
-
message: '#^PHPDoc tag @param references unknown parameter\: \$projectId$#'
identifier: parameter.notFound
count: 1
path: src/Appwrite/Platform/Action.php
-
message: '#^Variable \$output in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
@@ -456,6 +594,12 @@ parameters:
count: 1
path: src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php
-
message: '#^Variable \$currentDocumentId on left side of \?\? always exists and is always null\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php
-
message: '#^Offset ''deviceBrand'' does not exist on int\.$#'
identifier: offsetAccess.notFound
@@ -1020,6 +1164,12 @@ parameters:
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^PHPDoc tag @var above a method has no effect\.$#'
identifier: varTag.misplaced
count: 2
path: src/Appwrite/Template/Template.php
-
message: '#^PHPDoc tag @param has invalid value \(Document \$this\)\: Unexpected token "\$this", expected variable at offset 69 on line 4$#'
identifier: phpDoc.parseError
@@ -1032,6 +1182,35 @@ parameters:
count: 1
path: src/Appwrite/Utopia/Database/Documents/User.php
-
message: '#^Unsafe call to private method Appwrite\\Utopia\\Request\\Filters\\V17\:\:appendSymbol\(\) through static\:\:\.$#'
identifier: staticClassAccess.privateMethod
count: 4
path: src/Appwrite/Utopia/Request/Filters/V17.php
-
message: '#^Unsafe call to private method Appwrite\\Utopia\\Request\\Filters\\V17\:\:isSpecialChar\(\) through static\:\:\.$#'
identifier: staticClassAccess.privateMethod
count: 1
path: src/Appwrite/Utopia/Request/Filters/V17.php
-
message: '#^PHPDoc tag @param has invalid value \(callable The callback to show sensitive information for\)\: Unexpected token "The", expected variable at offset 91 on line 4$#'
identifier: phpDoc.parseError
count: 1
path: src/Appwrite/Utopia/Response.php
-
message: '#^PHPDoc tag @return with type Appwrite\\Utopia\\Response\\Filter is incompatible with native type array\.$#'
identifier: return.phpDocType
count: 1
path: src/Appwrite/Utopia/Response.php
-
message: '#^PHPDoc tag @return with type string is incompatible with native type Utopia\\Database\\Document\.$#'
identifier: return.phpDocType
count: 1
path: src/Appwrite/Utopia/Response/Model/User.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Databases\\Legacy\\DatabasesStringTypesTest\:\:\$setupCache through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
@@ -1067,12 +1246,108 @@ parameters:
count: 8
path: tests/e2e/Services/Functions/FunctionsCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsClientTest\:\:\$cachedDeployment through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/GraphQL/FunctionsClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsClientTest\:\:\$cachedExecution through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/FunctionsClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsClientTest\:\:\$cachedFunction through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/GraphQL/FunctionsClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsServerTest\:\:\$cachedDeployment through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/FunctionsServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsServerTest\:\:\$cachedExecution through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/FunctionsServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsServerTest\:\:\$cachedFunction through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/FunctionsServerTest.php
-
message: '#^Binary operation "\+" between string and 1 results in an error\.$#'
identifier: binaryOp.invalid
count: 1
path: tests/e2e/Services/GraphQL/Legacy/AbuseTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\Legacy\\DatabaseClientTest\:\:\$bulkData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\Legacy\\DatabaseClientTest\:\:\$collection through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\Legacy\\DatabaseClientTest\:\:\$database through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\Legacy\\DatabaseClientTest\:\:\$document through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedEmail through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 2
path: tests/e2e/Services/GraphQL/MessagingTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedProviders through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 8
path: tests/e2e/Services/GraphQL/MessagingTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedPush through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/MessagingTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedSms through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/MessagingTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedSubscriber through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/MessagingTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedTopic through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 9
path: tests/e2e/Services/GraphQL/MessagingTest.php
-
message: '#^Variable \$from in empty\(\) is never defined\.$#'
identifier: empty.variable
@@ -1085,18 +1360,174 @@ parameters:
count: 1
path: tests/e2e/Services/GraphQL/StorageClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\StorageClientTest\:\:\$cachedBucket through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/GraphQL/StorageClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\StorageClientTest\:\:\$cachedFile through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/StorageClientTest.php
-
message: '#^Method Tests\\E2E\\Services\\GraphQL\\StorageServerTest\:\:testGetFileDownload\(\) should return array but return statement is missing\.$#'
identifier: return.missing
count: 1
path: tests/e2e/Services/GraphQL/StorageServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\StorageServerTest\:\:\$cachedBucket through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/StorageServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\StorageServerTest\:\:\$cachedFile through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 6
path: tests/e2e/Services/GraphQL/StorageServerTest.php
-
message: '#^Binary operation "\+" between string and 1 results in an error\.$#'
identifier: binaryOp.invalid
count: 1
path: tests/e2e/Services/GraphQL/TablesDB/AbuseTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedBooleanColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedBulkData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedDatabase through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedDatetimeColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedEmailColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedEnumColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedFloatColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedIPColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedIndexData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 7
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedIntegerColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedRelationshipColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedRowData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedStringColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedTableData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedURLColumnData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsClientTest\:\:\$cachedMembership through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/TeamsClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsClientTest\:\:\$cachedTeam through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/GraphQL/TeamsClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsServerTest\:\:\$cachedMembership through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/GraphQL/TeamsServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsServerTest\:\:\$cachedTeam through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/GraphQL/TeamsServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsServerTest\:\:\$cachedTeamWithPrefs through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/GraphQL/TeamsServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\UsersTest\:\:\$cachedUser through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/GraphQL/UsersTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\UsersTest\:\:\$cachedUserTarget through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 5
path: tests/e2e/Services/GraphQL/UsersTest.php
-
message: '#^Variable \$from in empty\(\) is never defined\.$#'
identifier: empty.variable
@@ -1168,6 +1599,36 @@ parameters:
identifier: method.notFound
count: 1
path: tests/e2e/Services/TablesDB/Permissions/TablesDBPermissionsTeamTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensConsoleClientTest\:\:\$bucketAndFileData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/Tokens/TokensConsoleClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensConsoleClientTest\:\:\$tokenData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/Tokens/TokensConsoleClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensCustomClientTest\:\:\$bucketAndFileData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/Tokens/TokensCustomClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensCustomServerTest\:\:\$bucketAndFileData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/Tokens/TokensCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensCustomServerTest\:\:\$tokenData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/Tokens/TokensCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Users\\UsersCustomServerTest\:\:\$cachedHashedPasswordUsers through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
@@ -1227,3 +1688,75 @@ parameters:
identifier: method.notFound
count: 1
path: tests/unit/Event/EventTest.php
-
message: '#^Call to method parse\(\) on an unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter\.$#'
identifier: class.notFound
count: 6
path: tests/unit/Utopia/Response/Filters/V16Test.php
-
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V16Test\:\:\$filter \(Tests\\Unit\\Utopia\\Response\\Filters\\Filter\) does not accept Appwrite\\Utopia\\Response\\Filters\\V16\.$#'
identifier: assign.propertyType
count: 1
path: tests/unit/Utopia/Response/Filters/V16Test.php
-
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V16Test\:\:\$filter has unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter as its type\.$#'
identifier: class.notFound
count: 1
path: tests/unit/Utopia/Response/Filters/V16Test.php
-
message: '#^Call to method parse\(\) on an unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter\.$#'
identifier: class.notFound
count: 5
path: tests/unit/Utopia/Response/Filters/V17Test.php
-
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V17Test\:\:\$filter \(Tests\\Unit\\Utopia\\Response\\Filters\\Filter\) does not accept Appwrite\\Utopia\\Response\\Filters\\V17\.$#'
identifier: assign.propertyType
count: 1
path: tests/unit/Utopia/Response/Filters/V17Test.php
-
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V17Test\:\:\$filter has unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter as its type\.$#'
identifier: class.notFound
count: 1
path: tests/unit/Utopia/Response/Filters/V17Test.php
-
message: '#^Call to method parse\(\) on an unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter\.$#'
identifier: class.notFound
count: 4
path: tests/unit/Utopia/Response/Filters/V18Test.php
-
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V18Test\:\:\$filter \(Tests\\Unit\\Utopia\\Response\\Filters\\Filter\) does not accept Appwrite\\Utopia\\Response\\Filters\\V18\.$#'
identifier: assign.propertyType
count: 1
path: tests/unit/Utopia/Response/Filters/V18Test.php
-
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V18Test\:\:\$filter has unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter as its type\.$#'
identifier: class.notFound
count: 1
path: tests/unit/Utopia/Response/Filters/V18Test.php
-
message: '#^Call to method parse\(\) on an unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter\.$#'
identifier: class.notFound
count: 11
path: tests/unit/Utopia/Response/Filters/V19Test.php
-
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V19Test\:\:\$filter \(Tests\\Unit\\Utopia\\Response\\Filters\\Filter\) does not accept Appwrite\\Utopia\\Response\\Filters\\V19\.$#'
identifier: assign.propertyType
count: 1
path: tests/unit/Utopia/Response/Filters/V19Test.php
-
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V19Test\:\:\$filter has unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter as its type\.$#'
identifier: class.notFound
count: 1
path: tests/unit/Utopia/Response/Filters/V19Test.php
+10 -105
View File
@@ -16,123 +16,28 @@
<testsuite name="unit">
<directory>./tests/unit</directory>
</testsuite>
<testsuite name="General">
<directory>./tests/e2e/General</directory>
<directory>./tests/e2e/Scopes</directory>
</testsuite>
<testsuite name="Console">
<directory>./tests/e2e/Services/Console</directory>
</testsuite>
<testsuite name="Health">
<directory>./tests/e2e/Services/Health</directory>
</testsuite>
<testsuite name="Locale">
<directory>./tests/e2e/Services/Locale</directory>
</testsuite>
<testsuite name="Tokens">
<directory>./tests/e2e/Services/Tokens</directory>
</testsuite>
<testsuite name="VCS">
<directory>./tests/e2e/Services/VCS</directory>
</testsuite>
<testsuite name="Webhooks">
<directory>./tests/e2e/Services/Webhooks</directory>
</testsuite>
<testsuite name="GeneralGroup">
<directory>./tests/e2e/General</directory>
<directory>./tests/e2e/Scopes</directory>
<directory>./tests/e2e/Services/Console</directory>
<directory>./tests/e2e/Services/Health</directory>
<directory>./tests/e2e/Services/Locale</directory>
<directory>./tests/e2e/Services/Messaging</directory>
<directory>./tests/e2e/Services/Proxy</directory>
<directory>./tests/e2e/Services/Storage</directory>
<directory>./tests/e2e/Services/Tokens</directory>
<directory>./tests/e2e/Services/VCS</directory>
<directory>./tests/e2e/Services/Webhooks</directory>
</testsuite>
<testsuite name="Account">
<directory>./tests/e2e/Services/Account</directory>
</testsuite>
<testsuite name="Avatars">
<directory>./tests/e2e/Services/Avatars</directory>
</testsuite>
<testsuite name="Databases">
<directory>./tests/e2e/Services/Databases</directory>
</testsuite>
<testsuite name="Functions">
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
<file>./tests/e2e/Services/Functions/FunctionsCustomServerTest.php</file>
<file>./tests/e2e/Services/Functions/FunctionsCustomClientTest.php</file>
</testsuite>
<testsuite name="FunctionsSchedule">
<directory>./tests/e2e/Services/FunctionsSchedule</directory>
</testsuite>
<testsuite name="GraphQL">
<directory>./tests/e2e/Services/GraphQL</directory>
</testsuite>
<testsuite name="Messaging">
<directory>./tests/e2e/Services/Messaging</directory>
</testsuite>
<testsuite name="Migrations">
<directory>./tests/e2e/Services/Migrations</directory>
</testsuite>
<testsuite name="Project">
<directory>./tests/e2e/Services/Project</directory>
</testsuite>
<testsuite name="ProjectWebhooks">
<directory>./tests/e2e/Services/ProjectWebhooks</directory>
</testsuite>
<testsuite name="Projects">
<directory>./tests/e2e/Services/Projects</directory>
</testsuite>
<testsuite name="Proxy">
<directory>./tests/e2e/Services/Proxy</directory>
</testsuite>
<testsuite name="Realtime">
<directory>./tests/e2e/Services/Realtime</directory>
</testsuite>
<testsuite name="Sites">
<directory>./tests/e2e/Services/Sites</directory>
</testsuite>
<testsuite name="Storage">
<directory>./tests/e2e/Services/Storage</directory>
</testsuite>
<testsuite name="TablesDB">
<directory>./tests/e2e/Services/TablesDB</directory>
</testsuite>
<testsuite name="Teams">
<directory>./tests/e2e/Services/Teams</directory>
</testsuite>
<testsuite name="Users">
<directory>./tests/e2e/Services/Users</directory>
</testsuite>
<testsuite name="e2e">
<file>./tests/e2e/Client.php</file>
<directory>./tests/e2e/General</directory>
<directory>./tests/e2e/Scopes</directory>
<directory>./tests/e2e/Services/Teams</directory>
<directory>./tests/e2e/Services/Realtime</directory>
<directory>./tests/e2e/Services/Account</directory>
<directory>./tests/e2e/Services/Avatars</directory>
<directory>./tests/e2e/Services/Users</directory>
<directory>./tests/e2e/Services/Console</directory>
<directory>./tests/e2e/Services/Avatars</directory>
<directory>./tests/e2e/Services/Databases</directory>
<directory>./tests/e2e/Services/FunctionsSchedule</directory>
<directory>./tests/e2e/Services/GraphQL</directory>
<directory>./tests/e2e/Services/Health</directory>
<directory>./tests/e2e/Services/Locale</directory>
<directory>./tests/e2e/Services/Projects</directory>
<directory>./tests/e2e/Services/Storage</directory>
<directory>./tests/e2e/Services/Tokens</directory>
<directory>./tests/e2e/Services/Webhooks</directory>
<directory>./tests/e2e/Services/ProjectWebhooks</directory>
<directory>./tests/e2e/Services/Messaging</directory>
<directory>./tests/e2e/Services/Migrations</directory>
<directory>./tests/e2e/Services/Project</directory>
<directory>./tests/e2e/Services/ProjectWebhooks</directory>
<directory>./tests/e2e/Services/Projects</directory>
<directory>./tests/e2e/Services/Proxy</directory>
<directory>./tests/e2e/Services/Realtime</directory>
<directory>./tests/e2e/Services/Sites</directory>
<directory>./tests/e2e/Services/Storage</directory>
<directory>./tests/e2e/Services/TablesDB</directory>
<directory>./tests/e2e/Services/Teams</directory>
<directory>./tests/e2e/Services/Tokens</directory>
<directory>./tests/e2e/Services/Users</directory>
<directory>./tests/e2e/Services/VCS</directory>
<directory>./tests/e2e/Services/Webhooks</directory>
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
<file>./tests/e2e/Services/Functions/FunctionsCustomServerTest.php</file>
<file>./tests/e2e/Services/Functions/FunctionsCustomClientTest.php</file>
+1 -1
View File
@@ -155,7 +155,7 @@ abstract class OAuth2
/**
* @param string $code
*
* @return int
* @return string
*/
public function getAccessTokenExpiry(string $code): int
{
+1 -1
View File
@@ -108,7 +108,7 @@ class Disqus extends OAuth2
}
/**
* @param string $accessToken
* @param string $token
*
* @return string
*/
+1 -1
View File
@@ -33,7 +33,7 @@ class PersonalData extends Password
/**
* Is valid.
*
* @param mixed $password
* @param mixed $value
*
* @return bool
*/
+2 -1
View File
@@ -183,7 +183,8 @@ class TransactionState
if (!isset($state[$collectionId])) {
return $baseCount;
}
$committedDocs = $dbForDatabases->find($collectionId, $queries);
$committedDocs = $dbForDatabases->find($collectionId, \array_merge($queries, [Query::select(['$id'])]));
$committedDocIds = [];
foreach ($committedDocs as $doc) {
$committedDocIds[$doc->getId()] = true;
+6
View File
@@ -6,8 +6,14 @@ use DeviceDetector\DeviceDetector;
class Detector
{
/**
* @param string
*/
protected $userAgent = '';
/**
* @param DeviceDetector
*/
protected $detctor;
/**
+3
View File
@@ -12,6 +12,9 @@ class Compose
*/
protected $compose = [];
/**
* @var string $data
*/
public function __construct(string $data)
{
$this->compose = yaml_parse($data);
+3
View File
@@ -11,6 +11,9 @@ class Service
*/
protected $service = [];
/**
* @var string $path
*/
public function __construct(array $service)
{
$this->service = $service;
+3
View File
@@ -9,6 +9,9 @@ class Env
*/
protected $vars = [];
/**
* @var string $data
*/
public function __construct(string $data)
{
$data = explode("\n", $data);
+4 -5
View File
@@ -101,8 +101,7 @@ class Mail extends Event
/**
* Sets preview for the mail event.
*
* @param string $preview
* @return self
* @return string
*/
public function setPreview(string $preview): self
{
@@ -116,7 +115,7 @@ class Mail extends Event
*
* @return string
*/
public function getPreview(): string
public function getPreview(string $preview): string
{
return $this->preview;
}
@@ -182,7 +181,7 @@ class Mail extends Event
/**
* Set SMTP port
*
* @param int $port
* @param int port
* @return self
*/
public function setSmtpPort(int $port): self
@@ -218,7 +217,7 @@ class Mail extends Event
/**
* Set SMTP secure
*
* @param string $secure
* @param string $password
* @return self
*/
public function setSmtpSecure(string $secure): self
+2 -2
View File
@@ -4,7 +4,7 @@ namespace Appwrite\Event\Message;
use Utopia\Database\Document;
final class Usage extends Base
class Usage extends Base
{
/**
* @param Document $project
@@ -40,7 +40,7 @@ final class Usage extends Base
*/
public static function fromArray(array $data): static
{
return new static(
return new self(
project: new Document($data['project'] ?? []),
metrics: $data['metrics'] ?? [],
reduce: array_map(fn (array $doc) => new Document($doc), $data['reduce'] ?? []),
+2 -2
View File
@@ -86,7 +86,7 @@ class Messaging extends Event
/**
* Returns message document for the messaging event.
*
* @return Document
* @return string
*/
public function getMessage(): Document
{
@@ -96,7 +96,7 @@ class Messaging extends Event
/**
* Sets message ID for the messaging event.
*
* @param string $messageId
* @param string $message
* @return self
*/
public function setMessageId(string $messageId): self
+4 -18
View File
@@ -8,18 +8,6 @@ use Utopia\Database\Query;
class EventProcessor
{
/**
* @param array<mixed> $events
* @return array<string, bool>
*/
private function getEventMap(array $events): array
{
return \array_fill_keys(
\array_map('strval', \array_unique($events)),
true
);
}
/**
* Get function events for a project, using Redis cache
* @param Document|null $project
@@ -38,7 +26,7 @@ class EventProcessor
$cacheKey = \sprintf(
'%s-cache-%s:%s:%s:project:%s:functions:events',
$dbForProject->getCacheName(),
$hostname,
$hostname ?? '',
$dbForProject->getNamespace(),
$dbForProject->getTenant(),
$project->getId()
@@ -48,9 +36,7 @@ class EventProcessor
$cachedFunctionEvents = $dbForProject->getCache()->load($cacheKey, $ttl);
if ($cachedFunctionEvents !== false) {
$decoded = \json_decode($cachedFunctionEvents, true);
return \is_array($decoded) ? $this->getEventMap(\array_keys($decoded)) : [];
return \json_decode($cachedFunctionEvents, true) ?? [];
}
$events = [];
@@ -77,7 +63,7 @@ class EventProcessor
}
}
$uniqueEvents = $this->getEventMap($events);
$uniqueEvents = \array_flip(\array_unique($events));
$dbForProject->getCache()->save($cacheKey, \json_encode($uniqueEvents));
return $uniqueEvents;
@@ -111,6 +97,6 @@ class EventProcessor
}
}
return $this->getEventMap($events);
return \array_flip(\array_unique($events));
}
}
+7 -7
View File
@@ -101,16 +101,16 @@ class Mapper
if (\is_array($modelName)) {
foreach ($modelName as $name) {
$models[] = self::$models[$name];
$models[] = static::$models[$name];
}
} else {
$models[] = self::$models[$modelName];
$models[] = static::$models[$modelName];
}
}
} else {
// If single response, get its model and wrap in array
$modelName = $responses->getModel();
$models = [self::$models[$modelName]];
$models = [static::$models[$modelName]];
}
foreach ($models as $model) {
@@ -425,7 +425,7 @@ class Mapper
'name' => $unionName,
'types' => $types,
'resolveType' => static function ($object) use ($unionName) {
return self::getUnionImplementation($unionName, $object);
return static::getUnionImplementation($unionName, $object);
},
]);
@@ -440,11 +440,11 @@ class Mapper
switch ($name) {
case 'Attributes':
return self::getColumnImplementation($object);
return static::getColumnImplementation($object);
case 'Columns':
return self::getColumnImplementation($object, true);
return static::getColumnImplementation($object, true);
case 'HashOptions':
return self::getHashOptionsImplementation($object);
return static::getHashOptionsImplementation($object);
}
throw new Exception('Unknown union type: ' . $name);
+1 -1
View File
@@ -37,7 +37,7 @@ class Action extends UtopiaAction
* Foreach Document
* Call provided callback for each document in the collection
*
* @param Database $database
* @param string $projectId
* @param string $collection
* @param array $queries
* @param callable $callback
@@ -43,7 +43,6 @@ class Install extends Action
->param('database', '', new WhiteList(['mongodb', 'mariadb', 'postgresql']), 'Database adapter', true)
->param('installId', '', new Text(64, 0), 'Installation ID', true)
->param('retryStep', null, new Nullable(new WhiteList([Server::STEP_DOCKER_COMPOSE, Server::STEP_ENV_VARS, Server::STEP_DOCKER_CONTAINERS], true)), 'Retry from step', true)
->param('migrate', false, new \Utopia\Validator\Boolean(true), 'Run database migration after upgrade', true)
->inject('request')
->inject('response')
->inject('swooleResponse')
@@ -65,7 +64,6 @@ class Install extends Action
string $database,
string $installId,
?string $retryStep,
bool $migrate,
Request $request,
Response $response,
SwooleResponse $swooleResponse,
@@ -323,28 +321,6 @@ class Install extends Action
}
};
$responseSent = false;
$onComplete = function () use ($wantsStream, $swooleResponse, $response, $installId, $state, &$responseSent) {
if ($responseSent) {
return;
}
$responseSent = true;
$state->updateGlobalLock($installId, Server::STATUS_COMPLETED);
if ($wantsStream) {
$this->writeSseEvent($swooleResponse, 'done', ['installId' => $installId, 'success' => true]);
usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS);
$swooleResponse->write(": keepalive\n\n");
usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS);
$swooleResponse->end();
} else {
$response->json([
'success' => true,
'installId' => $installId,
'message' => 'Installation completed successfully',
]);
}
};
$installer->performInstallation(
$httpPort ?: $config->getDefaultHttpPort(),
$httpsPort ?: $config->getDefaultHttpsPort(),
@@ -355,12 +331,23 @@ class Install extends Action
$progress,
$retryStep,
$config->isUpgrade(),
$account,
$onComplete,
$migrate,
$account
);
$onComplete();
if ($wantsStream) {
$this->writeSseEvent($swooleResponse, 'done', ['installId' => $installId, 'success' => true]);
usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS);
$swooleResponse->write(": keepalive\n\n");
usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS);
$swooleResponse->end();
} else {
$response->json([
'success' => true,
'installId' => $installId,
'message' => 'Installation completed successfully',
]);
}
$state->updateGlobalLock($installId, Server::STATUS_COMPLETED);
} catch (\Throwable $e) {
$this->handleInstallationError($e, $installId, $wantsStream, $response, $swooleResponse, $state);
}
@@ -24,7 +24,7 @@ class View extends Action
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/')
->desc('Serve installer UI')
->param('step', 1, new Integer(true), 'Step number (1-6)', true)
->param('step', 1, new Integer(true), 'Step number (1-5)', true)
->param('partial', null, new Nullable(new Text(1, 0)), 'Render partial step only', true)
->inject('request')
->inject('response')
@@ -52,13 +52,10 @@ class View extends Action
$defaultEmailCertificates = 'walterobrien@example.com';
}
$step = max(1, min(6, $step));
$step = max(1, min(5, $step));
if ($isUpgrade && ($step === 2 || $step === 3)) {
$step = 4;
}
if (!$isUpgrade && $step === 6) {
$step = 4;
}
$partialFile = $paths['views'] . "/installer/templates/steps/step-{$step}.phtml";
if (!is_file($partialFile)) {

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