diff --git a/.env b/.env index 1cc5b84a3..581af6d97 100644 --- a/.env +++ b/.env @@ -3,6 +3,7 @@ _APP_ENV=development _APP_SYSTEM_EMAIL_NAME=Appwrite _APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io +_APP_SYSTEM_RESPONSE_FORMAT= _APP_OPTIONS_ABUSE=disabled _APP_OPTIONS_FORCE_HTTPS=disabled _APP_OPENSSL_KEY_V1=your-secret-key @@ -31,8 +32,10 @@ _APP_STORAGE_LIMIT=10000000 _APP_FUNCTIONS_TIMEOUT=900 _APP_FUNCTIONS_CONTAINERS=10 _APP_FUNCTIONS_CPUS=1 -_APP_FUNCTIONS_MEMORY=128 -_APP_FUNCTIONS_MEMORY_SWAP=128 +_APP_FUNCTIONS_MEMORY=256 +_APP_FUNCTIONS_MEMORY_SWAP=256 _APP_MAINTENANCE_INTERVAL=86400 -_APP_SYSTEM_RESPONSE_FORMAT= -_APP_USAGE_STATS=enabled \ No newline at end of file +_APP_MAINTENANCE_RETENTION_EXECUTION=1209600 +_APP_MAINTENANCE_RETENTION_ABUSE=86400 +_APP_MAINTENANCE_RETENTION_AUDIT=1209600 +_APP_USAGE_STATS=enabled diff --git a/build.sh b/.travis-ci/build.sh similarity index 100% rename from build.sh rename to .travis-ci/build.sh diff --git a/.travis-ci/deploy.sh b/.travis-ci/deploy.sh new file mode 100644 index 000000000..b4c132022 --- /dev/null +++ b/.travis-ci/deploy.sh @@ -0,0 +1 @@ +echo 'Nothing to deploy right now.' \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 6d87d519a..8406222a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ +dist: xenial + arch: - amd64 os: linux -language: minimal +language: shell notifications: email: @@ -20,11 +22,11 @@ before_install: - sudo service docker start - > if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then - set +x - echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin - set -x + echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin fi - docker --version +- docker buildx create --use +- chmod -R u+x ./.travis-ci install: - docker-compose up -d @@ -36,3 +38,11 @@ script: - docker-compose exec appwrite doctor - docker-compose exec appwrite vars - docker-compose exec appwrite test + +deploy: + - provider: script + edge: true + script: ./.travis-ci/deploy.sh + on: + repo: appwrite/appwrite + branch: deploy diff --git a/CHANGES.md b/CHANGES.md index 1418fb453..d34b6c95f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -37,8 +37,11 @@ - Added new environment variables for ClamAV hostname and port ([#780](https://github.com/appwrite/appwrite/pull/780)) - New OAuth adapter for Box.com (@armino-dev - [#420](https://github.com/appwrite/appwrite/issues/410)) - New OAuth adapter for PayPal sandbox (@armino-dev - [#420](https://github.com/appwrite/appwrite/issues/410)) +- New OAuth adapter for Tradeshift (@armino-dev - [#855](https://github.com/appwrite/appwrite/pull/855)) +- New OAuth adapter for Tradeshift sandbox (@armino-dev - [#855](https://github.com/appwrite/appwrite/pull/855)) - Introducing new permssion types: role:guest, role:member, role:app - Disabled rate-limits on server side integrations +- Refactored migration script ### User Interface @@ -105,6 +108,7 @@ - Fixed OAuth redirect when using the self-hosted instance default success URL ([#454](https://github.com/appwrite/appwrite/issues/454)) - Fixed bug denying authentication with Github OAuth provider - Fixed a bug making read permission overwrite write permission in some cases +- Fixed consistent property names in databases by enforcing camel case ## Security @@ -113,6 +117,7 @@ - Now using your `_APP_SYSTEM_EMAIL_ADDRESS` as the email address for issuing and renewing SSL certificates - Block iframe access to Appwrite console using the `X-Frame-Options` header. - Fixed `roles` param input validator +- API Keys are now stored encrypted # Version 0.6.2 (PRE-RELEASE) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73e03d1e7..e3448a5dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,23 @@ cd appwrite docker-compose up -d ``` -After finishing the installation process, you can start writing and editing code. To compile new CSS and JS distribution files, use 'less' and 'build' tasks using gulp as a task manager. +### Code Autocompletion + +To get proper autocompletion for all the different functions and classes in the codebase, you'll need to install Appwrite dependencies on your local machine. You can easily do that with PHP's package manager, [Composer](https://getcomposer.org/). If you don't have Composer installed, you can use the Docker Hub image to get the same result: + +```bash +docker run --rm --interactive --tty \ + --volume $PWD:/app \ + composer install +``` + +### User Interface + +Appwrite uses an internal micro-framework called Litespeed.js to build simple UI components in vanilla JS and [less](http://lesscss.org/) for compiling CSS code. To apply any of your changes to the UI, use the `gulp build` or `gulp less` commands, and restart the Appwrite main container to load the new static files to memory using `docker-compose restart appwrite`. + +### Get Started + +After finishing the installation process, you can start writing and editing code. ## Architecture @@ -216,7 +232,7 @@ For us to find the right balance, please open an issue explaining your ideas bef This will allow the Appwrite community to have sufficient discussion about the new feature value and how it fits in the product roadmap and vision. -This is also important for the Appwrite lead developers to be able to give technical input and different emphasis regarding the feature design and architecture. +This is also important for the Appwrite lead developers to be able to give technical input and different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc). ## Build diff --git a/Dockerfile b/Dockerfile index 1b7979838..77fd8184a 100755 --- a/Dockerfile +++ b/Dockerfile @@ -120,7 +120,11 @@ ENV _APP_SERVER=swoole \ _APP_SETUP=self-hosted \ _APP_VERSION=$VERSION \ _APP_USAGE_STATS=enabled \ + # 14 Days = 1209600 s + _APP_MAINTENANCE_RETENTION_EXECUTION=1209600 \ + _APP_MAINTENANCE_RETENTION_AUDIT=1209600 \ # 1 Day = 86400 s + _APP_MAINTENANCE_RETENTION_ABUSE=86400 \ _APP_MAINTENANCE_INTERVAL=86400 #ENV _APP_SMTP_SECURE '' #ENV _APP_SMTP_USERNAME '' diff --git a/Dockerfile.nginx b/Dockerfile.nginx deleted file mode 100644 index b7acd23a7..000000000 --- a/Dockerfile.nginx +++ /dev/null @@ -1,185 +0,0 @@ -FROM ubuntu:18.04 AS builder - -LABEL maintainer="team@appwrite.io" - -ARG TESTING=false - -ENV TZ=Asia/Tel_Aviv \ - DEBIAN_FRONTEND=noninteractive \ - PHP_VERSION=7.4 \ - PHP_REDIS_VERSION=5.2.1 - -RUN \ - apt-get update && \ - apt-get install -y --no-install-recommends --no-install-suggests ca-certificates software-properties-common wget git openssl && \ - LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php && \ - apt-get update && \ - apt-get install -y --no-install-recommends --no-install-suggests make php$PHP_VERSION php$PHP_VERSION-dev zip unzip php$PHP_VERSION-zip && \ - # Redis Extension - wget -q https://github.com/phpredis/phpredis/archive/$PHP_REDIS_VERSION.tar.gz && \ - tar -xf $PHP_REDIS_VERSION.tar.gz && \ - cd phpredis-$PHP_REDIS_VERSION && \ - phpize$PHP_VERSION && \ - ./configure && \ - make && \ - # Composer - wget https://getcomposer.org/composer.phar && \ - chmod +x ./composer.phar && \ - mv ./composer.phar /usr/bin/composer && \ - #Brotli - cd / && \ - git clone https://github.com/eustas/ngx_brotli.git && \ - cd ngx_brotli && git submodule update --init && cd .. - -WORKDIR /usr/local/src/ - -# Updating PHP Dependencies and Auto-loading... - -ENV TESTING=$TESTING - -COPY composer.* /usr/local/src/ - -RUN composer update --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist \ - `if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi` - -FROM ubuntu:18.04 -LABEL maintainer="team@appwrite.io" - -ARG VERSION=dev - -ENV TZ=Asia/Tel_Aviv \ - DEBIAN_FRONTEND=noninteractive \ - PHP_VERSION=7.4 \ - _APP_SERVER=nginx \ - _APP_ENV=production \ - _APP_DOMAIN=localhost \ - _APP_DOMAIN_TARGET=localhost \ - _APP_HOME=https://appwrite.io \ - _APP_EDITION=community \ - _APP_OPTIONS_ABUSE=enabled \ - _APP_OPTIONS_FORCE_HTTPS=disabled \ - _APP_OPENSSL_KEY_V1=your-secret-key \ - _APP_STORAGE_LIMIT=10000000 \ - _APP_STORAGE_ANTIVIRUS=enabled \ - _APP_REDIS_HOST=redis \ - _APP_REDIS_PORT=6379 \ - _APP_DB_HOST=mariadb \ - _APP_DB_PORT=3306 \ - _APP_DB_USER=root \ - _APP_DB_PASS=password \ - _APP_DB_SCHEMA=appwrite \ - _APP_INFLUXDB_HOST=influxdb \ - _APP_INFLUXDB_PORT=8086 \ - _APP_STATSD_HOST=telegraf \ - _APP_STATSD_PORT=8125 \ - _APP_SMTP_HOST=smtp \ - _APP_SMTP_PORT=25 \ - _APP_SETUP=self-hosted \ - _APP_VERSION=$VERSION -#ENV _APP_SMTP_SECURE '' -#ENV _APP_SMTP_USERNAME '' -#ENV _APP_SMTP_PASSWORD '' - -COPY --from=builder /phpredis-5.2.1/modules/redis.so /usr/lib/php/20190902/ -COPY --from=builder /phpredis-5.2.1/modules/redis.so /usr/lib/php/20190902/ -COPY --from=builder /ngx_brotli /ngx_brotli - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN \ - apt-get update && \ - apt-get install -y --no-install-recommends --no-install-suggests wget ca-certificates software-properties-common build-essential libpcre3-dev zlib1g-dev libssl-dev openssl gnupg htop supervisor && \ - LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php && \ - add-apt-repository universe && \ - add-apt-repository ppa:certbot/certbot && \ - apt-get update && \ - apt-get install -y --no-install-recommends --no-install-suggests php$PHP_VERSION php$PHP_VERSION-fpm \ - php$PHP_VERSION-mysqlnd php$PHP_VERSION-curl php$PHP_VERSION-imagick php$PHP_VERSION-mbstring php$PHP_VERSION-dom certbot && \ - # Nginx - wget http://nginx.org/download/nginx-1.19.0.tar.gz && \ - tar -xzvf nginx-1.19.0.tar.gz && rm nginx-1.19.0.tar.gz && \ - cd nginx-1.19.0 && \ - ./configure --prefix=/usr/share/nginx \ - --sbin-path=/usr/sbin/nginx \ - --modules-path=/usr/lib/nginx/modules \ - --conf-path=/etc/nginx/nginx.conf \ - --error-log-path=/var/log/nginx/error.log \ - --http-log-path=/var/log/nginx/access.log \ - --pid-path=/run/nginx.pid \ - --lock-path=/var/lock/nginx.lock \ - --user=www-data \ - --group=www-data \ - --build=Ubuntu \ - --with-http_gzip_static_module \ - --with-http_ssl_module \ - --with-http_v2_module \ - --add-module=/ngx_brotli && \ - make && \ - make install && \ - rm -rf ../nginx-1.19.0 && \ - # Redis Extension - echo extension=redis.so >> /etc/php/$PHP_VERSION/fpm/conf.d/redis.ini && \ - echo extension=redis.so >> /etc/php/$PHP_VERSION/cli/conf.d/redis.ini && \ - # Cleanup - cd ../ && \ - apt-get purge -y --auto-remove wget software-properties-common build-essential libpcre3-dev zlib1g-dev libssl-dev gnupg && \ - apt-get clean && \ - rm -rf /ngx_brotli && \ - rm -rf /var/lib/apt/lists/* - -# Set Upload Limit (default to 100MB) -RUN echo "upload_max_filesize = ${_APP_STORAGE_LIMIT}" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini -RUN echo "post_max_size = ${_APP_STORAGE_LIMIT}" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini -RUN echo "opcache.preload_user=www-data" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini -RUN echo "opcache.preload=/usr/src/code/app/preload.php" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini -RUN echo "opcache.enable_cli = 1" >> /etc/php/$PHP_VERSION/fpm/conf.d/appwrite.ini - -# Add logs file -RUN echo "" >> /var/log/appwrite.log - -# Nginx Configuration (with self-signed ssl certificates) -COPY ./docker/nginx.conf.template /etc/nginx/nginx.conf.template -COPY ./docker/ssl/cert.pem /etc/nginx/ssl/cert.pem -COPY ./docker/ssl/key.pem /etc/nginx/ssl/key.pem - -# PHP Configuration -RUN mkdir -p /var/run/php -COPY ./docker/www.conf /etc/php/$PHP_VERSION/fpm/pool.d/www.conf - -# Add PHP Source Code -COPY ./app /usr/src/code/app -COPY ./bin /usr/local/bin -COPY ./docs /usr/src/code/docs -COPY ./public /usr/src/code/public -COPY ./src /usr/src/code/src -COPY --from=builder /usr/local/src/vendor /usr/src/code/vendor - -RUN mkdir -p /storage/uploads && \ - mkdir -p /storage/cache && \ - mkdir -p /storage/config && \ - mkdir -p /storage/certificates && \ - mkdir -p /storage/functions && \ - chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \ - chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \ - chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \ - chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \ - chown -Rf www-data.www-data /storage/functions && chmod -Rf 0755 /storage/functions - -# Supervisord Conf -COPY ./docker/supervisord.conf /etc/supervisord.conf - -# Executables -RUN chmod +x /usr/local/bin/start -RUN chmod +x /usr/local/bin/doctor -RUN chmod +x /usr/local/bin/migrate -RUN chmod +x /usr/local/bin/test - -# Letsencrypt Permissions -RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/ - -EXPOSE 80 - -WORKDIR /usr/src/code - -CMD ["/bin/bash", "/usr/local/bin/start"] diff --git a/README.md b/README.md index 7ece1f7cb..e013271c0 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:0.7.0 --version 0.7.0 + appwrite/appwrite:0.7.0 ``` ### Windows @@ -65,7 +65,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:0.7.0 --version 0.7.0 + appwrite/appwrite:0.7.0 ``` #### PowerShell @@ -75,7 +75,7 @@ docker run -it --rm , --volume /var/run/docker.sock:/var/run/docker.sock , --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw , --entrypoint="install" , - appwrite/appwrite:0.7.0 --version 0.7.0 + appwrite/appwrite:0.7.0 ``` Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-linux native hosts, the server might take a few minutes to start after installation completes. diff --git a/app/config/collections.php b/app/config/collections.php index a9451d4a5..9170b0757 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -228,7 +228,7 @@ $collections = [ [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Password Update Date', - 'key' => 'password-update', + 'key' => 'passwordUpdate', 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, 'default' => '', 'required' => true, @@ -827,6 +827,7 @@ $collections = [ 'type' => Database::SYSTEM_VAR_TYPE_TEXT, 'default' => '', 'required' => false, + 'filter' => ['encrypt'], ], ], ], diff --git a/app/config/environments.php b/app/config/environments.php index 228e7e460..9f1c5638e 100644 --- a/app/config/environments.php +++ b/app/config/environments.php @@ -1,15 +1,29 @@ [ +$environments = [ + 'node-14.5' => [ 'name' => 'Node.js', 'version' => '14.5', 'base' => 'node:14.5-alpine', 'image' => 'appwrite/env-node-14.5:1.0.0', 'build' => '/usr/src/code/docker/environments/node-14.5', 'logo' => 'node.png', + 'supports' => [System::X86, System::PPC, System::ARM], + ], + 'node-15.5' => [ + 'name' => 'Node.js', + 'version' => '15.5', + 'base' => 'node:15.5-alpine', + 'image' => 'appwrite/env-node-15.5:1.0.0', + 'build' => '/usr/src/code/docker/environments/node-15.5', + 'logo' => 'node.png', + 'supports' => [System::X86, System::PPC, System::ARM], ], 'php-7.4' => [ 'name' => 'PHP', @@ -18,6 +32,7 @@ return [ 'image' => 'appwrite/env-php-7.4:1.0.0', 'build' => '/usr/src/code/docker/environments/php-7.4', 'logo' => 'php.png', + 'supports' => [System::X86, System::PPC, System::ARM], ], 'php-8.0' => [ 'name' => 'PHP', @@ -26,6 +41,7 @@ return [ 'image' => 'appwrite/env-php-8.0:1.0.0', 'build' => '/usr/src/code/docker/environments/php-8.0', 'logo' => 'php.png', + 'supports' => [System::X86, System::PPC, System::ARM], ], 'ruby-2.7' => [ 'name' => 'Ruby', @@ -34,6 +50,16 @@ return [ 'image' => 'appwrite/env-ruby-2.7:1.0.2', 'build' => '/usr/src/code/docker/environments/ruby-2.7', 'logo' => 'ruby.png', + 'supports' => [System::X86, System::PPC, System::ARM], + ], + 'ruby-3.0' => [ + 'name' => 'Ruby', + 'version' => '3.0', + 'base' => 'ruby:3.0-alpine', + 'image' => 'appwrite/env-ruby-3.0:1.0.0', + 'build' => '/usr/src/code/docker/environments/ruby-3.0', + 'logo' => 'ruby.png', + 'supports' => [System::X86, System::PPC, System::ARM], ], 'python-3.8' => [ 'name' => 'Python', @@ -42,6 +68,7 @@ return [ 'image' => 'appwrite/env-python-3.8:1.0.0', 'build' => '/usr/src/code/docker/environments/python-3.8', 'logo' => 'python.png', + 'supports' => [System::X86, System::PPC, System::ARM], ], 'deno-1.2' => [ 'name' => 'Deno', @@ -50,6 +77,7 @@ return [ 'image' => 'appwrite/env-deno-1.2:1.0.0', 'build' => '/usr/src/code/docker/environments/deno-1.2', 'logo' => 'deno.png', + 'supports' => [System::X86, System::PPC, System::ARM], ], 'deno-1.5' => [ 'name' => 'Deno', @@ -58,13 +86,53 @@ return [ 'image' => 'appwrite/env-deno-1.5:1.0.0', 'build' => '/usr/src/code/docker/environments/deno-1.5', 'logo' => 'deno.png', + 'supports' => [System::X86, System::PPC, System::ARM], ], - // 'dart-2.8' => [ - // 'name' => 'Dart', - // 'version' => '2.8', - // 'base' => 'google/dart:2.8', - // 'image' => 'appwrite/env-dart:2.8', - // 'build' => '/usr/src/code/docker/environments/dart-2.8', - // 'logo' => 'dart.png', - // ], -]; \ No newline at end of file + 'deno-1.6' => [ + 'name' => 'Deno', + 'version' => '1.6', + 'base' => 'hayd/deno:alpine-1.6.0', + 'image' => 'appwrite/env-deno-1.6:1.0.0', + 'build' => '/usr/src/code/docker/environments/deno-1.6', + 'logo' => 'deno.png', + 'supports' => [System::X86, System::PPC, System::ARM], + ], + 'dart-2.10' => [ + 'name' => 'Dart', + 'version' => '2.10', + 'base' => 'google/dart:2.10', + 'image' => 'appwrite/env-dart-2.10:1.0.0', + 'build' => '/usr/src/code/docker/environments/dart-2.10', + 'logo' => 'dart.png', + 'supports' => [System::X86], + ], + 'dotnet-3.1' => [ + 'name' => '.NET', + 'version' => '3.1', + 'base' => 'mcr.microsoft.com/dotnet/runtime:3.1-alpine', + 'image' => 'appwrite/env-dotnet-3.1:1.0.0', + 'build' => '/usr/src/code/docker/environments/dotnet-3.1', + 'logo' => 'dotnet.png', + 'supports' => [System::X86, System::ARM], + ], + 'dotnet-5.0' => [ + 'name' => '.NET', + 'version' => '5.0', + 'base' => 'mcr.microsoft.com/dotnet/runtime:5.0-alpine', + 'image' => 'appwrite/env-dotnet-5.0:1.0.0', + 'build' => '/usr/src/code/docker/environments/dotnet-5.0', + 'logo' => 'dotnet.png', + 'supports' => [System::X86, System::ARM], + ], +]; + +$allowList = empty(App::getEnv('_APP_FUNCTIONS_ENVS', null)) ? false : \explode(',', App::getEnv('_APP_FUNCTIONS_ENVS', null)); + +$environments = array_filter($environments, function ($environment, $key) use ($allowList) { + $isAllowed = $allowList && in_array($key, $allowList); + $isSupported = in_array(System::getArchEnum(), $environment["supports"]); + + return $allowList ? ($isAllowed && $isSupported) : $isSupported; +}, ARRAY_FILTER_USE_BOTH); + +return $environments; \ No newline at end of file diff --git a/app/config/roles.php b/app/config/roles.php index 506b2403c..78dd24ad4 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -2,7 +2,7 @@ use Appwrite\Auth\Auth; -$logged = [ +$member = [ 'public', 'home', 'console', @@ -66,7 +66,7 @@ return [ ], Auth::USER_ROLE_MEMBER => [ 'label' => 'Member', - 'scopes' => \array_merge($logged, []), + 'scopes' => \array_merge($member, []), ], Auth::USER_ROLE_ADMIN => [ 'label' => 'Admin', @@ -78,7 +78,7 @@ return [ ], Auth::USER_ROLE_OWNER => [ 'label' => 'Owner', - 'scopes' => \array_merge($logged, $admins, []), + 'scopes' => \array_merge($member, $admins, []), ], Auth::USER_ROLE_APP => [ 'label' => 'Application', diff --git a/app/config/services.php b/app/config/services.php index e500f9a24..90a452cfc 100644 --- a/app/config/services.php +++ b/app/config/services.php @@ -2,98 +2,126 @@ return [ '/' => [ + 'key' => 'homepage', 'name' => 'Homepage', 'controller' => 'web/home.php', 'sdk' => false, + 'docs' => false, 'tests' => false, ], 'console/' => [ + 'key' => 'console', 'name' => 'Console', 'controller' => 'web/console.php', 'sdk' => false, + 'docs' => false, 'tests' => false, ], 'v1/account' => [ + 'key' => 'account', 'name' => 'Account', 'description' => '/docs/services/account.md', 'controller' => 'api/account.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/avatars' => [ + 'key' => 'avatars', 'name' => 'Avatars', 'description' => '/docs/services/avatars.md', 'controller' => 'api/avatars.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/database' => [ + 'key' => 'database', 'name' => 'Database', 'description' => '/docs/services/database.md', 'controller' => 'api/database.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/locale' => [ + 'key' => 'locale', 'name' => 'Locale', 'description' => '/docs/services/locale.md', 'controller' => 'api/locale.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/health' => [ + 'key' => 'health', 'name' => 'Health', 'description' => '/docs/services/health.md', 'controller' => 'api/health.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/projects' => [ + 'key' => 'projects', 'name' => 'Projects', 'controller' => 'api/projects.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/storage' => [ + 'key' => 'storage', 'name' => 'Storage', 'description' => '/docs/services/storage.md', 'controller' => 'api/storage.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/teams' => [ + 'key' => 'teams', 'name' => 'Teams', 'description' => '/docs/services/teams.md', 'controller' => 'api/teams.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/users' => [ + 'key' => 'users', 'name' => 'Users', 'description' => '/docs/services/users.md', 'controller' => 'api/users.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/functions' => [ - 'name' => 'Users', + 'key' => 'functions', + 'name' => 'Functions', 'description' => '/docs/services/functions.md', 'controller' => 'api/functions.php', 'sdk' => true, + 'docs' => true, 'tests' => false, ], 'v1/mock' => [ + 'key' => 'mock', 'name' => 'Mock', 'description' => '', 'controller' => 'mock.php', 'sdk' => false, + 'docs' => false, 'tests' => true, ], 'v1/graphql' => [ + 'key' => 'graphql', 'name' => 'GraphQL', 'description' => 'GraphQL Endpoint', 'controller' => 'api/graphql.php', 'sdk' => false, + 'docs' => false, 'tests' => false, ], ]; diff --git a/app/config/variables.php b/app/config/variables.php index d713acd5b..5a5e0e951 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -1,5 +1,7 @@ 'General', @@ -22,7 +24,7 @@ return [ 'question' => '', ], [ - 'name' => '_APP_OPTIONS_FORCE_HTTPS', + 'name' => '_APP_OPTIONS_FORCE_HTTPS', 'description' => 'Allows you to force HTTPS connection to your API. This feature redirects any HTTP call to HTTPS and adds the \'Strict-Transport-Security\' header to all HTTP responses. By default, set to \'disabled\'. To enable, set to \'enabled\'. This feature will work only when your ports are set to default 80 and 443.', 'introduction' => '', 'default' => 'enabled', @@ -51,7 +53,7 @@ return [ 'introduction' => '', 'default' => 'localhost', 'required' => true, - 'question' => "Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.", + 'question' => 'Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.', ], [ 'name' => '_APP_CONSOLE_WHITELIST_EMAILS', @@ -63,7 +65,7 @@ return [ ], [ 'name' => '_APP_CONSOLE_WHITELIST_DOMAINS', - 'description' => "This option allows you to limit creation of users to Appwrite console for users sharing the same email domains. This option is very useful for team working with company emails domain.\n\nTo enable this option, pass a list of allowed email domains separated by a comma.", + 'description' => 'This option allows you to limit creation of users to Appwrite console for users sharing the same email domains. This option is very useful for team working with company emails domain.\n\nTo enable this option, pass a list of allowed email domains separated by a comma.', 'introduction' => '', 'default' => '', 'required' => false, @@ -71,7 +73,7 @@ return [ ], [ 'name' => '_APP_CONSOLE_WHITELIST_IPS', - 'description' => "This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\n\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma.", + 'description' => 'This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\n\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma.', 'introduction' => '', 'default' => '', 'required' => false, @@ -93,6 +95,14 @@ return [ 'required' => false, 'question' => '', ], + [ + 'name' => '_APP_SYSTEM_RESPONSE_FORMAT', + 'description' => 'Use this environment variable to set the default Appwrite HTTP response format to support an older version of Appwrite. This option is useful to overcome breaking changes between versions. You can also use the `X-Appwrite-Response-Format` HTTP request header to overwrite the response for a specific request. This variable accepts any valid Appwrite version. To use the current version format, leave the value of the variable empty.', + 'introduction' => '0.7.0', + 'default' => '', + 'required' => false, + 'question' => '', + ], [ 'name' => '_APP_USAGE_STATS', 'description' => 'This variable allows you to disable the collection and displaying of usage stats. This value is set to \'enabled\' by default, to disable the usage stats set the value to \'disabled\'. When disabled, it\'s recommended to turn off the Worker Usage, Influxdb and Telegraf containers for better resource usage.', @@ -155,7 +165,7 @@ return [ ], [ 'name' => '_APP_DB_USER', - 'description' => 'MariaDB server user name. Default value is: \'root\'.', + 'description' => 'MariaDB server user name. Default value is: \'user\'.', 'introduction' => '', 'default' => 'user', 'required' => false, @@ -217,7 +227,7 @@ return [ ], [ 'category' => 'SMTP', - 'description' => "Appwrite is using an SMTP server for emailing your projects users and server admins. The SMTP env vars are used to allow Appwrite server to connect to the SMTP container.\n\nIf running in production, it might be easier to use a 3rd party SMTP server as it might be a little more difficult to set up a production SMTP server that will not send all your emails into your user's SPAM folder.", + 'description' => 'Appwrite is using an SMTP server for emailing your projects users and server admins. The SMTP env vars are used to allow Appwrite server to connect to the SMTP container.\n\nIf running in production, it might be easier to use a 3rd party SMTP server as it might be a little more difficult to set up a production SMTP server that will not send all your emails into your user\'s SPAM folder.', 'variables' => [ [ 'name' => '_APP_SMTP_HOST', @@ -331,7 +341,7 @@ return [ 'name' => '_APP_FUNCTIONS_MEMORY', 'description' => 'The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is 128.', 'introduction' => '0.7.0', - 'default' => '128', + 'default' => '256', 'required' => false, 'question' => '', ], @@ -339,7 +349,15 @@ return [ 'name' => '_APP_FUNCTIONS_MEMORY_SWAP', 'description' => 'The maximum amount of swap memory a single cloud function is allowed to use in megabytes. The default value is 128.', 'introduction' => '0.7.0', - 'default' => '128', + 'default' => '256', + 'required' => false, + 'question' => '', + ], + [ + 'name' => '_APP_FUNCTIONS_ENVS', + 'description' => 'This option allows you to limit the available environments for cloud functions. This option is very useful for low-cost servers to safe disk space.\n\nTo enable/activate this option, pass a list of allowed environments separated by a comma.\n\nCurrently, supported environments are: ' . \implode(', ', \array_keys(Config::getParam('providers'))), + 'introduction' => '0.7.0', + 'default' => 'node-14.5,deno-1.6,php-7.4,python-3.8,ruby-3.0,dotnet-5.0', 'required' => false, 'question' => '', ], @@ -356,13 +374,31 @@ return [ 'required' => false, 'question' => '', ], + [ + 'name' => '_APP_MAINTENANCE_RETENTION_EXECUTION', + 'description' => 'The maximum duration (in seconds) upto which to retain execution logs. The default value is 1209600 seconds (14 days).', + 'introduction' => '0.7.0', + 'default' => '1209600', + 'required' => false, + 'question' => '', + ], + [ + 'name' => '_APP_MAINTENANCE_RETENTION_AUDIT', + 'description' => 'IThe maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).', + 'introduction' => '0.7.0', + 'default' => '1209600', + 'required' => false, + 'question' => '', + ], + [ + 'name' => '_APP_MAINTENANCE_RETENTION_ABUSE', + 'description' => 'The maximum duration (in seconds) upto which to retain abuse logs. The default value is 86400 seconds (1 day).', + 'introduction' => '0.7.0', + 'default' => '86400', + 'required' => false, + 'question' => '', + ] ], ], ], - [ - 'name' => '_APP_SYSTEM_RESPONSE_FORMAT', - 'default' => '', - 'required' => false, - 'question' => '', - ], ]; \ No newline at end of file diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9e366abba..9adba0f6c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -100,7 +100,7 @@ App::post('/v1/account') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash($password), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => $name, @@ -512,7 +512,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'emailVerification' => true, 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => $name, @@ -1456,7 +1456,7 @@ App::put('/v1/account/recovery') $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ 'password' => Auth::passwordHash($password), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'emailVerification' => true, ])); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 0572b8137..e5c663ffd 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -4,11 +4,11 @@ use Appwrite\Database\Database; use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Validator\UID; -use Appwrite\Storage\Storage; -use Appwrite\Storage\Validator\File; -use Appwrite\Storage\Validator\FileSize; -use Appwrite\Storage\Validator\FileType; -use Appwrite\Storage\Validator\Upload; +use Utopia\Storage\Storage; +use Utopia\Storage\Validator\File; +use Utopia\Storage\Validator\FileExt; +use Utopia\Storage\Validator\FileSize; +use Utopia\Storage\Validator\Upload; use Appwrite\Utopia\Response; use Appwrite\Task\Validator\Cron; use Utopia\App; @@ -44,6 +44,9 @@ App::post('/v1/functions') ->inject('response') ->inject('projectDB') ->action(function ($name, $execute, $env, $vars, $events, $schedule, $timeout, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + $function = $projectDB->createDocument([ '$collection' => Database::SYSTEM_COLLECTION_FUNCTIONS, '$permissions' => [ @@ -91,6 +94,9 @@ App::get('/v1/functions') ->inject('response') ->inject('projectDB') ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + $results = $projectDB->getCollection([ 'limit' => $limit, 'offset' => $offset, @@ -122,6 +128,9 @@ App::get('/v1/functions/:functionId') ->inject('response') ->inject('projectDB') ->action(function ($functionId, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { @@ -272,13 +281,19 @@ App::put('/v1/functions/:functionId') ->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true) ->inject('response') ->inject('projectDB') - ->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $projectDB) { + ->inject('project') + ->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $projectDB, $project) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Database\Document $project */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { throw new Exception('Function not found', 404); } + $original = $function->getAttribute('schedule', ''); $cron = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? CronExpression::factory($schedule) : null; $next = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null; @@ -291,28 +306,23 @@ App::put('/v1/functions/:functionId') 'vars' => $vars, 'events' => $events, 'schedule' => $schedule, - 'schedulePrevious' => null, 'scheduleNext' => $next, - 'timeout' => $timeout, + 'timeout' => $timeout, ])); - if ($next) { - ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [ - - ]); - - // ->setParam('projectId', $project->getId()) - // ->setParam('event', $route->getLabel('event', '')) - // ->setParam('payload', []) - // ->setParam('functionId', null) - // ->setParam('executionId', null) - // ->setParam('trigger', 'event') - } - if (false === $function) { throw new Exception('Failed saving function to DB', 500); } + if ($next && $schedule !== $original) { + ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [ + 'projectId' => $project->getId(), + 'functionId' => $function->getId(), + 'executionId' => null, + 'trigger' => 'schedule', + ]); // Async task rescheduale + } + $response->dynamic($function, Response::MODEL_FUNCTION); }); @@ -331,7 +341,12 @@ App::patch('/v1/functions/:functionId/tag') ->param('tag', '', new UID(), 'Tag unique ID.') ->inject('response') ->inject('projectDB') - ->action(function ($functionId, $tag, $response, $projectDB) { + ->inject('project') + ->action(function ($functionId, $tag, $response, $projectDB, $project) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Database\Document $project */ + $function = $projectDB->getDocument($functionId); $tag = $projectDB->getDocument($tag); @@ -344,14 +359,23 @@ App::patch('/v1/functions/:functionId/tag') } $schedule = $function->getAttribute('schedule', ''); - $cron = (!empty($function->getAttribute('tag')&& !empty($schedule))) ? CronExpression::factory($schedule) : null; - $next = (!empty($function->getAttribute('tag')&& !empty($schedule))) ? $cron->getNextRunDate()->format('U') : null; + $cron = (empty($function->getAttribute('tag')) && !empty($schedule)) ? CronExpression::factory($schedule) : null; + $next = (empty($function->getAttribute('tag')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null; $function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [ 'tag' => $tag->getId(), 'scheduleNext' => $next, ])); + if ($next) { // Init first schedule + ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [ + 'projectId' => $project->getId(), + 'functionId' => $function->getId(), + 'executionId' => null, + 'trigger' => 'schedule', + ]); // Async task rescheduale + } + if (false === $function) { throw new Exception('Failed saving function to DB', 500); } @@ -405,28 +429,33 @@ App::post('/v1/functions/:functionId/tags') ->label('sdk.namespace', 'functions') ->label('sdk.method', 'createTag') ->label('sdk.description', '/docs/references/functions/create-tag.md') + ->label('sdk.packaging', true) ->label('sdk.request.type', 'multipart/form-data') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TAG) ->param('functionId', '', new UID(), 'Function unique ID.') ->param('command', '', new Text('1028'), 'Code execution command.') - ->param('code', [], new File(), 'Gzip file containing your code.', false) - // ->param('code', '', new Text(128), 'Code package. Use the '.APP_NAME.' code packager to create a deployable package file.') + ->param('file', null, new File(), 'Gzip file with your code package.', false) ->inject('request') ->inject('response') ->inject('projectDB') ->inject('usage') - ->action(function ($functionId, $command, $code, $request, $response, $projectDB, $usage) { + ->action(function ($functionId, $command, $file, $request, $response, $projectDB, $usage) { + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $usage */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { throw new Exception('Function not found', 404); } - $file = $request->getFiles('code'); + $file = $request->getFiles('file'); $device = Storage::getDevice('functions'); - $fileType = new FileType([FileType::FILE_TYPE_GZIP]); + $fileExt = new FileExt([FileExt::TYPE_GZIP]); $fileSize = new FileSize(App::getEnv('_APP_STORAGE_LIMIT', 0)); $upload = new Upload(); @@ -439,10 +468,9 @@ App::post('/v1/functions/:functionId/tags') $file['tmp_name'] = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name']; $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; - // Check if file type is allowed (feature for project settings?) - // if (!$fileType->isValid($file['tmp_name'])) { - // throw new Exception('File type not allowed', 400); - // } + if (!$fileExt->isValid($file['name'])) { // Check if file type is allowed + throw new Exception('File type not allowed', 400); + } if (!$fileSize->isValid($file['size'])) { // Check if file size is exceeding allowed limit throw new Exception('File size not allowed', 400); @@ -506,6 +534,9 @@ App::get('/v1/functions/:functionId/tags') ->inject('response') ->inject('projectDB') ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { @@ -545,6 +576,9 @@ App::get('/v1/functions/:functionId/tags/:tagId') ->inject('response') ->inject('projectDB') ->action(function ($functionId, $tagId, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { @@ -581,6 +615,10 @@ App::delete('/v1/functions/:functionId/tags/:tagId') ->inject('projectDB') ->inject('usage') ->action(function ($functionId, $tagId, $response, $projectDB, $usage) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $usage */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { @@ -727,6 +765,9 @@ App::get('/v1/functions/:functionId/executions') ->inject('response') ->inject('projectDB') ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { @@ -766,6 +807,9 @@ App::get('/v1/functions/:functionId/executions/:executionId') ->inject('response') ->inject('projectDB') ->action(function ($functionId, $executionId, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 74b17f241..696247f3d 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -2,8 +2,8 @@ use Utopia\App; use Utopia\Exception; -use Appwrite\Storage\Device\Local; -use Appwrite\Storage\Storage; +use Utopia\Storage\Device\Local; +use Utopia\Storage\Storage; use Appwrite\ClamAV\Network; use Appwrite\Event\Event; @@ -287,10 +287,6 @@ App::get('/v1/health/stats') // Currently only used internally $response ->json([ - 'server' => [ - 'name' => 'nginx', - 'version' => \shell_exec('nginx -v 2>&1'), - ], 'storage' => [ 'used' => Storage::human($device->getDirectorySize($device->getRoot().'/')), 'partitionTotal' => Storage::human($device->getPartitionTotalSpace()), diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index b70fe9f95..18e023347 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -13,11 +13,11 @@ use Appwrite\ClamAV\Network; use Appwrite\Database\Database; use Appwrite\Database\Document; use Appwrite\Database\Validator\UID; -use Appwrite\Storage\Storage; -use Appwrite\Storage\Validator\File; -use Appwrite\Storage\Validator\FileSize; -use Appwrite\Storage\Validator\Upload; -use Appwrite\Storage\Compression\Algorithms\GZIP; +use Utopia\Storage\Storage; +use Utopia\Storage\Validator\File; +use Utopia\Storage\Validator\FileSize; +use Utopia\Storage\Validator\Upload; +use Utopia\Storage\Compression\Algorithms\GZIP; use Appwrite\Resize\Resize; use Appwrite\OpenSSL\OpenSSL; use Appwrite\Utopia\Response; diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 084bdeeb1..5ebeb6366 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -324,7 +324,7 @@ App::post('/v1/teams/:teamId/memberships') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => $name, diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 76e20aad9..a0cff9723 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -62,7 +62,7 @@ App::post('/v1/users') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash($password), - 'password-update' => \time(), + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => $name, diff --git a/app/controllers/general.php b/app/controllers/general.php index e718cc9b5..f5a3c09c9 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -14,8 +14,8 @@ use Appwrite\Database\Database; use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Network\Validator\Origin; -use Appwrite\Storage\Device\Local; -use Appwrite\Storage\Storage; +use Utopia\Storage\Device\Local; +use Utopia\Storage\Storage; use Appwrite\Utopia\Response\Filters\V06; use Utopia\CLI\Console; @@ -51,7 +51,11 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo $port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT); $refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()).'://'.((\in_array($origin, $clients)) - ? $origin : 'localhost') . (!empty($port) ? ':'.$port : ''); + ? $origin : 'localhost').(!empty($port) ? ':'.$port : ''); + + $refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible + ? $refDomain + : (!empty($protocol) ? $protocol : $request->getProtocol()).'://'.$origin.(!empty($port) ? ':'.$port : ''); $selfDomain = new Domain($request->getHostname()); $endDomain = new Domain((string)$origin); @@ -79,9 +83,6 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo : '.'.$request->getHostname() ); - Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); - Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId())); - /* * Response format */ @@ -110,13 +111,13 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo } $response->addHeader('Strict-Transport-Security', 'max-age='.(60 * 60 * 24 * 126)); // 126 days - } + } $response ->addHeader('Server', 'Appwrite') ->addHeader('X-Content-Type-Options', 'nosniff') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version, Cache-Control, Expires, Pragma') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-SDK-Version, Cache-Control, Expires, Pragma') ->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $refDomain) ->addHeader('Access-Control-Allow-Credentials', 'true') @@ -236,7 +237,7 @@ App::options(function ($request, $response) { $response ->addHeader('Server', 'Appwrite') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version, Cache-Control, Expires, Pragma, X-Fallback-Cookies') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-SDK-Version, Cache-Control, Expires, Pragma, X-Fallback-Cookies') ->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $origin) ->addHeader('Access-Control-Allow-Credentials', 'true') @@ -255,8 +256,11 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) { $template = ($route) ? $route->getLabel('error', null) : null; if (php_sapi_name() === 'cli') { - Console::error('[Error] Method: '.$route->getMethod()); - Console::error('[Error] URL: '.$route->getURL()); + if($route) { + Console::error('[Error] Method: '.$route->getMethod()); + Console::error('[Error] URL: '.$route->getURL()); + } + Console::error('[Error] Type: '.get_class($error)); Console::error('[Error] Message: '.$error->getMessage()); Console::error('[Error] File: '.$error->getFile()); diff --git a/app/controllers/mock.php b/app/controllers/mock.php index 7f88376b1..0c265f0aa 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -8,7 +8,7 @@ use Utopia\Validator\Numeric; use Utopia\Validator\Text; use Utopia\Validator\ArrayList; use Utopia\Validator\Host; -use Appwrite\Storage\Validator\File; +use Utopia\Storage\Validator\File; App::get('/v1/mock/tests/foo') ->desc('Mock a get request for SDK tests') diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index bc2707f24..8dc0c097c 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -6,8 +6,8 @@ use Utopia\App; use Utopia\Exception; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; -use Appwrite\Storage\Device\Local; -use Appwrite\Storage\Storage; +use Utopia\Storage\Device\Local; +use Utopia\Storage\Storage; App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes) { /** @var Utopia\App $utopia */ diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index b8fc5a621..6b3e105aa 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -7,7 +7,7 @@ use Utopia\Domains\Domain; use Appwrite\Database\Database; use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Validator\UID; -use Appwrite\Storage\Storage; +use Utopia\Storage\Storage; App::init(function ($layout) { /** @var Utopia\View $layout */ diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 5b4e9b731..c4cb6de31 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -191,7 +191,7 @@ App::get('/error/:code') $layout ->setParam('title', 'Error'.' - '.APP_NAME) ->setParam('body', $page); - }, ['']); + }); App::get('/specs/:format') ->groups(['web', 'home']) @@ -216,6 +216,7 @@ App::get('/specs/:format') $routes = []; $models = []; + $services = []; $keys = [ APP_PLATFORM_CLIENT => [ @@ -317,6 +318,20 @@ App::get('/specs/:format') } } + foreach (Config::getParam('services', []) as $key => $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' => (!empty($service['description'])) ? file_get_contents(realpath(__DIR__.'/../../..'.$service['description'])) : '', + ]; + } + $models = $response->getModels(); foreach ($models as $key => $value) { @@ -327,11 +342,11 @@ App::get('/specs/:format') switch ($format) { case 'swagger2': - $format = new Swagger2($utopia, $routes, $models, $keys[$platform], $security[$platform]); + $format = new Swagger2($utopia, $services, $routes, $models, $keys[$platform], $security[$platform]); break; case 'open-api3': - $format = new OpenAPI3($utopia, $routes, $models, $keys[$platform], $security[$platform]); + $format = new OpenAPI3($utopia, $services, $routes, $models, $keys[$platform], $security[$platform]); break; default: diff --git a/app/http.php b/app/http.php index b3d7dcaac..9e45bc3b5 100644 --- a/app/http.php +++ b/app/http.php @@ -18,9 +18,10 @@ use Utopia\CLI\Console; ini_set('memory_limit','512M'); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); +ini_set('default_socket_timeout', -1); error_reporting(E_ALL); -$http = new Server("0.0.0.0", 80); +$http = new Server("0.0.0.0", App::getEnv('PORT', 80)); $payloadSize = max(4000000 /* 4mb */, App::getEnv('_APP_STORAGE_LIMIT', 10000000 /* 10mb */)); diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php index e3985c8e7..2918d4f1e 100644 --- a/app/tasks/doctor.php +++ b/app/tasks/doctor.php @@ -3,8 +3,8 @@ global $cli; use Appwrite\ClamAV\Network; -use Appwrite\Storage\Device\Local; -use Appwrite\Storage\Storage; +use Utopia\Storage\Device\Local; +use Utopia\Storage\Storage; use Utopia\App; use Utopia\CLI\Console; use Utopia\Domains\Domain; diff --git a/app/tasks/install.php b/app/tasks/install.php index 8ca090c77..f855a086c 100644 --- a/app/tasks/install.php +++ b/app/tasks/install.php @@ -6,14 +6,12 @@ use Appwrite\Docker\Compose; use Appwrite\Docker\Env; use Utopia\CLI\Console; use Utopia\Config\Config; -use Utopia\Validator\Mock; use Utopia\View; $cli ->task('install') ->desc('Install Appwrite') - ->param('version', APP_VERSION_STABLE, new Mock(), 'Appwrite version', true) - ->action(function ($version) { + ->action(function () { /** * 1. Start - DONE * 2. Check for older setup and get older version - DONE @@ -48,7 +46,7 @@ $cli if (null !== $path && !\file_exists(\dirname($path))) { if (!@\mkdir(\dirname($path), 0755, true)) { Console::error('Can\'t create directory '.\dirname($path)); - exit(1); + Console::exit(1); } } @@ -130,7 +128,7 @@ $cli $templateForCompose ->setParam('httpPort', $httpPort) ->setParam('httpsPort', $httpsPort) - ->setParam('version', $version) + ->setParam('version', APP_VERSION_STABLE) ; $templateForEnv @@ -139,25 +137,32 @@ $cli if(!file_put_contents($path.'/docker-compose.yml', $templateForCompose->render(false))) { Console::error('Failed to save Docker Compose file'); - exit(1); + Console::exit(1); } if(!file_put_contents($path.'/.env', $templateForEnv->render(false))) { Console::error('Failed to save environment variables file'); - exit(1); + Console::exit(1); } + $env = ''; $stdout = ''; $stderr = ''; + foreach ($input as $key => $value) { + if($value) { + $env .= $key.'='.$value.' '; + } + } + Console::log("Running \"docker-compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes\""); - $exit = Console::execute("docker-compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr); + $exit = Console::execute("${env} docker-compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr); if ($exit !== 0) { Console::error("Failed to install Appwrite dockers"); Console::error($stderr); - exit($exit); + Console::exit($exit); } else { Console::success("Appwrite installed successfully"); } diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php index f8786bcac..eccdf61b1 100644 --- a/app/tasks/maintenance.php +++ b/app/tasks/maintenance.php @@ -4,64 +4,52 @@ global $cli; require_once __DIR__.'/../init.php'; -use Appwrite\Database\Database; -use Appwrite\Database\Adapter\MySQL as MySQLAdapter; -use Appwrite\Database\Adapter\Redis as RedisAdapter; use Appwrite\Event\Event; use Utopia\App; use Utopia\CLI\Console; -use Utopia\Config\Config; - -// TODO: Think of a better way to access consoleDB -function getConsoleDB() { - global $register; - $consoleDB = new Database(); - $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); - $consoleDB->setNamespace('app_console'); // Main DB - $consoleDB->setMocks(Config::getParam('collections', [])); - return $consoleDB; -} - -function notifyDeleteExecutionLogs() -{ - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_EXECUTIONS - ]); -} - -function notifyDeleteAbuseLogs(int $interval) -{ - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_ABUSE, - 'timestamp' => time() - $interval - ]); -} - -function notifyDeleteAuditLogs(int $interval) -{ - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_AUDIT, - 'timestamp' => time() - $interval - ]); -} $cli ->task('maintenance') ->desc('Schedules maintenance tasks and publishes them to resque') ->action(function () { + Console::title('Maintenance V1'); + Console::success(APP_NAME.' maintenance process v1 has started'); + + function notifyDeleteExecutionLogs(int $interval) + { + Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ + 'type' => DELETE_TYPE_EXECUTIONS, + 'timestamp' => time() - $interval + ]); + } + + function notifyDeleteAbuseLogs(int $interval) + { + Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ + 'type' => DELETE_TYPE_ABUSE, + 'timestamp' => time() - $interval + ]); + } + + function notifyDeleteAuditLogs(int $interval) + { + Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ + 'type' => DELETE_TYPE_AUDIT, + 'timestamp' => time() - $interval + ]); + } + // # of days in seconds (1 day = 86400s) $interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400'); - //Convert Seconds to microseconds - $intervalMicroseconds = $interval * 1000000; - - $consoleDB = getConsoleDB(); - - Console::loop(function() use ($consoleDB, $interval){ - Console::info("[ MAINTENANCE TASK ] Notifying deletes workers every {$interval} seconds"); - notifyDeleteExecutionLogs(); - notifyDeleteAbuseLogs($interval); - notifyDeleteAuditLogs($interval); - - }, $intervalMicroseconds); + $executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600'); + $auditLogRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', '1209600'); + $abuseLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', '86400'); + Console::loop(function() use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention){ + $time = date('d-m-Y H:i:s', time()); + Console::info("[{$time}] Notifying deletes workers every {$interval} seconds"); + notifyDeleteExecutionLogs($executionLogsRetention); + notifyDeleteAbuseLogs($abuseLogsRetention); + notifyDeleteAuditLogs($auditLogRetention); + }, $interval); }); \ No newline at end of file diff --git a/app/tasks/migrate.php b/app/tasks/migrate.php index bba8ad416..f9e92b24b 100644 --- a/app/tasks/migrate.php +++ b/app/tasks/migrate.php @@ -5,187 +5,26 @@ global $cli, $register, $projectDB, $console; use Utopia\Config\Config; use Utopia\CLI\Console; use Appwrite\Database\Database; -use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Adapter\MySQL as MySQLAdapter; use Appwrite\Database\Adapter\Redis as RedisAdapter; - -$callbacks = [ - '0.4.0' => function() { - Console::log('I got nothing to do.'); - }, - - '0.5.0' => function($project) use ($register, $projectDB) { - $db = $register->get('db'); - - Console::log('Migrating project: '.$project->getAttribute('name').' ('.$project->getId().')'); - - // Update all documents $uid -> $id - - $limit = 30; - $sum = 30; - $offset = 0; - - while ($sum >= 30) { - $all = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => 'DESC', - ]); - - $sum = \count($all); - - Console::log('Migrating: '.$offset.' / '.$projectDB->getSum()); - - foreach($all as $document) { - $document = fixDocument($document); - - if(empty($document->getId())) { - throw new Exception('Missing ID'); - } - - try { - $new = $projectDB->overwriteDocument($document->getArrayCopy()); - } catch (\Throwable $th) { - var_dump($document); - Console::error('Failed to update document: '.$th->getMessage()); - continue; - } - - if($new->getId() !== $document->getId()) { - throw new Exception('Duplication Error'); - } - } - - $offset = $offset + $limit; - } - - $schema = $_SERVER['_APP_DB_SCHEMA'] ?? ''; - - try { - $statement = $db->prepare(" - - CREATE TABLE IF NOT EXISTS `template.database.unique` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `key` varchar(128) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `index1` (`key`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - - CREATE TABLE IF NOT EXISTS `{$schema}`.`app_{$project->getId()}.database.unique` LIKE `template.database.unique`; - ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` DROP COLUMN IF EXISTS `userType`; - ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` DROP INDEX IF EXISTS `index_1`; - ALTER TABLE `{$schema}`.`app_{$project->getId()}.audit.audit` ADD INDEX IF NOT EXISTS `index_1` (`userId` ASC); - "); - - $statement->closeCursor(); - - $statement->execute(); - } - catch (\Exception $e) { - Console::error('Failed to alter table for project: '.$project->getId().' with message: '.$e->getMessage().'/'); - } - }, -]; - -function fixDocument(Document $document) { - $providers = Config::getParam('providers'); - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_PROJECTS){ - foreach($providers as $key => $provider) { - if(!empty($document->getAttribute('usersOauth'.\ucfirst($key).'Appid'))) { - $document - ->setAttribute('usersOauth2'.\ucfirst($key).'Appid', $document->getAttribute('usersOauth'.\ucfirst($key).'Appid', '')) - ->removeAttribute('usersOauth'.\ucfirst($key).'Appid') - ; - } - - if(!empty($document->getAttribute('usersOauth'.\ucfirst($key).'Secret'))) { - $document - ->setAttribute('usersOauth2'.\ucfirst($key).'Secret', $document->getAttribute('usersOauth'.\ucfirst($key).'Secret', '')) - ->removeAttribute('usersOauth'.\ucfirst($key).'Secret') - ; - } - } - } - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_WEBHOOKS){ - $document->setAttribute('security', ($document->getAttribute('security')) ? true : false); - } - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_TASKS){ - $document->setAttribute('security', ($document->getAttribute('security')) ? true : false); - } - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_USERS) { - foreach($providers as $key => $provider) { - if(!empty($document->getAttribute('oauth'.\ucfirst($key)))) { - $document - ->setAttribute('oauth2'.\ucfirst($key), $document->getAttribute('oauth'.\ucfirst($key), '')) - ->removeAttribute('oauth'.\ucfirst($key)) - ; - } - - if(!empty($document->getAttribute('oauth'.\ucfirst($key).'AccessToken'))) { - $document - ->setAttribute('oauth2'.\ucfirst($key).'AccessToken', $document->getAttribute('oauth'.\ucfirst($key).'AccessToken', '')) - ->removeAttribute('oauth'.\ucfirst($key).'AccessToken') - ; - } - } - - if($document->getAttribute('confirm', null) !== null) { - $document - ->setAttribute('emailVerification', $document->getAttribute('confirm', $document->getAttribute('emailVerification', false))) - ->removeAttribute('confirm') - ; - } - } - - if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_PLATFORMS) { - if($document->getAttribute('url', null) !== null) { - $document - ->setAttribute('hostname', \parse_url($document->getAttribute('url', $document->getAttribute('hostname', '')), PHP_URL_HOST)) - ->removeAttribute('url') - ; - } - } - - $document - ->setAttribute('$id', $document->getAttribute('$uid', $document->getAttribute('$id'))) - ->removeAttribute('$uid') - ; - - foreach($document as &$attr) { // Handle child documents - if($attr instanceof Document) { - $attr = fixDocument($attr); - } - - if(\is_array($attr)) { - foreach($attr as &$child) { - if($child instanceof Document) { - $child = fixDocument($child); - } - } - } - } - - return $document; -} +use Appwrite\Migration\Version; $cli ->task('migrate') - ->action(function () use ($register, $callbacks) { + ->action(function () use ($register) { Console::success('Starting Data Migration'); $consoleDB = new Database(); - $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); - $consoleDB->setNamespace('app_console'); // Main DB - $consoleDB->setMocks(Config::getParam('collections', [])); - + $consoleDB + ->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)) + ->setNamespace('app_console') // Main DB + ->setMocks(Config::getParam('collections', [])); + $projectDB = new Database(); - $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); - $projectDB->setMocks(Config::getParam('collections', [])); + $projectDB + ->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)) + ->setMocks(Config::getParam('collections', [])); $console = $consoleDB->getDocument('console'); @@ -197,13 +36,14 @@ $cli $projects = [$console]; $count = 0; - while ($sum >= 30) { - foreach($projects as $project) { - - $projectDB->setNamespace('app_'.$project->getId()); + $migration = new Version\V06($register->get('db')); //TODO: remove hardcoded version and move to dynamic migration + while ($sum > 0) { + foreach ($projects as $project) { try { - $callbacks['0.5.0']($project, $projectDB); + $migration + ->setProject($project, $projectDB) + ->execute(); } catch (\Throwable $th) { throw $th; Console::error('Failed to update project ("'.$project->getId().'") version with error: '.$th->getMessage()); @@ -221,9 +61,11 @@ $cli $sum = \count($projects); $offset = $offset + $limit; $count = $count + $sum; - - Console::log('Fetched '.$count.'/'.$consoleDB->getSum().' projects...'); + + if ($sum > 0) { + Console::log('Fetched '.$count.'/'.$consoleDB->getSum().' projects...'); + } } Console::success('Data Migration Completed'); - }); \ No newline at end of file + }); diff --git a/app/tasks/sdks.php b/app/tasks/sdks.php index 50d260115..66ed66ed3 100644 --- a/app/tasks/sdks.php +++ b/app/tasks/sdks.php @@ -100,6 +100,8 @@ 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. + 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': $config = new Deno(); @@ -124,6 +126,8 @@ 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)"; break; case 'go': $config = new Go(); @@ -215,5 +219,5 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND } } - exit(); + Console::exit(); }); diff --git a/app/views/console/account/index.phtml b/app/views/console/account/index.phtml index 84a18521d..3f1e7decc 100644 --- a/app/views/console/account/index.phtml +++ b/app/views/console/account/index.phtml @@ -29,6 +29,8 @@
Update Password PLEASE NOTE: Account deletion is irreversible.

getParam('version', '').'.'.APP_CACHE_BUSTER;
- Manage Your Server API Keys + Manage Your Server API Keys
@@ -221,6 +239,8 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled',true);

New Web App

getParam('usageStatsEnabled',true);