Merge branch 'feat-database-indexing' of https://github.com/appwrite/appwrite into feat-prefs-improvement

This commit is contained in:
Torsten Dittmann
2022-01-04 16:18:38 +01:00
342 changed files with 4025 additions and 2204 deletions
+3 -1
View File
@@ -18,7 +18,7 @@ _APP_REDIS_PORT=6379
_APP_DB_HOST=mariadb
_APP_DB_PORT=3306
_APP_DB_SCHEMA=appwrite
_APP_DB_USER=root
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_STORAGE_ANTIVIRUS=disabled
_APP_STORAGE_ANTIVIRUS_HOST=clamav
@@ -43,3 +43,5 @@ _APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
_APP_USAGE_STATS=enabled
_APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
docker pull php:8.0-cli-alpine
docker compose build --progress=plain
docker compose up -d
sleep 10
sleep 30
- name: Doctor
run: docker compose exec -T appwrite doctor
+1 -1
View File
@@ -15,7 +15,7 @@ then
exit 1
fi
if test $(find "./app/db/DBIP/dbip-country-lite-2021-10.mmdb" -mmin +259200)
if test $(find "./app/db/DBIP/dbip-country-lite-2021-12.mmdb" -mmin +259200)
then
printf "${RED}GEO country DB has not been updated for more than 6 months. Go to https://db-ip.com/db/download/ip-to-country-lite to download a newer version${NC}\n"
fi
+1
View File
@@ -65,6 +65,7 @@ script:
exit 1
fi
- docker-compose logs appwrite
- docker-compose logs appwrite-realtime
- docker-compose logs mariadb
- docker-compose logs appwrite-worker-functions
- docker-compose exec appwrite doctor
+72 -10
View File
@@ -1,17 +1,77 @@
# Version 1.0.0
# Version 0.12.0
## Features
- Grouped auth related attributes in project collection. Introduced new attribute `auths` and removed all attributes related to auth methods and `usersAuthLimit` as well, all these are grouped under `auths` attribute
- Grouped oAuth related attributes in project collection. Introduced new attribute `providers` and removed all attributes related to OAuth2 providers. All OAuth2 attributes are grouped under `providers`
- Project model changed, `userAuth<AuthMethod>` => `auth<AuthMethod>` example `userAuthEmailPassword` => `authEmailPassword`, also `userOauth2<Provider>...` => `provider<Provider>...` example `userOauth2GithubAppid` => `providerGithubAppid`
# Version 0.12.0
## Breaking Changes (Read before upgrading!)
- Multiple HealthAPI response models were changed to new (better) schema
- Completely rewritten Database service: **Breaking Change**
- Collection rules are now attributes
- Filters for have been replaced with a new, more powerful syntax
- Custom indexes for more performant queries
- Enum Attributes
- Maximum `sum` returned does not exceed 5000 documents anymore **Breaking Change**
- **DEPRECATED** Nested documents has been removed
- **DEPRECATED** Wildcard rule has been removed
- You can now set custom IDs when creating following resources:
- User
- Team
- Function
- Project
- File
- Collection
- Document
- All resources with custom ID support required you to set an ID now
- Passing `unique()` will generate a unique ID
- Auto-generated ID's are now 20 characters long
- Wildcard permissions `*` are now `role:all` **Breaking Change**
- Collections can be enabled and disabled
- Permissions are now found as top-level keys `$read` and `$write` instead of nested under `$permissions`
- Accessing collections with insufficient permissions now return a `401` isntead of `404` status code
- Offset cannot be higher than 5000 now and cursor pagination is required
- Added Cursor pagination to all endpoints that provide pagination by offset
- Added new Usage worker to aggregate usage statistics
- Added new Database worker to handle heavy database tasks in the background
- Added detailed Usage statistics to following services in the Console:
- Users
- Storage
- Database
- You can now disable/enable following services in the Console:
- Account
- Avatars
- Database
- Locale
- Health
- Storage
- Teams
- Users
- Functions
- Fixed several memory leaks in the Console
- Added pagination to account activities in the Console
- Added following events from User service to Webhooks and Functions:
- `users.update.email`
- `users.update.name`
- `users.update.password`
- Added new environment variables to enable error logging:
- The `_APP_LOGGING_PROVIDER` variable allows you to enable the logger set the value to one of `sentry`, `raygun`, `appsignal`.
- The `_APP_LOGGING_CONFIG` variable configures authentication to 3rd party error logging providers. If using Sentry, this should be 'SENTRY_API_KEY;SENTRY_APP_ID'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key.
- Added new environment variable `_APP_USAGE_AGGREGATION_INTERVAL` to configure the usage worker interval
- Added negative rotation values to file preview endpoint
- Multiple responses from the Health service were changed to new (better) schema **Breaking Change**
- Method `health.getAntiVirus()` has been renamed to `health.getAntivirus()`
- Added following langauges to the Locale service:
- Latin
- Sindhi
- Telugu
- **DEPRECATED** Tasks service **Breaking Change**
## Bugs
- Fixed `/v1/avatars/initials` when no space in the name, will try to split by `_`
- Fixed all audit logs now saving all relevant informations
- Fixed Health endpoints for `db` and `cache`
## Security
- Increased minimum password length to 8 and removed maximum length
- Upgraded Redis to 6.2
- Upgraded InfluxDB to 1.4.0
- Upgraded Telegraf to 1.3.0
# Version 0.11.0
@@ -24,6 +84,8 @@
- Deno 1.12
- Deno 1.13
- Deno 1.14
- PHP 8.1
- Node 17
- Added translations:
- German `de` by @SoftCreatR in https://github.com/appwrite/appwrite/pull/1790
- Hebrew `he` by @Kokoden in https://github.com/appwrite/appwrite/pull/1846
+11 -1
View File
@@ -288,13 +288,23 @@ The Runtimes for all supported cloud functions (multicore builds) can be found a
For generating a new console SDK follow the next steps:
1. Update the console spec file located at `app/config/specs/0.10.x.console.json` from the dynamic version located at `https://localhost/specs/swagger2?platform=console`
1. Update the console spec file located at `app/config/specs/swagger2-0.12.x.console.json` from the dynamic version located at `https://localhost/specs/swagger2?platform=console`
2. Generate a new SDK using the command `php app/cli.php sdks`
3. Change your working dir using `cd app/sdks/console-web`
4. Build the new SDK `npm run build`
5. Copy `iife/sdk.js` to `appwrite.js`
6. Go back to the root of the project `run npm run build`
## Checklist for Releasing SDKs
Things to remember when releasing SDKs
* Update the Changelogs in **docs/sdks** (right now only Dart and Flutter are using these)
* Update **GETTING_STARTED.md** in **docs/sdks** for each SDKs if any changes in the related APIs in there
* Update SDK versions as required on **app/config/platforms.php**
* Generate SDKs using the command `php app/cli.php sdks` and follow the instructions
* Release new tags on GitHub repository for each SDKs
## Debug
Appwrite uses [yasd](https://github.com/swoole/yasd) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension or if you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection.
+7 -4
View File
@@ -8,7 +8,7 @@ WORKDIR /usr/local/src/
COPY composer.lock /usr/local/src/
COPY composer.json /usr/local/src/
RUN composer update --ignore-platform-reqs --optimize-autoloader \
RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
@@ -29,9 +29,9 @@ FROM php:8.0-cli-alpine as compile
ARG DEBUG=false
ENV DEBUG=$DEBUG
ENV PHP_REDIS_VERSION=5.3.4 \
ENV PHP_REDIS_VERSION=5.3.5 \
PHP_MONGODB_VERSION=1.9.1 \
PHP_SWOOLE_VERSION=v4.8.3 \
PHP_SWOOLE_VERSION=v4.8.5 \
PHP_IMAGICK_VERSION=3.5.1 \
PHP_YAML_VERSION=2.2.2 \
PHP_MAXMINDDB_VERSION=v1.11.0
@@ -181,7 +181,9 @@ ENV _APP_SERVER=swoole \
_APP_MAINTENANCE_RETENTION_AUDIT=1209600 \
# 1 Day = 86400 s
_APP_MAINTENANCE_RETENTION_ABUSE=86400 \
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_INTERVAL=86400 \
_APP_LOGGING_PROVIDER= \
_APP_LOGGING_CONFIG=
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
@@ -257,6 +259,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/realtime && \
chmod +x /usr/local/bin/schedule && \
chmod +x /usr/local/bin/sdks && \
chmod +x /usr/local/bin/specs && \
chmod +x /usr/local/bin/ssl && \
chmod +x /usr/local/bin/test && \
chmod +x /usr/local/bin/vars && \
+2 -1
View File
@@ -1,6 +1,6 @@
<?php
require_once __DIR__.'/init.php';
require_once __DIR__.'/controllers/general.php';
use Utopia\App;
use Utopia\CLI\CLI;
@@ -13,6 +13,7 @@ include 'tasks/maintenance.php';
include 'tasks/install.php';
include 'tasks/migrate.php';
include 'tasks/sdks.php';
include 'tasks/specs.php';
include 'tasks/ssl.php';
include 'tasks/vars.php';
include 'tasks/usage.php';
+4 -4
View File
@@ -729,7 +729,7 @@ $collections = [
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
@@ -740,7 +740,7 @@ $collections = [
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
@@ -751,7 +751,7 @@ $collections = [
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
@@ -1001,7 +1001,7 @@ $collections = [
'size' => 65535,
'signed' => true,
'required' => false,
'default' => [],
'default' => new \stdClass(),
'array' => false,
'filters' => ['json'],
],
+14 -14
View File
@@ -15,7 +15,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '5.0.0',
'version' => '6.0.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -63,7 +63,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '2.1.0',
'version' => '3.0.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -81,7 +81,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '0.1.1',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -116,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '0.2.1',
'version' => '0.3.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -190,7 +190,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '3.0.0',
'version' => '4.0.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -208,7 +208,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '1.0.0',
'version' => '2.0.0',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@@ -226,7 +226,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '2.3.2',
'version' => '3.0.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -244,7 +244,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '0.5.1',
'version' => '0.6.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -262,7 +262,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '2.4.1',
'version' => '3.0.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -316,7 +316,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.3.0',
'version' => '0.4.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => false,
@@ -334,7 +334,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '2.0.0',
'version' => '3.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -352,7 +352,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '0.12.1',
'version' => '0.13.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://github.com/appwrite/sdk-for-cli',
'enabled' => true,
@@ -370,7 +370,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '0.1.1',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -392,7 +392,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '0.1.0',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+18
View File
@@ -150,6 +150,24 @@ return [
'question' => '',
'filter' => ''
],
[
'name' => '_APP_LOGGING_PROVIDER',
'description' => 'This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, to enable the logger set the value to one of \'sentry\', \'raygun\', \'appsignal\'',
'introduction' => '0.12.0',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_LOGGING_CONFIG',
'description' => 'This variable configures authentication to 3rd party error logging providers. If using Sentry, this should be \'SENTRY_API_KEY;SENTRY_APP_ID\'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key.',
'introduction' => '0.12.0',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_USAGE_AGGREGATION_INTERVAL',
'description' => 'Interval value containing the number of seconds that the Appwrite usage process should wait before aggregating stats and syncing it to mariadb from InfluxDB. The default value is 30 seconds.',
+131 -131
View File
@@ -51,14 +51,14 @@ App::post('/v1/account')
->inject('request')
->inject('response')
->inject('project')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($userId, $email, $password, $name, $request, $response, $project, $dbForInternal, $audits, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($userId, $email, $password, $name, $request, $response, $project, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@@ -79,7 +79,7 @@ App::post('/v1/account')
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
$sum = $dbForInternal->count('users', [
$sum = $dbForProject->count('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
], APP_LIMIT_USERS);
@@ -89,8 +89,8 @@ App::post('/v1/account')
}
try {
$userId = $userId == 'unique()' ? $dbForInternal->getId() : $userId;
$user = Authorization::skip(fn() => $dbForInternal->createDocument('users', new Document([
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$read' => ['role:all'],
'$write' => ['user:' . $userId],
@@ -102,7 +102,7 @@ App::post('/v1/account')
'registration' => \time(),
'reset' => false,
'name' => $name,
'prefs' => [],
'prefs' => new \stdClass(),
'sessions' => [],
'tokens' => [],
'memberships' => [],
@@ -149,15 +149,15 @@ App::post('/v1/account/sessions')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('request')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audits')
->inject('usage')
->action(function ($email, $password, $request, $response, $dbForInternal, $locale, $geodb, $audits, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($email, $password, $request, $response, $dbForProject, $locale, $geodb, $audits, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
@@ -166,7 +166,7 @@ App::post('/v1/account/sessions')
$email = \strtolower($email);
$protocol = $request->getProtocol();
$profile = $dbForInternal->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
$profile = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) {
$audits
@@ -188,7 +188,7 @@ App::post('/v1/account/sessions')
$secret = Auth::tokenGenerator();
$session = new Document(array_merge(
[
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'userId' => $profile->getId(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $email,
@@ -202,13 +202,13 @@ App::post('/v1/account/sessions')
Authorization::setRole('user:' . $profile->getId());
$session = $dbForInternal->createDocument('sessions', $session
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$read', ['user:' . $profile->getId()])
->setAttribute('$write', ['user:' . $profile->getId()])
);
$profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND);
$profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile);
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile);
$audits
->setParam('userId', $profile->getId())
@@ -267,7 +267,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
->inject('response')
->inject('project')
->action(function ($provider, $success, $failure, $scopes, $request, $response, $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
@@ -320,7 +320,7 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->inject('request')
->inject('response')
->action(function ($projectId, $provider, $code, $state, $request, $response) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
$domain = $request->getHostname();
@@ -347,7 +347,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->inject('request')
->inject('response')
->action(function ($projectId, $provider, $code, $state, $request, $response) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
$domain = $request->getHostname();
@@ -376,17 +376,17 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('response')
->inject('project')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('geodb')
->inject('audits')
->inject('events')
->inject('usage')
->action(function ($provider, $code, $state, $request, $response, $project, $user, $dbForInternal, $geodb, $audits, $events, $usage) use ($oauthDefaultSuccess) {
/** @var Utopia\Swoole\Request $request */
->action(function ($provider, $code, $state, $request, $response, $project, $user, $dbForProject, $geodb, $audits, $events, $usage) use ($oauthDefaultSuccess) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@@ -458,13 +458,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
if ($current === $session['$id']) {
unset($sessions[$key]);
$dbForInternal->deleteDocument('sessions', $session->getId());
$dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions));
$dbForProject->deleteDocument('sessions', $session->getId());
$dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions));
}
}
}
$user = ($user->isEmpty()) ? $dbForInternal->findOne('sessions', [ // Get user by provider id
$user = ($user->isEmpty()) ? $dbForProject->findOne('sessions', [ // Get user by provider id
new Query('provider', QUERY::TYPE_EQUAL, [$provider]),
new Query('providerUid', QUERY::TYPE_EQUAL, [$oauth2ID]),
]) : $user;
@@ -473,13 +473,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$name = $oauth2->getUserName($accessToken);
$email = $oauth2->getUserEmail($accessToken);
$user = $dbForInternal->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
$user = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
if ($user === false || $user->isEmpty()) { // Last option -> create the user, generate random password
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
$sum = $dbForInternal->count('users', [ new Query('deleted', Query::TYPE_EQUAL, [false]),], APP_LIMIT_COUNT);
$sum = $dbForProject->count('users', [ new Query('deleted', Query::TYPE_EQUAL, [false]),], APP_LIMIT_COUNT);
if ($sum >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501);
@@ -487,8 +487,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
try {
$userId = $dbForInternal->getId();
$user = Authorization::skip(fn() => $dbForInternal->createDocument('users', new Document([
$userId = $dbForProject->getId();
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$read' => ['role:all'],
'$write' => ['user:' . $userId],
@@ -500,7 +500,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'registration' => \time(),
'reset' => false,
'name' => $name,
'prefs' => [],
'prefs' => new \stdClass(),
'sessions' => [],
'tokens' => [],
'memberships' => [],
@@ -524,7 +524,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$secret = Auth::tokenGenerator();
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session = new Document(array_merge([
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'userId' => $user->getId(),
'provider' => $provider,
'providerUid' => $oauth2ID,
@@ -552,12 +552,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
Authorization::setRole('user:' . $user->getId());
$session = $dbForInternal->createDocument('sessions', $session
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$read', ['user:' . $user->getId()])
->setAttribute('$write', ['user:' . $user->getId()])
);
$user = $dbForInternal->updateDocument('users', $user->getId(), $user);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$audits
->setParam('userId', $user->getId())
@@ -621,16 +621,16 @@ App::post('/v1/account/sessions/magic-url')
->inject('request')
->inject('response')
->inject('project')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('audits')
->inject('events')
->inject('mails')
->action(function ($userId, $email, $url, $request, $response, $project, $dbForInternal, $locale, $audits, $events, $mails) {
/** @var Utopia\Swoole\Request $request */
->action(function ($userId, $email, $url, $request, $response, $project, $dbForProject, $locale, $audits, $events, $mails) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
@@ -644,13 +644,13 @@ App::post('/v1/account/sessions/magic-url')
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$user = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]);
$user = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]);
if (!$user) {
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
$sum = $dbForInternal->count('users', [
$sum = $dbForProject->count('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
], APP_LIMIT_COUNT);
@@ -659,9 +659,9 @@ App::post('/v1/account/sessions/magic-url')
}
}
$userId = $userId == 'unique()' ? $dbForInternal->getId() : $userId;
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
$user = Authorization::skip(fn () => $dbForInternal->createDocument('users', new Document([
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$read' => ['role:all'],
'$write' => ['user:' . $userId],
@@ -672,7 +672,7 @@ App::post('/v1/account/sessions/magic-url')
'passwordUpdate' => \time(),
'registration' => \time(),
'reset' => false,
'prefs' => [],
'prefs' => new \stdClass(),
'sessions' => [],
'tokens' => [],
'memberships' => [],
@@ -689,7 +689,7 @@ App::post('/v1/account/sessions/magic-url')
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
$token = new Document([
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'userId' => $user->getId(),
'type' => Auth::TOKEN_TYPE_MAGIC_URL,
'secret' => Auth::hash($loginSecret), // One way hash encryption to protect DB leak
@@ -702,7 +702,7 @@ App::post('/v1/account/sessions/magic-url')
$user->setAttribute('tokens', $token, Document::SET_TYPE_APPEND);
$user = $dbForInternal->updateDocument('users', $user->getId(), $user);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
if (false === $user) {
throw new Exception('Failed to save user to DB', 500);
@@ -766,21 +766,21 @@ App::put('/v1/account/sessions/magic-url')
->param('secret', '', new Text(256), 'Valid verification token.')
->inject('request')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audits')
->action(function ($userId, $secret, $request, $response, $dbForInternal, $locale, $geodb, $audits) {
->action(function ($userId, $secret, $request, $response, $dbForProject, $locale, $geodb, $audits) {
/** @var string $userId */
/** @var string $secret */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -798,7 +798,7 @@ App::put('/v1/account/sessions/magic-url')
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session = new Document(array_merge(
[
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'userId' => $user->getId(),
'provider' => Auth::SESSION_PROVIDER_MAGIC_URL,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
@@ -814,7 +814,7 @@ App::put('/v1/account/sessions/magic-url')
Authorization::setRole('user:' . $user->getId());
$session = $dbForInternal->createDocument('sessions', $session
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$read', ['user:' . $user->getId()])
->setAttribute('$write', ['user:' . $user->getId()])
);
@@ -836,7 +836,7 @@ App::put('/v1/account/sessions/magic-url')
->setAttribute('tokens', $tokens);
$user = $dbForInternal->updateDocument('users', $user->getId(), $user);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
if (false === $user) {
throw new Exception('Failed saving user to DB', 500);
@@ -894,17 +894,17 @@ App::post('/v1/account/sessions/anonymous')
->inject('locale')
->inject('user')
->inject('project')
->inject('dbForInternal')
->inject('dbForProject')
->inject('geodb')
->inject('audits')
->inject('usage')
->action(function ($request, $response, $locale, $user, $project, $dbForInternal, $geodb, $audits, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($request, $response, $locale, $user, $project, $dbForProject, $geodb, $audits, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@@ -922,7 +922,7 @@ App::post('/v1/account/sessions/anonymous')
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
$sum = $dbForInternal->count('users', [
$sum = $dbForProject->count('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
], APP_LIMIT_COUNT);
@@ -931,8 +931,8 @@ App::post('/v1/account/sessions/anonymous')
}
}
$userId = $dbForInternal->getId();
$user = Authorization::skip(fn() => $dbForInternal->createDocument('users', new Document([
$userId = $dbForProject->getId();
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$read' => ['role:all'],
'$write' => ['user:' . $userId],
@@ -944,7 +944,7 @@ App::post('/v1/account/sessions/anonymous')
'registration' => \time(),
'reset' => false,
'name' => null,
'prefs' => [],
'prefs' => new \stdClass(),
'sessions' => [],
'tokens' => [],
'memberships' => [],
@@ -960,7 +960,7 @@ App::post('/v1/account/sessions/anonymous')
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session = new Document(array_merge(
[
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'userId' => $user->getId(),
'provider' => Auth::SESSION_PROVIDER_ANONYMOUS,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
@@ -976,12 +976,12 @@ App::post('/v1/account/sessions/anonymous')
Authorization::setRole('user:' . $user->getId());
$session = $dbForInternal->createDocument('sessions', $session
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$read', ['user:' . $user->getId()])
->setAttribute('$write', ['user:' . $user->getId()])
);
$user = $dbForInternal->updateDocument('users', $user->getId(),
$user = $dbForProject->updateDocument('users', $user->getId(),
$user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND));
$audits
@@ -1178,18 +1178,18 @@ App::get('/v1/account/logs')
->inject('user')
->inject('locale')
->inject('geodb')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($limit, $offset, $response, $user, $locale, $geodb, $dbForInternal, $usage) {
->action(function ($limit, $offset, $response, $user, $locale, $geodb, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$audit = new Audit($dbForInternal);
$audit = new Audit($dbForProject);
$auditEvents = [
'account.create',
'account.delete',
@@ -1262,13 +1262,13 @@ App::get('/v1/account/sessions/:sessionId')
->inject('response')
->inject('user')
->inject('locale')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($sessionId, $response, $user, $locale, $dbForInternal, $usage) {
->action(function ($sessionId, $response, $user, $locale, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Locale\Locale $locale */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$sessions = $user->getAttribute('sessions', []);
@@ -1314,17 +1314,17 @@ App::patch('/v1/account/name')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($name, $response, $user, $dbForInternal, $audits, $usage) {
->action(function ($name, $response, $user, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->updateDocument('users', $user->getId(), $user
$user = $dbForProject->updateDocument('users', $user->getId(), $user
->setAttribute('name', $name)
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email')]))
);
@@ -1358,13 +1358,13 @@ App::patch('/v1/account/password')
->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true)
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($password, $oldPassword, $response, $user, $dbForInternal, $audits, $usage) {
->action(function ($password, $oldPassword, $response, $user, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@@ -1373,7 +1373,7 @@ App::patch('/v1/account/password')
throw new Exception('Invalid credentials', 401);
}
$user = $dbForInternal->updateDocument('users', $user->getId(), $user
$user = $dbForProject->updateDocument('users', $user->getId(), $user
->setAttribute('password', Auth::passwordHash($password))
->setAttribute('passwordUpdate', \time())
);
@@ -1406,13 +1406,13 @@ App::patch('/v1/account/email')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($email, $password, $response, $user, $dbForInternal, $audits, $usage) {
->action(function ($email, $password, $response, $user, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@@ -1426,14 +1426,14 @@ App::patch('/v1/account/email')
}
$email = \strtolower($email);
$profile = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
$profile = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
if ($profile) {
throw new Exception('User already registered', 409);
}
try {
$user = $dbForInternal->updateDocument('users', $user->getId(), $user
$user = $dbForProject->updateDocument('users', $user->getId(), $user
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
->setAttribute('email', $email)
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
@@ -1470,17 +1470,17 @@ App::patch('/v1/account/prefs')
->param('prefs', [], new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($prefs, $response, $user, $dbForInternal, $audits, $usage) {
->action(function ($prefs, $response, $user, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
$audits
->setParam('event', 'account.update.prefs')
@@ -1507,21 +1507,21 @@ App::delete('/v1/account')
->inject('request')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('events')
->inject('usage')
->action(function ($request, $response, $user, $dbForInternal, $audits, $events, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($request, $response, $user, $dbForProject, $audits, $events, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$protocol = $request->getProtocol();
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', false));
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', false));
// TODO Seems to be related to users.php/App::delete('/v1/users/:userId'). Can we share code between these two? Do todos below apply to users.php?
@@ -1576,16 +1576,16 @@ App::delete('/v1/account/sessions/:sessionId')
->inject('request')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('audits')
->inject('events')
->inject('usage')
->action(function ($sessionId, $request, $response, $user, $dbForInternal, $locale, $audits, $events, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($sessionId, $request, $response, $user, $dbForProject, $locale, $audits, $events, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
@@ -1602,7 +1602,7 @@ App::delete('/v1/account/sessions/:sessionId')
if ($sessionId == $session->getId()) {
unset($sessions[$key]);
$dbForInternal->deleteDocument('sessions', $session->getId());
$dbForProject->deleteDocument('sessions', $session->getId());
$audits
->setParam('userId', $user->getId())
@@ -1630,7 +1630,7 @@ App::delete('/v1/account/sessions/:sessionId')
;
}
$dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions));
$dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions));
$events
->setParam('eventData', $response->output($session, Response::MODEL_SESSION))
@@ -1662,16 +1662,16 @@ App::delete('/v1/account/sessions')
->inject('request')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('audits')
->inject('events')
->inject('usage')
->action(function ($request, $response, $user, $dbForInternal, $locale, $audits, $events, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($request, $response, $user, $dbForProject, $locale, $audits, $events, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
@@ -1681,7 +1681,7 @@ App::delete('/v1/account/sessions')
$sessions = $user->getAttribute('sessions', []);
foreach ($sessions as $session) {/** @var Document $session */
$dbForInternal->deleteDocument('sessions', $session->getId());
$dbForProject->deleteDocument('sessions', $session->getId());
$audits
->setParam('userId', $user->getId())
@@ -1709,7 +1709,7 @@ App::delete('/v1/account/sessions')
}
}
$dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
$dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
$numOfSessions = count($sessions);
@@ -1740,22 +1740,22 @@ App::post('/v1/account/recovery')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->label('abuse-key', ['url:{url},email:{param-email}', 'ip:{ip}'])
->param('email', '', new Email(), 'User email.')
->param('url', '', function ($clients) {return new Host($clients);}, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients'])
->inject('request')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('project')
->inject('locale')
->inject('mails')
->inject('audits')
->inject('events')
->inject('usage')
->action(function ($email, $url, $request, $response, $dbForInternal, $project, $locale, $mails, $audits, $events, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($email, $url, $request, $response, $dbForProject, $project, $locale, $mails, $audits, $events, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Event\Event $mails */
@@ -1772,7 +1772,7 @@ App::post('/v1/account/recovery')
$isAppUser = Auth::isAppUser($roles);
$email = \strtolower($email);
$profile = $dbForInternal->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
$profile = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
if (!$profile) {
throw new Exception('User not found', 404);
@@ -1786,7 +1786,7 @@ App::post('/v1/account/recovery')
$secret = Auth::tokenGenerator();
$recovery = new Document([
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'userId' => $profile->getId(),
'type' => Auth::TOKEN_TYPE_RECOVERY,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
@@ -1799,7 +1799,7 @@ App::post('/v1/account/recovery')
$profile->setAttribute('tokens', $recovery, Document::SET_TYPE_APPEND);
$profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile);
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile);
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
@@ -1860,12 +1860,12 @@ App::put('/v1/account/recovery')
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
->param('passwordAgain', '', new Password(), 'Repeat new user password. Must be at least 8 chars.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($userId, $secret, $password, $passwordAgain, $response, $dbForInternal, $audits, $usage) {
->action(function ($userId, $secret, $password, $passwordAgain, $response, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@@ -1873,7 +1873,7 @@ App::put('/v1/account/recovery')
throw new Exception('Passwords must match', 400);
}
$profile = $dbForInternal->getDocument('users', $userId);
$profile = $dbForProject->getDocument('users', $userId);
if ($profile->isEmpty() || $profile->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -1888,7 +1888,7 @@ App::put('/v1/account/recovery')
Authorization::setRole('user:' . $profile->getId());
$profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
->setAttribute('password', Auth::passwordHash($password))
->setAttribute('passwordUpdate', \time())
->setAttribute('emailVerification', true)
@@ -1905,7 +1905,7 @@ App::put('/v1/account/recovery')
}
}
$dbForInternal->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens));
$dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens));
$audits
->setParam('userId', $profile->getId())
@@ -1938,18 +1938,18 @@ App::post('/v1/account/verification')
->inject('response')
->inject('project')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('audits')
->inject('events')
->inject('mails')
->inject('usage')
->action(function ($url, $request, $response, $project, $user, $dbForInternal, $locale, $audits, $events, $mails, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($url, $request, $response, $project, $user, $dbForProject, $locale, $audits, $events, $mails, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
@@ -1969,7 +1969,7 @@ App::post('/v1/account/verification')
$expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM;
$verification = new Document([
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'userId' => $user->getId(),
'type' => Auth::TOKEN_TYPE_VERIFICATION,
'secret' => Auth::hash($verificationSecret), // One way hash encryption to protect DB leak
@@ -1982,7 +1982,7 @@ App::post('/v1/account/verification')
$user->setAttribute('tokens', $verification, Document::SET_TYPE_APPEND);
$user = $dbForInternal->updateDocument('users', $user->getId(), $user);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]);
@@ -2042,17 +2042,17 @@ App::put('/v1/account/verification')
->param('secret', '', new Text(256), 'Valid verification token.')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('usage')
->action(function ($userId, $secret, $response, $user, $dbForInternal, $audits, $usage) {
->action(function ($userId, $secret, $response, $user, $dbForProject, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$profile = $dbForInternal->getDocument('users', $userId);
$profile = $dbForProject->getDocument('users', $userId);
if ($profile->isEmpty()) {
throw new Exception('User not found', 404);
@@ -2067,7 +2067,7 @@ App::put('/v1/account/verification')
Authorization::setRole('user:' . $profile->getId());
$profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
/**
* We act like we're updating and validating
@@ -2080,7 +2080,7 @@ App::put('/v1/account/verification')
}
}
$dbForInternal->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens));
$dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens));
$audits
->setParam('userId', $profile->getId())
File diff suppressed because it is too large Load Diff
+82 -84
View File
@@ -48,13 +48,13 @@ App::post('/v1/functions')
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($functionId, $name, $execute, $runtime, $vars, $events, $schedule, $timeout, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($functionId, $name, $execute, $runtime, $vars, $events, $schedule, $timeout, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$functionId = ($functionId == 'unique()') ? $dbForInternal->getId() : $functionId;
$function = $dbForInternal->createDocument('functions', new Document([
$functionId = ($functionId == 'unique()') ? $dbForProject->getId() : $functionId;
$function = $dbForProject->createDocument('functions', new Document([
'$id' => $functionId,
'execute' => $execute,
'dateCreated' => time(),
@@ -94,13 +94,13 @@ App::get('/v1/functions')
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
if (!empty($cursor)) {
$cursorFunction = $dbForInternal->getDocument('functions', $cursor);
$cursorFunction = $dbForProject->getDocument('functions', $cursor);
if ($cursorFunction->isEmpty()) {
throw new Exception("Function '{$cursor}' for the 'cursor' value not found.", 400);
@@ -114,8 +114,8 @@ App::get('/v1/functions')
}
$response->dynamic(new Document([
'functions' => $dbForInternal->find('functions', $queries, $limit, $offset, [], [$orderType], $cursorFunction ?? null, $cursorDirection),
'sum' => $dbForInternal->count('functions', $queries, APP_LIMIT_COUNT),
'functions' => $dbForProject->find('functions', $queries, $limit, $offset, [], [$orderType], $cursorFunction ?? null, $cursorDirection),
'sum' => $dbForProject->count('functions', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_FUNCTION_LIST);
});
@@ -160,12 +160,12 @@ App::get('/v1/functions/:functionId')
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($functionId, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($functionId, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$function = $dbForInternal->getDocument('functions', $functionId);
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
@@ -187,16 +187,14 @@ App::get('/v1/functions/:functionId/usage')
->param('functionId', '', new UID(), 'Function ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('project')
->inject('dbForInternal')
->inject('register')
->action(function ($functionId, $range, $response, $project, $dbForInternal, $register) {
->inject('dbForProject')
->action(function ($functionId, $range, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Registry\Registry $register */
$function = $dbForInternal->getDocument('functions', $functionId);
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
@@ -231,12 +229,12 @@ App::get('/v1/functions/:functionId/usage')
$stats = [];
Authorization::skip(function() use ($dbForInternal, $periods, $range, $metrics, &$stats) {
Authorization::skip(function() use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForInternal->find('stats', [
$requestDocs = $dbForProject->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $limit, 0, ['time'], [Database::ORDER_DESC]);
@@ -298,14 +296,14 @@ App::put('/v1/functions/:functionId')
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
->param('timeout', 15, new Range(1, 900), 'Maximum execution time in seconds.', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('project')
->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $dbForInternal, $project) {
->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $dbForProject, $project) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Document $project */
$function = $dbForInternal->getDocument('functions', $functionId);
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
@@ -315,7 +313,7 @@ App::put('/v1/functions/:functionId')
$cron = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? new CronExpression($schedule) : null;
$next = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0;
$function = $dbForInternal->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
'execute' => $execute,
'dateUpdated' => time(),
'name' => $name,
@@ -355,15 +353,15 @@ App::patch('/v1/functions/:functionId/tag')
->param('functionId', '', new UID(), 'Function ID.')
->param('tag', '', new UID(), 'Tag ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('project')
->action(function ($functionId, $tag, $response, $dbForInternal, $project) {
->action(function ($functionId, $tag, $response, $dbForProject, $project) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Document $project */
$function = $dbForInternal->getDocument('functions', $functionId);
$tag = $dbForInternal->getDocument('tags', $tag);
$function = $dbForProject->getDocument('functions', $functionId);
$tag = $dbForProject->getDocument('tags', $tag);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
@@ -377,7 +375,7 @@ App::patch('/v1/functions/:functionId/tag')
$cron = (empty($function->getAttribute('tag')) && !empty($schedule)) ? new CronExpression($schedule) : null;
$next = (empty($function->getAttribute('tag')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0;
$function = $dbForInternal->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
'tag' => $tag->getId(),
'scheduleNext' => (int)$next,
])));
@@ -408,20 +406,20 @@ App::delete('/v1/functions/:functionId')
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', '', new UID(), 'Function ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('deletes')
->action(function ($functionId, $response, $dbForInternal, $deletes) {
->action(function ($functionId, $response, $dbForProject, $deletes) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $deletes */
$function = $dbForInternal->getDocument('functions', $functionId);
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
}
if (!$dbForInternal->deleteDocument('functions', $function->getId())) {
if (!$dbForProject->deleteDocument('functions', $function->getId())) {
throw new Exception('Failed to remove function from DB', 500);
}
@@ -452,15 +450,15 @@ App::post('/v1/functions/:functionId/tags')
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', false)
->inject('request')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($functionId, $command, $file, $request, $response, $dbForInternal, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($functionId, $command, $file, $request, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $usage */
$function = $dbForInternal->getDocument('functions', $functionId);
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
@@ -501,8 +499,8 @@ App::post('/v1/functions/:functionId/tags')
throw new Exception('Failed moving file', 500);
}
$tagId = $dbForInternal->getId();
$tag = $dbForInternal->createDocument('tags', new Document([
$tagId = $dbForProject->getId();
$tag = $dbForProject->createDocument('tags', new Document([
'$id' => $tagId,
'$read' => [],
'$write' => [],
@@ -541,19 +539,19 @@ App::get('/v1/functions/:functionId/tags')
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($functionId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($functionId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$function = $dbForInternal->getDocument('functions', $functionId);
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
}
if (!empty($cursor)) {
$cursorTag = $dbForInternal->getDocument('tags', $cursor);
$cursorTag = $dbForProject->getDocument('tags', $cursor);
if ($cursorTag->isEmpty()) {
throw new Exception("Tag '{$cursor}' for the 'cursor' value not found.", 400);
@@ -568,8 +566,8 @@ App::get('/v1/functions/:functionId/tags')
$queries[] = new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]);
$results = $dbForInternal->find('tags', $queries, $limit, $offset, [], [$orderType], $cursorTag ?? null, $cursorDirection);
$sum = $dbForInternal->count('tags', $queries, APP_LIMIT_COUNT);
$results = $dbForProject->find('tags', $queries, $limit, $offset, [], [$orderType], $cursorTag ?? null, $cursorDirection);
$sum = $dbForProject->count('tags', $queries, APP_LIMIT_COUNT);
$response->dynamic(new Document([
'tags' => $results,
@@ -591,18 +589,18 @@ App::get('/v1/functions/:functionId/tags/:tagId')
->param('functionId', '', new UID(), 'Function ID.')
->param('tagId', '', new UID(), 'Tag ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($functionId, $tagId, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($functionId, $tagId, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$function = $dbForInternal->getDocument('functions', $functionId);
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
}
$tag = $dbForInternal->getDocument('tags', $tagId);
$tag = $dbForProject->getDocument('tags', $tagId);
if ($tag->getAttribute('functionId') !== $function->getId()) {
throw new Exception('Tag not found', 404);
@@ -629,20 +627,20 @@ App::delete('/v1/functions/:functionId/tags/:tagId')
->param('functionId', '', new UID(), 'Function ID.')
->param('tagId', '', new UID(), 'Tag ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($functionId, $tagId, $response, $dbForInternal, $usage) {
->action(function ($functionId, $tagId, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $usage */
$function = $dbForInternal->getDocument('functions', $functionId);
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
}
$tag = $dbForInternal->getDocument('tags', $tagId);
$tag = $dbForProject->getDocument('tags', $tagId);
if ($tag->getAttribute('functionId') !== $function->getId()) {
throw new Exception('Tag not found', 404);
@@ -655,13 +653,13 @@ App::delete('/v1/functions/:functionId/tags/:tagId')
$device = Storage::getDevice('functions');
if ($device->delete($tag->getAttribute('path', ''))) {
if (!$dbForInternal->deleteDocument('tags', $tag->getId())) {
if (!$dbForProject->deleteDocument('tags', $tag->getId())) {
throw new Exception('Failed to remove tag from DB', 500);
}
}
if($function->getAttribute('tag') === $tag->getId()) { // Reset function tag
$function = $dbForInternal->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
'tag' => '',
])));
}
@@ -692,21 +690,21 @@ App::post('/v1/functions/:functionId/executions')
// ->param('async', 1, new Range(0, 1), 'Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.', true)
->inject('response')
->inject('project')
->inject('dbForInternal')
->inject('dbForProject')
->inject('user')
->action(function ($functionId, $data, /*$async,*/ $response, $project, $dbForInternal, $user) {
->action(function ($functionId, $data, /*$async,*/ $response, $project, $dbForProject, $user) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Document $user */
$function = Authorization::skip(fn() => $dbForInternal->getDocument('functions', $functionId));
$function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId));
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
}
$tag = Authorization::skip(fn() => $dbForInternal->getDocument('tags', $function->getAttribute('tag')));
$tag = Authorization::skip(fn() => $dbForProject->getDocument('tags', $function->getAttribute('tag')));
if ($tag->getAttribute('functionId') !== $function->getId()) {
throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404);
@@ -722,9 +720,9 @@ App::post('/v1/functions/:functionId/executions')
throw new Exception($validator->getDescription(), 401);
}
$executionId = $dbForInternal->getId();
$executionId = $dbForProject->getId();
$execution = Authorization::skip(fn() => $dbForInternal->createDocument('executions', new Document([
$execution = Authorization::skip(fn() => $dbForProject->createDocument('executions', new Document([
'$id' => $executionId,
'$read' => (!$user->isEmpty()) ? ['user:' . $user->getId()] : [],
'$write' => [],
@@ -794,19 +792,19 @@ App::get('/v1/functions/:functionId/executions')
->param('cursor', '', new UID(), 'ID of the execution used as the starting point for the query, excluding the execution itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($functionId, $limit, $offset, $search, $cursor, $cursorDirection, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($functionId, $limit, $offset, $search, $cursor, $cursorDirection, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$function = Authorization::skip(fn() => $dbForInternal->getDocument('functions', $functionId));
$function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId));
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
}
if (!empty($cursor)) {
$cursorExecution = $dbForInternal->getDocument('executions', $cursor);
$cursorExecution = $dbForProject->getDocument('executions', $cursor);
if ($cursorExecution->isEmpty()) {
throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400);
@@ -821,8 +819,8 @@ App::get('/v1/functions/:functionId/executions')
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
}
$results = $dbForInternal->find('executions', $queries, $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection);
$sum = $dbForInternal->count('executions', $queries, APP_LIMIT_COUNT);
$results = $dbForProject->find('executions', $queries, $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection);
$sum = $dbForProject->count('executions', $queries, APP_LIMIT_COUNT);
$response->dynamic(new Document([
'executions' => $results,
@@ -844,18 +842,18 @@ App::get('/v1/functions/:functionId/executions/:executionId')
->param('functionId', '', new UID(), 'Function ID.')
->param('executionId', '', new UID(), 'Execution ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($functionId, $executionId, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($functionId, $executionId, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$function = Authorization::skip(fn() => $dbForInternal->getDocument('functions', $functionId));
$function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId));
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
}
$execution = $dbForInternal->getDocument('executions', $executionId);
$execution = $dbForProject->getDocument('executions', $executionId);
if ($execution->getAttribute('functionId') !== $function->getId()) {
throw new Exception('Execution not found', 404);
+1 -2
View File
@@ -341,8 +341,7 @@ App::get('/v1/health/anti-virus')
$output['version'] = @$antivirus->version();
$output['status'] = (@$antivirus->ping()) ? 'pass' : 'fail';
} catch( \Exception $e) {
$output['status'] = 'offline';
$output['version'] = '';
throw new Exception('Antivirus is not available', 500);
}
}
+3 -7
View File
@@ -21,7 +21,7 @@ App::get('/v1/locale')
->inject('locale')
->inject('geodb')
->action(function ($request, $response, $locale, $geodb) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
@@ -224,9 +224,7 @@ App::get('/v1/locale/currencies')
$list = Config::getParam('locale-currencies');
$list = array_map(function($node) {
return new Document($node);
}, $list);
$list = array_map(fn($node) => new Document($node), $list);
$response->dynamic(new Document(['currencies' => $list, 'sum' => \count($list)]), Response::MODEL_CURRENCY_LIST);
});
@@ -249,9 +247,7 @@ App::get('/v1/locale/languages')
$list = Config::getParam('locale-languages');
$list = array_map(function($node) {
return new Document($node);
}, $list);
$list = array_map(fn ($node) => new Document($node), $list);
$response->dynamic(new Document(['languages' => $list, 'sum' => \count($list)]), Response::MODEL_LANGUAGE_LIST);
});
+16 -20
View File
@@ -57,13 +57,11 @@ App::post('/v1/projects')
->param('legalTaxId', '', new Text(256), 'Project legal Tax ID. Max length: 256 chars.', true)
->inject('response')
->inject('dbForConsole')
->inject('dbForInternal')
->inject('dbForExternal')
->action(function ($projectId, $name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForInternal, $dbForExternal) {
->inject('dbForProject')
->action(function ($projectId, $name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */
/** @var Utopia\Database\Database $dbForProject */
$team = $dbForConsole->getDocument('teams', $teamId);
@@ -106,15 +104,13 @@ App::post('/v1/projects')
$collections = Config::getParam('collections', []); /** @var array $collections */
$dbForInternal->setNamespace('project_' . $project->getId() . '_internal');
$dbForInternal->create();
$dbForExternal->setNamespace('project_' . $project->getId() . '_external');
$dbForExternal->create();
$dbForProject->setNamespace('_project_' . $project->getId());
$dbForProject->create('appwrite');
$audit = new Audit($dbForInternal);
$audit = new Audit($dbForProject);
$audit->setup();
$adapter = new TimeLimit('', 0, 1, $dbForInternal);
$adapter = new TimeLimit('', 0, 1, $dbForProject);
$adapter->setup();
foreach ($collections as $key => $collection) {
@@ -143,7 +139,7 @@ App::post('/v1/projects')
]);
}
$dbForInternal->createCollection($key, $attributes, $indexes);
$dbForProject->createCollection($key, $attributes, $indexes);
}
$response->setStatusCode(Response::STATUS_CODE_CREATED);
@@ -235,12 +231,12 @@ App::get('/v1/projects/:projectId/usage')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForConsole')
->inject('dbForInternal')
->inject('dbForProject')
->inject('register')
->action(function ($projectId, $range, $response, $dbForConsole, $dbForInternal, $register) {
->action(function ($projectId, $range, $response, $dbForConsole, $dbForProject, $register) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Registry\Registry $register */
$project = $dbForConsole->getDocument('projects', $projectId);
@@ -270,11 +266,11 @@ App::get('/v1/projects/:projectId/usage')
],
];
$dbForInternal->setNamespace('project_' . $projectId . '_internal');
$dbForProject->setNamespace('_project_' . $projectId);
$metrics = [
'requests',
'network',
'requests',
'network',
'executions',
'users.count',
'database.documents.count',
@@ -284,12 +280,12 @@ App::get('/v1/projects/:projectId/usage')
$stats = [];
Authorization::skip(function() use ($dbForInternal, $periods, $range, $metrics, &$stats) {
Authorization::skip(function() use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForInternal->find('stats', [
$requestDocs = $dbForProject->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $limit, 0, ['time'], [Database::ORDER_DESC]);
+49 -49
View File
@@ -47,14 +47,14 @@ App::post('/v1/storage/files')
->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->inject('request')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('user')
->inject('audits')
->inject('usage')
->action(function ($fileId, $file, $read, $write, $request, $response, $dbForInternal, $user, $audits, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($fileId, $file, $read, $write, $request, $response, $dbForProject, $user, $audits, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
@@ -146,8 +146,8 @@ App::post('/v1/storage/files')
$sizeActual = $device->getFileSize($path);
$fileId = ($fileId == 'unique()') ? $dbForInternal->getId() : $fileId;
$file = $dbForInternal->createDocument('files', new Document([
$fileId = ($fileId == 'unique()') ? $dbForProject->getId() : $fileId;
$file = $dbForProject->createDocument('files', new Document([
'$id' => $fileId,
'$read' => (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? [], // By default set read permissions for user
'$write' => (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [], // By default set write permissions for user
@@ -202,15 +202,15 @@ App::get('/v1/storage/files')
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal, $usage) {
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
if (!empty($cursor)) {
$cursorFile = $dbForInternal->getDocument('files', $cursor);
$cursorFile = $dbForProject->getDocument('files', $cursor);
if ($cursorFile->isEmpty()) {
throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400);
@@ -229,8 +229,8 @@ App::get('/v1/storage/files')
;
$response->dynamic(new Document([
'files' => $dbForInternal->find('files', $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection),
'sum' => $dbForInternal->count('files', $queries, APP_LIMIT_COUNT),
'files' => $dbForProject->find('files', $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection),
'sum' => $dbForProject->count('files', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_FILE_LIST);
});
@@ -247,14 +247,14 @@ App::get('/v1/storage/files/:fileId')
->label('sdk.response.model', Response::MODEL_FILE)
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($fileId, $response, $dbForInternal, $usage) {
->action(function ($fileId, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$file = $dbForInternal->getDocument('files', $fileId);
$file = $dbForProject->getDocument('files', $fileId);
if (empty($file->getId())) {
throw new Exception('File not found', 404);
@@ -292,13 +292,13 @@ App::get('/v1/storage/files/:fileId/preview')
->inject('request')
->inject('response')
->inject('project')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal, $usage) {
/** @var Utopia\Swoole\Request $request */
->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $stats */
$storage = 'files';
@@ -322,7 +322,7 @@ App::get('/v1/storage/files/:fileId/preview')
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5($fileId.$width.$height.$gravity.$quality.$borderWidth.$borderColor.$borderRadius.$opacity.$rotation.$background.$storage.$output);
$file = $dbForInternal->getDocument('files', $fileId);
$file = $dbForProject->getDocument('files', $fileId);
if (empty($file->getId())) {
throw new Exception('File not found', 404);
@@ -440,14 +440,14 @@ App::get('/v1/storage/files/:fileId/download')
->label('sdk.methodType', 'location')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($fileId, $response, $dbForInternal, $usage) {
->action(function ($fileId, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$file = $dbForInternal->getDocument('files', $fileId);
$file = $dbForProject->getDocument('files', $fileId);
if (empty($file->getId())) {
throw new Exception('File not found', 404);
@@ -505,14 +505,14 @@ App::get('/v1/storage/files/:fileId/view')
->label('sdk.methodType', 'location')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($fileId, $response, $dbForInternal, $usage) {
->action(function ($fileId, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$file = $dbForInternal->getDocument('files', $fileId);
$file = $dbForProject->getDocument('files', $fileId);
$mimes = Config::getParam('storage-mimes');
if (empty($file->getId())) {
@@ -583,13 +583,13 @@ App::put('/v1/storage/files/:fileId')
->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('user')
->inject('audits')
->inject('usage')
->action(function ($fileId, $read, $write, $response, $dbForInternal, $user, $audits, $usage) {
->action(function ($fileId, $read, $write, $response, $dbForProject, $user, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
@@ -612,13 +612,13 @@ App::put('/v1/storage/files/:fileId')
}
}
$file = $dbForInternal->getDocument('files', $fileId);
$file = $dbForProject->getDocument('files', $fileId);
if (empty($file->getId())) {
throw new Exception('File not found', 404);
}
$file = $dbForInternal->updateDocument('files', $fileId, new Document(\array_merge($file->getArrayCopy(), [
$file = $dbForProject->updateDocument('files', $fileId, new Document(\array_merge($file->getArrayCopy(), [
'$read' => $read,
'$write' => $write,
'bucketId' => '',
@@ -650,18 +650,18 @@ App::delete('/v1/storage/files/:fileId')
->label('sdk.response.model', Response::MODEL_NONE)
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('events')
->inject('audits')
->inject('usage')
->action(function ($fileId, $response, $dbForInternal, $events, $audits, $usage) {
->action(function ($fileId, $response, $dbForProject, $events, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */
$file = $dbForInternal->getDocument('files', $fileId);
$file = $dbForProject->getDocument('files', $fileId);
if (empty($file->getId())) {
throw new Exception('File not found', 404);
@@ -670,7 +670,7 @@ App::delete('/v1/storage/files/:fileId')
$device = Storage::getDevice('files');
if ($device->delete($file->getAttribute('path', ''))) {
if (!$dbForInternal->deleteDocument('files', $fileId)) {
if (!$dbForProject->deleteDocument('files', $fileId)) {
throw new Exception('Failed to remove file from DB', 500);
}
}
@@ -705,10 +705,10 @@ App::get('/v1/storage/usage')
->label('sdk.response.model', Response::MODEL_USAGE_STORAGE)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($range, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($range, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
@@ -738,12 +738,12 @@ App::get('/v1/storage/usage')
$stats = [];
Authorization::skip(function() use ($dbForInternal, $periods, $range, $metrics, &$stats) {
Authorization::skip(function() use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForInternal->find('stats', [
$requestDocs = $dbForProject->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $limit, 0, ['time'], [Database::ORDER_DESC]);
@@ -797,10 +797,10 @@ App::get('/v1/storage/:bucketId/usage')
->param('bucketId', '', new UID(), 'Bucket ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($bucketId, $range, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($bucketId, $range, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
// TODO: Check if the storage bucket exists else throw 404
@@ -835,12 +835,12 @@ App::get('/v1/storage/:bucketId/usage')
$stats = [];
Authorization::skip(function() use ($dbForInternal, $periods, $range, $metrics, &$stats) {
Authorization::skip(function() use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForInternal->find('stats', [
$requestDocs = $dbForProject->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $limit, 0, ['time'], [Database::ORDER_DESC]);
+87 -87
View File
@@ -39,19 +39,19 @@ App::post('/v1/teams')
->param('roles', ['owner'], new ArrayList(new Key()), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.', true)
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('events')
->action(function ($teamId, $name, $roles, $response, $user, $dbForInternal, $events) {
->action(function ($teamId, $name, $roles, $response, $user, $dbForProject, $events) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$teamId = $teamId == 'unique()' ? $dbForInternal->getId() : $teamId;
$team = Authorization::skip(fn() => $dbForInternal->createDocument('teams', new Document([
$teamId = $teamId == 'unique()' ? $dbForProject->getId() : $teamId;
$team = Authorization::skip(fn() => $dbForProject->createDocument('teams', new Document([
'$id' => $teamId ,
'$read' => ['team:'.$teamId],
'$write' => ['team:'.$teamId .'/owner'],
@@ -74,11 +74,11 @@ App::post('/v1/teams')
'secret' => '',
]);
$membership = $dbForInternal->createDocument('memberships', $membership);
$membership = $dbForProject->createDocument('memberships', $membership);
// Attach user to team
$user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
$user = $dbForInternal->updateDocument('users', $user->getId(), $user);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
}
if (!empty($user->getId())) {
@@ -107,13 +107,13 @@ App::get('/v1/teams')
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
if (!empty($cursor)) {
$cursorTeam = $dbForInternal->getDocument('teams', $cursor);
$cursorTeam = $dbForProject->getDocument('teams', $cursor);
if ($cursorTeam->isEmpty()) {
throw new Exception("Team '{$cursor}' for the 'cursor' value not found.", 400);
@@ -126,8 +126,8 @@ App::get('/v1/teams')
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
}
$results = $dbForInternal->find('teams', $queries, $limit, $offset, [], [$orderType], $cursorTeam ?? null, $cursorDirection);
$sum = $dbForInternal->count('teams', $queries, APP_LIMIT_COUNT);
$results = $dbForProject->find('teams', $queries, $limit, $offset, [], [$orderType], $cursorTeam ?? null, $cursorDirection);
$sum = $dbForProject->count('teams', $queries, APP_LIMIT_COUNT);
$response->dynamic(new Document([
'teams' => $results,
@@ -148,12 +148,12 @@ App::get('/v1/teams/:teamId')
->label('sdk.response.model', Response::MODEL_TEAM)
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($teamId, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($teamId, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$team = $dbForInternal->getDocument('teams', $teamId);
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
@@ -177,18 +177,18 @@ App::put('/v1/teams/:teamId')
->param('teamId', '', new UID(), 'Team ID.')
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
->inject('response')
->inject('dbForInternal')
->action(function ($teamId, $name, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($teamId, $name, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$team = $dbForInternal->getDocument('teams', $teamId);
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
$team = $dbForInternal->updateDocument('teams', $team->getId(),$team
$team = $dbForProject->updateDocument('teams', $team->getId(),$team
->setAttribute('name', $name)
->setAttribute('search', implode(' ', [$teamId, $name]))
);
@@ -209,33 +209,33 @@ App::delete('/v1/teams/:teamId')
->label('sdk.response.model', Response::MODEL_NONE)
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('events')
->inject('deletes')
->action(function ($teamId, $response, $dbForInternal, $events, $deletes) {
->action(function ($teamId, $response, $dbForProject, $events, $deletes) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $deletes */
$team = $dbForInternal->getDocument('teams', $teamId);
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
$memberships = $dbForInternal->find('memberships', [
$memberships = $dbForProject->find('memberships', [
new Query('teamId', Query::TYPE_EQUAL, [$teamId]),
], 2000, 0); // TODO fix members limit
// TODO delete all members individually from the user object
foreach ($memberships as $membership) {
if (!$dbForInternal->deleteDocument('memberships', $membership->getId())) {
if (!$dbForProject->deleteDocument('memberships', $membership->getId())) {
throw new Exception('Failed to remove membership for team from DB', 500);
}
}
if (!$dbForInternal->deleteDocument('teams', $teamId)) {
if (!$dbForProject->deleteDocument('teams', $teamId)) {
throw new Exception('Failed to remove team from DB', 500);
}
@@ -273,15 +273,15 @@ App::post('/v1/teams/:teamId/memberships')
->inject('response')
->inject('project')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('audits')
->inject('mails')
->action(function ($teamId, $email, $roles, $url, $name, $response, $project, $user, $dbForInternal, $locale, $audits, $mails) {
->action(function ($teamId, $email, $roles, $url, $name, $response, $project, $user, $dbForProject, $locale, $audits, $mails) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $mails */
@@ -294,20 +294,20 @@ App::post('/v1/teams/:teamId/memberships')
$email = \strtolower($email);
$name = (empty($name)) ? $email : $name;
$team = $dbForInternal->getDocument('teams', $teamId);
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
$invitee = $dbForInternal->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
$invitee = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address
if (empty($invitee)) { // Create new user if no user with same email found
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed.
$sum = $dbForInternal->count('users', [], APP_LIMIT_USERS);
$sum = $dbForProject->count('users', [], APP_LIMIT_USERS);
if($sum >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501);
@@ -315,8 +315,8 @@ App::post('/v1/teams/:teamId/memberships')
}
try {
$userId = $dbForInternal->getId();
$invitee = Authorization::skip(fn() => $dbForInternal->createDocument('users', new Document([
$userId = $dbForProject->getId();
$invitee = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$read' => ['user:'.$userId, 'role:all'],
'$write' => ['user:'.$userId],
@@ -333,7 +333,7 @@ App::post('/v1/teams/:teamId/memberships')
'registration' => \time(),
'reset' => false,
'name' => $name,
'prefs' => [],
'prefs' => new \stdClass(),
'sessions' => [],
'tokens' => [],
'memberships' => [],
@@ -353,7 +353,7 @@ App::post('/v1/teams/:teamId/memberships')
$secret = Auth::tokenGenerator();
$membership = new Document([
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'$read' => ['role:all'],
'$write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'],
'userId' => $invitee->getId(),
@@ -367,20 +367,20 @@ App::post('/v1/teams/:teamId/memberships')
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
try {
$membership = Authorization::skip(fn() => $dbForInternal->createDocument('memberships', $membership));
$membership = Authorization::skip(fn() => $dbForProject->createDocument('memberships', $membership));
} catch (Duplicate $th) {
throw new Exception('User has already been invited or is already a member of this team', 409);
}
$team->setAttribute('sum', $team->getAttribute('sum', 0) + 1);
$team = Authorization::skip(fn() => $dbForInternal->updateDocument('teams', $team->getId(), $team));
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
// Attach user to team
$invitee->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
$invitee = Authorization::skip(fn() => $dbForInternal->updateDocument('users', $invitee->getId(), $invitee));
$invitee = Authorization::skip(fn() => $dbForProject->updateDocument('users', $invitee->getId(), $invitee));
} else {
try {
$membership = $dbForInternal->createDocument('memberships', $membership);
$membership = $dbForProject->createDocument('memberships', $membership);
} catch (Duplicate $th) {
throw new Exception('User has already been invited or is already a member of this team', 409);
}
@@ -438,32 +438,32 @@ App::get('/v1/teams/:teamId/memberships')
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->action(function ($teamId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($teamId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$team = $dbForInternal->getDocument('teams', $teamId);
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
if (!empty($cursor)) {
$cursorMembership = $dbForInternal->getDocument('memberships', $cursor);
$cursorMembership = $dbForProject->getDocument('memberships', $cursor);
if ($cursorMembership->isEmpty()) {
throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400);
}
}
$memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, [], [$orderType], $cursorMembership ?? null, $cursorDirection);
$sum = $dbForInternal->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT);
$memberships = $dbForProject->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, [], [$orderType], $cursorMembership ?? null, $cursorDirection);
$sum = $dbForProject->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT);
$memberships = array_filter($memberships, fn(Document $membership) => !empty($membership->getAttribute('userId')));
$memberships = array_map(function($membership) use ($dbForInternal) {
$user = $dbForInternal->getDocument('users', $membership->getAttribute('userId'));
$memberships = array_map(function($membership) use ($dbForProject) {
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$membership
->setAttribute('name', $user->getAttribute('name'))
@@ -493,24 +493,24 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('response')
->inject('dbForInternal')
->action(function ($teamId, $membershipId, $response, $dbForInternal) {
->inject('dbForProject')
->action(function ($teamId, $membershipId, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$team = $dbForInternal->getDocument('teams', $teamId);
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
$membership = $dbForInternal->getDocument('memberships', $membershipId);
$membership = $dbForProject->getDocument('memberships', $membershipId);
if($membership->isEmpty() || empty($membership->getAttribute('userId'))) {
throw new Exception('Membership not found', 404);
}
$user = $dbForInternal->getDocument('users', $membership->getAttribute('userId'));
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$membership
->setAttribute('name', $user->getAttribute('name'))
@@ -538,26 +538,26 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->inject('request')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForInternal, $audits) {
/** @var Utopia\Swoole\Request $request */
->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForProject, $audits) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
$team = $dbForInternal->getDocument('teams', $teamId);
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
$membership = $dbForInternal->getDocument('memberships', $membershipId);
$membership = $dbForProject->getDocument('memberships', $membershipId);
if ($membership->isEmpty()) {
throw new Exception('Membership not found', 404);
}
$profile = $dbForInternal->getDocument('users', $membership->getAttribute('userId'));
$profile = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
if ($profile->isEmpty()) {
throw new Exception('User not found', 404);
}
@@ -572,7 +572,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
// Update the roles
$membership->setAttribute('roles', $roles);
$membership = $dbForInternal->updateDocument('memberships', $membership->getId(), $membership);
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
// TODO sync updated membership in the user $profile object using TYPE_REPLACE
@@ -604,20 +604,20 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('request')
->inject('response')
->inject('user')
->inject('dbForInternal')
->inject('dbForProject')
->inject('geodb')
->inject('audits')
->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $dbForInternal, $geodb, $audits) {
/** @var Utopia\Swoole\Request $request */
->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $dbForProject, $geodb, $audits) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $user */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Event\Event $audits */
$protocol = $request->getProtocol();
$membership = $dbForInternal->getDocument('memberships', $membershipId);
$membership = $dbForProject->getDocument('memberships', $membershipId);
if ($membership->isEmpty()) {
throw new Exception('Membership not found', 404);
@@ -627,7 +627,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception('Team IDs don\'t match', 404);
}
$team = Authorization::skip(fn() => $dbForInternal->getDocument('teams', $teamId));
$team = Authorization::skip(fn() => $dbForProject->getDocument('teams', $teamId));
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
@@ -642,7 +642,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
}
if ($user->isEmpty()) {
$user = $dbForInternal->getDocument('users', $userId); // Get user
$user = $dbForProject->getDocument('users', $userId); // Get user
}
if ($membership->getAttribute('userId') !== $user->getId()) {
@@ -668,7 +668,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$secret = Auth::tokenGenerator();
$session = new Document(array_merge([
'$id' => $dbForInternal->getId(),
'$id' => $dbForProject->getId(),
'userId' => $user->getId(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
@@ -679,7 +679,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
$session = $dbForInternal->createDocument('sessions', $session
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$read', ['user:'.$user->getId()])
->setAttribute('$write', ['user:'.$user->getId()])
);
@@ -688,10 +688,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Authorization::setRole('user:'.$userId);
$user = $dbForInternal->updateDocument('users', $user->getId(), $user);
$membership = $dbForInternal->updateDocument('memberships', $membership->getId(), $membership);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
$team = Authorization::skip(fn() => $dbForInternal->updateDocument('teams', $team->getId(), $team->setAttribute('sum', $team->getAttribute('sum', 0) + 1)));
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('sum', $team->getAttribute('sum', 0) + 1)));
$audits
->setParam('userId', $user->getId())
@@ -730,16 +730,16 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->inject('events')
->action(function ($teamId, $membershipId, $response, $dbForInternal, $audits, $events) {
->action(function ($teamId, $membershipId, $response, $dbForProject, $audits, $events) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $events */
$membership = $dbForInternal->getDocument('memberships', $membershipId);
$membership = $dbForProject->getDocument('memberships', $membershipId);
if ($membership->isEmpty()) {
throw new Exception('Invite not found', 404);
@@ -749,19 +749,19 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception('Team IDs don\'t match', 404);
}
$user = $dbForInternal->getDocument('users', $membership->getAttribute('userId'));
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
if ($user->isEmpty()) {
throw new Exception('User not found', 404);
}
$team = $dbForInternal->getDocument('teams', $teamId);
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception('Team not found', 404);
}
if (!$dbForInternal->deleteDocument('memberships', $membership->getId())) {
if (!$dbForProject->deleteDocument('memberships', $membership->getId())) {
throw new Exception('Failed to remove membership from DB', 500);
}
@@ -778,11 +778,11 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$user->setAttribute('memberships', $memberships);
Authorization::skip(fn() => $dbForInternal->updateDocument('users', $user->getId(), $user));
Authorization::skip(fn() => $dbForProject->updateDocument('users', $user->getId(), $user));
if ($membership->getAttribute('confirm')) { // Count only confirmed members
$team->setAttribute('sum', \max($team->getAttribute('sum', 0) - 1, 0));
$team = $dbForInternal->updateDocument('teams', $team->getId(), $team);
$team = $dbForProject->updateDocument('teams', $team->getId(), $team);
}
$audits
+83 -83
View File
@@ -39,18 +39,18 @@ App::post('/v1/users')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($userId, $email, $password, $name, $response, $dbForInternal, $usage) {
->action(function ($userId, $email, $password, $name, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$email = \strtolower($email);
try {
$userId = $userId == 'unique()' ? $dbForInternal->getId() : $userId;
$user = $dbForInternal->createDocument('users', new Document([
$userId = $userId == 'unique()' ? $dbForProject->getId() : $userId;
$user = $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$read' => ['role:all'],
'$write' => ['user:'.$userId],
@@ -62,7 +62,7 @@ App::post('/v1/users')
'registration' => \time(),
'reset' => false,
'name' => $name,
'prefs' => [],
'prefs' => new \stdClass(),
'sessions' => [],
'tokens' => [],
'memberships' => [],
@@ -99,15 +99,15 @@ App::get('/v1/users')
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal, $usage) {
->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
if (!empty($cursor)) {
$cursorUser = $dbForInternal->getDocument('users', $cursor);
$cursorUser = $dbForProject->getDocument('users', $cursor);
if ($cursorUser->isEmpty()) {
throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400);
@@ -127,8 +127,8 @@ App::get('/v1/users')
;
$response->dynamic(new Document([
'users' => $dbForInternal->find('users', $queries, $limit, $offset, [], [$orderType], $cursorUser ?? null, $cursorDirection),
'sum' => $dbForInternal->count('users', $queries, APP_LIMIT_COUNT),
'users' => $dbForProject->find('users', $queries, $limit, $offset, [], [$orderType], $cursorUser ?? null, $cursorDirection),
'sum' => $dbForProject->count('users', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_USER_LIST);
});
@@ -145,14 +145,14 @@ App::get('/v1/users/:userId')
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new UID(), 'User ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $usage) {
->action(function ($userId, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -177,14 +177,14 @@ App::get('/v1/users/:userId/prefs')
->label('sdk.response.model', Response::MODEL_PREFERENCES)
->param('userId', '', new UID(), 'User ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $usage) {
->action(function ($userId, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -211,16 +211,16 @@ App::get('/v1/users/:userId/sessions')
->label('sdk.response.model', Response::MODEL_SESSION_LIST)
->param('userId', '', new UID(), 'User ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $locale, $usage) {
->action(function ($userId, $response, $dbForProject, $locale, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -245,7 +245,7 @@ App::get('/v1/users/:userId/sessions')
'sessions' => $sessions,
'sum' => count($sessions),
]), Response::MODEL_SESSION_LIST);
}, ['response', 'dbForInternal', 'locale']);
});
App::get('/v1/users/:userId/logs')
->desc('Get User Logs')
@@ -262,25 +262,25 @@ App::get('/v1/users/:userId/logs')
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('usage')
->action(function ($userId, $limit, $offset, $response, $dbForInternal, $locale, $geodb, $usage) {
->action(function ($userId, $limit, $offset, $response, $dbForProject, $locale, $geodb, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Locale\Locale $locale */
/** @var MaxMind\Db\Reader $geodb */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
}
$audit = new Audit($dbForInternal);
$audit = new Audit($dbForProject);
$auditEvents = [
'account.create',
'account.delete',
@@ -367,20 +367,20 @@ App::patch('/v1/users/:userId/status')
->param('userId', '', new UID(), 'User ID.')
->param('status', null, new Boolean(true), 'User Status. To activate the user pass `true` and to block the user pass `false`.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($userId, $status, $response, $dbForInternal, $usage) {
->action(function ($userId, $status, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
}
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status));
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status));
$usage
->setParam('users.update', 1)
@@ -403,20 +403,20 @@ App::patch('/v1/users/:userId/verification')
->param('userId', '', new UID(), 'User ID.')
->param('emailVerification', false, new Boolean(), 'User email verification status.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($userId, $emailVerification, $response, $dbForInternal, $usage) {
->action(function ($userId, $emailVerification, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
}
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification));
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification));
$usage
->setParam('users.update', 1)
@@ -439,20 +439,20 @@ App::patch('/v1/users/:userId/name')
->param('userId', '', new UID(), 'User ID.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->action(function ($userId, $name, $response, $dbForInternal, $audits) {
->action(function ($userId, $name, $response, $dbForProject, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
}
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('name', $name));
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('name', $name));
$audits
->setParam('userId', $user->getId())
@@ -478,14 +478,14 @@ App::patch('/v1/users/:userId/password')
->param('userId', '', new UID(), 'User ID.')
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->action(function ($userId, $password, $response, $dbForInternal, $audits) {
->action(function ($userId, $password, $response, $dbForProject, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -495,7 +495,7 @@ App::patch('/v1/users/:userId/password')
->setAttribute('password', Auth::passwordHash($password))
->setAttribute('passwordUpdate', \time());
$user = $dbForInternal->updateDocument('users', $user->getId(), $user);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$audits
->setParam('userId', $user->getId())
@@ -521,14 +521,14 @@ App::patch('/v1/users/:userId/email')
->param('userId', '', new UID(), 'User ID.')
->param('email', '', new Email(), 'User email.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('audits')
->action(function ($userId, $email, $response, $dbForInternal, $audits) {
->action(function ($userId, $email, $response, $dbForProject, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $audits */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -542,7 +542,7 @@ App::patch('/v1/users/:userId/email')
$email = \strtolower($email);
try {
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('email', $email));
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('email', $email));
} catch(Duplicate $th) {
throw new Exception('Email already exists', 409);
}
@@ -571,20 +571,20 @@ App::patch('/v1/users/:userId/prefs')
->param('userId', '', new UID(), 'User ID.')
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('usage')
->action(function ($userId, $prefs, $response, $dbForInternal, $usage) {
->action(function ($userId, $prefs, $response, $dbForProject, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
}
$user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
$usage
->setParam('users.update', 1)
@@ -606,16 +606,16 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->param('userId', '', new UID(), 'User ID.')
->param('sessionId', null, new UID(), 'Session ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('events')
->inject('usage')
->action(function ($userId, $sessionId, $response, $dbForInternal, $events, $usage) {
->action(function ($userId, $sessionId, $response, $dbForProject, $events, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -628,7 +628,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
if ($sessionId == $session->getId()) {
unset($sessions[$key]);
$dbForInternal->deleteDocument('sessions', $session->getId());
$dbForProject->deleteDocument('sessions', $session->getId());
$user->setAttribute('sessions', $sessions);
@@ -636,7 +636,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->setParam('eventData', $response->output($user, Response::MODEL_USER))
;
$dbForInternal->updateDocument('users', $user->getId(), $user);
$dbForProject->updateDocument('users', $user->getId(), $user);
}
}
@@ -661,16 +661,16 @@ App::delete('/v1/users/:userId/sessions')
->label('sdk.response.model', Response::MODEL_NONE)
->param('userId', '', new UID(), 'User ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('events')
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $events, $usage) {
->action(function ($userId, $response, $dbForProject, $events, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -679,10 +679,10 @@ App::delete('/v1/users/:userId/sessions')
$sessions = $user->getAttribute('sessions', []);
foreach ($sessions as $key => $session) { /** @var Document $session */
$dbForInternal->deleteDocument('sessions', $session->getId());
$dbForProject->deleteDocument('sessions', $session->getId());
}
$dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
$dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', []));
$events
->setParam('eventData', $response->output($user, Response::MODEL_USER))
@@ -708,18 +708,18 @@ App::delete('/v1/users/:userId')
->label('sdk.response.model', Response::MODEL_NONE)
->param('userId', '', function () {return new UID();}, 'User ID.')
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('events')
->inject('deletes')
->inject('usage')
->action(function ($userId, $response, $dbForInternal, $events, $deletes, $usage) {
->action(function ($userId, $response, $dbForProject, $events, $deletes, $usage) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $deletes */
/** @var Appwrite\Stats\Stats $usage */
$user = $dbForInternal->getDocument('users', $userId);
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
throw new Exception('User not found', 404);
@@ -735,7 +735,7 @@ App::delete('/v1/users/:userId')
->setAttribute("deleted", true)
;
$dbForInternal->updateDocument('users', $userId, $user);
$dbForProject->updateDocument('users', $userId, $user);
$deletes
->setParam('type', DELETE_TYPE_DOCUMENT)
@@ -764,13 +764,13 @@ App::get('/v1/users/usage')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(function($value) { return "oauth-".$value; }, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn($value) => "oauth-".$value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
->inject('response')
->inject('dbForInternal')
->inject('dbForProject')
->inject('register')
->action(function ($range, $provider, $response, $dbForInternal) {
->action(function ($range, $provider, $response, $dbForProject) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
@@ -806,12 +806,12 @@ App::get('/v1/users/usage')
$stats = [];
Authorization::skip(function() use ($dbForInternal, $periods, $range, $metrics, &$stats) {
Authorization::skip(function() use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForInternal->find('stats', [
$requestDocs = $dbForProject->find('stats', [
new Query('period', Query::TYPE_EQUAL, [$period]),
new Query('metric', Query::TYPE_EQUAL, [$metric]),
], $limit, 0, ['time'], [Database::ORDER_DESC]);
+97 -19
View File
@@ -3,7 +3,9 @@
require_once __DIR__.'/../init.php';
use Utopia\App;
use Utopia\Swoole\Request;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\View;
use Utopia\Exception;
@@ -14,10 +16,12 @@ use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Response\Filters\V06;
use Appwrite\Utopia\Response\Filters\V07;
use Appwrite\Utopia\Response\Filters\V08;
use Appwrite\Utopia\Response\Filters\V11;
use Utopia\CLI\Console;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Appwrite\Utopia\Request\Filters\V12;
Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
@@ -25,7 +29,7 @@ Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
App::init(function ($utopia, $request, $response, $console, $project, $dbForConsole, $user, $locale, $clients) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $console */
/** @var Utopia\Database\Document $project */
@@ -34,6 +38,25 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
/** @var Utopia\Locale\Locale $locale */
/** @var array $clients */
/*
* Request format
*/
$route = $utopia->match($request);
Request::setRoute($route);
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($requestFormat) {
switch($requestFormat) {
case version_compare ($requestFormat , '0.12.0', '<') :
Request::setFilter(new V12());
break;
default:
Request::setFilter(null);
}
} else {
Request::setFilter(null);
}
$domain = $request->getHostname();
$domains = Config::getParam('domains', []);
if (!array_key_exists($domain, $domains)) {
@@ -47,20 +70,25 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
} else {
Authorization::disable();
$certificate = $dbForConsole->findOne('certificates', [
$domainDocument = $dbForConsole->findOne('domains', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
]);
if (empty($certificate)) {
$certificate = new Document([
if (!$domainDocument) {
$domainDocument = new Document([
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
]);
$certificate = $dbForConsole->createDocument('certificates', $certificate);
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in a few seconds...');
Resque::enqueue('v1-certificates', 'CertificatesV1', [
'document' => $certificate,
'document' => $domainDocument,
'domain' => $domain->get(),
'validateTarget' => false,
'validateCNAME' => false,
@@ -74,13 +102,10 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
Config::setParam('domains', $domains);
}
$localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
$locale->setDefault($localeParam);
};
$route = $utopia->match($request);
}
if ($project->isEmpty()) {
throw new Exception('Project not found', 404);
@@ -142,6 +167,8 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
break;
case version_compare ($responseFormat , '0.8.0', '<=') :
Response::setFilter(new V08());
case version_compare ($responseFormat , '0.11.0', '<=') :
Response::setFilter(new V11());
break;
default:
Response::setFilter(null);
@@ -278,7 +305,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons
}, ['utopia', 'request', 'response', 'console', 'project', 'dbForConsole', 'user', 'locale', 'clients']);
App::options(function ($request, $response) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
$origin = $request->getOrigin();
@@ -293,19 +320,72 @@ App::options(function ($request, $response) {
->noContent();
}, ['request', 'response']);
App::error(function ($error, $utopia, $request, $response, $layout, $project) {
App::error(function ($error, $utopia, $request, $response, $layout, $project, $logger, $loggerBreadcrumbs) {
/** @var Exception $error */
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Utopia\View $layout */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Logger\Logger $logger */
/** @var Utopia\Logger\Log\Breadcrumb[] $loggerBreadcrumbs */
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->match($request);
if($logger) {
if($error->getCode() >= 500 || $error->getCode() === 0) {
try {
$user = $utopia->getResource('user');
/** @var Appwrite\Database\Document $user */
} catch(\Throwable $th) {
// All good, user is optional information for logger
}
$log = new Utopia\Logger\Log();
if(isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', Authorization::$roles);
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: '.$responseCode);
}
}
if ($error instanceof PDOException) {
throw $error;
}
$route = $utopia->match($request);
$template = ($route) ? $route->getLabel('error', null) : null;
if (php_sapi_name() === 'cli') {
@@ -322,8 +402,6 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
Console::error('[Error] Line: '.$error->getLine());
}
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
switch ($error->getCode()) { // Don't show 500 errors!
case 400: // Error allowed publicly
case 401: // Error allowed publicly
@@ -390,7 +468,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) {
$response->dynamic(new Document($output),
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR);
}, ['error', 'utopia', 'request', 'response', 'layout', 'project']);
}, ['error', 'utopia', 'request', 'response', 'layout', 'project', 'logger', 'loggerBreadcrumbs']);
App::get('/manifest.json')
->desc('Progressive app manifest file')
+4 -4
View File
@@ -205,7 +205,7 @@ App::get('/v1/mock/tests/general/download')
->label('sdk.mock', true)
->inject('response')
->action(function ($response) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
$response
->setContentType('text/plain')
@@ -236,7 +236,7 @@ App::post('/v1/mock/tests/general/upload')
->inject('request')
->inject('response')
->action(function ($x, $y, $z, $file, $request, $response) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Swoole\Response $response */
$file = $request->getFiles('file');
@@ -373,7 +373,7 @@ App::get('/v1/mock/tests/general/get-cookie')
->label('sdk.mock', true)
->inject('request')
->action(function ($request) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
if ($request->getCookie('cookieName', '') !== 'cookieValue') {
throw new Exception('Missing cookie value', 400);
@@ -551,7 +551,7 @@ App::get('/v1/mock/tests/general/oauth2/failure')
App::shutdown(function($utopia, $response, $request) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
$result = [];
+39 -29
View File
@@ -11,9 +11,9 @@ use Utopia\Database\Document;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $usage, $deletes, $database, $dbForInternal, $mode) {
App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $usage, $deletes, $database, $dbForProject, $mode) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
@@ -24,7 +24,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
/** @var Appwrite\Event\Event $deletes */
/** @var Appwrite\Event\Event $database */
/** @var Appwrite\Event\Event $functions */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId()));
Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId()));
@@ -38,41 +38,51 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
/*
* Abuse Check
*/
$timeLimit = new TimeLimit($route->getLabel('abuse-key', 'url:{url},ip:{ip}'), $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForInternal);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname().$route->getPath())
;
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
// TODO make sure we get array here
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if(!empty($value)) {
$timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value);
}
foreach ($abuseKeyLabel as $abuseKey) {
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname().$route->getPath());
$timeLimitArray[] = $timeLimit;
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit()) {
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
;
}
$closestLimit = null;
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (($abuse->check() // Route is rate-limited
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if(!empty($value)) {
$timeLimit->setParam('{param-'.$key.'}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
$closestLimit = $timeLimit->remaining();
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
;
}
if (($abuse->check() // Route is rate-limited
&& App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') // Abuse is not disabled
&& (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key
{
throw new Exception('Too many requests', 429);
throw new Exception('Too many requests', 429);
}
}
/*
@@ -120,11 +130,11 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
$database
->setParam('projectId', $project->getId())
;
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'usage', 'deletes', 'database', 'dbForInternal', 'mode'], 'api');
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api');
App::init(function ($utopia, $request, $project) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Database\Document $project */
$route = $utopia->match($request);
@@ -177,7 +187,7 @@ App::init(function ($utopia, $request, $project) {
App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $database, $mode) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Appwrite\Event\Event $events */
+1 -1
View File
@@ -5,7 +5,7 @@ use Utopia\Config\Config;
App::init(function ($utopia, $request, $response, $layout) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Utopia\View $layout */
-226
View File
@@ -245,232 +245,6 @@ App::get('/error/:code')
->setParam('body', $page);
});
App::get('/specs/:format')
->groups(['web', 'home'])
->label('scope', 'public')
->label('docs', false)
->label('origin', '*')
->param('format', 'swagger2', new WhiteList(['swagger2', 'open-api3'], true), 'Spec format.', true)
->param('platform', APP_PLATFORM_CLIENT, new WhiteList([APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER, APP_PLATFORM_CONSOLE], true), 'Choose target platform.', true)
->param('tests', 0, function () {return new Range(0, 1);}, 'Include only test services.', true)
->inject('utopia')
->inject('request')
->inject('response')
->action(function ($format, $platform, $tests, $utopia, $request, $response) {
/** @var Utopia\App $utopia */
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
$platforms = [
'client' => APP_PLATFORM_CLIENT,
'server' => APP_PLATFORM_SERVER,
'console' => APP_PLATFORM_CONSOLE,
];
$authCounts = [
'client' => 1,
'server' => 2,
'console' => 1,
];
$routes = [];
$models = [];
$services = [];
$keys = [
APP_PLATFORM_CLIENT => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
],
APP_PLATFORM_SERVER => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
],
APP_PLATFORM_CONSOLE => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
'Mode' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Mode',
'description' => '',
'in' => 'header',
],
],
];
foreach ($utopia->getRoutes() as $key => $method) {
foreach ($method as $route) { /** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if(empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
if ($route->getLabel('sdk.mock', false) && !$tests) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $tests) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
continue;
}
$routes[] = $route;
$modelLabel = $route->getLabel('sdk.response.model', 'none');
$model = \is_array($modelLabel) ? \array_map(function($m) use($response) {
return $response->getModel($m);
}, $modelLabel) : $response->getModel($modelLabel);
}
}
foreach (Config::getParam('services', []) as $service) {
if(!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']) {
continue;
}
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
];
}
$models = $response->getModels();
foreach ($models as $key => $value) {
if($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
}
}
switch ($format) {
case 'swagger2':
$format = new Swagger2($utopia, $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
case 'open-api3':
$format = new OpenAPI3($utopia, $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
default:
throw new Exception('Format not found', 404);
break;
}
$specs = new Specification($format);
$format
->setParam('name', APP_NAME)
->setParam('description', 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)')
->setParam('endpoint', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/v1')
->setParam('version', APP_VERSION_STABLE)
->setParam('terms', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/policy/terms')
->setParam('support.email', App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM))
->setParam('support.url', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/support')
->setParam('contact.name', APP_NAME.' Team')
->setParam('contact.email', App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM))
->setParam('contact.url', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/support')
->setParam('license.name', 'BSD-3-Clause')
->setParam('license.url', 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE')
->setParam('docs.description', 'Full API docs, specs and tutorials')
->setParam('docs.url', App::getEnv('_APP_HOME', $request->getProtocol().'://'.$request->getHostname()).'/docs')
;
$response
->json($specs->parse());
});
App::get('/versions')
->desc('Get Version')
->groups(['web', 'home'])
Binary file not shown.
Binary file not shown.
+121 -42
View File
@@ -15,7 +15,9 @@ use Utopia\Audit\Audit;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\Database\Document;
use Utopia\Swoole\Files;
use Utopia\Swoole\Request;
use Appwrite\Utopia\Request;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
$http = new Server("0.0.0.0", App::getEnv('PORT', 80));
@@ -78,54 +80,70 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
$dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */
if(!$dbForConsole->exists()) {
Console::success('[Setup] - Server database init started...');
$collections = Config::getParam('collections', []); /** @var array $collections */
Console::success('[Setup] - Server database init started...');
$collections = Config::getParam('collections', []); /** @var array $collections */
if(!$dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'))) {
$redis->flushAll();
$dbForConsole->create();
Console::success('[Setup] - Creating database: appwrite...');
$dbForConsole->create(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
}
try {
Console::success('[Setup] - Creating metadata table: appwrite...');
$dbForConsole->createMetadata();
} catch (\Throwable $th) {
Console::success('[Setup] - Skip: metadata table already exists');
}
if($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForConsole);
$audit->setup();
}
if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
$adapter = new TimeLimit("", 0, 1, $dbForConsole);
$adapter->setup();
}
foreach ($collections as $key => $collection) {
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
$attributes = [];
$indexes = [];
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => $attribute['$id'],
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
]);
}
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
'$id' => $index['$id'],
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
$dbForConsole->createCollection($key, $attributes, $indexes);
foreach ($collections as $key => $collection) {
if(!$dbForConsole->getCollection($key)->isEmpty()) {
continue;
}
Console::success('[Setup] - Server database init completed...');
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
$attributes = [];
$indexes = [];
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => $attribute['$id'],
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
]);
}
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
'$id' => $index['$id'],
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
$dbForConsole->createCollection($key, $attributes, $indexes);
}
Console::success('[Setup] - Server database init completed...');
});
Console::success('Server started successfully (max payload is '.number_format($payloadSize).' bytes)');
@@ -170,6 +188,59 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$app->run($request, $response);
} catch (\Throwable $th) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$logger = $app->getResource("logger");
if($logger) {
try {
$user = $app->getResource('user');
/** @var Appwrite\Database\Document $user */
} catch(\Throwable $_th) {
// All good, user is optional information for logger
}
$loggerBreadcrumbs = $app->getResource("loggerBreadcrumbs");
$route = $app->match($request);
$log = new Utopia\Logger\Log();
if(isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($th->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($th));
$log->addTag('code', $th->getCode());
// $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $th->getFile());
$log->addExtra('line', $th->getLine());
$log->addExtra('trace', $th->getTraceAsString());
$log->addExtra('roles', Authorization::$roles);
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: '.$responseCode);
}
Console::error('[Error] Type: '.get_class($th));
Console::error('[Error] Message: '.$th->getMessage());
Console::error('[Error] File: '.$th->getFile());
@@ -184,12 +255,20 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$swooleResponse->setStatusCode(500);
if(App::isDevelopment()) {
$swooleResponse->end('error: '.$th->getMessage());
}
else {
$swooleResponse->end('500: Server Error');
}
$output = ((App::isDevelopment())) ? [
'message' => 'Error: '. $th->getMessage(),
'code' => 500,
'file' => $th->getFile(),
'line' => $th->getLine(),
'trace' => $th->getTrace(),
'version' => $version,
] : [
'message' => 'Error: Server Error',
'code' => 500,
'version' => $version,
];
$swooleResponse->end(\json_encode($output));
} finally {
/** @var PDOPool $dbPool */
$dbPool = $register->get('dbPool');
+62 -43
View File
@@ -30,6 +30,7 @@ use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Stats\Stats;
use Appwrite\Utopia\View;
use Utopia\App;
use Utopia\Logger\Logger;
use Utopia\Config\Config;
use Utopia\Locale\Locale;
use Utopia\Registry\Registry;
@@ -144,7 +145,7 @@ Config::load('locale-continents', __DIR__.'/config/locale/continents.php');
Config::load('storage-logos', __DIR__.'/config/storage/logos.php');
Config::load('storage-mimes', __DIR__.'/config/storage/mimes.php');
Config::load('storage-inputs', __DIR__.'/config/storage/inputs.php');
Config::load('storage-outputs', __DIR__.'/config/storage/outputs.php');
Config::load('storage-outputs', __DIR__.'/config/storage/outputs.php');
$user = App::getEnv('_APP_REDIS_USER','');
$pass = App::getEnv('_APP_REDIS_PASS','');
@@ -375,6 +376,22 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function($attribute) {
/*
* Registry
*/
$register->set('logger', function () { // Register error logger
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
if(empty($providerName) || empty($providerConfig)) {
return null;
}
if(!Logger::hasProvider($providerName)) {
throw new Exception("Logging provider not supported. Logging disabled.");
}
$classname = '\\Utopia\\Logger\\Adapter\\'.\ucfirst($providerName);
$adapter = new $classname($providerConfig);
return new Logger($adapter);
});
$register->set('dbPool', function () { // Register DB connection
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbPort = App::getEnv('_APP_DB_PORT', '');
@@ -392,7 +409,7 @@ $register->set('dbPool', function () { // Register DB connection
->withOptions([
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
])
, 16);
, 64);
return $pool;
});
@@ -412,7 +429,7 @@ $register->set('redisPool', function () {
->withPort($redisPort)
->withAuth($redisAuth)
->withDbIndex(0)
, 16);
, 64);
return $pool;
});
@@ -467,7 +484,7 @@ $register->set('smtp', function () {
return $mail;
});
$register->set('geodb', function () {
return new Reader(__DIR__.'/db/DBIP/dbip-country-lite-2021-10.mmdb');
return new Reader(__DIR__.'/db/DBIP/dbip-country-lite-2021-12.mmdb');
});
$register->set('db', function () { // This is usually for our workers or CLI commands scope
$dbHost = App::getEnv('_APP_DB_HOST', '');
@@ -581,6 +598,14 @@ Locale::setLanguageFromJSON('zh-tw', __DIR__.'/config/locale/translations/zh-tw.
]);
// Runtime Execution
App::setResource('logger', function($register) {
return $register->get('logger');
}, ['register']);
App::setResource('loggerBreadcrumbs', function() {
return [];
});
App::setResource('register', fn() => $register);
App::setResource('layout', function($locale) {
@@ -602,7 +627,7 @@ App::setResource('usage', function($register) {
return new Stats($register->get('statsd'));
}, ['register']);
App::setResource('clients', function($request, $console, $project) {
App::setResource('clients', function ($request, $console, $project) {
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => 'platforms',
'name' => 'Current Host',
@@ -614,34 +639,35 @@ App::setResource('clients', function($request, $console, $project) {
* Get All verified client URLs for both console and current projects
* + Filter for duplicated entries
*/
$clientsConsole = \array_map(function ($node) {
return $node['hostname'];
}, \array_filter($console->getAttribute('platforms', []), function ($node) {
if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) {
return true;
}
$clientsConsole = \array_map(
fn ($node) => $node['hostname'],
\array_filter(
$console->getAttribute('platforms', []),
fn ($node) => (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname']))
)
);
return false;
}));
$clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($node) {
return $node['hostname'];
}, \array_filter($project->getAttribute('platforms', []), function ($node) {
if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) {
return true;
}
return false;
}))));
$clients = \array_unique(
\array_merge(
$clientsConsole,
\array_map(
fn ($node) => $node['hostname'],
\array_filter(
$project->getAttribute('platforms', []),
fn ($node) => (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname']))
)
)
)
);
return $clients;
}, ['request', 'console', 'project']);
App::setResource('user', function($mode, $project, $console, $request, $response, $dbForInternal, $dbForConsole) {
/** @var Utopia\Swoole\Request $request */
App::setResource('user', function($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Database $dbForConsole */
/** @var string $mode */
@@ -675,7 +701,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response
$user = new Document(['$id' => '', '$collection' => 'users']);
}
else {
$user = $dbForInternal->getDocument('users', Auth::$unique);
$user = $dbForProject->getDocument('users', Auth::$unique);
}
}
else {
@@ -710,7 +736,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response
$jwtSessionId = $payload['sessionId'] ?? '';
if($jwtUserId && $jwtSessionId) {
$user = $dbForInternal->getDocument('users', $jwtUserId);
$user = $dbForProject->getDocument('users', $jwtUserId);
}
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
@@ -719,10 +745,10 @@ App::setResource('user', function($mode, $project, $console, $request, $response
}
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForInternal', 'dbForConsole']);
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
App::setResource('project', function($dbForConsole, $request, $console) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Document $console */
@@ -781,20 +807,12 @@ App::setResource('console', function() {
]);
}, []);
App::setResource('dbForInternal', function($db, $cache, $project) {
App::setResource('dbForProject', function($db, $cache, $project) {
$cache = new Cache(new RedisCache($cache));
$database = new Database(new MariaDB($db), $cache);
$database->setNamespace('project_'.$project->getId().'_internal');
return $database;
}, ['db', 'cache', 'project']);
App::setResource('dbForExternal', function($db, $cache, $project) {
$cache = new Cache(new RedisCache($cache));
$database = new Database(new MariaDB($db), $cache);
$database->setNamespace('project_'.$project->getId().'_external');
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace('_project_'.$project->getId());
return $database;
}, ['db', 'cache', 'project']);
@@ -803,13 +821,14 @@ App::setResource('dbForConsole', function($db, $cache) {
$cache = new Cache(new RedisCache($cache));
$database = new Database(new MariaDB($db), $cache);
$database->setNamespace('project_console_internal');
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace('_project_console');
return $database;
}, ['db', 'cache']);
App::setResource('mode', function($request) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Request $request */
/**
* Defines the mode for the request:
+63 -23
View File
@@ -14,6 +14,7 @@ use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Logger\Log;
use Utopia\Database\Database;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Cache;
@@ -22,7 +23,7 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
use Utopia\Swoole\Request;
use Appwrite\Utopia\Request;
use Utopia\WebSocket\Server;
use Utopia\WebSocket\Adapter;
@@ -51,6 +52,43 @@ $adapter->setPackageMaxLength(64000); // Default maximum Package Size (64kb)
$server = new Server($adapter);
$logError = function(Throwable $error, string $action) use ($register) {
$logger = $register->get('logger');
if($logger) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace("realtime");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', get_class($error));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
$responseCode = $logger->addLog($log);
Console::info('Realtime log pushed with status code: '.$responseCode);
}
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $error->getMessage());
Console::error('[Error] File: ' . $error->getFile());
Console::error('[Error] Line: ' . $error->getLine());
};
$server->error($logError);
function getDatabase(Registry &$register, string $namespace)
{
$db = $register->get('dbPool')->get();
@@ -58,6 +96,7 @@ function getDatabase(Registry &$register, string $namespace)
$cache = new Cache(new RedisCache($redis));
$database = new Database(new MariaDB($db), $cache);
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace($namespace);
return [
@@ -69,15 +108,16 @@ function getDatabase(Registry &$register, string $namespace)
];
};
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument) {
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
sleep(5); // wait for the initial database schema to be ready
Console::success('Server started succefully');
/**
* Create document for this worker to share stats across Containers.
*/
go(function () use ($register, $containerId, &$statsDocument) {
go(function () use ($register, $containerId, &$statsDocument, $logError) {
try {
[$database, $returnDatabase] = getDatabase($register, 'project_console_internal');
[$database, $returnDatabase] = getDatabase($register, '_project_console');
$document = new Document([
'$id' => $database->getId(),
'$collection' => 'realtime',
@@ -89,10 +129,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
]);
$statsDocument = Authorization::skip(fn() => $database->createDocument('realtime', $document));
} catch (\Throwable $th) {
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
call_user_func($logError, $th, "createWorkerDocument");
} finally {
call_user_func($returnDatabase);
}
@@ -101,7 +138,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
/**
* Save current connections to the Database every 5 seconds.
*/
Timer::tick(5000, function () use ($register, $stats, $containerId, &$statsDocument) {
Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
/** @var Document $statsDocument */
foreach ($stats as $projectId => $value) {
$connections = $stats->get($projectId, 'connections') ?? 0;
@@ -133,7 +170,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
}
try {
[$database, $returnDatabase] = getDatabase($register, 'project_console_internal');
[$database, $returnDatabase] = getDatabase($register, '_project_console');
$statsDocument
->setAttribute('timestamp', time())
@@ -141,29 +178,26 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
Authorization::skip(fn() => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (\Throwable $th) {
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
call_user_func($logError, $th, "updateWorkerDocument");
} finally {
call_user_func($returnDatabase);
}
});
});
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime) {
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) {
Console::success('Worker ' . $workerId . ' started succefully');
$attempts = 0;
$start = time();
Timer::tick(5000, function () use ($server, $register, $realtime, $stats) {
Timer::tick(5000, function () use ($server, $register, $realtime, $stats, $logError) {
/**
* Sending current connections to project channels on the console project every 5 seconds.
*/
if ($realtime->hasSubscriber('console', 'role:member', 'project')) {
[$database, $returnDatabase] = getDatabase($register, 'project_console_internal');
[$database, $returnDatabase] = getDatabase($register, '_project_console');
$payload = [];
@@ -267,7 +301,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
return;
}
[$database, $returnDatabase] = getDatabase($register, 'project_' . $projectId . '_internal');
[$database, $returnDatabase] = getDatabase($register, '_project_' . $projectId);
$user = $database->getDocument('users', $userId);
@@ -299,6 +333,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
}
});
} catch (\Throwable $th) {
call_user_func($logError, $th, "pubSubConnection");
Console::error('Pub/sub error: ' . $th->getMessage());
$register->get('redisPool')->put($redis);
$attempts++;
@@ -311,7 +347,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
Console::error('Failed to restart pub/sub...');
});
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime) {
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) {
$app = new App('UTC');
$request = new Request($request);
$response = new Response(new SwooleResponse());
@@ -340,7 +376,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$cache = new Cache(new RedisCache($redis));
$database = new Database(new MariaDB($db), $cache);
$database->setNamespace('project_' . $project->getId() . '_internal');
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace('_project_' . $project->getId());
/*
* Project Check
@@ -361,7 +398,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$abuse = new Abuse($timeLimit);
if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
throw new Exception('Too many requests', 1013);
}
@@ -407,6 +444,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$stats->incr($project->getId(), 'connections');
$stats->incr($project->getId(), 'connectionsTotal');
} catch (\Throwable $th) {
call_user_func($logError, $th, "initServer");
$response = [
'type' => 'error',
'data' => [
@@ -444,7 +483,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
$cache = new Cache(new RedisCache($redis));
$database = new Database(new MariaDB($db), $cache);
$database->setNamespace('project_' . $realtime->connections[$connection]['projectId'] . '_internal');
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace('_project_' . $realtime->connections[$connection]['projectId']);
/*
* Abuse Check
@@ -470,7 +510,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
}
switch ($message['type']) {
/**
/**
* This type is used to authenticate.
*/
case 'authentication':
+11
View File
@@ -3,6 +3,7 @@
global $cli;
use Appwrite\ClamAV\Network;
use Utopia\Logger\Logger;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\App;
@@ -82,6 +83,16 @@ $cli
Console::log('🟢 HTTPS force option is enabled');
}
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
if(empty($providerName) || empty($providerConfig) || !Logger::hasProvider($providerName)) {
Console::log('🔴 Logging adapter is disabled');
} else {
Console::log('🟢 Logging adapter is enabled (' . $providerName . ')');
}
\sleep(0.2);
try {
-2
View File
@@ -2,8 +2,6 @@
global $cli;
require_once __DIR__.'/../init.php';
use Appwrite\Event\Event;
use Utopia\App;
use Utopia\CLI\Console;
+50 -46
View File
@@ -30,38 +30,38 @@ $cli
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
$message = ($git) ? Console::confirm('Please enter your commit message:') : '';
if(!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x'])) {
if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', 'latest'])) {
throw new Exception('Unknown version given');
}
foreach($platforms as $key => $platform) {
foreach($platform['languages'] as $language) {
if($selected !== $language['key'] && $selected !== '*') {
foreach ($platforms as $key => $platform) {
foreach ($platform['languages'] as $language) {
if ($selected !== $language['key'] && $selected !== '*') {
continue;
}
if(!$language['enabled']) {
Console::warning($language['name'].' for '.$platform['name'] . ' is disabled');
if (!$language['enabled']) {
Console::warning($language['name'] . ' for ' . $platform['name'] . ' is disabled');
continue;
}
Console::info('Fetching API Spec for '.$language['name'].' for '.$platform['name'] . ' (version: '.$version.')');
$spec = file_get_contents(__DIR__.'/../config/specs/'.$version.'.'.$language['family'].'.json');
Console::info('Fetching API Spec for ' . $language['name'] . ' for ' . $platform['name'] . ' (version: ' . $version . ')');
$spec = file_get_contents(__DIR__ . '/../config/specs/swagger2-' . $version . '-' . $language['family'] . '.json');
$cover = 'https://appwrite.io/images/github.png';
$result = \realpath(__DIR__.'/..').'/sdks/'.$key.'-'.$language['key'];
$resultExamples = \realpath(__DIR__.'/../..').'/docs/examples/'.$version.'/'.$key.'-'.$language['key'];
$target = \realpath(__DIR__.'/..').'/sdks/git/'.$language['key'].'/';
$readme = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/README.md');
$result = \realpath(__DIR__ . '/..') . '/sdks/' . $key . '-' . $language['key'];
$resultExamples = \realpath(__DIR__ . '/../..') . '/docs/examples/' . $version . '/' . $key . '-' . $language['key'];
$target = \realpath(__DIR__ . '/..') . '/sdks/git/' . $language['key'] . '/';
$readme = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/README.md');
$readme = ($readme) ? \file_get_contents($readme) : '';
$gettingStarted = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/GETTING_STARTED.md');
$gettingStarted = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/GETTING_STARTED.md');
$gettingStarted = ($gettingStarted) ? \file_get_contents($gettingStarted) : '';
$examples = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/EXAMPLES.md');
$examples = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/EXAMPLES.md');
$examples = ($examples) ? \file_get_contents($examples) : '';
$changelog = \realpath(__DIR__ . '/../../docs/sdks/'.$language['key'].'/CHANGELOG.md');
$changelog = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/CHANGELOG.md');
$changelog = ($changelog) ? \file_get_contents($changelog) : '# Change Log';
$warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check [previous releases]('.$language['url'].'/releases).**';
$warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check [previous releases](' . $language['url'] . '/releases).**';
$license = 'BSD-3-Clause';
$licenseContent = 'Copyright (c) ' . date('Y') . ' Appwrite (https://appwrite.io) and individual contributors.
All rights reserved.
@@ -105,7 +105,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$config = new Node();
$config->setNPMPackage('node-appwrite');
$config->setBowerPackage('appwrite');
$warning = $warning."\n\n > This is the Node.js SDK for integrating with Appwrite from your Node.js server-side code.
$warning = $warning . "\n\n > This is the Node.js SDK for integrating with Appwrite from your Node.js server-side code.
If you're looking to integrate from the browser, you should check [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web)";
break;
case 'deno':
@@ -115,7 +115,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$config = new Python();
$config->setPipPackage('appwrite');
$license = 'BSD License'; // license edited due to classifiers in pypi
break;
break;
case 'ruby':
$config = new Ruby();
$config->setGemPackage('appwrite');
@@ -131,14 +131,14 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
case 'dart':
$config = new Dart();
$config->setPackageName('dart_appwrite');
$warning = $warning."\n\n > This is the Dart SDK for integrating with Appwrite from your Dart server-side code. If you're looking for the Flutter SDK you should check [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter)";
$warning = $warning . "\n\n > This is the Dart SDK for integrating with Appwrite from your Dart server-side code. If you're looking for the Flutter SDK you should check [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter)";
break;
case 'go':
$config = new Go();
break;
case 'swift':
$config = new Swift();
$warning = $warning."\n\n > This is the Swift SDK for integrating with Appwrite from your Swift server-side code. If you're looking for the Apple SDK you should check [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)";
$warning = $warning . "\n\n > This is the Swift SDK for integrating with Appwrite from your Swift server-side code. If you're looking for the Apple SDK you should check [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)";
break;
case 'apple':
$config = new SwiftClient();
@@ -152,10 +152,10 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
break;
case 'kotlin':
$config = new Kotlin();
$warning = $warning."\n\n > This is the Kotlin SDK for integrating with Appwrite from your Kotlin server-side code. If you're looking for the Android SDK you should check [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android)";
$warning = $warning . "\n\n > This is the Kotlin SDK for integrating with Appwrite from your Kotlin server-side code. If you're looking for the Android SDK you should check [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android)";
break;
default:
throw new Exception('Language "'.$language['key'].'" not supported');
throw new Exception('Language "' . $language['key'] . '" not supported');
break;
}
@@ -189,10 +189,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
->setTwitter(APP_SOCIAL_TWITTER_HANDLE)
->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD)
->setDefaultHeaders([
'X-Appwrite-Response-Format' => '0.11.0',
])
;
'X-Appwrite-Response-Format' => '0.12.0',
]);
try {
$sdk->generate($result);
} catch (Exception $exception) {
@@ -204,38 +203,43 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$gitUrl = $language['gitUrl'];
$gitBranch = $language['gitBranch'];
if(!$production) {
$gitUrl = 'git@github.com:aw-tests/'.$language['gitRepoName'].'.git';
if (!$production) {
$gitUrl = 'git@github.com:aw-tests/' . $language['gitRepoName'] . '.git';
}
if($git && !empty($gitUrl)) {
\exec('rm -rf '.$target.' && \
mkdir -p '.$target.' && \
cd '.$target.' && \
git init --initial-branch='.$gitBranch.' && \
git remote add origin '.$gitUrl.' && \
if ($git && !empty($gitUrl)) {
\exec('rm -rf ' . $target . ' && \
mkdir -p ' . $target . ' && \
cd ' . $target . ' && \
git init --initial-branch=' . $gitBranch . ' && \
git remote add origin ' . $gitUrl . ' && \
git fetch && \
git pull '.$gitUrl.' && \
rm -rf '.$target.'/* && \
cp -r '.$result.'/ '.$target.'/ && \
git pull ' . $gitUrl . ' && \
rm -rf ' . $target . '/* && \
cp -r ' . $result . '/* ' . $target . '/ && \
git add . && \
git commit -m "'.$message.'" && \
git push -u origin '.$gitBranch.'
git commit -m "' . $message . '" && \
git push -u origin ' . $gitBranch . '
');
Console::success("Pushed {$language['name']} SDK to {$gitUrl}");
\exec('rm -rf '.$target);
\exec('rm -rf ' . $target);
Console::success("Remove temp directory '{$target}' for {$language['name']} SDK");
}
$docDirectories = $language['docDirectories'] ?? [''];
if ($version === 'latest') {
continue;
}
foreach ($docDirectories as $languageTitle => $path) {
$languagePath = strtolower($languageTitle !== 0 ? '/'.$languageTitle : '');
$languagePath = strtolower($languageTitle !== 0 ? '/' . $languageTitle : '');
\exec(
'mkdir -p '.$resultExamples.$languagePath.' && \
cp -r '.$result.'/docs/examples'.$languagePath.' '.$resultExamples
'mkdir -p ' . $resultExamples . $languagePath . ' && \
cp -r ' . $result . '/docs/examples' . $languagePath . ' ' . $resultExamples
);
Console::success("Copied code examples for {$language['name']} SDK to: {$resultExamples}");
}
+267
View File
@@ -0,0 +1,267 @@
<?php
global $cli;
use Utopia\Validator\Text;
use Appwrite\Specification\Format\OpenAPI3;
use Appwrite\Specification\Format\Swagger2;
use Appwrite\Specification\Specification;
use Appwrite\Utopia\Response;
use Swoole\Http\Response as HttpResponse;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Request;
use Utopia\Validator\WhiteList;
$cli
->task('specs')
->param('version', 'latest', new Text(8), 'Spec version', true)
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
->action(function ($version, $mode) use ($register) {
$db = $register->get('db');
$redis = $register->get('cache');
$appRoutes = App::getRoutes();
$response = new Response(new HttpResponse());
$mocks = ($mode === 'mocks');
App::setResource('request', fn () => new Request);
App::setResource('response', fn () => $response);
App::setResource('db', fn () => $db);
App::setResource('cache', fn () => $redis);
$platforms = [
'client' => APP_PLATFORM_CLIENT,
'server' => APP_PLATFORM_SERVER,
'console' => APP_PLATFORM_CONSOLE,
];
$authCounts = [
'client' => 1,
'server' => 2,
'console' => 1,
];
$keys = [
APP_PLATFORM_CLIENT => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
],
APP_PLATFORM_SERVER => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
],
APP_PLATFORM_CONSOLE => [
'Project' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Project',
'description' => 'Your project ID',
'in' => 'header',
],
'Key' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Key',
'description' => 'Your secret API key',
'in' => 'header',
],
'JWT' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-JWT',
'description' => 'Your secret JSON Web Token',
'in' => 'header',
],
'Locale' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Locale',
'description' => '',
'in' => 'header',
],
'Mode' => [
'type' => 'apiKey',
'name' => 'X-Appwrite-Mode',
'description' => '',
'in' => 'header',
],
],
];
foreach (['swagger2', 'open-api3'] as $format) {
foreach ($platforms as $platform) {
$routes = [];
$models = [];
$services = [];
foreach ($appRoutes as $key => $method) {
foreach ($method as $route) {
/** @var \Utopia\Route $route */
$routeSecurity = $route->getLabel('sdk.auth', []);
$sdkPlatofrms = [];
foreach ($routeSecurity as $value) {
switch ($value) {
case APP_AUTH_TYPE_SESSION:
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
$sdkPlatofrms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
$sdkPlatofrms[] = APP_PLATFORM_CONSOLE;
break;
}
}
if (empty($routeSecurity)) {
$sdkPlatofrms[] = APP_PLATFORM_CLIENT;
}
if (!$route->getLabel('docs', true)) {
continue;
}
if ($route->getLabel('sdk.mock', false) && !$mocks) {
continue;
}
if (!$route->getLabel('sdk.mock', false) && $mocks) {
continue;
}
if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatofrms)) {
continue;
}
$routes[] = $route;
$modelLabel = $route->getLabel('sdk.response.model', 'none');
\is_array($modelLabel) ? \array_map(function ($m) use ($response) {
return $response->getModel($m);
}, $modelLabel) : $response->getModel($modelLabel);
}
}
foreach (Config::getParam('services', []) as $service) {
if (
!isset($service['docs']) // Skip service if not part of the public API
|| !isset($service['sdk'])
|| !$service['docs']
|| !$service['sdk']
) {
continue;
}
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
];
}
$models = $response->getModels();
foreach ($models as $key => $value) {
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
unset($models[$key]);
}
}
switch ($format) {
case 'swagger2':
$formatInstance = new Swagger2(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
case 'open-api3':
$formatInstance = new OpenAPI3(new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0);
break;
default:
throw new Exception('Format not found: ' . $format);
break;
}
$specs = new Specification($formatInstance);
$endpoint = App::getEnv('_APP_HOME', '[HOSTNAME]');
$email = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$formatInstance
->setParam('name', APP_NAME)
->setParam('description', 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)')
->setParam('endpoint', 'https://HOSTNAME/v1')
->setParam('version', APP_VERSION_STABLE)
->setParam('terms', $endpoint . '/policy/terms')
->setParam('support.email', $email)
->setParam('support.url', $endpoint . '/support')
->setParam('contact.name', APP_NAME . ' Team')
->setParam('contact.email', $email)
->setParam('contact.url', $endpoint . '/support')
->setParam('license.name', 'BSD-3-Clause')
->setParam('license.url', 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE')
->setParam('docs.description', 'Full API docs, specs and tutorials')
->setParam('docs.url', $endpoint . '/docs');
if ($mocks) {
$path = __DIR__ . '/../config/specs/' . $format . '-mocks-' . $platform . '.json';
if (!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save mocks spec file: ' . $path);
}
Console::success('Saved mocks spec file: ' . realpath($path));
continue;
}
$path = __DIR__ . '/../config/specs/' . $format . '-' . $version . '-' . $platform . '.json';
if (!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save spec file: ' . $path);
}
Console::success('Saved spec file: ' . realpath($path));
}
}
});
+11 -14
View File
@@ -2,8 +2,6 @@
global $cli, $register;
require_once __DIR__ . '/../init.php';
use Utopia\App;
use Utopia\Cache\Adapter\Redis;
use Utopia\Cache\Cache;
@@ -223,7 +221,9 @@ $cli
$cacheAdapter = new Cache(new Redis($redis));
$dbForProject = new Database(new MariaDB($db), $cacheAdapter);
$dbForConsole = new Database(new MariaDB($db), $cacheAdapter);
$dbForConsole->setNamespace('project_console_internal');
$dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$dbForConsole->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$dbForConsole->setNamespace('_project_console');
$latestTime = [];
@@ -276,9 +276,7 @@ $cli
$filters = $options['filters'] ?? []; // Some metrics might have additional filters, like function's status
if (!empty($filters)) {
$filters = ' AND ' . implode(' AND ', array_map(function ($filter, $value) {
return "\"{$filter}\"='{$value}'";
}, array_keys($filters), array_values($filters)));
$filters = ' AND ' . implode(' AND ', array_map(fn ($filter, $value) => "\"{$filter}\"='{$value}'", array_keys($filters), array_values($filters)));
} else {
$filters = '';
}
@@ -291,7 +289,7 @@ $cli
$projectId = $point['projectId'];
if (!empty($projectId) && $projectId !== 'console') {
$dbForProject->setNamespace('project_' . $projectId . '_internal');
$dbForProject->setNamespace('_project_' . $projectId);
$metricUpdated = $metric;
if (!empty($groupBy)) {
@@ -371,7 +369,7 @@ $cli
$projectId = $project->getId();
// Get total storage
$dbForProject->setNamespace('project_' . $projectId . '_internal');
$dbForProject->setNamespace('_project_' . $projectId);
$storageTotal = $dbForProject->sum('files', 'sizeOriginal') + $dbForProject->sum('tags', 'size');
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
@@ -435,9 +433,8 @@ $cli
foreach ($collections as $collection => $options) {
try {
$dbForProject->setNamespace("project_{$projectId}_{$options['namespace']}");
$dbForProject->setNamespace("_project_{$projectId}");
$count = $dbForProject->count($collection);
$dbForProject->setNamespace("project_{$projectId}_internal");
$metricPrefix = $options['metricPrefix'] ?? '';
$metric = empty($metricPrefix) ? "{$collection}.count" : "{$metricPrefix}.{$collection}.count";
@@ -491,7 +488,7 @@ $cli
$subCollectionCounts = []; //total project level count of sub collections
do { // Loop over all the parent collection document for each sub collection
$dbForProject->setNamespace("project_{$projectId}_{$options['namespace']}");
$dbForProject->setNamespace("_project_{$projectId}");
$parents = $dbForProject->find($collection, [], 100, cursor: $latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections
if (empty($parents)) {
@@ -502,12 +499,12 @@ $cli
foreach ($parents as $parent) {
foreach ($subCollections as $subCollection => $subOptions) { // Sub collection counts, like database.collections.collectionId.documents.count
$dbForProject->setNamespace("project_{$projectId}_{$subOptions['namespace']}");
$dbForProject->setNamespace("_project_{$projectId}");
$count = $dbForProject->count($parent->getId());
$subCollectionCounts[$subCollection] = ($subCollectionCounts[$subCollection] ?? 0) + $count; // Project level counts for sub collections like database.documents.count
$dbForProject->setNamespace("project_{$projectId}_internal");
$dbForProject->setNamespace("_project_{$projectId}");
$metric = empty($metricPrefix) ? "{$collection}.{$parent->getId()}.{$subCollection}.count" : "{$metricPrefix}.{$collection}.{$parent->getId()}.{$subCollection}.count";
$time = (int) (floor(time() / 1800) * 1800); // Time rounded to nearest 30 minutes
@@ -557,7 +554,7 @@ $cli
* Inserting project level counts for sub collections like database.documents.count
*/
foreach ($subCollectionCounts as $subCollection => $count) {
$dbForProject->setNamespace("project_{$projectId}_internal");
$dbForProject->setNamespace("_project_{$projectId}");
$metric = empty($metricPrefix) ? "{$subCollection}.count" : "{$metricPrefix}.{$subCollection}.count";
+1 -1
View File
@@ -211,7 +211,7 @@
required
maxlength="36"
class=""
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_.-]{1,36}$"
name="projectId" />
<label>Name</label>
+291 -376
View File
@@ -47,7 +47,7 @@ $logs = $this->getParam('logs', null);
data-param-order-types-cast-to="array"
data-scope="sdk"
data-name="project-documents"
x-data="{project: new URLSearchParams(location.search).get('project')}">
x-data="{ project: new URLSearchParams(location.search).get('project') }">
<div data-ls-if="0 == {{project-documents.sum}}" class="box margin-bottom">
<h3 class="margin-bottom-small text-bold">No Documents Found</h3>
@@ -101,7 +101,6 @@ $logs = $this->getParam('logs', null);
data-service="database.listDocuments"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-types="DESC"
data-param-order-types-cast-to="array"
@@ -118,7 +117,6 @@ $logs = $this->getParam('logs', null);
data-service="database.listDocuments"
data-event="submit"
data-param-collection-id="{{router.params.id}}"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-types="DESC"
data-param-order-types-cast-to="array"
@@ -155,10 +153,8 @@ $logs = $this->getParam('logs', null);
<th width="100"></th>
<th width="130">Attribute ID</th>
<th width="100">Type</th>
<th>Length</th>
<th>Default</th>
<th></th>
<th width="80"></th>
<th width="32"></th>
</tr>
</thead>
<tbody data-ls-loop="project-collection.attributes" data-ls-as="attribute">
@@ -186,52 +182,116 @@ $logs = $this->getParam('logs', null);
<span class="text-size-small" data-ls-if="{{attribute.format}}" data-ls-bind="({{attribute.format}})"></span>
</td>
<td data-title="Length:">
<span class="text-size-small" data-ls-if="{{attribute.size}}" data-ls-bind="{{attribute.size}}"></span>
</td>
<td data-title="Default:">
<span class="text-size-small" data-ls-bind="{{attribute.default}}" data-ls-attr="title={{attribute.default}}"></span>
<span class="text-fade text-size-small" data-ls-if="{{attribute.default}} != ''">n/a</span>
</td>
<td data-title="">
<span class="text-size-small text-danger" data-ls-if="{{attribute.required}}">required</span>
</td>
<td data-title="">
<form
data-ls-if="{{attribute.status}} !== 'deleting'"
class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Collection Attribute"
data-service="database.deleteAttribute"
data-scope="sdk"
data-event="submit"
data-confirm="Are you sure you want to delete this attribute?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted attribute successfully"
data-success-param-trigger-events="database.deleteAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to delete attribute"
data-failure-param-alert-classname="error">
<div data-ui-modal class="box modal sticky-footer width-large close" data-button-class="icon-dot-3 link" data-button-element="i">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="key" data-ls-bind="{{attribute.key}}" />
<h1>
<i data-ls-if="!{{attribute.format}}" data-ls-attrs="class=icon-{{attribute.type}}"></i>
<i data-ls-if="{{attribute.format}}" data-ls-attrs="class=icon-{{attribute.format}}"></i>
<span data-ls-bind="{{attribute.key}}"></span>
</h1>
<button class="danger small">Delete</button>
</form>
<hr />
<div class="row responsive modalize">
<form>
<div class="col span-12">
<label>Key</label>
<div class="input-copy">
<input type="text" data-ls-bind="{{attribute.key}}" data-forms-copy disabled />
</div>
<label>Type</label>
<input type="text" data-ls-bind="{{attribute.type}}" disabled />
<div data-ls-if="{{attribute.format}}">
<label>Format</label>
<input type="text" data-ls-bind="{{attribute.format}}" class="full-width" disabled />
</div>
<div class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label>Required</label>
<input type="text" data-ls-bind="{{attribute.required}}" disabled />
</div>
<div class="col span-6 margin-bottom-small">
<label>Array</label>
<input type="text" data-ls-bind="{{attribute.array}}" disabled />
</div>
</div>
<div data-ls-if="{{attribute.default}}">
<label>Default Value</label>
<input type="text" data-ls-bind="{{attribute.default}}" class="full-width" disabled />
</div>
<div data-ls-if="{{attribute.size}}">
<label for="size">Length</label>
<input type="text" data-ls-bind="{{attribute.size}}" class="full-width" disabled />
</div>
<div data-ls-if="{{attribute.format}} == 'enum'">
<label>Elements</label>
<span data-ls-loop="attribute.elements" data-ls-as="element">
<input type="text" data-ls-bind="{{element}}" class="full-width" disabled />
</span>
</div>
<div data-ls-if="{{attribute.min}} && {{attribute.max}}" class="row responsive thin">
<div class="col span-6 margin-bottom-small">
<label>Min</label>
<input type="text" name="min" data-ls-bind="{{attribute.min}}" class="full-width" disabled />
</div>
<div class="col span-6 margin-bottom-small">
<label>Max</label>
<input type="text" name="max" data-ls-bind="{{attribute.max}}" class="full-width" disabled />
</div>
</div>
</div>
</form>
</div>
<footer>
<button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
<form
data-ls-if="{{attribute.status}} !== 'deleting'"
class="pull-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Collection Attribute"
data-service="database.deleteAttribute"
data-scope="sdk"
data-event="submit"
data-confirm="Are you sure you want to delete this attribute?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted attribute successfully"
data-success-param-trigger-events="database.deleteAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to delete attribute"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="key" data-ls-bind="{{attribute.key}}" />
<button class="danger">Delete</button>
</form>
</footer>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="drop-list pull-start" data-ls-ui-open="" data-button-aria="Choose Platform" data-button-text="Add Attribute" data-button-class="button" data-blur="1">
<div class="drop-list pull-start" data-ls-ui-open="" data-button-aria="Choose Attribute" data-button-text="Add Attribute" data-button-class="button" data-blur="1">
<ul>
<li>
<div class="link new-attribute-string"><i class="avatar icon-string"></i> New String Attribute</div>
@@ -293,6 +353,7 @@ $logs = $this->getParam('logs', null);
<span data-ls-if="{{index.status}} == 'processing'" class="text-size-small text-info">processing&nbsp;</span>
<span data-ls-if="{{index.status}} == 'failed'" class="text-size-small text-danger">failed&nbsp;</span>
<span data-ls-if="{{index.status}} == 'deleting'" class="text-size-small text-danger">deleting&nbsp;</span>
<span data-ls-if="{{index.status}} == 'stuck'" class="text-size-small text-danger">stuck&nbsp;</span>
</td>
<td data-title="Index Key: ">
@@ -543,76 +604,54 @@ $logs = $this->getParam('logs', null);
<h1>Add String Attribute</h1>
<form
id="add-string-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (string)"
data-service="database.createStringAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-string-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (string)"
data-service="database.createStringAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="string-key">Attribute ID</label>
<input id="string-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="string-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<label for="string-length">Size</label>
<input id="string-length" name="size" type="number" class="margin-bottom" autocomplete="off" required value="255" data-cast-to="integer" />
<div class="margin-bottom">
<input type="hidden" name="required" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input type="hidden" name="array" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<div>
<label for="xdefault">Default Value</label>
<label for="xdefault">Default Value</label>
<template x-if="!(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
</div>
</template>
<template x-if="(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-string-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-integer">
@@ -620,36 +659,37 @@ $logs = $this->getParam('logs', null);
<h1>Add Integer Attribute</h1>
<form
id="add-integer-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (integer)"
data-service="database.createIntegerAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-integer-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (integer)"
data-service="database.createIntegerAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="integer-key">Attribute ID</label>
<input id="integer-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="integer-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input type="hidden" name="required" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input type="hidden" name="array" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<div class="row responsive thin">
@@ -665,36 +705,17 @@ $logs = $this->getParam('logs', null);
<label for="integer-default">Default Value</label>
<input id="integer-default" name="xdefault" type="number" step="1" class="margin-bottom-large" autocomplete="off" data-cast-to="integer">
<template x-if="!(array || required)">
<input name="xdefault" type="number" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="number" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-integer-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-float">
@@ -702,36 +723,37 @@ $logs = $this->getParam('logs', null);
<h1>Add Float Attribute</h1>
<form
id="add-float-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (float)"
data-service="database.createFloatAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-float-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (float)"
data-service="database.createFloatAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="float-key">Attribute ID</label>
<input id="float-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="float-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input type="hidden" name="required" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input type="hidden" name="array" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<div class="row responsive thin">
@@ -746,36 +768,18 @@ $logs = $this->getParam('logs', null);
</div>
<label for="float-default">Default Value</label>
<input id="float-default" name="xdefault" type="number" step="0.01" class="margin-bottom-large" autocomplete="off" data-cast-to="float">
<template x-if="!(array || required)">
<input name="xdefault" type="number" step="0.01" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="number" step="0.01" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-float-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-email">
@@ -783,69 +787,51 @@ $logs = $this->getParam('logs', null);
<h1>Add Email Attribute</h1>
<form
id="add-email-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (email)"
data-service="database.createEmailAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-email-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (email)"
data-service="database.createEmailAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="email-key">Attribute ID</label>
<input id="email-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="128" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="email-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="128" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="string-default">Default Value</label>
<input id="string-default" name="xdefault" type="email" class="margin-bottom-large" autocomplete="off">
<template x-if="!(array || required)">
<input name="xdefault" type="email" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="email" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-email-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-boolean">
@@ -853,71 +839,54 @@ $logs = $this->getParam('logs', null);
<h1>Add Boolean Attribute</h1>
<form
id="add-boolean-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (boolean)"
data-service="database.createBooleanAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-boolean-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (boolean)"
data-service="database.createBooleanAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="boolean-key">Attribute ID</label>
<input id="boolean-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="boolean-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom-large">
<input name="xdefault" type="hidden" data-forms-switch data-cast-to="boolean" autocomplete="off" /> &nbsp; Default Value <span class="tooltip" data-tooltip="Whether this attribute is set to true or false on creation"><i class="icon-info-circled"></i></span>
<template x-if="!(array || required)">
<input name="xdefault" class="button switch" type="checkbox" />
</template>
<template x-if="(array || required)">
<input name="xdefault" class="button switch" type="checkbox" disabled />
</template>
&nbsp; Default Value <span class="tooltip" data-tooltip="Whether this attribute is set to true or false on creation"><i class="icon-info-circled"></i></span>
</div>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-boolean-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["uncheck", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
@@ -926,69 +895,51 @@ $logs = $this->getParam('logs', null);
<h1>Add IP Attribute</h1>
<form
id="add-ip-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (IP)"
data-service="database.createIpAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
<form
id="add-ip-attribute"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Collection Attribute (IP)"
data-service="database.createIpAttribute"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created new attribute successfully"
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="ip-key">Attribute ID</label>
<input id="ip-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="ip-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="string-default">Default Value</label>
<input id="string-default" name="xdefault" type="text" data-forms-ip-address class="margin-bottom-large" autocomplete="off">
<template x-if="!(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-ip-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-url">
@@ -1011,54 +962,36 @@ $logs = $this->getParam('logs', null);
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="url-key">Attribute ID</label>
<input id="url-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="url-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="string-default">Default Value</label>
<input id="string-default" name="xdefault" type="url" title="Valid URL address" class="margin-bottom-large" autocomplete="off">
<template x-if="!(array || required)">
<input name="xdefault" type="url" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="url" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-url-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-attribute-enum">
@@ -1081,14 +1014,15 @@ $logs = $this->getParam('logs', null);
data-success-param-trigger-events="database.createAttribute"
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error">
data-failure-param-alert-classname="error"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="enum-key">Attribute ID</label>
<input id="enum-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="enum-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<label>Elements</label>
@@ -1108,44 +1042,25 @@ $logs = $this->getParam('logs', null);
</div>
<div class="margin-bottom">
<input name="required" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
<input x-model="required" name="required" class="button switch" type="checkbox" /> &nbsp; Required <span class="tooltip" data-tooltip="Mark whether this is a required attribute"><i class="icon-info-circled"></i></span>
</div>
<div class="margin-bottom">
<input name="array" type="hidden" data-forms-switch data-cast-to="boolean" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
<input x-model="array" name="array" class="button switch" type="checkbox" /> &nbsp; Array <span class="tooltip" data-tooltip="Mark whether this attribute should act as an array"><i class="icon-info-circled"></i></span>
</div>
<label for="enunm-default">Default Value</label>
<input id="enum-default" name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
<template x-if="!(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off">
</template>
<template x-if="(array || required)">
<input name="xdefault" type="text" class="margin-bottom-large" autocomplete="off" disabled value="">
</template>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
<script type="text/javascript">
(() => {
const form = document.getElementById("add-enum-attribute");
const fields = {
xdefault: {
oneOf: {
if: ["array", "required"],
then: {
xdefault: ["unvalue", "disable"]
},
else: {
required: ["enable"],
array: ["enable"],
xdefault: ["enable"]
}
}
}
};
form.addEventListener("change", event => {
formValidation(form, fields);
});
})();
</script>
</div>
<div data-ui-modal class="modal box close sticky-footer" data-button-alias=".new-index">
@@ -1184,8 +1099,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="index-key">Index Key</label>
<input id="index-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<input id="index-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore, hyphen and dot</div>
<label for="index-type">Type</label>
<select id="index-type" name="type">
@@ -1234,4 +1149,4 @@ $logs = $this->getParam('logs', null);
</script>
<script type="text/html" id="template-attribute-body-first">
<span class="text-fade" data-ls-bind="{{node.$id}}" data-general-copy data-class="icon-docs note copy text-fade pull-end"></span>
</script>
</script>
+2 -2
View File
@@ -83,12 +83,12 @@ $logs = $this->getParam('logs', null);
name="documentId"
id="documentId"
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$" />
<?php endif; ?>
<fieldset name="data" data-cast-to="object" data-ls-attrs="x-init=doc = {{project-document}}" x-data="{doc: {}}">
<ul data-ls-attrs="x-init=attributes = {{project-collection.attributes}}" x-data="{attributes: []}">
<template x-for="attr in attributes">
<template x-for="attr in attributes.filter(a => a.status === 'available')">
<li>
<label>
<div x-text="attr.key" class="margin-bottom-tiny"></div>
+1 -1
View File
@@ -102,7 +102,7 @@
data-validator="database.getCollection"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
name="collectionId" />
<label for="collection-name">Name</label>
+1 -1
View File
@@ -113,7 +113,7 @@ $runtimes = $this->getParam('runtimes', []);
data-validator="functions.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
name="functionId" />
<label for="name">Name</label>
+1 -1
View File
@@ -245,7 +245,7 @@ $fileLimitHuman = $this->getParam('fileLimitHuman', 0);
data-validator="storage.getFile"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
name="fileId"
id="fileId" />
+2 -2
View File
@@ -163,7 +163,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
data-validator="users.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
id="userId"
name="userId" />
@@ -311,7 +311,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
data-validator="teams.get"
required
maxlength="36"
pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$"
pattern="^[a-zA-Z0-9][a-zA-Z0-9._-]{1,36}$"
id="teamId"
name="teamId" />
+4 -1
View File
@@ -106,6 +106,8 @@ services:
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_RUNTIMES
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STATSD_HOST
- _APP_STATSD_PORT
- _APP_MAINTENANCE_INTERVAL
@@ -136,6 +138,7 @@ services:
networks:
- appwrite
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
@@ -380,7 +383,7 @@ services:
- _APP_REDIS_PASS
mariadb:
image: appwrite/mariadb:1.2.0 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
restart: unless-stopped
networks:
+6 -2
View File
@@ -11,6 +11,10 @@ Console::success(APP_NAME . ' audits worker v1 has started');
class AuditsV1 extends Worker
{
public function getName(): string {
return "audits";
}
public function init(): void
{
}
@@ -28,8 +32,8 @@ class AuditsV1 extends Worker
$ip = $this->args['ip'];
$data = $this->args['data'];
$dbForInternal = $this->getInternalDB($projectId);
$audit = new Audit($dbForInternal);
$dbForProject = $this->getProjectDB($projectId);
$audit = new Audit($dbForProject);
$audit->log($userId, $event, $resource, $userAgent, $ip, '', [
'userName' => $userName,
+5 -1
View File
@@ -16,6 +16,10 @@ Console::success(APP_NAME . ' certificates worker v1 has started');
class CertificatesV1 extends Worker
{
public function getName(): string {
return "certificates";
}
public function init(): void
{
}
@@ -160,7 +164,7 @@ class CertificatesV1 extends Worker
'certificateId' => $certificate->getId(),
]));
$certificate = $dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate);
$certificate = $dbForConsole->updateDocument('domains', $certificate->getId(), $certificate);
if(!$certificate) {
throw new Exception('Failed saving domain to DB');
+23 -26
View File
@@ -69,8 +69,7 @@ class DatabaseV1 extends Worker
protected function createAttribute(Document $collection, Document $attribute, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
$event = 'database.attributes.update';
$collectionId = $collection->getId();
@@ -87,13 +86,13 @@ class DatabaseV1 extends Worker
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if(!$dbForExternal->createAttribute($collectionId, $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
if(!$dbForProject->createAttribute('collection_' . $collectionId, $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
throw new Exception('Failed to create Attribute');
}
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available'));
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available'));
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed'));
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed'));
} finally {
$target = Realtime::fromPayload($event, $attribute, $project);
@@ -110,7 +109,7 @@ class DatabaseV1 extends Worker
);
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedDocument('collections', $collectionId);
}
/**
@@ -121,8 +120,7 @@ class DatabaseV1 extends Worker
protected function deleteAttribute(Document $collection, Document $attribute, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
$event = 'database.attributes.delete';
$collectionId = $collection->getId();
@@ -137,13 +135,13 @@ class DatabaseV1 extends Worker
// - failed: attribute was never created
// - stuck: attribute was available but cannot be removed
try {
if($status !== 'failed' && !$dbForExternal->deleteAttribute($collectionId, $key)) {
if($status !== 'failed' && !$dbForProject->deleteAttribute('collection_' . $collectionId, $key)) {
throw new Exception('Failed to delete Attribute');
}
$dbForInternal->deleteDocument('attributes', $attribute->getId());
$dbForProject->deleteDocument('attributes', $attribute->getId());
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'stuck'));
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'stuck'));
} finally {
$target = Realtime::fromPayload($event, $attribute, $project);
@@ -182,7 +180,7 @@ class DatabaseV1 extends Worker
$orders = \array_values(\array_diff($orders, [$orders[$found]]));
if (empty($attributes)) {
$dbForInternal->deleteDocument('indexes', $index->getId());
$dbForProject->deleteDocument('indexes', $index->getId());
} else {
$index
->setAttribute('attributes', $attributes, Document::SET_TYPE_ASSIGN)
@@ -205,13 +203,14 @@ class DatabaseV1 extends Worker
if ($exists) { // Delete the duplicate if created, else update in db
$this->deleteIndex($collection, $index, $projectId);
} else {
$dbForInternal->updateDocument('indexes', $index->getId(), $index);
$dbForProject->updateDocument('indexes', $index->getId(), $index);
}
}
}
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedCollection('collection_' . $collectionId);
}
/**
@@ -222,8 +221,7 @@ class DatabaseV1 extends Worker
protected function createIndex(Document $collection, Document $index, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
$event = 'database.indexes.update';
$collectionId = $collection->getId();
@@ -235,13 +233,13 @@ class DatabaseV1 extends Worker
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if(!$dbForExternal->createIndex($collectionId, $key, $type, $attributes, $lengths, $orders)) {
if(!$dbForProject->createIndex('collection_' . $collectionId, $key, $type, $attributes, $lengths, $orders)) {
throw new Exception('Failed to create Index');
}
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available'));
$dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available'));
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed'));
$dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed'));
} finally {
$target = Realtime::fromPayload($event, $index, $project);
@@ -258,7 +256,7 @@ class DatabaseV1 extends Worker
);
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedDocument('collections', $collectionId);
}
/**
@@ -269,8 +267,7 @@ class DatabaseV1 extends Worker
protected function deleteIndex(Document $collection, Document $index, string $projectId): void
{
$dbForConsole = $this->getConsoleDB();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
$collectionId = $collection->getId();
$key = $index->getAttribute('key');
@@ -279,13 +276,13 @@ class DatabaseV1 extends Worker
$project = $dbForConsole->getDocument('projects', $projectId);
try {
if($status !== 'failed' && !$dbForExternal->deleteIndex($collectionId, $key)) {
if($status !== 'failed' && !$dbForProject->deleteIndex('collection_' . $collectionId, $key)) {
throw new Exception('Failed to delete index');
}
$dbForInternal->deleteDocument('indexes', $index->getId());
$dbForProject->deleteDocument('indexes', $index->getId());
} catch (\Throwable $th) {
Console::error($th->getMessage());
$dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'stuck'));
$dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'stuck'));
} finally {
$target = Realtime::fromPayload($event, $index, $project);
@@ -302,6 +299,6 @@ class DatabaseV1 extends Worker
);
}
$dbForInternal->deleteCachedDocument('collections', $collectionId);
$dbForProject->deleteCachedDocument('collections', $collectionId);
}
}
+52 -26
View File
@@ -23,6 +23,10 @@ class DeletesV1 extends Worker
*/
protected $consoleDB = null;
public function getName(): string {
return "deletes";
}
public function init(): void
{
}
@@ -63,7 +67,17 @@ class DeletesV1 extends Worker
break;
case DELETE_TYPE_AUDIT:
$this->deleteAuditLogs($this->args['timestamp']);
$timestamp = $this->args['timestamp'] ?? 0;
$document = new Document($this->args['document'] ?? []);
if (!empty($timestamp)) {
$this->deleteAuditLogs($this->args['timestamp']);
}
if (!$document->isEmpty()) {
$this->deleteAuditLogsByResource('document/' . $document->getId(), $projectId);
}
break;
case DELETE_TYPE_ABUSE:
@@ -100,18 +114,19 @@ class DeletesV1 extends Worker
{
$collectionId = $document->getId();
$dbForInternal = $this->getInternalDB($projectId);
$dbForExternal = $this->getExternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
$dbForExternal->deleteCollection($collectionId);
$dbForProject->deleteCollection('collection_' . $collectionId);
$this->deleteByGroup('attributes', [
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
], $dbForInternal);
], $dbForProject);
$this->deleteByGroup('indexes', [
new Query('collectionId', Query::TYPE_EQUAL, [$collectionId])
], $dbForInternal);
], $dbForProject);
$this->deleteAuditLogsByResource('collection/' . $collectionId, $projectId);
}
/**
@@ -121,17 +136,17 @@ class DeletesV1 extends Worker
protected function deleteUsageStats(int $timestamp1d, int $timestamp30m)
{
$this->deleteForProjectIds(function (string $projectId) use ($timestamp1d, $timestamp30m) {
$dbForInternal = $this->getInternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
// Delete Usage stats
$this->deleteByGroup('stats', [
new Query('time', Query::TYPE_LESSER, [$timestamp1d]),
new Query('period', Query::TYPE_EQUAL, ['1d']),
], $dbForInternal);
], $dbForProject);
$this->deleteByGroup('stats', [
new Query('time', Query::TYPE_LESSER, [$timestamp30m]),
new Query('period', Query::TYPE_EQUAL, ['30m']),
], $dbForInternal);
], $dbForProject);
});
}
@@ -146,7 +161,7 @@ class DeletesV1 extends Worker
// Delete Memberships
$this->deleteByGroup('memberships', [
new Query('teamId', Query::TYPE_EQUAL, [$teamId])
], $this->getInternalDB($projectId));
], $this->getProjectDB($projectId));
}
/**
@@ -157,8 +172,7 @@ class DeletesV1 extends Worker
$projectId = $document->getId();
// Delete all DBs
$this->getExternalDB($projectId)->delete();
$this->getInternalDB($projectId)->delete();
$this->getProjectDB($projectId)->delete($projectId);
// Delete all storage directories
$uploads = new Local(APP_STORAGE_UPLOADS . '/app-' . $document->getId());
@@ -180,13 +194,13 @@ class DeletesV1 extends Worker
// Delete Memberships and decrement team membership counts
$this->deleteByGroup('memberships', [
new Query('userId', Query::TYPE_EQUAL, [$userId])
], $this->getInternalDB($projectId), function (Document $document) use ($projectId) {
], $this->getProjectDB($projectId), function (Document $document) use ($projectId) {
if ($document->getAttribute('confirm')) { // Count only confirmed members
$teamId = $document->getAttribute('teamId');
$team = $this->getInternalDB($projectId)->getDocument('teams', $teamId);
$team = $this->getProjectDB($projectId)->getDocument('teams', $teamId);
if (!$team->isEmpty()) {
$team = $this->getInternalDB($projectId)->updateDocument('teams', $teamId, new Document(\array_merge($team->getArrayCopy(), [
$team = $this->getProjectDB($projectId)->updateDocument('teams', $teamId, new Document(\array_merge($team->getArrayCopy(), [
'sum' => \max($team->getAttribute('sum', 0) - 1, 0), // Ensure that sum >= 0
])));
}
@@ -200,11 +214,11 @@ class DeletesV1 extends Worker
protected function deleteExecutionLogs(int $timestamp): void
{
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
$dbForInternal = $this->getInternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
// Delete Executions
$this->deleteByGroup('executions', [
new Query('dateCreated', Query::TYPE_LESSER, [$timestamp])
], $dbForInternal);
], $dbForProject);
});
}
@@ -214,11 +228,11 @@ class DeletesV1 extends Worker
protected function deleteRealtimeUsage(int $timestamp): void
{
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
$dbForInternal = $this->getInternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
// Delete Dead Realtime Logs
$this->deleteByGroup('realtime', [
new Query('timestamp', Query::TYPE_LESSER, [$timestamp])
], $dbForInternal);
], $dbForProject);
});
}
@@ -232,8 +246,8 @@ class DeletesV1 extends Worker
}
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
$dbForInternal = $this->getInternalDB($projectId);
$timeLimit = new TimeLimit("", 0, 1, $dbForInternal);
$dbForProject = $this->getProjectDB($projectId);
$timeLimit = new TimeLimit("", 0, 1, $dbForProject);
$abuse = new Abuse($timeLimit);
$status = $abuse->cleanup($timestamp);
@@ -252,8 +266,8 @@ class DeletesV1 extends Worker
throw new Exception('Failed to delete audit logs. No timestamp provided');
}
$this->deleteForProjectIds(function (string $projectId) use ($timestamp) {
$dbForInternal = $this->getInternalDB($projectId);
$audit = new Audit($dbForInternal);
$dbForProject = $this->getProjectDB($projectId);
$audit = new Audit($dbForProject);
$status = $audit->cleanup($timestamp);
if (!$status) {
throw new Exception('Failed to delete Audit logs for project' . $projectId);
@@ -261,19 +275,31 @@ class DeletesV1 extends Worker
});
}
/**
* @param int $timestamp
*/
protected function deleteAuditLogsByResource(string $resource, string $projectId): void
{
$dbForProject = $this->getProjectDB($projectId);
$this->deleteByGroup(Audit::COLLECTION, [
new Query('resource', Query::TYPE_EQUAL, [$resource])
], $dbForProject);
}
/**
* @param Document $document function document
* @param string $projectId
*/
protected function deleteFunction(Document $document, string $projectId): void
{
$dbForInternal = $this->getInternalDB($projectId);
$dbForProject = $this->getProjectDB($projectId);
$device = new Local(APP_STORAGE_FUNCTIONS . '/app-' . $projectId);
// Delete Tags
$this->deleteByGroup('tags', [
new Query('functionId', Query::TYPE_EQUAL, [$document->getId()])
], $dbForInternal, function (Document $document) use ($device) {
], $dbForProject, function (Document $document) use ($device) {
if ($device->delete($document->getAttribute('path', ''))) {
Console::success('Delete code tag: ' . $document->getAttribute('path', ''));
@@ -285,7 +311,7 @@ class DeletesV1 extends Worker
// Delete Executions
$this->deleteByGroup('executions', [
new Query('functionId', Query::TYPE_EQUAL, [$document->getId()])
], $dbForInternal);
], $dbForProject);
}
+5 -1
View File
@@ -100,6 +100,10 @@ class FunctionsV1 extends Worker
public array $allowed = [];
public function getName(): string {
return "functions";
}
public function init(): void
{
}
@@ -118,7 +122,7 @@ class FunctionsV1 extends Worker
$userId = $this->args['userId'] ?? '';
$jwt = $this->args['jwt'] ?? '';
$database = $this->getInternalDB($projectId);
$database = $this->getProjectDB($projectId);
switch ($trigger) {
case 'event':
+4
View File
@@ -13,6 +13,10 @@ Console::success(APP_NAME . ' mails worker v1 has started' . "\n");
class MailsV1 extends Worker
{
public function getName(): string {
return "mails";
}
public function init(): void
{
}
+4
View File
@@ -11,6 +11,10 @@ Console::success(APP_NAME . ' webhooks worker v1 has started');
class WebhooksV1 extends Worker
{
public function getName(): string {
return "webhooks";
}
public function init(): void
{
}
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php specs $@
+7 -7
View File
@@ -38,14 +38,15 @@
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.6.*",
"utopia-php/framework": "0.19.*",
"utopia-php/abuse": "0.6.*",
"utopia-php/framework": "0.19.5",
"utopia-php/logger": "0.1.*",
"utopia-php/abuse": "0.7.*",
"utopia-php/analytics": "0.2.*",
"utopia-php/audit": "0.7.*",
"utopia-php/audit": "0.8.*",
"utopia-php/cache": "0.4.*",
"utopia-php/cli": "0.11.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.12.*",
"utopia-php/database": "0.13.*",
"utopia-php/locale": "0.4.*",
"utopia-php/orchestration": "0.2.*",
"utopia-php/registry": "0.5.*",
@@ -53,7 +54,7 @@
"utopia-php/domains": "1.1.*",
"utopia-php/swoole": "0.3.*",
"utopia-php/storage": "0.5.*",
"utopia-php/websocket": "0.0.*",
"utopia-php/websocket": "0.1.0",
"utopia-php/image": "0.5.*",
"resque/php-resque": "1.3.6",
@@ -65,9 +66,8 @@
"adhocore/jwt": "1.1.2",
"slickdeals/statsd": "3.1.0"
},
"repositories": [],
"require-dev": {
"appwrite/sdk-generator": "0.16.3",
"appwrite/sdk-generator": "0.17.0",
"phpunit/phpunit": "9.5.10",
"swoole/ide-helper": "4.8.3",
"textalk/websocket": "1.5.5",
Generated
+218 -77
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c755b0ae991777da3e44b0442690fa46",
"content-hash": "d32b727a743b3a8811ec64c56f308694",
"packages": [
{
"name": "adhocore/jwt",
@@ -115,16 +115,16 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.6.1",
"version": "0.6.2",
"source": {
"type": "git",
"url": "https://github.com/appwrite/php-runtimes.git",
"reference": "a42434de2fbd60818244c1a9b2ac0429ad0ef9ee"
"reference": "33e087933f4353b1b3aad8d00e32ef82af18a292"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/php-runtimes/zipball/a42434de2fbd60818244c1a9b2ac0429ad0ef9ee",
"reference": "a42434de2fbd60818244c1a9b2ac0429ad0ef9ee",
"url": "https://api.github.com/repos/appwrite/php-runtimes/zipball/33e087933f4353b1b3aad8d00e32ef82af18a292",
"reference": "33e087933f4353b1b3aad8d00e32ef82af18a292",
"shasum": ""
},
"require": {
@@ -164,9 +164,9 @@
],
"support": {
"issues": "https://github.com/appwrite/php-runtimes/issues",
"source": "https://github.com/appwrite/php-runtimes/tree/0.6.1"
"source": "https://github.com/appwrite/php-runtimes/tree/0.6.2"
},
"time": "2021-10-21T11:32:25+00:00"
"time": "2021-12-31T07:16:48+00:00"
},
{
"name": "chillerlan/php-qrcode",
@@ -1820,22 +1820,22 @@
},
{
"name": "utopia-php/abuse",
"version": "0.6.3",
"version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
"reference": "d63e928c2c50b367495a499a85ba9806ee274c5e"
"reference": "52fb20e39e2e9619948bc0a73b52e10caa71350d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/d63e928c2c50b367495a499a85ba9806ee274c5e",
"reference": "d63e928c2c50b367495a499a85ba9806ee274c5e",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/52fb20e39e2e9619948bc0a73b52e10caa71350d",
"reference": "52fb20e39e2e9619948bc0a73b52e10caa71350d",
"shasum": ""
},
"require": {
"ext-pdo": "*",
"php": ">=7.4",
"utopia-php/database": ">=0.6 <1.0"
"php": ">=8.0",
"utopia-php/database": ">=0.11 <1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.4",
@@ -1867,9 +1867,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
"source": "https://github.com/utopia-php/abuse/tree/0.6.3"
"source": "https://github.com/utopia-php/abuse/tree/0.7.0"
},
"time": "2021-08-16T18:38:31+00:00"
"time": "2021-12-27T13:06:45+00:00"
},
{
"name": "utopia-php/analytics",
@@ -1928,21 +1928,21 @@
},
{
"name": "utopia-php/audit",
"version": "0.7.0",
"version": "0.8.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "485cdd2354db7eb8f7aa74bbe39c39b583e99c04"
"reference": "b46dc42614a69437c45eb229249b6a6d000122c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/485cdd2354db7eb8f7aa74bbe39c39b583e99c04",
"reference": "485cdd2354db7eb8f7aa74bbe39c39b583e99c04",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/b46dc42614a69437c45eb229249b6a6d000122c1",
"reference": "b46dc42614a69437c45eb229249b6a6d000122c1",
"shasum": ""
},
"require": {
"ext-pdo": "*",
"php": ">=7.4",
"php": ">=8.0",
"utopia-php/database": ">=0.11 <1.0"
},
"require-dev": {
@@ -1975,9 +1975,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/0.7.0"
"source": "https://github.com/utopia-php/audit/tree/0.8.0"
},
"time": "2021-11-17T17:23:42+00:00"
"time": "2021-12-27T13:05:56+00:00"
},
{
"name": "utopia-php/cache",
@@ -2138,16 +2138,16 @@
},
{
"name": "utopia-php/database",
"version": "0.12.1",
"version": "0.13.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "af512b7a00cc7c6e30fa03efbc5fd7e77a93e2df"
"reference": "bf92279b707b3a10ee5ec5df5c065023b2221357"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/af512b7a00cc7c6e30fa03efbc5fd7e77a93e2df",
"reference": "af512b7a00cc7c6e30fa03efbc5fd7e77a93e2df",
"url": "https://api.github.com/repos/utopia-php/database/zipball/bf92279b707b3a10ee5ec5df5c065023b2221357",
"reference": "bf92279b707b3a10ee5ec5df5c065023b2221357",
"shasum": ""
},
"require": {
@@ -2195,9 +2195,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.12.1"
"source": "https://github.com/utopia-php/database/tree/0.13.2"
},
"time": "2021-12-13T14:57:32+00:00"
"time": "2022-01-04T10:51:22+00:00"
},
{
"name": "utopia-php/domains",
@@ -2255,17 +2255,11 @@
},
{
"name": "utopia-php/framework",
"version": "0.19.3",
"version": "dev-fix-get-args-after-init",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "4c6c841d738cec458b73fec5aedd40fd43bd41a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/4c6c841d738cec458b73fec5aedd40fd43bd41a7",
"reference": "4c6c841d738cec458b73fec5aedd40fd43bd41a7",
"shasum": ""
"url": "https://github.com/utopia-php/framework",
"reference": "36a42dce039f043288673f0ff46284353543624d"
},
"require": {
"php": ">=8.0.0"
@@ -2280,7 +2274,6 @@
"Utopia\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@@ -2296,11 +2289,7 @@
"php",
"upf"
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.19.3"
},
"time": "2021-12-17T13:04:13+00:00"
"time": "2022-01-03T08:38:34+00:00"
},
{
"name": "utopia-php/image",
@@ -2405,6 +2394,72 @@
},
"time": "2021-07-24T11:35:55+00:00"
},
{
"name": "utopia-php/logger",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/logger.git",
"reference": "a7d626e349e8736e46d4d75f5ba686b40e73c097"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/logger/zipball/a7d626e349e8736e46d4d75f5ba686b40e73c097",
"reference": "a7d626e349e8736e46d4d75f5ba686b40e73c097",
"shasum": ""
},
"require": {
"php": ">=8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"vimeo/psalm": "4.0.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\Logger\\": "src/Logger"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Eldad Fux",
"email": "eldad@appwrite.io"
},
{
"name": "Matej Bačo",
"email": "matej@appwrite.io"
},
{
"name": "Christy Jacob",
"email": "christy@appwrite.io"
}
],
"description": "Utopia Logger library is simple and lite library for logging information, such as errors or warnings. This library is aiming to be as simple and easy to learn and use.",
"keywords": [
"appsignal",
"errors",
"framework",
"logger",
"logging",
"logs",
"php",
"raygun",
"sentry",
"upf",
"utopia",
"warnings"
],
"support": {
"issues": "https://github.com/utopia-php/logger/issues",
"source": "https://github.com/utopia-php/logger/tree/0.1.0"
},
"time": "2021-12-20T06:57:26+00:00"
},
{
"name": "utopia-php/orchestration",
"version": "0.2.1",
@@ -2730,16 +2785,16 @@
},
{
"name": "utopia-php/websocket",
"version": "0.0.1",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/websocket.git",
"reference": "808317ef4ea0683c2c82dee5d543b1c8378e2e1b"
"reference": "51fcb86171400d8aa40d76c54593481fd273dab5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/websocket/zipball/808317ef4ea0683c2c82dee5d543b1c8378e2e1b",
"reference": "808317ef4ea0683c2c82dee5d543b1c8378e2e1b",
"url": "https://api.github.com/repos/utopia-php/websocket/zipball/51fcb86171400d8aa40d76c54593481fd273dab5",
"reference": "51fcb86171400d8aa40d76c54593481fd273dab5",
"shasum": ""
},
"require": {
@@ -2782,9 +2837,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/websocket/issues",
"source": "https://github.com/utopia-php/websocket/tree/0.0.1"
"source": "https://github.com/utopia-php/websocket/tree/0.1.0"
},
"time": "2021-07-11T13:09:44+00:00"
"time": "2021-12-20T10:50:09+00:00"
},
{
"name": "webmozart/assert",
@@ -3134,16 +3189,16 @@
},
{
"name": "composer/semver",
"version": "3.2.6",
"version": "3.2.7",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "83e511e247de329283478496f7a1e114c9517506"
"reference": "deac27056b57e46faf136fae7b449eeaa71661ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/83e511e247de329283478496f7a1e114c9517506",
"reference": "83e511e247de329283478496f7a1e114c9517506",
"url": "https://api.github.com/repos/composer/semver/zipball/deac27056b57e46faf136fae7b449eeaa71661ee",
"reference": "deac27056b57e46faf136fae7b449eeaa71661ee",
"shasum": ""
},
"require": {
@@ -3195,7 +3250,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.2.6"
"source": "https://github.com/composer/semver/tree/3.2.7"
},
"funding": [
{
@@ -3211,7 +3266,7 @@
"type": "tidelift"
}
],
"time": "2021-10-25T11:34:17+00:00"
"time": "2022-01-04T09:57:54+00:00"
},
{
"name": "composer/xdebug-handler",
@@ -5655,16 +5710,16 @@
},
{
"name": "symfony/console",
"version": "v6.0.1",
"version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "fafd9802d386bf1c267e0249ddb7ceb14dcfdad4"
"reference": "dd434fa8d69325e5d210f63070014d889511fcb3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/fafd9802d386bf1c267e0249ddb7ceb14dcfdad4",
"reference": "fafd9802d386bf1c267e0249ddb7ceb14dcfdad4",
"url": "https://api.github.com/repos/symfony/console/zipball/dd434fa8d69325e5d210f63070014d889511fcb3",
"reference": "dd434fa8d69325e5d210f63070014d889511fcb3",
"shasum": ""
},
"require": {
@@ -5730,7 +5785,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.0.1"
"source": "https://github.com/symfony/console/tree/v6.0.2"
},
"funding": [
{
@@ -5746,7 +5801,7 @@
"type": "tidelift"
}
],
"time": "2021-12-09T12:47:37+00:00"
"time": "2021-12-27T21:05:08+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
@@ -5993,6 +6048,82 @@
],
"time": "2021-05-27T12:26:48+00:00"
},
{
"name": "symfony/polyfill-php72",
"version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "9a142215a36a3888e30d0a9eeea9766764e96976"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976",
"reference": "9a142215a36a3888e30d0a9eeea9766764e96976",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-05-27T09:17:38+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.0.0",
@@ -6077,16 +6208,16 @@
},
{
"name": "symfony/string",
"version": "v6.0.1",
"version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32"
"reference": "bae261d0c3ac38a1f802b4dfed42094296100631"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/0cfed595758ec6e0a25591bdc8ca733c1896af32",
"reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32",
"url": "https://api.github.com/repos/symfony/string/zipball/bae261d0c3ac38a1f802b4dfed42094296100631",
"reference": "bae261d0c3ac38a1f802b4dfed42094296100631",
"shasum": ""
},
"require": {
@@ -6142,7 +6273,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.0.1"
"source": "https://github.com/symfony/string/tree/v6.0.2"
},
"funding": [
{
@@ -6158,7 +6289,7 @@
"type": "tidelift"
}
],
"time": "2021-12-08T15:13:44+00:00"
"time": "2021-12-16T22:13:01+00:00"
},
{
"name": "textalk/websocket",
@@ -6261,22 +6392,23 @@
},
{
"name": "twig/twig",
"version": "v2.14.8",
"version": "v2.14.10",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "06b450a2326aa879faa2061ff72fe1588b3ab043"
"reference": "95fb194cd4dd6ac373a27af2bde2bad5d3f27aba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/06b450a2326aa879faa2061ff72fe1588b3ab043",
"reference": "06b450a2326aa879faa2061ff72fe1588b3ab043",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/95fb194cd4dd6ac373a27af2bde2bad5d3f27aba",
"reference": "95fb194cd4dd6ac373a27af2bde2bad5d3f27aba",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"php": ">=7.1.3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php72": "^1.8"
},
"require-dev": {
"psr/container": "^1.0",
@@ -6324,7 +6456,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v2.14.8"
"source": "https://github.com/twigphp/Twig/tree/v2.14.10"
},
"funding": [
{
@@ -6336,7 +6468,7 @@
"type": "tidelift"
}
],
"time": "2021-11-25T13:38:06+00:00"
"time": "2022-01-03T21:13:26+00:00"
},
{
"name": "vimeo/psalm",
@@ -6496,9 +6628,18 @@
"time": "2015-12-17T08:42:14+00:00"
}
],
"aliases": [],
"aliases": [
{
"package": "utopia-php/framework",
"version": "dev-fix-get-args-after-init",
"alias": "0.19",
"alias_normalized": "0.19.0.0"
}
],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {
"utopia-php/framework": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
+22 -1
View File
@@ -125,6 +125,8 @@ services:
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_RUNTIMES
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-realtime:
entrypoint: realtime
@@ -154,7 +156,9 @@ services:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
# - ./vendor:/usr/src/code/vendor
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
@@ -168,6 +172,8 @@ services:
- _APP_DB_USER
- _APP_DB_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-audits:
entrypoint: worker-audits
@@ -193,6 +199,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-webhooks:
entrypoint: worker-webhooks
@@ -215,6 +223,8 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-deletes:
entrypoint: worker-deletes
@@ -244,6 +254,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-database:
entrypoint: worker-database
@@ -270,6 +282,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-certificates:
entrypoint: worker-certificates
@@ -288,6 +302,7 @@ services:
- mariadb
environment:
- _APP_ENV
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -298,6 +313,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-functions:
entrypoint: worker-functions
@@ -338,6 +355,8 @@ services:
- _APP_STATSD_PORT
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-mails:
entrypoint: worker-mails
@@ -366,6 +385,8 @@ services:
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-maintenance:
entrypoint: maintenance
@@ -441,7 +462,7 @@ services:
- _APP_REDIS_PASS
mariadb:
image: appwrite/mariadb:1.2.0 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
networks:
- appwrite
@@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Account account = new Account(client);
account.createMagicURLSession(
"",
"[USER_ID]",
"email@example.com",
new Continuation<Object>() {
@NotNull
@@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Account account = new Account(client);
account.create(
"",
"[USER_ID]",
"email@example.com",
"password",
new Continuation<Object>() {
@@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Account account = new Account(client);
account.updateMagicURLSession(
"",
"[USER_ID]",
"[SECRET]"
new Continuation<Object>() {
@NotNull
@@ -20,7 +20,7 @@ public class MainActivity extends AppCompatActivity {
database.createDocument(
"[COLLECTION_ID]",
"",
"[DOCUMENT_ID]",
mapOf( "a" to "b" ),
new Continuation<Object>() {
@NotNull
@@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Storage storage = new Storage(client);
storage.createFile(
"",
"[FILE_ID]",
File("./path-to-files/image.jpg"),
new Continuation<Object>() {
@NotNull
@@ -19,7 +19,7 @@ public class MainActivity extends AppCompatActivity {
Teams teams = new Teams(client);
teams.create(
"",
"[TEAM_ID]",
"[NAME]",
new Continuation<Object>() {
@NotNull
@@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = account.createMagicURLSession(
userId = "",
userId = "[USER_ID]",
email = "email@example.com",
)
val json = response.body?.string()
@@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = account.create(
userId = "",
userId = "[USER_ID]",
email = "email@example.com",
password = "password",
)
@@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = account.updateMagicURLSession(
userId = "",
userId = "[USER_ID]",
secret = "[SECRET]"
)
val json = response.body?.string()
@@ -19,7 +19,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = database.createDocument(
collectionId = "[COLLECTION_ID]",
documentId = "",
documentId = "[DOCUMENT_ID]",
data = mapOf( "a" to "b" ),
)
val json = response.body?.string()
@@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = storage.createFile(
fileId = "",
fileId = "[FILE_ID]",
file = File("./path-to-files/image.jpg"),
)
val json = response.body?.string()
@@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() {
GlobalScope.launch {
val response = teams.create(
teamId = "",
teamId = "[TEAM_ID]",
name = "[NAME]",
)
val json = response.body?.string()
@@ -7,7 +7,7 @@ func main() {
let account = Account(client)
account.createMagicURLSession(
userId: "",
userId: "[USER_ID]",
email: "email@example.com"
) { result in
switch result {

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