1
0
Fork 0
mirror of synced 2024-06-27 02:31:04 +12:00

Merge branch 'master' of https://github.com/appwrite/appwrite into feat-db-pools-eldad

This commit is contained in:
Eldad Fux 2022-10-15 22:08:16 +03:00
commit 7484656be3
69 changed files with 814 additions and 538 deletions

2
.env
View file

@ -61,7 +61,7 @@ _APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_SMS_PROVIDER=sms://mock
_APP_SMS_PROVIDER=sms://username:password@mock
_APP_SMS_FROM=+123456789
_APP_STORAGE_LIMIT=30000000
_APP_STORAGE_PREVIEW_LIMIT=20000000

View file

@ -21,6 +21,11 @@ Happy contributing!
(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)
### Have you added your change to the [Changelog](https://github.com/appwrite/appwrite/blob/master/CHANGES.md)?
(The CHANGES.md file tracks all the changes that make it to the `main` branch. Add your change to this file in the following format)
- One line description of your PR [#pr_number](Link to your PR)
### Have you read the [Contributing Guidelines on issues](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md)?
(Write your answer here.)

View file

@ -4,7 +4,7 @@ on: [pull_request]
jobs:
tests:
name: Unit & E2E
runs-on: self-hosted
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@ -23,12 +23,14 @@ jobs:
# Upstream bug causes buildkit pulls to fail so prefetch base images
# https://github.com/moby/moby/issues/41864
run: |
echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env
export COMPOSE_INTERACTIVE_NO_CLI
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
export BUILDKIT_PROGRESS=plain
docker pull composer:2.0
docker pull php:8.0-cli-alpine
docker compose build --progress=plain
docker compose build appwrite
docker compose up -d
sleep 10
sleep 30
- name: Doctor
run: docker compose exec -T appwrite doctor
@ -37,10 +39,3 @@ jobs:
- name: Run Tests
run: docker compose exec -T appwrite test --debug
- name: Teardown
if: always()
run: |
docker ps -aq | xargs docker rm --force || true
docker volume prune --force || true
docker network prune --force || true

View file

@ -1,6 +1,28 @@
# Version 1.1.0
## Bugs
- Fix license detection for Flutter and Dart SDKs [#4435](https://github.com/appwrite/appwrite/pull/4435)
# Version 1.0.3
## Bugs
- Fix document audit deletion [#4429](https://github.com/appwrite/appwrite/pull/4429)
- Fix attribute and index deletion when deleting a collection [#4429](https://github.com/appwrite/appwrite/pull/4429)
# Version 1.0.2
## Bugs
- Fixed nullable values in functions variables [#3885](https://github.com/appwrite/appwrite/pull/3885)
- Fixed migration for audit by migrating the `time` attribute [#4038](https://github.com/appwrite/appwrite/pull/4038)
- Fixed default value for creating Boolean Attribute [#4040](https://github.com/appwrite/appwrite/pull/4040)
- Fixed phone authentication code to be hashed in the internal database [#3906](https://github.com/appwrite/appwrite/pull/3906)
- Fixed `/v1/teams/:teamId/memberships/:membershipId` response [#3883](https://github.com/appwrite/appwrite/pull/3883)
- Fixed removing variables when function is deleted [#3884](https://github.com/appwrite/appwrite/pull/3884)
- Fixed scheduled function not being triggered [#3908](https://github.com/appwrite/appwrite/pull/3908)
- Fixed Phone Provider configuration [#3862](https://github.com/appwrite/appwrite/pull/3883)
- Fixed Queries with `0` values [utopia-php/database#194](https://github.com/utopia-php/database/pull/194)
# Version 1.0.1
## Bugs
- Fixed migration for abuse by migrating the `time` attribute [3839](https://github.com/appwrite/appwrite/pull/3839)
- Fixed migration for abuse by migrating the `time` attribute [#3839](https://github.com/appwrite/appwrite/pull/3839)
# Version 1.0.0
## BREAKING CHANGES

View file

@ -93,6 +93,7 @@ git clone git@github.com:[YOUR_FORK_HERE]/appwrite.git
cd appwrite
docker compose build
docker compose up -d
```

View file

@ -8,13 +8,16 @@
<br />
</p>
<!-- [![Hacktoberfest](https://img.shields.io/static/v1?label=hacktoberfest&message=friendly&color=90a88b&style=flat-square)](https://hacktoberfest.appwrite.io) -->
<!-- [![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite) -->
[![Hacktoberfest](https://img.shields.io/static/v1?label=hacktoberfest&message=friendly&color=191120&style=flat-square)](https://hacktoberfest.appwrite.io)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github)
[![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite)
[![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite)
[![Build Status](https://img.shields.io/github/workflow/status/appwrite/appwrite/Tests?label=tests&style=flat-square)](https://github.com/appwrite/appwrite/actions)
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
[![翻译](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md)
[![周边商店](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io)
<!-- [![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite) -->
<!-- [![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md) -->
<!-- [![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io) -->
[English](README.md) | 简体中文
@ -59,7 +62,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.0.1
appwrite/appwrite:1.0.3
```
### Windows
@ -71,7 +74,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.0.1
appwrite/appwrite:1.0.3
```
#### PowerShell
@ -81,7 +84,7 @@ docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:1.0.1
appwrite/appwrite:1.0.3
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。

View file

@ -11,15 +11,17 @@
<br />
</p>
<!-- [![Hacktoberfest](https://img.shields.io/static/v1?label=hacktoberfest&message=friendly&color=90a88b&style=flat-square)](https://hacktoberfest.appwrite.io) -->
<!-- [![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite) -->
[![Hacktoberfest](https://img.shields.io/static/v1?label=hacktoberfest&message=ready&color=191120&style=flat-square)](https://hacktoberfest.appwrite.io)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github)
[![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite)
[![Build Status](https://img.shields.io/github/workflow/status/appwrite/appwrite/Tests?label=tests&style=flat-square)](https://github.com/appwrite/appwrite/actions)
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
[![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md)
[![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io)
<!-- [![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite) -->
<!-- [![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md) -->
<!-- [![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io) -->
English | [简体中文](README-CN.md)
@ -73,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.0.1
appwrite/appwrite:1.0.3
```
### Windows
@ -85,17 +87,17 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.0.1
appwrite/appwrite:1.0.3
```
#### PowerShell
```powershell
docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:1.0.1
docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.0.3
```
Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after installation completes.

View file

@ -523,17 +523,6 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('region'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('description'),
'type' => Database::VAR_STRING,
@ -3032,17 +3021,6 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('region'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('value'),
'type' => Database::VAR_INTEGER,

View file

@ -1,7 +1,7 @@
<?php
/**
* List of publicly accessiable system events
* List of publicly accessible system events
*/
use Appwrite\Utopia\Response;

View file

@ -1,9 +0,0 @@
<?php
return [
'default' => [
'name' => 'Default',
'default' => true,
'disabled' => false,
]
];

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,7 +2,6 @@
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\SMS\Adapter\Mock;
use Appwrite\Auth\Validator\Password;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
@ -928,7 +927,7 @@ App::post('/v1/account/sessions/phone')
])));
}
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator();
$secret = Auth::codeGenerator();
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_PHONE);
$token = new Document([
@ -1416,7 +1415,7 @@ App::get('/v1/account/sessions/:sessionId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SESSION)
->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to get the current device session.')
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to get the current device session.')
->inject('response')
->inject('user')
->inject('locale')
@ -1694,7 +1693,7 @@ App::delete('/v1/account/sessions/:sessionId')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('abuse-limit', 100)
->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to delete the current device session.')
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to delete the current device session.')
->inject('request')
->inject('response')
->inject('user')
@ -1767,7 +1766,7 @@ App::patch('/v1/account/sessions/:sessionId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SESSION)
->label('abuse-limit', 10)
->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to update the current device session.')
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to update the current device session.')
->inject('request')
->inject('response')
->inject('user')
@ -2256,7 +2255,7 @@ App::post('/v1/account/verification/phone')
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$verificationSecret = Auth::tokenGenerator();
$secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$digits : Auth::codeGenerator();
$secret = Auth::codeGenerator();
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM);
$verification = new Document([

View file

@ -89,11 +89,11 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
}
// Must throw here since dbForProject->createAttribute is performed by db worker
if ($required && $default) {
if ($required && isset($default)) {
throw new Exception(Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, 'Cannot set default value for required attribute');
}
if ($array && $default) {
if ($array && isset($default)) {
throw new Exception(Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, 'Cannot set default value for array attributes');
}
@ -1783,7 +1783,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Index Key.')
->inject('response')
->inject('dbForProject')
@ -1856,7 +1856,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('documentId', '', new CustomId(), 'Document ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
->inject('response')
@ -2074,8 +2074,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', null, new UID(), 'Document ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', '', new UID(), 'Document ID.')
->inject('response')
->inject('dbForProject')
->inject('mode')
@ -2137,7 +2137,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('documentId', null, new UID(), 'Document ID.')
->param('documentId', '', new UID(), 'Document ID.')
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true)
->inject('response')
->inject('dbForProject')
@ -2243,8 +2243,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID.')
->param('documentId', null, new UID(), 'Document ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('documentId', '', new UID(), 'Document ID.')
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->inject('response')
@ -2376,8 +2376,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', null, new UID(), 'Document ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', '', new UID(), 'Document ID.')
->inject('response')
->inject('dbForProject')
->inject('events')
@ -2534,7 +2534,7 @@ App::get('/v1/databases/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
@ -2648,7 +2648,7 @@ App::get('/v1/databases/:databaseId/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
@ -2763,7 +2763,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}

View file

@ -71,8 +71,13 @@ App::post('/v1/functions')
->param('enabled', true, new Boolean(), 'Is function enabled?', true)
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->inject('events')
->action(function (string $functionId, string $name, array $execute, string $runtime, array $events, string $schedule, int $timeout, bool $enabled, Response $response, Database $dbForProject, Event $eventsInstance) {
->action(function (string $functionId, string $name, array $execute, string $runtime, array $events, string $schedule, int $timeout, bool $enabled, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) {
$cron = !empty($schedule) ? new CronExpression($schedule) : null;
$next = !empty($schedule) ? DateTime::format($cron->getNextRunDate()) : null;
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$function = $dbForProject->createDocument('functions', new Document([
@ -86,11 +91,22 @@ App::post('/v1/functions')
'schedule' => $schedule,
'scheduleUpdatedAt' => DateTime::now(),
'schedulePrevious' => null,
'scheduleNext' => null,
'scheduleNext' => $next,
'timeout' => $timeout,
'search' => implode(' ', [$functionId, $name, $runtime])
]));
if ($next) {
// Async task reschedule
$functionEvent = new Func();
$functionEvent
->setFunction($function)
->setType('schedule')
->setUser($user)
->setProject($project)
->schedule(new \DateTime($next));
}
$eventsInstance->setParam('functionId', $function->getId());
$response
@ -281,7 +297,7 @@ App::get('/v1/functions/:functionId/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
@ -384,7 +400,7 @@ App::get('/v1/functions/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
@ -442,11 +458,9 @@ App::put('/v1/functions/:functionId')
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$original = $function->getAttribute('schedule', '');
$cron = (!empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null;
$next = (!empty($function->getAttribute('deployment')) && !empty($schedule)) ? DateTime::format($cron->getNextRunDate()) : null;
$cron = !empty($schedule) ? new CronExpression($schedule) : null;
$next = !empty($schedule) ? DateTime::format($cron->getNextRunDate()) : null;
$scheduleUpdatedAt = $schedule !== $original ? DateTime::now() : $function->getAttribute('scheduleUpdatedAt');
$enabled ??= $function->getAttribute('enabled', true);
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
@ -454,14 +468,14 @@ App::put('/v1/functions/:functionId')
'name' => $name,
'events' => $events,
'schedule' => $schedule,
'scheduleUpdatedAt' => $scheduleUpdatedAt,
'scheduleUpdatedAt' => DateTime::now(),
'scheduleNext' => $next,
'timeout' => $timeout,
'enabled' => $enabled,
'search' => implode(' ', [$functionId, $name, $function->getAttribute('runtime')]),
])));
if ($next && $schedule !== $original) {
if ($next) {
// Async task reschedule
$functionEvent = new Func();
$functionEvent
@ -519,24 +533,10 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId')
throw new Exception(Exception::BUILD_NOT_READY);
}
$schedule = $function->getAttribute('schedule', '');
$cron = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null;
$next = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? DateTime::format($cron->getNextRunDate()) : null;
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
'deployment' => $deployment->getId(),
'scheduleNext' => $next,
'deployment' => $deployment->getId()
])));
if ($next) { // Init first schedule
$functionEvent = new Func();
$functionEvent
->setType('schedule')
->setFunction($function)
->setProject($project)
->schedule(new \DateTime($next));
}
$events
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
@ -935,6 +935,67 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
$response->noContent();
});
App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
->groups(['api', 'functions'])
->desc('Create Build')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createBuild')
->label('sdk.description', '/docs/references/functions/create-build.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->param('buildId', '', new UID(), 'Build unique ID.')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('events')
->action(function (string $functionId, string $deploymentId, string $buildId, Response $response, Database $dbForProject, Document $project, Event $events) {
$function = $dbForProject->getDocument('functions', $functionId);
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId));
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
if ($build->getAttribute('status') !== 'failed') {
throw new Exception(Exception::BUILD_IN_PROGRESS, 'Build not failed');
}
$events
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
// Retry the build
$buildEvent = new Build();
$buildEvent
->setType(BUILD_TYPE_RETRY)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->trigger();
$response->noContent();
});
App::post('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
->desc('Create Execution')
@ -1066,21 +1127,21 @@ App::post('/v1/functions/:functionId/executions')
}
$vars = array_reduce($function['vars'] ?? [], function (array $carry, Document $var) {
$carry[$var->getAttribute('key')] = $var->getAttribute('value');
$carry[$var->getAttribute('key')] = $var->getAttribute('value') ?? '';
return $carry;
}, []);
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_ID' => $function->getId(),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_TRIGGER' => 'http',
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
'APPWRITE_FUNCTION_DATA' => $data,
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_USER_ID' => $user->getId(),
'APPWRITE_FUNCTION_JWT' => $jwt,
'APPWRITE_FUNCTION_TRIGGER' => 'http',
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_FUNCTION_DATA' => $data ?? '',
'APPWRITE_FUNCTION_USER_ID' => $user->getId() ?? '',
'APPWRITE_FUNCTION_JWT' => $jwt ?? '',
]);
/** Execute function */
@ -1258,65 +1319,6 @@ App::get('/v1/functions/:functionId/executions/:executionId')
$response->dynamic($execution, Response::MODEL_EXECUTION);
});
App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
->groups(['api', 'functions'])
->desc('Retry Build')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'retryBuild')
->label('sdk.description', '/docs/references/functions/retry-build.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->param('buildId', '', new UID(), 'Build unique ID.')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('events')
->action(function (string $functionId, string $deploymentId, string $buildId, Response $response, Database $dbForProject, Document $project, Event $events) {
$function = $dbForProject->getDocument('functions', $functionId);
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId));
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
if ($build->getAttribute('status') !== 'failed') {
throw new Exception(Exception::BUILD_IN_PROGRESS, 'Build not failed');
}
$events
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
// Retry the build
$buildEvent = new Build();
$buildEvent
->setType(BUILD_TYPE_RETRY)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->trigger();
$response->noContent();
});
// Variables
App::post('/v1/functions/:functionId/variables')
@ -1332,7 +1334,7 @@ App::post('/v1/functions/:functionId/variables')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->param('functionId', null, new UID(), 'Function unique ID.', false)
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', false)
->inject('response')
@ -1384,7 +1386,7 @@ App::get('/v1/functions/:functionId/variables')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE_LIST)
->param('functionId', null, new UID(), 'Function unique ID.', false)
->param('functionId', '', new UID(), 'Function unique ID.', false)
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, Response $response, Database $dbForProject) {
@ -1411,8 +1413,8 @@ App::get('/v1/functions/:functionId/variables/:variableId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->param('functionId', null, new UID(), 'Function unique ID.', false)
->param('variableId', null, new UID(), 'Variable unique ID.', false)
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) {
@ -1447,8 +1449,8 @@ App::put('/v1/functions/:functionId/variables/:variableId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->param('functionId', null, new UID(), 'Function unique ID.', false)
->param('variableId', null, new UID(), 'Variable unique ID.', false)
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', true)
->inject('response')
@ -1499,8 +1501,8 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
->label('sdk.description', '/docs/references/functions/delete-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', null, new UID(), 'Function unique ID.', false)
->param('variableId', null, new UID(), 'Variable unique ID.', false)
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) {

View file

@ -60,7 +60,6 @@ App::post('/v1/projects')
->param('projectId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('teamId', '', new UID(), 'Team unique ID.')
->param('region', '', new Whitelist(array_keys(array_filter(Config::getParam('regions'), fn($config) => !$config['disabled']))), 'Project Region.')
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
->param('logo', '', new Text(1024), 'Project logo.', true)
->param('url', '', new URL(), 'Project URL.', true)
@ -107,7 +106,6 @@ App::post('/v1/projects')
'name' => $name,
'teamInternalId' => $team->getInternalId(),
'teamId' => $team->getId(),
'region' => $region,
'description' => $description,
'logo' => $logo,
'url' => $url,
@ -341,7 +339,7 @@ App::get('/v1/projects/:projectId/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
@ -595,7 +593,7 @@ App::post('/v1/projects/:projectId/webhooks')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_WEBHOOK)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
@ -683,8 +681,8 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_WEBHOOK)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('webhookId', null, new UID(), 'Webhook unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('webhookId', '', new UID(), 'Webhook unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
@ -717,8 +715,8 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_WEBHOOK)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('webhookId', null, new UID(), 'Webhook unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('webhookId', '', new UID(), 'Webhook unique ID.')
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
@ -771,8 +769,8 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_WEBHOOK)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('webhookId', null, new UID(), 'Webhook unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('webhookId', '', new UID(), 'Webhook unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
@ -809,8 +807,8 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
->label('sdk.method', 'deleteWebhook')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('webhookId', null, new UID(), 'Webhook unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('webhookId', '', new UID(), 'Webhook unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
@ -849,7 +847,7 @@ App::post('/v1/projects/:projectId/keys')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_KEY)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true)
@ -899,7 +897,7 @@ App::get('/v1/projects/:projectId/keys')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_KEY_LIST)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, Response $response, Database $dbForConsole) {
@ -931,8 +929,8 @@ App::get('/v1/projects/:projectId/keys/:keyId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_KEY)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('keyId', null, new UID(), 'Key unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
@ -965,8 +963,8 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_KEY)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('keyId', null, new UID(), 'Key unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true)
@ -1011,8 +1009,8 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
->label('sdk.method', 'deleteKey')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('keyId', null, new UID(), 'Key unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
@ -1051,7 +1049,7 @@ App::post('/v1/projects/:projectId/platforms')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PLATFORM)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.')
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
@ -1133,8 +1131,8 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PLATFORM)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('platformId', null, new UID(), 'Platform unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('platformId', '', new UID(), 'Platform unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
@ -1167,8 +1165,8 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PLATFORM)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('platformId', null, new UID(), 'Platform unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('platformId', '', new UID(), 'Platform unique ID.')
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
->param('key', '', new Text(256), 'Package name for android or bundle ID for iOS. Max length: 256 chars.', true)
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
@ -1214,8 +1212,8 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
->label('sdk.method', 'deletePlatform')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('platformId', null, new UID(), 'Platform unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('platformId', '', new UID(), 'Platform unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
@ -1254,7 +1252,7 @@ App::post('/v1/projects/:projectId/domains')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOMAIN)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('domain', null, new DomainValidator(), 'Domain name.')
->inject('response')
->inject('dbForConsole')
@ -1351,8 +1349,8 @@ App::get('/v1/projects/:projectId/domains/:domainId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOMAIN)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('domainId', null, new UID(), 'Domain unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('domainId', '', new UID(), 'Domain unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
@ -1385,8 +1383,8 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOMAIN)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('domainId', null, new UID(), 'Domain unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('domainId', '', new UID(), 'Domain unique ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
@ -1444,8 +1442,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
->label('sdk.method', 'deleteDomain')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('projectId', null, new UID(), 'Project unique ID.')
->param('domainId', null, new UID(), 'Domain unique ID.')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('domainId', '', new UID(), 'Domain unique ID.')
->inject('response')
->inject('dbForConsole')
->inject('deletes')

View file

@ -346,7 +346,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new CustomId(), 'File ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('file', [], new File(), 'Binary file.', false)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
@ -667,7 +667,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE_LIST)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
@ -744,7 +744,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('dbForProject')
@ -793,7 +793,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->label('sdk.methodType', 'location')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID')
->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true)
->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true)
@ -959,7 +959,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->param('bucketId', null, new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('bucketId', '', new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('request')
->inject('response')
@ -1099,7 +1099,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('request')
@ -1256,7 +1256,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission string. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->inject('response')
@ -1358,7 +1358,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->label('sdk.description', '/docs/references/storage/delete-file.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('dbForProject')
@ -1518,7 +1518,7 @@ App::get('/v1/storage/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
@ -1629,7 +1629,7 @@ App::get('/v1/storage/:bucketId/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}

View file

@ -553,7 +553,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->label('sdk.description', '/docs/references/teams/get-team-member.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('response')
@ -865,7 +865,7 @@ App::get('/v1/teams/:teamId/logs')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->param('teamId', null, new UID(), 'Team ID.')
->param('teamId', '', new UID(), 'Team ID.')
->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true)
->inject('response')
->inject('dbForProject')

View file

@ -290,10 +290,10 @@ App::post('/v1/users/scrypt')
->param('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt.')
->param('passwordSalt', '', new Text(128), 'Optional salt used to hash password.')
->param('passwordCpu', '', new Integer(), 'Optional CPU cost used to hash password.')
->param('passwordMemory', '', new Integer(), 'Optional memory cost used to hash password.')
->param('passwordParallel', '', new Integer(), 'Optional parallelization cost used to hash password.')
->param('passwordLength', '', new Integer(), 'Optional hash length used to hash password.')
->param('passwordCpu', 8, new Integer(), 'Optional CPU cost used to hash password.')
->param('passwordMemory', 14, new Integer(), 'Optional memory cost used to hash password.')
->param('passwordParallel', 1, new Integer(), 'Optional parallelization cost used to hash password.')
->param('passwordLength', 64, new Integer(), 'Optional hash length used to hash password.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForProject')
@ -888,7 +888,7 @@ App::patch('/v1/users/:userId/phone')
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
} catch (Duplicate $th) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
}
$events->setParam('userId', $user->getId());
@ -981,7 +981,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('userId', '', new UID(), 'User ID.')
->param('sessionId', null, new UID(), 'Session ID.')
->param('sessionId', '', new UID(), 'Session ID.')
->inject('response')
->inject('dbForProject')
->inject('events')
@ -1176,7 +1176,7 @@ App::get('/v1/users/usage')
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff),
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}

View file

@ -28,10 +28,6 @@ App::shutdown()
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
$header
->setParam('regions', Config::getParam('regions', []))
;
$footer
->setParam('home', App::getEnv('_APP_HOME', ''))
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))

View file

@ -28,6 +28,7 @@ $http = new Server("0.0.0.0", App::getEnv('PORT', 80));
$payloadSize = 6 * (1024 * 1024); // 6MB
$workerNumber = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6));
$http
->set([
'worker_num' => $workerNumber,

View file

@ -96,8 +96,8 @@ const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 500;
const APP_VERSION_STABLE = '1.0.1';
const APP_CACHE_BUSTER = 501;
const APP_VERSION_STABLE = '1.0.3';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@ -190,7 +190,6 @@ Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
Config::load('services', __DIR__ . '/config/services.php'); // List of services
Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
Config::load('regions', __DIR__ . '/config/regions.php'); // List of available regions
Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');
@ -1130,7 +1129,7 @@ App::setResource('sms', function () {
$secret = $dsn->getPassword();
return match ($dsn->getHost()) {
'mock' => new Mock('', ''), // used for tests
'mock' => new Mock($user, $secret), // used for tests
'twilio' => new Twilio($user, $secret),
'text-magic' => new TextMagic($user, $secret),
'telesign' => new Telesign($user, $secret),

View file

@ -72,7 +72,7 @@ Redistribution and use in source and binary forms, with or without modification,
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name Appwrite nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.';

View file

@ -82,8 +82,7 @@ $logError = function (Throwable $error, string $action = 'syncUsageStats') use (
function aggregateTimeseries(UtopiaDatabase $database, InfluxDatabase $influxDB, callable $logError): void
{
$interval = (int) App::getEnv('_APP_USAGE_TIMESERIES_INTERVAL', '30'); // 30 seconds (by default)
$region = App::getEnv('region', 'default');
$usage = new TimeSeries($region, $database, $influxDB, $logError);
$usage = new TimeSeries($database, $influxDB, $logError);
Console::loop(function () use ($interval, $usage) {
$now = date('d-m-Y H:i:s', time());
@ -101,9 +100,8 @@ function aggregateTimeseries(UtopiaDatabase $database, InfluxDatabase $influxDB,
function aggregateDatabase(UtopiaDatabase $database, callable $logError): void
{
$interval = (int) App::getEnv('_APP_USAGE_DATABASE_INTERVAL', '900'); // 15 minutes (by default)
$region = App::getEnv('region', 'default');
$usage = new Database($region, $database, $logError);
$aggregrator = new Aggregator($region, $database, $logError);
$usage = new Database($database, $logError);
$aggregrator = new Aggregator($database, $logError);
Console::loop(function () use ($interval, $usage, $aggregrator) {
$now = date('d-m-Y H:i:s', time());

View file

@ -1,6 +1,3 @@
<?php
$regions = $this->getParam('regions', []);
?>
<header class="clear" data-version>
<a href="/console" class="logo pull-start">
<img src="/images/appwrite.svg" alt="Appwrite Light Logo" class="force-light" loading="lazy" />
@ -218,18 +215,7 @@ $regions = $this->getParam('regions', []);
name="projectId" />
<label>Name</label>
<input type="text" class="full-width" name="name" required autocomplete="off" maxlength="128" />
<?php if(count($regions) > 1): ?>
<label>Region</label>
<select name="region" class="margin-bottom-xl">
<?php foreach($regions as $key => $region): ?>
<option <?php echo ($region['default'] ?? false) ? 'selected' : '' ?> <?php echo ($region['disabled'] ?? false ) ? 'disabled' : '' ?> value="<?php echo $key ?>"><?php echo $region['name'] ?></option>
<?php endforeach; ?>
</select>
<?php else: ?>
<input type="hidden" name="region" value="<?php echo array_key_first($regions) ?>" class="margin-bottom-xl" />
<?php endif; ?>
<input type="text" class="full-width margin-bottom-xl" name="name" required autocomplete="off" maxlength="128" />
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>

View file

@ -81,6 +81,8 @@ $escapedPermissions = \array_map(function ($perm) {
list="types"
type="text"
x-model="permission.role"
@keydown.enter="prevent($event)"
@keydown="clearPermission(index)"
@keyup="updatePermission(index)"/>
</td>
<?php foreach ($escapedPermissions as $permission): ?>

View file

@ -391,9 +391,9 @@ sort($patterns);
<tr>
<th width="30"></th>
<th width="160">Created</th>
<th width="150">Status</th>
<th width="120">Trigger</th>
<th width="80">Runtime</th>
<th width="100">Status</th>
<th width="80">Trigger</th>
<th width="60">Runtime</th>
<th width=""></th>
</tr>
</thead>
@ -422,11 +422,11 @@ sort($patterns);
<td data-title="">
<div data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-title="" style="display: flex;">
<button class="desktops-only pull-end link margin-start text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Stderr</button>
<!--button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button-->
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-response-{{execution.$id}}">Response</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-response-{{execution.$id}}">Response</button>
<!--button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button-->
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button>
<button class="phones-only-inline tablets-only-inline link text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Stderr</button>
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-response-{{execution.$id}}">
@ -841,6 +841,8 @@ sort($patterns);
data-failure-param-alert-text="Failed to execute function"
data-failure-param-alert-classname="error">
<input name="async" data-cast-to="bool" value="true" type="hidden" />
<label for="execution-data">Custom Data</label>
<textarea id="execution-data" name="data" autocomplete="off" class="margin-bottom" placeholder="Data string (optional)"></textarea>

View file

@ -109,6 +109,7 @@ class BuildsV1 extends Worker
/** Trigger Webhook */
$deploymentModel = new Deployment();
$deploymentUpdate = new Event(Event::WEBHOOK_QUEUE_NAME, Event::WEBHOOK_CLASS_NAME);
$deploymentUpdate
->setProject($project)

View file

@ -1,6 +1,7 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Event\Mail;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Resque\Worker;
use Utopia\App;
@ -374,19 +375,19 @@ class CertificatesV1 extends Worker
Console::warning('Cannot renew domain (' . $domain . ') on attempt no. ' . $attempt . ' certificate: ' . $errorMessage);
// Send mail to administratore mail
Resque::enqueue(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME, [
'from' => 'console',
'project' => 'console',
'name' => 'Appwrite Administrator',
'recipient' => App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'),
'url' => 'https://' . $domain,
'locale' => App::getEnv('_APP_LOCALE', 'en'),
'type' => MAIL_TYPE_CERTIFICATE,
'domain' => $domain,
'error' => $errorMessage,
'attempt' => $attempt
]);
$mail = new Mail();
$mail
->setType(MAIL_TYPE_CERTIFICATE)
->setRecipient(App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'))
->setUrl('https://' . $domain)
->setLocale(App::getEnv('_APP_LOCALE', 'en'))
->setName('Appwrite Administrator')
->setPayload([
'domain' => $domain,
'error' => $errorMessage,
'attempt' => $attempt
])
->trigger();
}
/**

View file

@ -195,21 +195,24 @@ class DeletesV1 extends Worker
protected function deleteCollection(Document $document, Document $project): void
{
$collectionId = $document->getId();
$databaseId = str_replace('database_', '', $document->getCollection());
$databaseId = $document->getAttribute('databaseId');
$databaseInternalId = $document->getAttribute('databaseInternalId');
$dbForProject = $this->getProjectDB($project);
$dbForProject->deleteCollection('database_' . $databaseId . '_collection_' . $document->getInternalId());
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $document->getInternalId());
$this->deleteByGroup('attributes', [
Query::equal('databaseId', [$databaseId]),
Query::equal('collectionId', [$collectionId])
], $dbForProject);
$this->deleteByGroup('indexes', [
Query::equal('databaseId', [$databaseId]),
Query::equal('collectionId', [$collectionId])
], $dbForProject);
$this->deleteAuditLogsByResource('collection/' . $collectionId, $project);
$this->deleteAuditLogsByResource('database/' . $databaseId . '/collection/' . $collectionId, $project);
}
/**
@ -415,6 +418,14 @@ class DeletesV1 extends Worker
$dbForProject = $this->getProjectDB($project);
$functionId = $document->getId();
/**
* Delete Variables
*/
Console::info("Deleting variables for function " . $functionId);
$this->deleteByGroup('variables', [
Query::equal('functionId', [$functionId])
], $dbForProject);
/**
* Delete Deployments
*/

View file

@ -271,17 +271,17 @@ class FunctionsV1 extends Worker
/** Collect environment variables */
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deploymentId,
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
'APPWRITE_FUNCTION_TRIGGER' => $trigger,
'APPWRITE_FUNCTION_EVENT' => $event,
'APPWRITE_FUNCTION_EVENT_DATA' => $eventData,
'APPWRITE_FUNCTION_DATA' => $data,
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_USER_ID' => $user->getId(),
'APPWRITE_FUNCTION_JWT' => $jwt,
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_FUNCTION_EVENT' => $event ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' => $eventData ?? '',
'APPWRITE_FUNCTION_DATA' => $data ?? '',
'APPWRITE_FUNCTION_USER_ID' => $user->getId() ?? '',
'APPWRITE_FUNCTION_JWT' => $jwt ?? '',
]);
/** Execute function */

View file

@ -32,9 +32,10 @@ class MailsV1 extends Worker
return;
}
$project = new Document($this->args['project']);
$project = new Document($this->args['project'] ?? []);
$user = new Document($this->args['user'] ?? []);
$team = new Document($this->args['team'] ?? []);
$payload = $this->args['payload'] ?? [];
$recipient = $this->args['recipient'];
$url = $this->args['url'];
@ -42,20 +43,20 @@ class MailsV1 extends Worker
$type = $this->args['type'];
$prefix = $this->getPrefix($type);
$locale = new Locale($this->args['locale']);
$projectName = $project->getAttribute('name', '[APP-NAME]');
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
if (!$this->doesLocaleExist($locale, $prefix)) {
$locale->setDefault('en');
}
$from = $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
$body = Template::fromFile(__DIR__ . '/../config/locale/templates/email-base.tpl');
$subject = '';
switch ($type) {
case MAIL_TYPE_CERTIFICATE:
$domain = $this->args['domain'];
$error = $this->args['error'];
$attempt = $this->args['attempt'];
$domain = $payload['domain'];
$error = $payload['error'];
$attempt = $payload['attempt'];
$subject = \sprintf($locale->getText("$prefix.subject"), $domain);
$body->setParam('{{domain}}', $domain);

View file

@ -1,6 +1,5 @@
<?php
use Appwrite\Auth\SMS;
use Appwrite\SMS\Adapter\Mock;
use Appwrite\SMS\Adapter\Telesign;
use Appwrite\SMS\Adapter\TextMagic;
@ -9,6 +8,7 @@ use Appwrite\SMS\Adapter\Msg91;
use Appwrite\SMS\Adapter\Vonage;
use Appwrite\DSN\DSN;
use Appwrite\Resque\Worker;
use Appwrite\SMS\Adapter;
use Utopia\App;
use Utopia\CLI\Console;
@ -19,7 +19,7 @@ Console::success(APP_NAME . ' messaging worker v1 has started' . "\n");
class MessagingV1 extends Worker
{
protected ?SMS $sms = null;
protected ?Adapter $sms = null;
protected ?string $from = null;
public function getName(): string
@ -34,7 +34,7 @@ class MessagingV1 extends Worker
$secret = $dsn->getPassword();
$this->sms = match ($dsn->getHost()) {
'mock' => new Mock('', ''), // used for tests
'mock' => new Mock($user, $secret), // used for tests
'twilio' => new Twilio($user, $secret),
'text-magic' => new TextMagic($user, $secret),
'telesign' => new Telesign($user, $secret),

View file

@ -70,12 +70,13 @@ services:
appwrite:
container_name: appwrite
<<: *x-logging
image: appwrite-dev
build:
context: .
args:
- DEBUG=false
- TESTING=true
- VERSION=dev
DEBUG: false
TESTING: true
VERSION: dev
ports:
- 9501:80
networks:
@ -181,8 +182,8 @@ services:
entrypoint: realtime
<<: *x-logging
container_name: appwrite-realtime
build:
context: .
image: appwrite-dev
restart: unless-stopped
ports:
- 9505:80
labels:
@ -227,8 +228,7 @@ services:
entrypoint: worker-audits
<<: *x-logging
container_name: appwrite-worker-audits
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -255,8 +255,7 @@ services:
entrypoint: worker-webhooks
<<: *x-logging
container_name: appwrite-worker-webhooks
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -282,8 +281,7 @@ services:
entrypoint: worker-deletes
<<: *x-logging
container_name: appwrite-worker-deletes
build:
context: .
image: appwrite-dev
networks:
- appwrite
depends_on:
@ -318,8 +316,7 @@ services:
entrypoint: worker-databases
<<: *x-logging
container_name: appwrite-worker-databases
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -347,8 +344,7 @@ services:
entrypoint: worker-builds
<<: *x-logging
container_name: appwrite-worker-builds
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -377,8 +373,7 @@ services:
entrypoint: worker-certificates
<<: *x-logging
container_name: appwrite-worker-certificates
build:
context: .
image: appwrite-dev
networks:
- appwrite
depends_on:
@ -410,8 +405,7 @@ services:
entrypoint: worker-functions
<<: *x-logging
container_name: appwrite-worker-functions
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -444,12 +438,7 @@ services:
<<: *x-logging
entrypoint: executor
stop_signal: SIGINT
build:
context: .
args:
- DEBUG=false
- TESTING=true
- VERSION=dev
image: appwrite-dev
networks:
appwrite:
runtimes:
@ -489,8 +478,7 @@ services:
entrypoint: worker-mails
<<: *x-logging
container_name: appwrite-worker-mails
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -522,8 +510,7 @@ services:
entrypoint: worker-messaging
<<: *x-logging
container_name: appwrite-worker-messaging
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -547,8 +534,7 @@ services:
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-maintenance
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -581,10 +567,7 @@ services:
- --type=timeseries
<<: *x-logging
container_name: appwrite-usage-timeseries
build:
context: .
args:
- DEBUG=false
image: appwrite-dev
networks:
- appwrite
volumes:
@ -617,10 +600,7 @@ services:
- --type=database
<<: *x-logging
container_name: appwrite-usage-database
build:
context: .
args:
- DEBUG=false
image: appwrite-dev
networks:
- appwrite
volumes:
@ -651,8 +631,7 @@ services:
entrypoint: schedule
<<: *x-logging
container_name: appwrite-schedule
build:
context: .
image: appwrite-dev
networks:
- appwrite
volumes:
@ -749,7 +728,7 @@ services:
# MailCatcher - An SMTP server. Catches all system emails and displays them in a nice UI.
# RequestCatcher - An HTTP server. Catches all system https calls and displays them using a simple HTTP API. Used to debug & tests webhooks and HTTP tasks
# RedisCommander - A nice UI for exploring Redis data
# Resque - A nice UI for exploring Reddis pub/sub, view the different queues workloads, pending and failed tasks
# Resque - A nice UI for exploring Redis pub/sub, view the different queues workloads, pending and failed tasks
# Chronograf - A nice UI for exploring InfluxDB data
# Webgrind - A nice UI for exploring and debugging code-level stuff

View file

@ -1,6 +1,11 @@
## 7.0.0-dev.2
## 7.1.0
* Role helper update
## 7.0.0
### NEW
* Support for Appwrite 1.0.0-RC1
* Support for Appwrite 1.0.0
* More verbose headers have been included in the Clients - `x-sdk-name`, `x-sdk-platform`, `x-sdk-language`, `x-sdk-version`
* Helper classes and methods for Permissions, Roles and IDs
* Helper methods to suport new queries
@ -26,7 +31,7 @@
3. `greater` renamed to `greaterThan`
4. `greaterEqual` renamed to `greaterThanEqual`
**Full Changelog for Appwrite 1.0.0-RC1 can be found here**: https://github.com/appwrite/appwrite/blob/master/CHANGES.md
**Full Changelog for Appwrite 1.0.0 can be found here**: https://github.com/appwrite/appwrite/blob/master/CHANGES.md
## 6.0.1
* Dependency upgrades

View file

@ -1,7 +1,11 @@
## 8.0.0-dev.2 Latest
## 8.1.0
* Role helper update
## 8.0.0
### NEW
* Support for Appwrite 1.0.0-RC1
* Support for Appwrite 1.0.0
* More verbose headers have been included in the Clients - `x-sdk-name`, `x-sdk-platform`, `x-sdk-language`, `x-sdk-version`
* Helper classes and methods for Permissions, Roles and IDs
* Helper methods to suport new queries
@ -21,7 +25,7 @@
4. `greaterEqual` renamed to `greaterThanEqual`
* `User` response model is now renamed to `Account`
**Full Changelog for Appwrite 1.0.0-RC1 can be found here**:
**Full Changelog for Appwrite 1.0.0 can be found here**:
https://github.com/appwrite/appwrite/blob/master/CHANGES.md
## 7.0.0

View file

@ -5,12 +5,12 @@ This document is part of the Appwrite contributors' guide. Before you continue r
## Getting Started
### Agenda
Adding new features may require various configurations options to be set by the users. And for such options, we use environment variables in Appwrite.
Adding new features may require various configuration options to be set by the users. And for such options, we use environment variables in Appwrite.
This tutorial will cover how to properly add a new environment variable in Appwrite.
### Naming environment variable
The environment variables in Appwrite are prefixed with `_APP_`. If it belongs to a specific category, the category name is appended as `_APP_REDIS` for the Redis category. The available categories are General, Redis, MariaDB, InfluxDB, StatsD, SMTP, Storage and Functions. Finally, a properly describing name is given to the variable. For example, `_APP_REDIS_HOST` is an environment variable for Redis connection host. You can find more information on available categories and existing environment variables in the [environment variables doc](https://appwrite.io/docs/environment-variables).
The environment variables in Appwrite are prefixed with `_APP_`. If it belongs to a specific category, the category name is appended as `_APP_REDIS` for the Redis category. The available categories are General, Redis, MariaDB, InfluxDB, StatsD, SMTP, Storage and Functions. Finally, a properly describing name is given to the variable. For example, `_APP_REDIS_HOST` is an environment variable for the Redis connection host. You can find more information on available categories and existing environment variables in the [environment variables doc](https://appwrite.io/docs/environment-variables).
### Describe new environment variable
First of all, we add the new environment variable to `app/config/variables.php` in the designated category. If none of the categories fit, add it to the General category. Copy the existing variable description to create a new one so that you will not miss any required fields.

View file

@ -62,7 +62,7 @@ We maintain a [`locale branch`](https://github.com/appwrite/appwrite/tree/locale
2. **en.json**
[en.json]((https://github.com/appwrite/appwrite/blob/locale/app/config/locale/translations/en.json)) contains the English translation for all the terms that are present in **terms.json**. You can use this file as a reference when making a contribution for your language.
[en.json](https://github.com/appwrite/appwrite/blob/locale/app/config/locale/translations/en.json) contains the English translation for all the terms that are present in **terms.json**. You can use this file as a reference when making a contribution for your language.
```json
{

View file

@ -519,14 +519,12 @@ class Projects extends Service{constructor(client){super(client);}
list(queries,search){return __awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof queries!=='undefined'){payload['queries']=queries;}
if(typeof search!=='undefined'){payload['search']=search;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
create(projectId,name,teamId,region,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
create(projectId,name,teamId,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
if(typeof region==='undefined'){throw new AppwriteException('Missing required parameter: "region"');}
let path='/projects';let payload={};if(typeof projectId!=='undefined'){payload['projectId']=projectId;}
if(typeof name!=='undefined'){payload['name']=name;}
if(typeof teamId!=='undefined'){payload['teamId']=teamId;}
if(typeof region!=='undefined'){payload['region']=region;}
if(typeof description!=='undefined'){payload['description']=description;}
if(typeof logo!=='undefined'){payload['logo']=logo;}
if(typeof url!=='undefined'){payload['url']=url;}
@ -539,11 +537,9 @@ if(typeof legalTaxId!=='undefined'){payload['legalTaxId']=legalTaxId;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
get(projectId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
let path='/projects/{projectId}'.replace('{projectId}',projectId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
update(projectId,name,region,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
update(projectId,name,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof region==='undefined'){throw new AppwriteException('Missing required parameter: "region"');}
let path='/projects/{projectId}'.replace('{projectId}',projectId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
if(typeof region!=='undefined'){payload['region']=region;}
if(typeof description!=='undefined'){payload['description']=description;}
if(typeof logo!=='undefined'){payload['logo']=logo;}
if(typeof url!=='undefined'){payload['url']=url;}
@ -4017,7 +4013,7 @@ this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePer
if(existing===undefined){let newPermission={role,create:false,read:false,update:false,xdelete:false,};newPermission[type]=true;this.permissions.push(newPermission);}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId){if(this.permissions.length>0&&!this.validate(formId,this.permissions.length-1)){return;}
this.permissions.push({role:'',create:false,read:false,update:false,xdelete:false,});},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},clearPermission(index){let currentRole=this.permissions[index].role;this.rawPermissions=this.rawPermissions.filter(p=>{let{type,role}=this.parsePermission(p);return role!==currentRole;});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
return key;},validate(formId,index){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input${index}`);const permission=this.permissions[index];input.setCustomValidity('');if(permission.role===''){input.setCustomValidity('Role is required');}else if(!Object.entries(permission).some(([k,v])=>!k.includes('role')&&v)){input.setCustomValidity('No permissions selected');}else if(this.permissions.some(p=>p.role===permission.role&&p!==permission)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();},prevent(event){event.preventDefault();event.stopPropagation();}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
@ -4149,7 +4145,7 @@ element.classList.add('scroll-to-bottom')}
else{element.classList.remove('scroll-to-bottom')
element.classList.add('scroll-to-top')}
position=direction;let current=Math.ceil(direction/window.innerHeight);element.setAttribute('data-views-total',Math.ceil(element.scrollHeight/window.innerHeight));element.setAttribute('data-views-current',current);if(element.scrollHeight<=(direction+element.offsetHeight+300)&&direction>0){element.classList.add('scroll-end')}
else{element.classList.remove('scroll-end')}};window.addEventListener('scroll',check,false);window.addEventListener('resize',check,false);check();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-setup",controller:function(element,console,form,alerts,router){element.addEventListener("submit",function(event){event.preventDefault();let loaderId=alerts.add({text:'Creating new project...',class:""},0);let formData=form.toJson(element);formData["name"]=formData["name"]||(element.dataset["defaultName"]||"");console.teams.create('unique()',formData["name"]||"").then(function(data){let team=data["$id"];formData=JSON.parse(JSON.stringify(formData).replace(new RegExp("{{teamId}}","g"),team));console.projects.create(formData["projectId"],formData["name"],team,formData['region']).then(function(project){alerts.remove(loaderId);window.location.href="/console/home?project="+project["$id"];},function(){throw new Error("Failed to setup project");});},function(){throw new Error("Setup failed creating project team");});});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-switch",controller:function(element,router,document){let check=function(c){if(!element.value){return;}
else{element.classList.remove('scroll-end')}};window.addEventListener('scroll',check,false);window.addEventListener('resize',check,false);check();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-setup",controller:function(element,console,form,alerts,router){element.addEventListener("submit",function(event){event.preventDefault();let loaderId=alerts.add({text:'Creating new project...',class:""},0);let formData=form.toJson(element);formData["name"]=formData["name"]||(element.dataset["defaultName"]||"");console.teams.create('unique()',formData["name"]||"").then(function(data){let team=data["$id"];formData=JSON.parse(JSON.stringify(formData).replace(new RegExp("{{teamId}}","g"),team));console.projects.create(formData["projectId"],formData["name"],team).then(function(project){alerts.remove(loaderId);window.location.href="/console/home?project="+project["$id"];},function(){throw new Error("Failed to setup project");});},function(){throw new Error("Setup failed creating project team");});});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-switch",controller:function(element,router,document){let check=function(c){if(!element.value){return;}
if(element.value===router.params.project){return;}
return router.change("/console/home?project="+element.value);};element.addEventListener("change",function(){check();});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-general-theme",controller:function(element,router,document){let toggle=function(c){if(document.body.classList.contains('theme-light')){document.body.classList.remove('theme-light');document.body.classList.add('theme-dark');window.localStorage.setItem('user-theme','theme-dark')}
else{document.body.classList.remove('theme-dark');document.body.classList.add('theme-light');window.localStorage.setItem('user-theme','theme-light')}};element.addEventListener("click",function(){toggle();});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-version",controller:function(alerts,env,cookie){let cookieName="version-update-"+env.VERSION.replace(/\./g,"_");if(!cookie.get(cookieName)){var xhr=new XMLHttpRequest();xhr.open('GET','https://appwrite.io/version',true);xhr.onload=function(){if(this.readyState==4&&this.status==200){let data=JSON.parse(this.responseText);let text='Appwrite version '+data.version+' is available, check the';if(isNewerVersion(env.VERSION,data.version)){alerts.add({text:text,class:"success",link:"https://github.com/appwrite/appwrite/releases",label:'release notes',callback:function(){cookie.set(cookieName,"true",365*10);}},0);}}};xhr.send(null);function isNewerVersion(oldVer,newVer){const oldParts=oldVer.split('.')

View file

@ -519,14 +519,12 @@ class Projects extends Service{constructor(client){super(client);}
list(queries,search){return __awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof queries!=='undefined'){payload['queries']=queries;}
if(typeof search!=='undefined'){payload['search']=search;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
create(projectId,name,teamId,region,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
create(projectId,name,teamId,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof teamId==='undefined'){throw new AppwriteException('Missing required parameter: "teamId"');}
if(typeof region==='undefined'){throw new AppwriteException('Missing required parameter: "region"');}
let path='/projects';let payload={};if(typeof projectId!=='undefined'){payload['projectId']=projectId;}
if(typeof name!=='undefined'){payload['name']=name;}
if(typeof teamId!=='undefined'){payload['teamId']=teamId;}
if(typeof region!=='undefined'){payload['region']=region;}
if(typeof description!=='undefined'){payload['description']=description;}
if(typeof logo!=='undefined'){payload['logo']=logo;}
if(typeof url!=='undefined'){payload['url']=url;}
@ -539,11 +537,9 @@ if(typeof legalTaxId!=='undefined'){payload['legalTaxId']=legalTaxId;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
get(projectId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
let path='/projects/{projectId}'.replace('{projectId}',projectId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
update(projectId,name,region,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
update(projectId,name,description,logo,url,legalName,legalCountry,legalState,legalCity,legalAddress,legalTaxId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
if(typeof name==='undefined'){throw new AppwriteException('Missing required parameter: "name"');}
if(typeof region==='undefined'){throw new AppwriteException('Missing required parameter: "region"');}
let path='/projects/{projectId}'.replace('{projectId}',projectId);let payload={};if(typeof name!=='undefined'){payload['name']=name;}
if(typeof region!=='undefined'){payload['region']=region;}
if(typeof description!=='undefined'){payload['description']=description;}
if(typeof logo!=='undefined'){payload['logo']=logo;}
if(typeof url!=='undefined'){payload['url']=url;}

View file

@ -607,7 +607,7 @@ this.rawPermissions=permissions;permissions.map(p=>{let{type,role}=this.parsePer
if(existing===undefined){let newPermission={role,create:false,read:false,update:false,xdelete:false,};newPermission[type]=true;this.permissions.push(newPermission);}
if(index!==-1){existing[type]=true;this.permissions[index]=existing;}});},addPermission(formId){if(this.permissions.length>0&&!this.validate(formId,this.permissions.length-1)){return;}
this.permissions.push({role:'',create:false,read:false,update:false,xdelete:false,});},updatePermission(index){setTimeout(()=>{const permission=this.permissions[index];Object.keys(permission).forEach(key=>{if(key==='role'){return;}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
const parsedKey=this.parseOutputPermission(key);const permissionString=this.buildPermission(parsedKey,permission.role);if(permission[key]){if(!this.rawPermissions.includes(permissionString)){this.rawPermissions.push(permissionString);}}else{this.rawPermissions=this.rawPermissions.filter(p=>{return!p.includes(permissionString);});}});});},clearPermission(index){let currentRole=this.permissions[index].role;this.rawPermissions=this.rawPermissions.filter(p=>{let{type,role}=this.parsePermission(p);return role!==currentRole;});},removePermission(index){let row=this.permissions.splice(index,1);if(row.length===1){this.rawPermissions=this.rawPermissions.filter(p=>!p.includes(row[0].role));}},parsePermission(permission){let parts=permission.split('(');let type=parts[0];let role=parts[1].replace(')','').replace(' ','').replaceAll('"','');return{type,role};},buildPermission(type,role){return`${type}("${role}")`},parseInputPermission(key){if(key==='delete'){return'xdelete';}
return key;},parseOutputPermission(key){if(key==='xdelete'){return'delete';}
return key;},validate(formId,index){const form=document.getElementById(formId);const input=document.getElementById(`${formId}Input${index}`);const permission=this.permissions[index];input.setCustomValidity('');if(permission.role===''){input.setCustomValidity('Role is required');}else if(!Object.entries(permission).some(([k,v])=>!k.includes('role')&&v)){input.setCustomValidity('No permissions selected');}else if(this.permissions.some(p=>p.role===permission.role&&p!==permission)){input.setCustomValidity('Role entry already exists');}
return form.reportValidity();},prevent(event){event.preventDefault();event.stopPropagation();}}));});})(window);(function(window){"use strict";window.ls.view.add({selector:"data-service",controller:function(element,view,container,form,alerts,expression,window){let action=element.dataset["service"];let service=element.dataset["name"]||null;let event=expression.parse(element.dataset["event"]);let confirm=element.dataset["confirm"]||"";let loading=element.dataset["loading"]||"";let loaderId=null;let scope=element.dataset["scope"]||"sdk";let success=element.dataset["success"]||"";let failure=element.dataset["failure"]||"";let running=false;let callbacks={hide:function(){return function(){return element.style.opacity='0';};},reset:function(){return function(){if("FORM"===element.tagName){return element.reset();}
@ -739,7 +739,7 @@ element.classList.add('scroll-to-bottom')}
else{element.classList.remove('scroll-to-bottom')
element.classList.add('scroll-to-top')}
position=direction;let current=Math.ceil(direction/window.innerHeight);element.setAttribute('data-views-total',Math.ceil(element.scrollHeight/window.innerHeight));element.setAttribute('data-views-current',current);if(element.scrollHeight<=(direction+element.offsetHeight+300)&&direction>0){element.classList.add('scroll-end')}
else{element.classList.remove('scroll-end')}};window.addEventListener('scroll',check,false);window.addEventListener('resize',check,false);check();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-setup",controller:function(element,console,form,alerts,router){element.addEventListener("submit",function(event){event.preventDefault();let loaderId=alerts.add({text:'Creating new project...',class:""},0);let formData=form.toJson(element);formData["name"]=formData["name"]||(element.dataset["defaultName"]||"");console.teams.create('unique()',formData["name"]||"").then(function(data){let team=data["$id"];formData=JSON.parse(JSON.stringify(formData).replace(new RegExp("{{teamId}}","g"),team));console.projects.create(formData["projectId"],formData["name"],team,formData['region']).then(function(project){alerts.remove(loaderId);window.location.href="/console/home?project="+project["$id"];},function(){throw new Error("Failed to setup project");});},function(){throw new Error("Setup failed creating project team");});});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-switch",controller:function(element,router,document){let check=function(c){if(!element.value){return;}
else{element.classList.remove('scroll-end')}};window.addEventListener('scroll',check,false);window.addEventListener('resize',check,false);check();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-setup",controller:function(element,console,form,alerts,router){element.addEventListener("submit",function(event){event.preventDefault();let loaderId=alerts.add({text:'Creating new project...',class:""},0);let formData=form.toJson(element);formData["name"]=formData["name"]||(element.dataset["defaultName"]||"");console.teams.create('unique()',formData["name"]||"").then(function(data){let team=data["$id"];formData=JSON.parse(JSON.stringify(formData).replace(new RegExp("{{teamId}}","g"),team));console.projects.create(formData["projectId"],formData["name"],team).then(function(project){alerts.remove(loaderId);window.location.href="/console/home?project="+project["$id"];},function(){throw new Error("Failed to setup project");});},function(){throw new Error("Setup failed creating project team");});});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-switch",controller:function(element,router,document){let check=function(c){if(!element.value){return;}
if(element.value===router.params.project){return;}
return router.change("/console/home?project="+element.value);};element.addEventListener("change",function(){check();});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-general-theme",controller:function(element,router,document){let toggle=function(c){if(document.body.classList.contains('theme-light')){document.body.classList.remove('theme-light');document.body.classList.add('theme-dark');window.localStorage.setItem('user-theme','theme-dark')}
else{document.body.classList.remove('theme-dark');document.body.classList.add('theme-light');window.localStorage.setItem('user-theme','theme-light')}};element.addEventListener("click",function(){toggle();});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-version",controller:function(alerts,env,cookie){let cookieName="version-update-"+env.VERSION.replace(/\./g,"_");if(!cookie.get(cookieName)){var xhr=new XMLHttpRequest();xhr.open('GET','https://appwrite.io/version',true);xhr.onload=function(){if(this.readyState==4&&this.status==200){let data=JSON.parse(this.responseText);let text='Appwrite version '+data.version+' is available, check the';if(isNewerVersion(env.VERSION,data.version)){alerts.add({text:text,class:"success",link:"https://github.com/appwrite/appwrite/releases",label:'release notes',callback:function(){cookie.set(cookieName,"true",365*10);}},0);}}};xhr.send(null);function isNewerVersion(oldVer,newVer){const oldParts=oldVer.split('.')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4084,7 +4084,6 @@
* @param {string} projectId
* @param {string} name
* @param {string} teamId
* @param {string} region
* @param {string} description
* @param {string} logo
* @param {string} url
@ -4097,7 +4096,7 @@
* @throws {AppwriteException}
* @returns {Promise}
*/
create(projectId, name, teamId, region, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId) {
create(projectId, name, teamId, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof projectId === 'undefined') {
throw new AppwriteException('Missing required parameter: "projectId"');
@ -4108,9 +4107,6 @@
if (typeof teamId === 'undefined') {
throw new AppwriteException('Missing required parameter: "teamId"');
}
if (typeof region === 'undefined') {
throw new AppwriteException('Missing required parameter: "region"');
}
let path = '/projects';
let payload = {};
if (typeof projectId !== 'undefined') {
@ -4122,9 +4118,6 @@
if (typeof teamId !== 'undefined') {
payload['teamId'] = teamId;
}
if (typeof region !== 'undefined') {
payload['region'] = region;
}
if (typeof description !== 'undefined') {
payload['description'] = description;
}
@ -4185,7 +4178,6 @@
*
* @param {string} projectId
* @param {string} name
* @param {string} region
* @param {string} description
* @param {string} logo
* @param {string} url
@ -4198,7 +4190,7 @@
* @throws {AppwriteException}
* @returns {Promise}
*/
update(projectId, name, region, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId) {
update(projectId, name, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof projectId === 'undefined') {
throw new AppwriteException('Missing required parameter: "projectId"');
@ -4206,17 +4198,11 @@
if (typeof name === 'undefined') {
throw new AppwriteException('Missing required parameter: "name"');
}
if (typeof region === 'undefined') {
throw new AppwriteException('Missing required parameter: "region"');
}
let path = '/projects/{projectId}'.replace('{projectId}', projectId);
let payload = {};
if (typeof name !== 'undefined') {
payload['name'] = name;
}
if (typeof region !== 'undefined') {
payload['region'] = region;
}
if (typeof description !== 'undefined') {
payload['description'] = description;
}

View file

@ -75,6 +75,14 @@
});
});
},
clearPermission(index) {
let currentRole = this.permissions[index].role;
this.rawPermissions = this.rawPermissions.filter(p => {
let {type, role} = this.parsePermission(p);
return role !== currentRole;
});
},
removePermission(index) {
let row = this.permissions.splice(index, 1);
if (row.length === 1) {

View file

@ -23,7 +23,7 @@
)
); //convert to JSON string
console.projects.create(formData["projectId"], formData["name"], team, formData['region']).then(
console.projects.create(formData["projectId"], formData["name"], team).then(
function(project) {
alerts.remove(loaderId);
//router.change("/console/home?project=" + project["$id"]);

View file

@ -836,6 +836,7 @@ label.switch {
border-radius: 21px;
background: var(--config-color-fade);
display: inline-block;
overflow: visible;
margin: 0;
padding: 5px;
.func-padding-start(5px);

View file

@ -44,6 +44,7 @@ abstract class Migration
'1.0.0-RC1' => 'V15',
'1.0.0' => 'V15',
'1.0.1' => 'V15',
'1.0.3' => 'V15'
];
/**

View file

@ -504,6 +504,7 @@ class V15 extends Migration
$this->createPermissionsColumn($id);
$this->migrateDateTimeAttribute($id, '_createdAt');
$this->migrateDateTimeAttribute($id, '_updatedAt');
$this->migrateDateTimeAttribute($id, 'time');
break;
case 'buckets':

View file

@ -49,11 +49,17 @@ class URL extends Validator
*/
public function isValid($value): bool
{
if (\filter_var($value, FILTER_VALIDATE_URL) === false) {
$sanitizedURL = '';
foreach (str_split($value) as $character) {
$sanitizedURL .= (ord($character) > 127) ? rawurlencode($character) : $character;
}
if (\filter_var($sanitizedURL, FILTER_VALIDATE_URL) === false) {
return false;
}
if (!empty($this->allowedSchemes) && !\in_array(\parse_url($value, PHP_URL_SCHEME), $this->allowedSchemes)) {
if (!empty($this->allowedSchemes) && !\in_array(\parse_url($sanitizedURL, PHP_URL_SCHEME), $this->allowedSchemes)) {
return false;
}

View file

@ -4,12 +4,13 @@ namespace Appwrite\SMS\Adapter;
use Appwrite\SMS\Adapter;
// Mock adapter used to E2E test worker
class Mock extends Adapter
{
/**
* @var string
*/
public static string $digits = '123456';
private string $endpoint = 'http://request-catcher:5000/mock-sms';
/**
* @param string $from
@ -19,6 +20,19 @@ class Mock extends Adapter
*/
public function send(string $from, string $to, string $message): void
{
return;
$this->request(
method: 'POST',
url: $this->endpoint,
payload: \json_encode([
'message' => $message,
'from' => $from,
'to' => $to
]),
headers: [
"content-type: application/json",
"x-username: {$this->user}",
"x-key: {$this->secret}",
]
);
}
}

View file

@ -4,12 +4,5 @@ namespace Appwrite\Usage;
abstract class Calculator
{
protected string $region;
public function __construct(string $region)
{
$this->region = $region;
}
abstract public function collect(): void;
}

View file

@ -3,7 +3,6 @@
namespace Appwrite\Usage\Calculators;
use Exception;
use Utopia\App;
use Appwrite\Usage\Calculator;
use DateTime;
use Utopia\Database\Database as UtopiaDatabase;
@ -25,9 +24,8 @@ class Database extends Calculator
],
];
public function __construct(string $region, UtopiaDatabase $database, callable $errorHandler = null)
public function __construct(UtopiaDatabase $database, callable $errorHandler = null)
{
parent::__construct($region);
$this->database = $database;
$this->errorHandler = $errorHandler;
}
@ -97,7 +95,6 @@ class Database extends Calculator
'time' => $time,
'metric' => $metric,
'value' => $value,
'region' => $this->region,
'type' => 2, // these are cumulative metrics
]));
} else {

View file

@ -2,7 +2,6 @@
namespace Appwrite\Usage\Calculators;
use Utopia\App;
use Appwrite\Usage\Calculator;
use Utopia\Database\Database;
use Utopia\Database\Document;
@ -279,9 +278,8 @@ class TimeSeries extends Calculator
'startTime' => '-24 hours',
];
public function __construct(string $region, Database $database, InfluxDatabase $influxDB, callable $errorHandler = null)
public function __construct(Database $database, InfluxDatabase $influxDB, callable $errorHandler = null)
{
parent::__construct($region);
$this->database = $database;
$this->influxDB = $influxDB;
$this->errorHandler = $errorHandler;
@ -317,7 +315,6 @@ class TimeSeries extends Calculator
'metric' => $metric,
'value' => $value,
'type' => $type,
'region' => $this->region,
]));
} else {
$this->database->updateDocument(

View file

@ -91,20 +91,17 @@ class V15 extends Filter
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseCreatedAtUpdatedAt($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_DOCUMENT:
$parsedResponse = $this->parseDocument($parsedResponse);
break;
case Response::MODEL_FILE:
$parsedResponse = $this->parsePermissionsCreatedAtUpdatedAt($parsedResponse);
break;
case Response::MODEL_DOCUMENT_LIST:
$listKey = 'documents';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseDocument($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_FILE_LIST:
$listKey = '';
switch ($model) {
case Response::MODEL_DOCUMENT_LIST:
$listKey = 'documents';
break;
case Response::MODEL_FILE_LIST:
$listKey = 'files';
break;
}
$listKey = 'files';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parsePermissionsCreatedAtUpdatedAt($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_EXECUTION:
@ -210,7 +207,11 @@ class V15 extends Filter
protected function parseDatetimeAttributes(array $content, array $attributes): array
{
foreach ($attributes as $attribute) {
if (isset($content[$attribute])) {
if (array_key_exists($attribute, $content)) {
if (empty($content[$attribute])) {
$content[$attribute] = 0;
continue;
}
$content[$attribute] = strtotime($content[$attribute]);
}
}
@ -314,6 +315,19 @@ class V15 extends Filter
return $content;
}
protected function parseDocument(array $content)
{
if (isset($content['$collectionId'])) {
$content['$collection'] = $content['$collectionId'];
unset($content['$collectionId']);
}
unset($content['$databaseId']);
$content = $this->parsePermissionsCreatedAtUpdatedAt($content);
return $content;
}
private function parseExecution($content)
{
unset($content['stdout']);

View file

@ -13,26 +13,26 @@ class AlgoScrypt extends Model
->addRule('costCpu', [
'type' => self::TYPE_INTEGER,
'description' => 'CPU complexity of computed hash.',
'default' => '',
'default' => 8,
'example' => 8,
])
->addRule('costMemory', [
'type' => self::TYPE_INTEGER,
'description' => 'Memory complexity of computed hash.',
'default' => '',
'default' => 14,
'example' => 14,
])
->addRule('costParallel', [
'type' => self::TYPE_INTEGER,
'description' => 'Parallelization of computed hash.',
'default' => '',
'default' => 1,
'example' => 1,
])
->addRule('length', [
'type' => self::TYPE_INTEGER,
'description' => 'Length used to compute hash.',
'default' => '',
'example' => 1,
'default' => 64,
'example' => 64,
])
;
}

View file

@ -26,6 +26,15 @@ class UsageTest extends Scope
parent::setUp();
}
protected static string $formatTz = 'Y-m-d\TH:i:s.vP';
protected function validateDates(array $metrics): void
{
foreach ($metrics as $metric) {
$this->assertIsObject(\DateTime::createFromFormat("Y-m-d\TH:i:s.vP", $metric['date']));
}
}
public function testPrepareUsersStats(): array
{
$project = $this->getProject(true);
@ -97,7 +106,9 @@ class UsageTest extends Scope
$this->assertEquals(30, count($res['requests']));
$this->assertEquals(30, count($res['users']));
$this->assertEquals($usersCount, $res['users'][array_key_last($res['users'])]['value']);
$this->validateDates($res['users']);
$this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']);
$this->validateDates($res['requests']);
$res = $this->client->call(Client::METHOD_GET, '/users/usage?range=30d', array_merge($cheaders, [
'x-appwrite-project' => $projectId,
@ -105,8 +116,11 @@ class UsageTest extends Scope
]));
$res = $res['body'];
$this->assertEquals(10, $res['usersCreate'][array_key_last($res['usersCreate'])]['value']);
$this->validateDates($res['usersCreate']);
$this->assertEquals(5, $res['usersRead'][array_key_last($res['usersRead'])]['value']);
$this->validateDates($res['usersRead']);
$this->assertEquals(5, $res['usersDelete'][array_key_last($res['usersDelete'])]['value']);
$this->validateDates($res['usersDelete']);
return ['projectId' => $projectId, 'headers' => $headers, 'requestsCount' => $requestsCount];
}
@ -176,7 +190,7 @@ class UsageTest extends Scope
'path' => realpath(__DIR__ . '/../../resources/disk-a/kitten-1.jpg'),
'name' => 'kitten-1.jpg',
],
];
];
for ($i = 0; $i < 10; $i++) {
$file = $files[$i % count($files)];
@ -257,7 +271,9 @@ class UsageTest extends Scope
$this->assertEquals(30, count($res['requests']));
$this->assertEquals(30, count($res['storage']));
$this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']);
$this->validateDates($res['requests']);
$this->assertEquals($storageTotal, $res['storage'][array_key_last($res['storage'])]['value']);
$this->validateDates($res['storage']);
$res = $this->client->call(Client::METHOD_GET, '/storage/usage?range=30d', array_merge($headers, [
'x-appwrite-project' => $projectId,
@ -265,14 +281,23 @@ class UsageTest extends Scope
]));
$res = $res['body'];
$this->assertEquals($storageTotal, $res['storage'][array_key_last($res['storage'])]['value']);
$this->validateDates($res['storage']);
$this->assertEquals($bucketsCount, $res['bucketsCount'][array_key_last($res['bucketsCount'])]['value']);
$this->validateDates($res['bucketsCount']);
$this->assertEquals($bucketsRead, $res['bucketsRead'][array_key_last($res['bucketsRead'])]['value']);
$this->validateDates($res['bucketsRead']);
$this->assertEquals($bucketsCreate, $res['bucketsCreate'][array_key_last($res['bucketsCreate'])]['value']);
$this->validateDates($res['bucketsCreate']);
$this->assertEquals($bucketsDelete, $res['bucketsDelete'][array_key_last($res['bucketsDelete'])]['value']);
$this->validateDates($res['bucketsDelete']);
$this->assertEquals($filesCount, $res['filesCount'][array_key_last($res['filesCount'])]['value']);
$this->validateDates($res['filesCount']);
$this->assertEquals($filesRead, $res['filesRead'][array_key_last($res['filesRead'])]['value']);
$this->validateDates($res['filesRead']);
$this->assertEquals($filesCreate, $res['filesCreate'][array_key_last($res['filesCreate'])]['value']);
$this->validateDates($res['filesCreate']);
$this->assertEquals($filesDelete, $res['filesDelete'][array_key_last($res['filesDelete'])]['value']);
$this->validateDates($res['filesDelete']);
$res = $this->client->call(Client::METHOD_GET, '/storage/' . $bucketId . '/usage?range=30d', array_merge($headers, [
'x-appwrite-project' => $projectId,
@ -483,8 +508,11 @@ class UsageTest extends Scope
$this->assertEquals(30, count($res['requests']));
$this->assertEquals(30, count($res['storage']));
$this->assertEquals($requestsCount, $res['requests'][array_key_last($res['requests'])]['value']);
$this->validateDates($res['requests']);
$this->assertEquals($collectionsCount, $res['collections'][array_key_last($res['collections'])]['value']);
$this->validateDates($res['collections']);
$this->assertEquals($documentsCount, $res['documents'][array_key_last($res['documents'])]['value']);
$this->validateDates($res['documents']);
$res = $this->client->call(Client::METHOD_GET, '/databases/usage?range=30d', array_merge($headers, [
'x-appwrite-project' => $projectId,
@ -492,21 +520,34 @@ class UsageTest extends Scope
]));
$res = $res['body'];
$this->assertEquals($databasesCount, $res['databasesCount'][array_key_last($res['databasesCount'])]['value']);
$this->validateDates($res['databasesCount']);
$this->assertEquals($collectionsCount, $res['collectionsCount'][array_key_last($res['collectionsCount'])]['value']);
$this->validateDates($res['collectionsCount']);
$this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']);
$this->validateDates($res['documentsCount']);
$this->assertEquals($databasesCreate, $res['databasesCreate'][array_key_last($res['databasesCreate'])]['value']);
$this->validateDates($res['databasesCreate']);
$this->assertEquals($databasesRead, $res['databasesRead'][array_key_last($res['databasesRead'])]['value']);
$this->validateDates($res['databasesRead']);
$this->assertEquals($databasesDelete, $res['databasesDelete'][array_key_last($res['databasesDelete'])]['value']);
$this->validateDates($res['databasesDelete']);
$this->assertEquals($collectionsCreate, $res['collectionsCreate'][array_key_last($res['collectionsCreate'])]['value']);
$this->validateDates($res['collectionsCreate']);
$this->assertEquals($collectionsRead, $res['collectionsRead'][array_key_last($res['collectionsRead'])]['value']);
$this->validateDates($res['collectionsRead']);
$this->assertEquals($collectionsUpdate, $res['collectionsUpdate'][array_key_last($res['collectionsUpdate'])]['value']);
$this->validateDates($res['collectionsUpdate']);
$this->assertEquals($collectionsDelete, $res['collectionsDelete'][array_key_last($res['collectionsDelete'])]['value']);
$this->validateDates($res['collectionsDelete']);
$this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']);
$this->validateDates($res['documentsCreate']);
$this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']);
$this->validateDates($res['documentsRead']);
$this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']);
$this->validateDates($res['documentsDelete']);
$res = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/usage?range=30d', array_merge($headers, [
'x-appwrite-project' => $projectId,
@ -514,16 +555,25 @@ class UsageTest extends Scope
]));
$res = $res['body'];
$this->assertEquals($collectionsCount, $res['collectionsCount'][array_key_last($res['collectionsCount'])]['value']);
$this->validateDates($res['collectionsCount']);
$this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']);
$this->validateDates($res['documentsCount']);
$this->assertEquals($collectionsCreate, $res['collectionsCreate'][array_key_last($res['collectionsCreate'])]['value']);
$this->validateDates($res['collectionsCreate']);
$this->assertEquals($collectionsRead, $res['collectionsRead'][array_key_last($res['collectionsRead'])]['value']);
$this->validateDates($res['collectionsRead']);
$this->assertEquals($collectionsUpdate, $res['collectionsUpdate'][array_key_last($res['collectionsUpdate'])]['value']);
$this->validateDates($res['collectionsUpdate']);
$this->assertEquals($collectionsDelete, $res['collectionsDelete'][array_key_last($res['collectionsDelete'])]['value']);
$this->validateDates($res['collectionsDelete']);
$this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']);
$this->validateDates($res['documentsCreate']);
$this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']);
$this->validateDates($res['documentsRead']);
$this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']);
$this->validateDates($res['documentsDelete']);
$res = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/usage?range=30d', array_merge($headers, [
'x-appwrite-project' => $projectId,
@ -531,10 +581,14 @@ class UsageTest extends Scope
]));
$res = $res['body'];
$this->assertEquals($documentsCount, $res['documentsCount'][array_key_last($res['documentsCount'])]['value']);
$this->validateDates($res['documentsCount']);
$this->assertEquals($documentsCreate, $res['documentsCreate'][array_key_last($res['documentsCreate'])]['value']);
$this->validateDates($res['documentsCreate']);
$this->assertEquals($documentsRead, $res['documentsRead'][array_key_last($res['documentsRead'])]['value']);
$this->validateDates($res['documentsRead']);
$this->assertEquals($documentsDelete, $res['documentsDelete'][array_key_last($res['documentsDelete'])]['value']);
$this->validateDates($res['documentsDelete']);
$data['requestsCount'] = $requestsCount;
return $data;
@ -667,8 +721,11 @@ class UsageTest extends Scope
$response = $response['body'];
$this->assertEquals($executions, $response['executionsTotal'][array_key_last($response['executionsTotal'])]['value']);
$this->validateDates($response['executionsTotal']);
$this->assertEquals($executionTime, $response['executionsTime'][array_key_last($response['executionsTime'])]['value']);
$this->validateDates($response['executionsTime']);
$this->assertEquals($failures, $response['executionsFailure'][array_key_last($response['executionsFailure'])]['value']);
$this->validateDates($response['executionsFailure']);
$response = $this->client->call(Client::METHOD_GET, '/functions/usage', $headers, [
'range' => '30d'
@ -688,9 +745,13 @@ class UsageTest extends Scope
$response = $response['body'];
$this->assertEquals($executions, $response['executionsTotal'][array_key_last($response['executionsTotal'])]['value']);
$this->validateDates($response['executionsTotal']);
$this->assertEquals($executionTime, $response['executionsTime'][array_key_last($response['executionsTime'])]['value']);
$this->validateDates($response['executionsTime']);
$this->assertGreaterThan(0, $response['buildsTime'][array_key_last($response['buildsTime'])]['value']);
$this->validateDates($response['buildsTime']);
$this->assertEquals($failures, $response['executionsFailure'][array_key_last($response['executionsFailure'])]['value']);
$this->validateDates($response['executionsFailure']);
}
protected function tearDown(): void

View file

@ -42,7 +42,6 @@ trait ProjectCustom
'x-appwrite-project' => 'console',
], [
'projectId' => ID::unique(),
'region' => 'default',
'name' => 'Demo Project',
'teamId' => $team['body']['$id'],
'description' => 'Demo Project Description',

View file

@ -716,7 +716,19 @@ class AccountCustomClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
$data['token'] = Mock::$digits;
\sleep(2);
$smsRequest = $this->getLastRequest();
$this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']);
$this->assertEquals('Appwrite Phone Authentication', $smsRequest['headers']['User-Agent']);
$this->assertEquals('username', $smsRequest['headers']['X-Username']);
$this->assertEquals('password', $smsRequest['headers']['X-Key']);
$this->assertEquals('POST', $smsRequest['method']);
$this->assertEquals('+123456789', $smsRequest['data']['from']);
$this->assertEquals($number, $smsRequest['data']['to']);
$data['token'] = $smsRequest['data']['message'];
$data['id'] = $userId;
$data['number'] = $number;
@ -931,7 +943,13 @@ class AccountCustomClientTest extends Scope
$this->assertEmpty($response['body']['secret']);
$this->assertEquals(true, DateTime::isValid($response['body']['expire']));
return $data;
\sleep(2);
$smsRequest = $this->getLastRequest();
return \array_merge($data, [
'token' => $smsRequest['data']['message']
]);
}
/**
@ -941,6 +959,7 @@ class AccountCustomClientTest extends Scope
{
$id = $data['id'] ?? '';
$session = $data['session'] ?? '';
$secret = $data['token'] ?? '';
/**
* Test for SUCCESS
@ -952,7 +971,7 @@ class AccountCustomClientTest extends Scope
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'userId' => $id,
'secret' => Mock::$digits,
'secret' => $secret,
]);
$this->assertEquals(200, $response['headers']['status-code']);
@ -967,7 +986,7 @@ class AccountCustomClientTest extends Scope
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'userId' => ID::custom('ewewe'),
'secret' => Mock::$digits,
'secret' => $secret,
]);
$this->assertEquals(404, $response['headers']['status-code']);

View file

@ -2968,4 +2968,52 @@ trait DatabasesBase
return [];
}
/**
* @depends testCreateDatabase
*/
public function testAttributeBooleanDefault(array $data): void
{
$databaseId = $data['databaseId'];
/**
* Test for SUCCESS
*/
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Boolean'
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$collectionId = $collection['body']['$id'];
$true = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/boolean', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'true',
'required' => false,
'default' => true
]);
$this->assertEquals(202, $true['headers']['status-code']);
$false = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/boolean', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'false',
'required' => false,
'default' => false
]);
$this->assertEquals(202, $false['headers']['status-code']);
}
}

View file

@ -116,7 +116,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
// Wait for deployment to be built.
sleep(10);
sleep(20);
$function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
@ -228,7 +228,7 @@ class FunctionsCustomClientTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
// Wait for deployment to be built.
sleep(10);
sleep(20);
$this->assertEquals(202, $deployment['headers']['status-code']);
@ -252,7 +252,7 @@ class FunctionsCustomClientTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
sleep(10);
sleep(20);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, [
'content-type' => 'application/json',
@ -326,7 +326,7 @@ class FunctionsCustomClientTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
// Wait for deployment to be built.
sleep(10);
sleep(20);
$this->assertEquals(202, $deployment['headers']['status-code']);
@ -395,7 +395,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals(202, $execution['headers']['status-code']);
sleep(10);
sleep(20);
$base = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
@ -556,7 +556,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
// Wait for deployment to be built.
sleep(10);
sleep(20);
$function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',

View file

@ -373,7 +373,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
// Wait for deployment to build.
sleep(30);
sleep(60);
return array_merge($data, ['deploymentId' => $deploymentId]);
}
@ -610,7 +610,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals('', $execution['body']['stderr']);
$this->assertEquals(0, $execution['body']['duration']);
sleep(5);
sleep(10);
$execution = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',
@ -637,7 +637,7 @@ class FunctionsCustomServerTest extends Scope
* Test for FAILURE
*/
sleep(10);
sleep(20);
return array_merge($data, ['executionId' => $executionId]);
}
@ -881,7 +881,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(20);
sleep(40);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
@ -894,7 +894,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $execution['headers']['status-code']);
sleep(10);
sleep(20);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
@ -965,7 +965,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(10);
sleep(20);
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
@ -988,7 +988,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
sleep(10);
sleep(20);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',
@ -1087,7 +1087,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(10);
sleep(20);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
@ -1103,7 +1103,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
sleep(10);
sleep(20);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',
@ -1200,7 +1200,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(30);
sleep(60);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
@ -1216,7 +1216,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
sleep(30);
sleep(60);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',
@ -1314,7 +1314,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(40);
sleep(80);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
@ -1330,7 +1330,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
sleep(10);
sleep(20);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',
@ -1428,7 +1428,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(30);
sleep(60);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
@ -1444,7 +1444,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
sleep(10);
sleep(20);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',

View file

@ -42,7 +42,6 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test',
'teamId' => $team['body']['$id'],
'region' => 'default',
]);
$this->assertEquals(201, $response['headers']['status-code']);
@ -65,7 +64,6 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => '',
'teamId' => $team['body']['$id'],
'region' => 'default'
]);
$this->assertEquals(400, $response['headers']['status-code']);
@ -76,7 +74,6 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()), [
'projectId' => ID::unique(),
'name' => 'Project Test',
'region' => 'default'
]);
$this->assertEquals(400, $response['headers']['status-code']);
@ -156,7 +153,6 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test 2',
'teamId' => $team['body']['$id'],
'region' => 'default'
]);
$this->assertEquals(201, $response['headers']['status-code']);
@ -692,7 +688,6 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test',
'teamId' => $team['body']['$id'],
'region' => 'default'
]);
$this->assertEquals(201, $project['headers']['status-code']);

View file

@ -126,6 +126,52 @@ trait TeamsBaseClient
return $data;
}
/**
* @depends testCreateTeamMembership
*/
public function testGetTeamMembership($data): void
{
$teamUid = $data['teamUid'] ?? '';
$membershipUid = $data['membershipUid'] ?? '';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertNotEmpty($response['body']['userName']);
$this->assertNotEmpty($response['body']['userEmail']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertNotEmpty($response['body']['teamName']);
$this->assertCount(2, $response['body']['roles']);
$this->assertEquals(false, DateTime::isValid($response['body']['joined'])); // is null in DB
$this->assertEquals(false, $response['body']['confirm']);
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid . 'dasdasd', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(404, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]);
$this->assertEquals(401, $response['headers']['status-code']);
}
/**
* @depends testCreateTeam
*/

View file

@ -33,6 +33,51 @@ trait TeamsBaseServer
return [];
}
/**
* @depends testCreateTeamMembership
*/
public function testGetTeamMembership($data): void
{
$teamUid = $data['teamUid'] ?? '';
$membershipUid = $data['membershipUid'] ?? '';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertNotEmpty($response['body']['userName']);
$this->assertNotEmpty($response['body']['userEmail']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertNotEmpty($response['body']['teamName']);
$this->assertCount(2, $response['body']['roles']);
$this->assertEquals(true, DateTime::isValid($response['body']['joined'])); // is null in DB
$this->assertEquals(true, $response['body']['confirm']);
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid . 'dasdasd', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(404, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $membershipUid, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]);
$this->assertEquals(401, $response['headers']['status-code']);
}
/**
* @depends testCreateTeam

View file

@ -180,89 +180,51 @@ trait UsersBase
*/
public function testCreateUserSessionHashed(array $data): void
{
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'md5@appwrite.io',
'password' => 'appwrite',
]);
$userIds = [ 'md5', 'bcrypt', 'argon2', 'sha512', 'scrypt', 'phpass', 'scrypt-modified' ];
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'md5');
foreach ($userIds as $userId) {
// Ensure sessions can be created with hashed passwords
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => $userId . '@appwrite.io',
'password' => 'appwrite',
]);
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'bcrypt@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals($userId, $response['body']['userId']);
}
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'bcrypt');
foreach ($userIds as $userId) {
// Ensure all passwords were re-hashed
$response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'argon2@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($userId, $response['body']['$id']);
$this->assertEquals($userId . '@appwrite.io', $response['body']['email']);
$this->assertEquals('argon2', $response['body']['hash']);
$this->assertStringStartsWith('$argon2', $response['body']['password']);
}
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'argon2');
foreach ($userIds as $userId) {
// Ensure sessions can be created after re-hashing of passwords
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => $userId . '@appwrite.io',
'password' => 'appwrite',
]);
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'sha512@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'sha512');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'scrypt@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'scrypt');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'phpass@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'phpass');
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => 'scrypt-modified@appwrite.io',
'password' => 'appwrite',
]);
$this->assertEquals($response['headers']['status-code'], 201);
$this->assertEquals($response['body']['userId'], 'scrypt-modified');
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals($userId, $response['body']['userId']);
}
}
/**
@ -964,6 +926,81 @@ trait UsersBase
return $data;
}
/**
* @depends testGetUser
*/
public function testUpdateUserNumber(array $data): array
{
/**
* Test for SUCCESS
*/
$updatedNumber = "+910000000000"; //dummy number
$user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/phone', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'number' => $updatedNumber,
]);
$this->assertEquals($user['headers']['status-code'], 200);
$this->assertEquals($user['body']['phone'], $updatedNumber);
$user = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($user['headers']['status-code'], 200);
$this->assertEquals($user['body']['phone'], $updatedNumber);
/**
* Test for FAILURE
*/
$errorType = "user_phone_already_exists";
$user1Id = "user1";
$statusCodeForUserPhoneAlredyExists = 409;
// adding same number ($updatedNumber) to different user i.e user1
$response = $this->client->call(Client::METHOD_PATCH, '/users/' . $user1Id . '/phone', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'number' => $updatedNumber,
]);
$this->assertEquals($response['headers']['status-code'], $statusCodeForUserPhoneAlredyExists);
$this->assertNotEmpty($response['body']);
$this->assertEquals($response['body']['type'], $errorType);
return $data;
}
/**
* @depends testUpdateUserNumber
*/
public function testUpdateUserNumberSearch($data): void
{
$id = $data['userId'] ?? '';
$newNumber = "+910000000000"; //dummy number
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $newNumber,
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['users']);
$this->assertCount(1, $response['body']['users']);
$this->assertEquals($response['body']['users'][0]['$id'], $id);
$this->assertEquals($response['body']['users'][0]['phone'], $newNumber);
}
/**
* @depends testGetUser

View file

@ -634,7 +634,7 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
// wait for timeout function to complete
sleep(10);
sleep(20);
$webhook = $this->getLastRequest();
$signatureExpected = self::getWebhookSignature($webhook, $this->getProject()['signatureKey']);

View file

@ -43,6 +43,7 @@ class URLTest extends TestCase
$this->assertEquals(false, $this->url->isValid('htt@s://example.com'));
$this->assertEquals(true, $this->url->isValid('http://www.example.com/foo%2\u00c2\u00a9zbar'));
$this->assertEquals(true, $this->url->isValid('http://www.example.com/?q=%3Casdf%3E'));
$this->assertEquals(true, $this->url->isValid('https://example.com/foo%2\u00c2\u00ä9zbär'));
}
public function testIsValidAllowedSchemes(): void

View file

@ -502,9 +502,34 @@ class V15Test extends TestCase
$this->assertEquals($expected, $result);
}
public function documentProvider(): array
{
return [
'basic document' => [
[
'$id' => '5e5ea5c16897e',
'$collectionId' => '5e5ea5c15117e',
'$databaseId' => '5e5ea5c15117e',
'$createdAt' => '2020-06-24T06:47:30.000Z',
'$updatedAt' => '2020-06-24T06:47:30.000Z',
'$permissions' => [Permission::read(Role::any())]
],
[
'$id' => '5e5ea5c16897e',
'$collection' => '5e5ea5c15117e',
'$createdAt' => 1592981250,
'$updatedAt' => 1592981250,
'$read' => ['role:all'],
'$write' => [],
],
],
];
}
/**
* @dataProvider createdAtUpdatedAtProvider
* @dataProvider permissionsProvider
* @dataProvider documentProvider
*/
public function testDocument(array $content, array $expected): void
{
@ -1077,6 +1102,14 @@ class V15Test extends TestCase
'providerAccessTokenExpiry' => 1592981250,
],
],
'empty values' => [
[
'providerAccessTokenExpiry' => '',
],
[
'providerAccessTokenExpiry' => 0,
],
],
];
}
@ -1089,7 +1122,7 @@ class V15Test extends TestCase
$result = $this->filter->parse($content, $model);
$this->assertEquals($expected, $result);
$this->assertSame($expected, $result);
}
/**