Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af25776d95 | ||
|
|
102a840854 | ||
|
|
dd928d5890 | ||
|
|
f179573954 | ||
|
|
6c39a97278 | ||
|
|
fc8b5a2afd | ||
|
|
23d0867f67 | ||
|
|
d32dea81e7 | ||
|
|
93a5a3d1db | ||
|
|
20c3c10152 | ||
|
|
1e85b5a95d | ||
|
|
3a43b6a56a | ||
|
|
7701d3a053 | ||
|
|
20fe16b403 | ||
|
|
a0b47df84b | ||
|
|
91c818c638 | ||
|
|
150d3572c4 | ||
|
|
149d8fe361 | ||
|
|
d040454473 | ||
|
|
754aaba68c | ||
|
|
db05793a3c | ||
|
|
9860b013fb | ||
|
|
02a9265a76 | ||
|
|
4f5f983564 | ||
|
|
8f230071c7 | ||
|
|
358916c277 | ||
|
|
4288261c25 | ||
|
|
345230e251 | ||
|
|
0ce6c3d876 | ||
|
|
dc62d7ff87 | ||
|
|
a5b0b8016a | ||
|
|
3e155cbe3f | ||
|
|
49f52c8efe | ||
|
|
f2b6d72511 | ||
|
|
8066aecc1e | ||
|
|
5924a7bd02 | ||
|
|
8fee018c18 | ||
|
|
1592135fab | ||
|
|
b21937ca3f | ||
|
|
f0380ef4f2 | ||
|
|
6fb5fb5676 | ||
|
|
8e6961a4dd | ||
|
|
1e968bda14 | ||
|
|
0201509c71 | ||
|
|
7a51b0e937 | ||
|
|
7701904376 | ||
|
|
7de0897335 | ||
|
|
a46e12f9ff | ||
|
|
3f0fcb40d0 | ||
|
|
0abfae8ca1 | ||
|
|
27249c236b | ||
|
|
9ac55e8806 | ||
|
|
848b997a5f | ||
|
|
da5addf2f2 | ||
|
|
7bd16180c3 | ||
|
|
e291d81be3 | ||
|
|
6842286a3a | ||
|
|
545d86d994 | ||
|
|
7708859d7d | ||
|
|
601569805f | ||
|
|
d7d98d9866 | ||
|
|
b7d7f43aef | ||
|
|
b0fe5f3e90 | ||
|
|
209cf6be1e | ||
|
|
1f46d390b1 | ||
|
|
59c02ddf84 | ||
|
|
1e2e82593f | ||
|
|
7e2ae34624 | ||
|
|
d70dd6a6bc | ||
|
|
e30952c48b | ||
|
|
3b53fc7cc4 | ||
|
|
7cfdde3d91 | ||
|
|
45e8314b49 | ||
|
|
df8e95a0a1 | ||
|
|
70f1a3eaf8 | ||
|
|
462b30b721 | ||
|
|
70102a18f7 | ||
|
|
ae57ac6781 | ||
|
|
108745e1a8 | ||
|
|
9ccb26d090 | ||
|
|
e25dd11049 | ||
|
|
c6a054f88d | ||
|
|
4a8befd110 |
@@ -27,14 +27,12 @@ _APP_DB_PASS=password
|
||||
_APP_DB_ROOT_PASS=rootsecretpassword
|
||||
_APP_CONNECTIONS_MAX=3100
|
||||
_APP_POOL_CLIENTS=14
|
||||
_APP_CONNECTIONS_DB_REPLICAS=db_fra1_02=mariadb://root:rootsecretpassword@mariadb:3306/appwrite
|
||||
_APP_CONNECTIONS_DB_PROJECT=db_fra1_02=mariadb://user:password@mariadb:3306/appwrite
|
||||
_APP_CONNECTIONS_DB_CONSOLE=db_fra1_01=mariadb://user:password@mariadb:3306/appwrite
|
||||
_APP_CONNECTIONS_CACHE=redis_fra1_01=redis://redis:6379
|
||||
_APP_CONNECTIONS_QUEUE=redis_fra1_01=redis://redis:6379
|
||||
_APP_CONNECTIONS_PUBSUB=redis_fra1_01=redis://redis:6379
|
||||
_APP_CONNECTIONS_STORAGE=local://localhost
|
||||
_APP_CONNECTIONS_BACKUPS_STORAGE=dospaces://ACCESS:SECRET@default/backups-v1?region=fra1
|
||||
_APP_STORAGE_ANTIVIRUS=disabled
|
||||
_APP_STORAGE_ANTIVIRUS_HOST=clamav
|
||||
_APP_STORAGE_ANTIVIRUS_PORT=3310
|
||||
@@ -47,10 +45,6 @@ _APP_SMTP_PORT=1025
|
||||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_USERS_STATS_RECIPIENTS=
|
||||
_APP_HAMSTER_INTERVAL=86400
|
||||
_APP_HAMSTER_TIME=21:00
|
||||
_APP_MIXPANEL_TOKEN=
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
@@ -77,8 +71,6 @@ _APP_USAGE_STATS=enabled
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=30
|
||||
_APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_REGION=default
|
||||
_APP_REGION=nyc1
|
||||
_APP_DOCKER_HUB_USERNAME=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
_APP_CONSOLE_GITHUB_SECRET=
|
||||
_APP_CONSOLE_GITHUB_APP_ID=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
@@ -1,46 +0,0 @@
|
||||
name: "Build and Publish"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- appwrite-*
|
||||
|
||||
jobs:
|
||||
build-publish:
|
||||
name: Build and Publish
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
submodules: recursive
|
||||
ref: db-pools-backups
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: appwrite/cloud
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
|
||||
- name: Build & Publish to DockerHub
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
build-args: |
|
||||
VERSION=${{ steps.meta.outputs.version }}
|
||||
VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
|
||||
VITE_GA_PROJECT=G-L7G2B6PLDS
|
||||
VITE_CONSOLE_MODE=cloud
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
@@ -11,4 +11,4 @@ debug/
|
||||
app/sdks
|
||||
dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
/backup-data/
|
||||
/.phpbench/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = cloud-cards
|
||||
branch = db-pools-support
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- Increase Traefik TCP + file limits [#4673](https://github.com/appwrite/appwrite/pull/4673)
|
||||
- Fix invited account verified status [#4776](https://github.com/appwrite/appwrite/pull/4776)
|
||||
- Get default region from environment on project create [#4780](https://github.com/appwrite/appwrite/pull/4780)
|
||||
- Store build output file size [#4844](https://github.com/appwrite/appwrite/pull/4844)
|
||||
- Fix max mimetype size [#4814](https://github.com/appwrite/appwrite/pull/4814)
|
||||
|
||||
# Version 1.1.2
|
||||
|
||||
@@ -20,16 +20,162 @@ WORKDIR /usr/local/src/console
|
||||
|
||||
ARG VITE_GA_PROJECT
|
||||
ARG VITE_CONSOLE_MODE
|
||||
ARG VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
|
||||
|
||||
ENV VITE_GA_PROJECT=$VITE_GA_PROJECT
|
||||
ENV VITE_CONSOLE_MODE=$VITE_CONSOLE_MODE
|
||||
ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
|
||||
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
||||
FROM appwrite/base:0.2.2 as final
|
||||
FROM php:8.0.18-cli-alpine3.15 as compile
|
||||
|
||||
ARG DEBUG=false
|
||||
ENV DEBUG=$DEBUG
|
||||
|
||||
ENV PHP_REDIS_VERSION=5.3.7 \
|
||||
PHP_MONGODB_VERSION=1.13.0 \
|
||||
PHP_SWOOLE_VERSION=v4.8.10 \
|
||||
PHP_IMAGICK_VERSION=3.7.0 \
|
||||
PHP_YAML_VERSION=2.2.2 \
|
||||
PHP_MAXMINDDB_VERSION=v1.11.0 \
|
||||
PHP_MEMCACHED_VERSION=v3.2.0 \
|
||||
PHP_ZSTD_VERSION="4504e4186e79b197cfcb75d4d09aa47ef7d92fe9 "
|
||||
|
||||
RUN \
|
||||
apk add --no-cache --virtual .deps \
|
||||
make \
|
||||
automake \
|
||||
autoconf \
|
||||
gcc \
|
||||
g++ \
|
||||
git \
|
||||
zlib-dev \
|
||||
brotli-dev \
|
||||
openssl-dev \
|
||||
yaml-dev \
|
||||
imagemagick \
|
||||
imagemagick-dev \
|
||||
libmaxminddb-dev \
|
||||
libmemcached-dev \
|
||||
zstd-dev
|
||||
|
||||
RUN docker-php-ext-install sockets
|
||||
|
||||
FROM compile AS redis
|
||||
RUN \
|
||||
# Redis Extension
|
||||
git clone --depth 1 --branch $PHP_REDIS_VERSION https://github.com/phpredis/phpredis.git && \
|
||||
cd phpredis && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
## Swoole Extension
|
||||
FROM compile AS swoole
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_SWOOLE_VERSION https://github.com/swoole/swoole-src.git && \
|
||||
cd swoole-src && \
|
||||
phpize && \
|
||||
./configure --enable-sockets --enable-http2 --enable-openssl && \
|
||||
make && make install && \
|
||||
cd ..
|
||||
|
||||
## Swoole Debugger setup
|
||||
RUN if [ "$DEBUG" == "true" ]; then \
|
||||
cd /tmp && \
|
||||
apk add boost-dev && \
|
||||
git clone --depth 1 https://github.com/swoole/yasd && \
|
||||
cd yasd && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install && \
|
||||
cd ..;\
|
||||
fi
|
||||
|
||||
## Imagick Extension
|
||||
FROM compile AS imagick
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_IMAGICK_VERSION https://github.com/imagick/imagick && \
|
||||
cd imagick && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
## YAML Extension
|
||||
FROM compile AS yaml
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_YAML_VERSION https://github.com/php/pecl-file_formats-yaml && \
|
||||
cd pecl-file_formats-yaml && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
## Maxminddb extension
|
||||
FROM compile AS maxmind
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_MAXMINDDB_VERSION https://github.com/maxmind/MaxMind-DB-Reader-php.git && \
|
||||
cd MaxMind-DB-Reader-php && \
|
||||
cd ext && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
# Mongodb Extension
|
||||
FROM compile as mongodb
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_MONGODB_VERSION https://github.com/mongodb/mongo-php-driver.git && \
|
||||
cd mongo-php-driver && \
|
||||
git submodule update --init && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
# Memcached Extension
|
||||
FROM compile as memcached
|
||||
RUN \
|
||||
git clone --depth 1 --branch $PHP_MEMCACHED_VERSION https://github.com/php-memcached-dev/php-memcached.git && \
|
||||
cd php-memcached && \
|
||||
phpize && \
|
||||
./configure && \
|
||||
make && make install
|
||||
|
||||
# Zstd Compression
|
||||
FROM compile as zstd
|
||||
RUN git clone --recursive -n https://github.com/kjdev/php-ext-zstd.git \
|
||||
&& cd php-ext-zstd \
|
||||
&& git checkout $PHP_ZSTD_VERSION \
|
||||
&& phpize \
|
||||
&& ./configure --with-libzstd \
|
||||
&& make && make install
|
||||
|
||||
# Rust Extensions Compile Image
|
||||
FROM php:8.0.18-cli as rust_compile
|
||||
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
|
||||
ENV PATH=/root/.cargo/bin:$PATH
|
||||
|
||||
RUN apt-get update && apt-get install musl-tools build-essential clang-11 git -y
|
||||
RUN rustup target add $(uname -m)-unknown-linux-musl
|
||||
|
||||
# Install ZigBuild for easier cross-compilation
|
||||
RUN curl https://ziglang.org/builds/zig-linux-$(uname -m)-0.10.0-dev.2674+d980c6a38.tar.xz --output /tmp/zig.tar.xz
|
||||
RUN tar -xf /tmp/zig.tar.xz -C /tmp/ && cp -r /tmp/zig-linux-$(uname -m)-0.10.0-dev.2674+d980c6a38 /tmp/zig/
|
||||
ENV PATH=/tmp/zig:$PATH
|
||||
RUN cargo install cargo-zigbuild
|
||||
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
||||
|
||||
FROM rust_compile as scrypt
|
||||
|
||||
WORKDIR /usr/local/lib/php/extensions/
|
||||
|
||||
RUN \
|
||||
git clone --depth 1 https://github.com/appwrite/php-scrypt.git && \
|
||||
cd php-scrypt && \
|
||||
cargo zigbuild --workspace --all-targets --target $(uname -m)-unknown-linux-musl --release && \
|
||||
mv target/$(uname -m)-unknown-linux-musl/release/libphp_scrypt.so target/libphp_scrypt.so
|
||||
|
||||
FROM php:8.0.18-cli-alpine3.15 as final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
@@ -83,6 +229,38 @@ ENV _APP_SERVER=swoole \
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000 \
|
||||
_APP_MAINTENANCE_INTERVAL=86400
|
||||
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
RUN \
|
||||
apk update \
|
||||
&& apk add --no-cache --virtual .deps \
|
||||
make \
|
||||
automake \
|
||||
autoconf \
|
||||
gcc \
|
||||
g++ \
|
||||
curl-dev \
|
||||
&& apk add --no-cache \
|
||||
libstdc++ \
|
||||
certbot \
|
||||
rsync \
|
||||
brotli-dev \
|
||||
yaml-dev \
|
||||
imagemagick \
|
||||
imagemagick-dev \
|
||||
libmaxminddb-dev \
|
||||
certbot \
|
||||
docker-cli \
|
||||
libgomp \
|
||||
&& docker-php-ext-install sockets opcache pdo_mysql \
|
||||
&& apk del .deps \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN \
|
||||
mkdir -p $DOCKER_CONFIG/cli-plugins \
|
||||
&& ARCH=$(uname -m) && if [ $ARCH == "armv7l" ]; then ARCH="armv7"; fi \
|
||||
&& curl -SL https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-$ARCH -o $DOCKER_CONFIG/cli-plugins/docker-compose \
|
||||
&& chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
|
||||
RUN \
|
||||
if [ "$DEBUG" == "true" ]; then \
|
||||
@@ -93,10 +271,18 @@ WORKDIR /usr/src/code
|
||||
|
||||
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
|
||||
COPY --from=node /usr/local/src/console/build /usr/src/code/console
|
||||
COPY --from=swoole /usr/local/lib/php/extensions/no-debug-non-zts-20200930/swoole.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/yasd.so* /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=redis /usr/local/lib/php/extensions/no-debug-non-zts-20200930/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=imagick /usr/local/lib/php/extensions/no-debug-non-zts-20200930/imagick.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=yaml /usr/local/lib/php/extensions/no-debug-non-zts-20200930/yaml.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=maxmind /usr/local/lib/php/extensions/no-debug-non-zts-20200930/maxminddb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=mongodb /usr/local/lib/php/extensions/no-debug-non-zts-20200930/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=memcached /usr/local/lib/php/extensions/no-debug-non-zts-20200930/memcached.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=scrypt /usr/local/lib/php/extensions/php-scrypt/target/libphp_scrypt.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
COPY --from=zstd /usr/local/lib/php/extensions/no-debug-non-zts-20200930/zstd.so /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
|
||||
|
||||
# Add Source Code
|
||||
COPY ./app /usr/src/code/app
|
||||
COPY ./public /usr/src/code/public
|
||||
COPY ./bin /usr/local/bin
|
||||
COPY ./docs /usr/src/code/docs
|
||||
COPY ./src /usr/src/code/src
|
||||
@@ -117,15 +303,10 @@ RUN mkdir -p /storage/uploads && \
|
||||
|
||||
# Executables
|
||||
RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
|
||||
chmod +x /usr/local/bin/clear-card-cache && \
|
||||
chmod +x /usr/local/bin/calc-users-stats && \
|
||||
chmod +x /usr/local/bin/calc-tier-stats && \
|
||||
chmod +x /usr/local/bin/patch-delete-project-collections && \
|
||||
chmod +x /usr/local/bin/maintenance && \
|
||||
chmod +x /usr/local/bin/db-backup && \
|
||||
chmod +x /usr/local/bin/db-restore && \
|
||||
chmod +x /usr/local/bin/patch-create-missing-schedules && \
|
||||
chmod +x /usr/local/bin/maintenance && \
|
||||
chmod +x /usr/local/bin/volume-sync && \
|
||||
chmod +x /usr/local/bin/edge-sync && \
|
||||
chmod +x /usr/local/bin/usage && \
|
||||
chmod +x /usr/local/bin/install && \
|
||||
chmod +x /usr/local/bin/migrate && \
|
||||
@@ -134,7 +315,6 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/sdks && \
|
||||
chmod +x /usr/local/bin/specs && \
|
||||
chmod +x /usr/local/bin/ssl && \
|
||||
chmod +x /usr/local/bin/hamster && \
|
||||
chmod +x /usr/local/bin/test && \
|
||||
chmod +x /usr/local/bin/vars && \
|
||||
chmod +x /usr/local/bin/worker-audits && \
|
||||
@@ -145,12 +325,23 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/worker-builds && \
|
||||
chmod +x /usr/local/bin/worker-mails && \
|
||||
chmod +x /usr/local/bin/worker-messaging && \
|
||||
chmod +x /usr/local/bin/worker-webhooks
|
||||
chmod +x /usr/local/bin/worker-webhooks && \
|
||||
chmod +x /usr/local/bin/worker-sync-out && \
|
||||
chmod +x /usr/local/bin/worker-sync-in
|
||||
|
||||
|
||||
|
||||
# Letsencrypt Permissions
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
# Enable Extensions
|
||||
RUN echo extension=swoole.so >> /usr/local/etc/php/conf.d/swoole.ini
|
||||
RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini
|
||||
RUN echo extension=imagick.so >> /usr/local/etc/php/conf.d/imagick.ini
|
||||
RUN echo extension=yaml.so >> /usr/local/etc/php/conf.d/yaml.ini
|
||||
RUN echo extension=maxminddb.so >> /usr/local/etc/php/conf.d/maxminddb.ini
|
||||
RUN echo extension=libphp_scrypt.so >> /usr/local/etc/php/conf.d/libphp_scrypt.ini
|
||||
RUN echo extension=zstd.so >> /usr/local/etc/php/conf.d/zstd.ini
|
||||
RUN if [ "$DEBUG" == "true" ]; then printf "zend_extension=yasd \nyasd.debug_mode=remote \nyasd.init_file=/usr/local/dev/yasd_init.php \nyasd.remote_port=9005 \nyasd.log_level=-1" >> /usr/local/etc/php/conf.d/yasd.ini; fi
|
||||
|
||||
RUN if [ "$DEBUG" == "true" ]; then echo "opcache.enable=0" >> /usr/local/etc/php/conf.d/appwrite.ini; fi
|
||||
@@ -161,12 +352,6 @@ RUN echo "default_socket_timeout=-1" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
RUN echo "opcache.jit_buffer_size=100M" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
RUN echo "opcache.jit=1235" >> /usr/local/etc/php/conf.d/appwrite.ini
|
||||
|
||||
RUN \
|
||||
apk update \
|
||||
&& apk add --no-cache \
|
||||
docker \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD [ "php", "app/http.php", "-dopcache.preload=opcache.preload=/usr/src/code/app/preload.php" ]
|
||||
@@ -3,6 +3,8 @@
|
||||
require_once __DIR__ . '/init.php';
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Utopia\CLI\CLI;
|
||||
@@ -17,11 +19,15 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
Authorization::disable();
|
||||
|
||||
CLI::setResource('register', fn()=>$register);
|
||||
global $register;
|
||||
|
||||
CLI::setResource('register', fn() => $register);
|
||||
|
||||
CLI::setResource('cache', function ($pools) {
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
@@ -31,8 +37,7 @@ CLI::setResource('cache', function ($pools) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
->getResource();
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
@@ -61,10 +66,11 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
$dbForConsole->setNamespace('console');
|
||||
|
||||
// Ensure tables exist
|
||||
$collections = Config::getParam('collections', [])['console'];
|
||||
$collections = Config::getParam('collections', []);
|
||||
$last = \array_key_last($collections);
|
||||
|
||||
if (!($dbForConsole->exists($dbForConsole->getDefaultDatabase(), $last))) { /** TODO cache ready variable using registry */
|
||||
if (!($dbForConsole->exists($dbForConsole->getDefaultDatabase(), $last))) {
|
||||
/** TODO cache ready variable using registry */
|
||||
throw new Exception('Tables not ready yet.');
|
||||
}
|
||||
|
||||
@@ -117,7 +123,8 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
}, ['pools', 'dbForConsole', 'cache']);
|
||||
|
||||
CLI::setResource('influxdb', function (Registry $register) {
|
||||
$client = $register->get('influxdb'); /** @var InfluxDB\Client $client */
|
||||
$client = $register->get('influxdb');
|
||||
/** @var InfluxDB\Client $client */
|
||||
$attempts = 0;
|
||||
$max = 10;
|
||||
$sleep = 1;
|
||||
@@ -140,10 +147,26 @@ CLI::setResource('influxdb', function (Registry $register) {
|
||||
return $database;
|
||||
}, ['register']);
|
||||
|
||||
CLI::setResource('queueForFunctions', function (Group $pools) {
|
||||
return new Func($pools->get('queue')->pop()->getResource());
|
||||
CLI::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
|
||||
CLI::setResource('queueForCertificates', function (Connection $queue) {
|
||||
return new Certificate($queue);
|
||||
}, ['queue']);
|
||||
|
||||
CLI::setResource('queueForDeletes', function (Connection $queue) {
|
||||
return new Delete($queue);
|
||||
}, ['queue']);
|
||||
|
||||
CLI::setResource('queueForFunctions', function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
}, ['queue']);
|
||||
|
||||
CLI::setResource('queueForEdgeSyncOut', function (Connection $queue) {
|
||||
return new Client('v1-sync-out', $queue);
|
||||
}, ['queue']);
|
||||
|
||||
CLI::setResource('logError', function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
[1297371,1759475,6360216,20852629,5857008,19310830,9708641,26739219,23742426,22174310,77877486,1477010,29069505,62933155,45863583,42211,176163,58045728,7091609,31401437,1911066,66096031,22432834,43054051,79051850,22895284,100597998,91385411,49699333,7818620,11004008,54898623,27856297,33250853,49375670,30630364,50206,47822499,7481165,52557347,13681567,20492520,51369094,51821861,50256986,28431370,13692220,24373771,15938422,27148250,14805534,36137226,36071208,40193621,56179878,53281158,48085134,19358691,32528768,19733683,37348419,13719696,18309412,42106787,53618500,5306011,4408379,35486736,18586611,15062564,27011453,56090587,44623032,25107942,81188,33922418,16717633,32809211,69401139,25815659,4104127,20889958,3102249,12476526,2635185,28495651,50957556,2791280,31023616,20955511,835733,471907,69008866,44906587,45271396,76054330,45748739,41908747,23402178,47356149,8921,36632821,3668741,71702982,29725587,26303198,785830,51410502,60089135,2847349,43172716,48546075,8216525,41161981,51828039,4334997,80918302,38534289,47860497,80036766,41341387,49818988,58487637,29237374,46913894,5148229,4377199,29686102,26272249,75117692,4090256,27357868,33062368,38664231,46695441,743291,22633385,6368283,11593067,45097959,43381712,3284228,1972717,33012425,61755381,25405707,3144291,44156359,5497267,7423905,20716175,28586681,5975506,23518097,22187384,24191952,7768078,971530,51240166,55633427,34207400,77061285,11719476,35950229,66742927,34406802,802933,50047839,39148877,26602940,9693472,44273767,19362725,31209978,30521594,686298,6237394,35039730,42580581,36671793,8502129,8466918,81866614,54903252,28373606,13381361,72331432,30694270,5355510,8209163,86675510,9453522,42496309,56145786,2149381,393945,22084723,52621436,8872447,5575392,29619660,5547479,8852116,11151445,4717349,17725274,65615065,18537755,29292618,53044263,26597930,10313411,55998629,77529288,17404636,33729848,19422168,17916404,66111735,10329006,33502846,398230,81643826,105039167,47522632,91655303,9774614,10603631,284924,60857954,22885912,116552306,36103454,794606,27729549,1754457,36594527,13899668,78664749,47406531,27698189,5305654,53345517,6756412,29176704,77790497,47504894,37251540,52361778,52200375,1351177,66022861,73975409,25745396,31433638,37118134,43210805,20317665,11923975,47187468,16362381,36751163,14959876,32362757,65529384,52352285,74085816,3628535,43902034,75667593,26132902,466713,617558,96806061,33605526,11290524,43621940,12446314,17146935,55018955,56096559,79797000,40014186,34449936,58387964,23368207,42414965,44056349,33743031,12294525,58251592,33755729,9021747,932084,11428067,97121933,80122730,60894542,58583793,56051809,32243289,9934371,90936802,74638775,65399526,77604,64524822,47782249,43633955,42793632,55969597,72334601,82395440,92818577,60866204,65016769,23725091,45892107,55308895,86314140,82756460,47685349,63562160,73419211,1613216,50882624,91469717,46166258,60927324,41763158,83607556,2171717,50497814,39427312,61322830,40076195,39419448,29397545,55090719,53259730,20885012,64558515,69677883,55741087,72426535,46033036,68477507,30376878,73700530,25518600,29922887,36229969,47573417,40424087,49054503,16880385,22801227,72848513,64347914,814402,49149679,55017867,49481876,67067955,31439735,63878173,80322286,43746210,17332970,22702905,62476876,89888292,75736952,54059881,90782137,63588969,57111920,63330165,70258211,46371923,17837758,59364507,52203828,60147326,18481195,74822422,9803078,67309607,60410049,47360939,19922556,90848252,24698014,58886915,63579762,96648934,68523530,60518745,37345795,3929651,54993657,52061363,43019989,5787917,94674993,71593494,17143469,10288548,1830380,71510505,59124772,2335145,70798495,46474346,49263351,52062536,63151043,65248303,26071571,53626355,43992469,60785452,63467479,71837281,19490891,58628586,38250310,7271718,1110414,57227290,11625672,85063520,88965873,70096901,42029519,85363195,64471630,69353350,66922161,2221746,100430077,12299813,62690310,68282006,99184676,2450,22989561,22212661,59973863,11232940,76688923,22321353,77732479,84286404,32268377,34828782,23068019,57074509,24620969,20735983,26173690,75809937,49760818,86646105,52617262]
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"eldad@appwrite.io": { "memberSince": "2020-10-15", "spot": "0", "gitHub": "eldadfux" },
|
||||
"christy@appwrite.io": { "memberSince": "2020-12-01", "spot": "1", "gitHub": "christyjacob4" },
|
||||
"torsten@appwrite.io": { "memberSince": "2020-12-28", "spot": "2", "gitHub": "torstendittmann" },
|
||||
"damodar@appwrite.io": { "memberSince": "2021-01-02", "spot": "3", "gitHub": "lohanidamodar" },
|
||||
"bradley@appwrite.io": { "memberSince": "2021-05-21", "spot": "5", "gitHub": "PineappleIOnic" },
|
||||
"jake@appwrite.io": { "memberSince": "2021-06-28", "spot": "6", "gitHub": "abnegate" },
|
||||
"sara@appwrite.io": { "memberSince": "2021-08-16", "spot": "7", "gitHub": "sarakaandorp" },
|
||||
"matej@appwrite.io": { "memberSince": "2021-08-23", "spot": "8", "gitHub": "meldiron" },
|
||||
"aditya@appwrite.io": { "memberSince": "2021-09-01", "spot": "9", "gitHub": "adityaoberai" },
|
||||
"wess@appwrite.io": { "memberSince": "2021-11-08", "spot": "12", "gitHub": "wess" },
|
||||
"may@appwrite.io": { "memberSince": "2021-11-28", "spot": "14", "gitHub": "MayEnder" },
|
||||
"elad@appwrite.io": { "memberSince": "2021-12-19", "spot": "15", "gitHub": "elad2412" },
|
||||
"vincent@appwrite.io": { "memberSince": "2022-01-01", "spot": "16", "gitHub": "gewenyu99" },
|
||||
"haimantika@appwrite.io": { "memberSince": "2022-04-01", "spot": "18", "gitHub": "Haimantika" },
|
||||
"chen@appwrite.io": { "memberSince": "2022-01-24", "spot": "19", "gitHub": "chenparnasa" },
|
||||
"tessa@appwrite.io": { "memberSince": "2022-04-21", "spot": "20", "gitHub": "tessamero" },
|
||||
"shimon@appwrite.io": { "memberSince": "2022-05-01", "spot": "23", "gitHub": "shimonewman" },
|
||||
"shmuel@appwrite.io": { "memberSince": "2022-03-20", "spot": "24", "gitHub": "fogelito" },
|
||||
"arman@appwrite.io": { "memberSince": "2022-04-04", "spot": "25", "gitHub": "ArmanNik" },
|
||||
"carla@appwrite.io": { "memberSince": "2022-04-04", "spot": "26", "gitHub": "heyCarla" },
|
||||
"emma@appwrite.io": { "memberSince": "2022-05-08", "spot": "27", "gitHub": "emmacarpagnano1" },
|
||||
"dylan@appwrite.io": { "memberSince": "2022-05-09", "spot": "28", "gitHub": "DylanG-64" },
|
||||
"steven@appwrite.io": { "memberSince": "2022-07-01", "spot": "30", "gitHub": "stnguyen90" },
|
||||
"jyoti@appwrite.io": { "memberSince": "2022-10-24", "spot": "31", "gitHub": "joeyouss" },
|
||||
"jade@appwrite.io": { "memberSince": "2022-10-31", "spot": "32", "gitHub": "dajebp" },
|
||||
"khushboo@appwrite.io": { "memberSince": "2021-11-08", "spot": "13", "gitHub": "vermakhushboo" },
|
||||
"thomas@appwrite.io": { "memberSince": "2022-11-03", "spot": "34", "gitHub": "TGlide" },
|
||||
"holly@appwrite.io": { "memberSince": "2022-12-05", "spot": "35", "gitHub": "HollyBarclay" },
|
||||
"laura@appwrite.io": { "memberSince": "2023-01-25", "spot": "36", "gitHub": "LauraDuRy" },
|
||||
"caio@appwrite.io": { "memberSince": "2023-03-27", "spot": "37", "gitHub": "ariascaio" },
|
||||
"luke@appwrite.io": { "memberSince": "2023-05-04", "spot": "38", "gitHub": "loks0n" }
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"bishwajeet.techmaster@gmail.com": { "memberSince": "2023-02-07" },
|
||||
"lucasaudart@gmail.com": { "memberSince": "2023-02-07" },
|
||||
"tkarmakar27112000@gmail.com": { "memberSince": "2023-02-07" },
|
||||
"alves.mckl@gmail.com": { "memberSince": "2023-02-07" },
|
||||
"dpns_nampula@rnlay.com": { "memberSince": "2023-02-07" },
|
||||
"a.stephensimon@outlook.com": { "memberSince": "2023-02-07" },
|
||||
"hidianapham@gmail.com": { "memberSince": "2023-02-07" }
|
||||
}
|
||||
@@ -88,11 +88,6 @@ return [
|
||||
'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
|
||||
'code' => 500,
|
||||
],
|
||||
Exception::GENERAL_CODES_DISABLED => [
|
||||
'name' => Exception::GENERAL_CODES_DISABLED,
|
||||
'description' => 'Invitation codes are disabled on this server. Please contact the server administrator.',
|
||||
'code' => 500,
|
||||
],
|
||||
|
||||
/** User Errors */
|
||||
Exception::USER_COUNT_EXCEEDED => [
|
||||
@@ -112,7 +107,7 @@ return [
|
||||
],
|
||||
Exception::USER_BLOCKED => [
|
||||
'name' => Exception::USER_BLOCKED,
|
||||
'description' => 'The current user has been blocked.',
|
||||
'description' => 'The current user has been blocked. You can unblock the user from the Appwrite console.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_INVALID_TOKEN => [
|
||||
@@ -130,8 +125,8 @@ return [
|
||||
'description' => 'Console registration is restricted to specific emails. Contact your administrator for more information.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_INVALID_CODE => [
|
||||
'name' => Exception::USER_INVALID_CODE,
|
||||
Exception::USER_CODE_INVALID => [
|
||||
'name' => Exception::USER_CODE_INVALID,
|
||||
'description' => 'The specified code is not valid. Contact your administrator for more information.',
|
||||
'code' => 401,
|
||||
],
|
||||
@@ -484,11 +479,6 @@ return [
|
||||
'description' => 'Project with the requested ID could not be found. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::PROJECT_ALREADY_EXISTS => [
|
||||
'name' => Exception::PROJECT_ALREADY_EXISTS,
|
||||
'description' => 'Project with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::PROJECT_UNKNOWN => [
|
||||
'name' => Exception::PROJECT_UNKNOWN,
|
||||
'description' => 'The project ID is either missing or not valid. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
|
||||
@@ -546,14 +536,9 @@ return [
|
||||
],
|
||||
Exception::DOMAIN_ALREADY_EXISTS => [
|
||||
'name' => Exception::DOMAIN_ALREADY_EXISTS,
|
||||
'description' => 'The requested domain is currently in use by a project.',
|
||||
'description' => 'A Domain with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOMAIN_FORBIDDEN => [
|
||||
'name' => Exception::DOMAIN_FORBIDDEN,
|
||||
'description' => 'The requested domain cannot be used as a custom domain.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::VARIABLE_NOT_FOUND => [
|
||||
'name' => Exception::VARIABLE_NOT_FOUND,
|
||||
'description' => 'Variable with the requested ID could not be found.',
|
||||
|
||||
@@ -2,8 +2,27 @@
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'name' => 'Default',
|
||||
'name' => 'default',
|
||||
'default' => true,
|
||||
'disabled' => false,
|
||||
]
|
||||
'domain' => '',
|
||||
],
|
||||
'nyc1' => [
|
||||
'name' => 'New york',
|
||||
'default' => false,
|
||||
'disabled' => false,
|
||||
'domain' => '172.17.0.1',
|
||||
],
|
||||
'spg1' => [
|
||||
'name' => 'Singapore',
|
||||
'default' => false,
|
||||
'disabled' => false,
|
||||
'domain' => '172.17.0.1',
|
||||
],
|
||||
'fra1' => [
|
||||
'name' => 'Frankfurt',
|
||||
'default' => false,
|
||||
'disabled' => false,
|
||||
'domain' => '172.17.0.1',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -199,4 +199,17 @@ return [
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'syncs' => [
|
||||
'key' => 'edge',
|
||||
'name' => 'edge',
|
||||
'subtitle' => 'Appwrite\'s cache edge sync Endpoint',
|
||||
'description' => 'Cache edge sync Endpoint',
|
||||
'controller' => 'api/edge.php',
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -82,12 +82,8 @@ App::post('/v1/account/invite')
|
||||
|
||||
$whitelistCodes = (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null)) : [];
|
||||
|
||||
if (empty($whitelistCodes)) {
|
||||
throw new Exception(Exception::GENERAL_CODES_DISABLED);
|
||||
}
|
||||
|
||||
if (!empty($whitelistCodes) && !\in_array($code, $whitelistCodes)) {
|
||||
throw new Exception(Exception::USER_INVALID_CODE);
|
||||
throw new Exception(Exception::USER_CODE_INVALID);
|
||||
}
|
||||
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
@@ -174,7 +170,7 @@ App::post('/v1/account')
|
||||
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
|
||||
$whitelistIPs = $project->getAttribute('authWhitelistIPs');
|
||||
|
||||
if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails) && !\in_array(strtoupper($email), $whitelistEmails)) {
|
||||
if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails)) {
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_WHITELISTED);
|
||||
}
|
||||
|
||||
@@ -566,10 +562,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
$name = $oauth2->getUserName($accessToken);
|
||||
$email = $oauth2->getUserEmail($accessToken);
|
||||
|
||||
if (empty($email)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'OAuth provider failed to return email.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is verified is not used yet, since we don't know after an accout is created anymore if it was verified or not.
|
||||
*/
|
||||
@@ -1789,12 +1781,6 @@ App::patch('/v1/account/status')
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
|
||||
}
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_ACCOUNT);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,16 +7,9 @@ use Appwrite\Utopia\Response;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\HexColor;
|
||||
use Utopia\Validator\Range;
|
||||
@@ -56,139 +49,11 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
->file($data)
|
||||
;
|
||||
unset($image);
|
||||
};
|
||||
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) {
|
||||
try {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = null;
|
||||
foreach ($sessions as $session) {
|
||||
if ($session->getAttribute('provider', '') === 'github') {
|
||||
$gitHubSession = $session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($gitHubSession)) {
|
||||
throw new Exception(Exception::GENERAL_UNKNOWN, 'GitHub session not found.');
|
||||
}
|
||||
|
||||
$provider = $gitHubSession->getAttribute('provider', '');
|
||||
$accessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
$accessTokenExpiry = $gitHubSession->getAttribute('providerAccessTokenExpiry');
|
||||
$refreshToken = $gitHubSession->getAttribute('providerRefreshToken');
|
||||
|
||||
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
|
||||
|
||||
if (!\class_exists($className)) {
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$oauth2 = new $className($appId, $appSecret, '', [], []);
|
||||
|
||||
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
|
||||
if ($isExpired) {
|
||||
try {
|
||||
$oauth2->refreshTokens($refreshToken);
|
||||
|
||||
$accessToken = $oauth2->getAccessToken('');
|
||||
$refreshToken = $oauth2->getRefreshToken('');
|
||||
|
||||
$verificationId = $oauth2->getUserID($accessToken);
|
||||
|
||||
if (empty($verificationId)) {
|
||||
throw new \Exception("Locked tokens."); // Race codition, handeled in catch
|
||||
}
|
||||
|
||||
$gitHubSession
|
||||
->setAttribute('providerAccessToken', $accessToken)
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Throwable $err) {
|
||||
$index = 0;
|
||||
do {
|
||||
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = new Document();
|
||||
foreach ($sessions as $session) {
|
||||
if ($session->getAttribute('provider', '') === 'github') {
|
||||
$gitHubSession = $session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$accessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
if ($accessToken !== $previousAccessToken) {
|
||||
break;
|
||||
}
|
||||
|
||||
$index++;
|
||||
\usleep(500000);
|
||||
} while ($index < 10);
|
||||
}
|
||||
}
|
||||
|
||||
$oauth2 = new $className($appId, $appSecret, '', [], []);
|
||||
$githubUser = $oauth2->getUserSlug($accessToken);
|
||||
$githubId = $oauth2->getUserID($accessToken);
|
||||
|
||||
return [
|
||||
'name' => $githubUser,
|
||||
'id' => $githubId
|
||||
];
|
||||
} catch (Exception $error) {
|
||||
if ($logger) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace('console');
|
||||
$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->addExtra('detailedTrace', $error->getTrace());
|
||||
|
||||
$log->setAction('avatarsGetGitHub');
|
||||
|
||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('GitHub error log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
App::get('/v1/avatars/credit-cards/:code')
|
||||
->desc('Get Credit Card Icon')
|
||||
->groups(['api', 'avatars'])
|
||||
@@ -295,7 +160,8 @@ App::get('/v1/avatars/image')
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
->file($data)
|
||||
;
|
||||
unset($image);
|
||||
});
|
||||
|
||||
@@ -408,7 +274,8 @@ App::get('/v1/avatars/favicon')
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->setContentType('image/x-icon')
|
||||
->file($data);
|
||||
->file($data)
|
||||
;
|
||||
}
|
||||
|
||||
$fetch = @\file_get_contents($outputHref, false);
|
||||
@@ -425,7 +292,8 @@ App::get('/v1/avatars/favicon')
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
->file($data)
|
||||
;
|
||||
unset($image);
|
||||
});
|
||||
|
||||
@@ -466,7 +334,8 @@ App::get('/v1/avatars/qr')
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->send($image->output('png', 9));
|
||||
->send($image->output('png', 9))
|
||||
;
|
||||
});
|
||||
|
||||
App::get('/v1/avatars/initials')
|
||||
@@ -550,680 +419,6 @@ App::get('/v1/avatars/initials')
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->file($image->getImageBlob());
|
||||
});
|
||||
|
||||
App::get('/v1/cards/cloud')
|
||||
->desc('Get Front Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud')
|
||||
->label('cache.resource', 'card/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
|
||||
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$name = $user->getAttribute('name', 'Anonymous');
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
$employeeNumber = $isEmployee ? $employees[$email]['spot'] : '';
|
||||
|
||||
if ($isHero) {
|
||||
$createdAt = new \DateTime($heroes[$email]['memberSince'] ?? '');
|
||||
} elseif ($isEmployee) {
|
||||
$createdAt = new \DateTime($employees[$email]['memberSince'] ?? '');
|
||||
}
|
||||
|
||||
if (!$isEmployee && !empty($githubName)) {
|
||||
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
|
||||
if (!empty($employeeGitHub)) {
|
||||
$isEmployee = true;
|
||||
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
|
||||
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$isPlatinum = $user->getInternalId() % 100 === 0;
|
||||
} else {
|
||||
$name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
|
||||
$createdAt = new \DateTime('now');
|
||||
$githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrian-junior' : 'walterobrian');
|
||||
$isHero = $mock === 'hero';
|
||||
$isContributor = $mock === 'contributor';
|
||||
$isEmployee = \str_starts_with($mock, 'employee');
|
||||
$employeeNumber = match ($mock) {
|
||||
'employee' => '1',
|
||||
'employee-2digit' => '18',
|
||||
default => ''
|
||||
};
|
||||
|
||||
$isPlatinum = $mock === 'platinum';
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$isContributor = false;
|
||||
$isHero = false;
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$isContributor = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$isHero = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
$memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o'));
|
||||
|
||||
$imagePath = $isGolden ? 'front-golden.png' : ($isPlatinum ? 'front-platinum.png' : 'front.png');
|
||||
|
||||
$baseImage = new \Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $imagePath);
|
||||
|
||||
if ($isEmployee) {
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/employee.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 35);
|
||||
|
||||
$text = new \ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
|
||||
$text->setFillColor(new \ImagickPixel('#FFFADF'));
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 2 ? 54 : 48);
|
||||
$text->setFontWeight(700);
|
||||
$metricsText = $baseImage->queryFontMetrics($text, $employeeNumber);
|
||||
|
||||
$hashtag = new \ImagickDraw();
|
||||
$hashtag->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$hashtag->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
|
||||
$hashtag->setFillColor(new \ImagickPixel('#FFFADF'));
|
||||
$hashtag->setFontSize(28);
|
||||
$hashtag->setFontWeight(700);
|
||||
$metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#');
|
||||
|
||||
$startX = 898;
|
||||
$totalWidth = $metricsHashtag['textWidth'] + 12 + $metricsText['textWidth'];
|
||||
|
||||
$hashtagX = ($metricsHashtag['textWidth'] / 2);
|
||||
$textX = $hashtagX + 12 + ($metricsText['textWidth'] / 2);
|
||||
|
||||
$hashtagX -= $totalWidth / 2;
|
||||
$textX -= $totalWidth / 2;
|
||||
|
||||
$hashtagX += $startX;
|
||||
$textX += $startX;
|
||||
|
||||
$baseImage->annotateImage($hashtag, $hashtagX, 150, 0, '#');
|
||||
$baseImage->annotateImage($text, $textX, 150, 0, $employeeNumber);
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/contributor.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34);
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/hero.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34);
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $name = \iconv("utf-8", "ascii//TRANSLIT", $name);
|
||||
// $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince);
|
||||
// $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName);
|
||||
|
||||
$text = new \ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont(__DIR__ . '/../../../public/fonts/Poppins-Bold.ttf');
|
||||
$text->setFillColor(new \ImagickPixel('#FFFFFF'));
|
||||
|
||||
if (\strlen($name) > 32) {
|
||||
$name = \substr($name, 0, 32);
|
||||
}
|
||||
|
||||
if (\strlen($name) <= 23) {
|
||||
$text->setFontSize(80);
|
||||
$scalingDown = false;
|
||||
} else {
|
||||
$text->setFontSize(54);
|
||||
$scalingDown = true;
|
||||
}
|
||||
$text->setFontWeight(700);
|
||||
$baseImage->annotateImage($text, 512, 477, 0, $name);
|
||||
|
||||
$text = new \ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-SemiBold.ttf');
|
||||
$text->setFillColor(new \ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC'));
|
||||
$text->setFontSize(27);
|
||||
$text->setFontWeight(600);
|
||||
$text->setTextKerning(1.08);
|
||||
$baseImage->annotateImage($text, 512, 541, 0, \strtoupper($memberSince));
|
||||
|
||||
if (!empty($githubName)) {
|
||||
$text = new \ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Regular.ttf');
|
||||
$text->setFillColor(new \ImagickPixel('#FFFFFF'));
|
||||
$text->setFontSize($scalingDown ? 28 : 32);
|
||||
$text->setFontWeight(400);
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$baseImage->annotateImage($text, 512 + 20 + 4, 373 + ($scalingDown ? 2 : 0), 0, $githubName);
|
||||
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$precisionFix = 5;
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2) - 20 - 4, 373 - ($metrics['textHeight'] - $precisionFix));
|
||||
}
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
});
|
||||
|
||||
App::get('/v1/cards/cloud-back')
|
||||
->desc('Get Back Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud-back')
|
||||
->label('cache.resource', 'card-back/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['golden', 'normal', 'platinum']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
|
||||
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$userId = $user->getId();
|
||||
$email = $user->getAttribute('email', '');
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $user->getInternalId() % 100 === 0;
|
||||
} else {
|
||||
$userId = '63e0bcf3c3eb803ba530';
|
||||
|
||||
$isGolden = $mock === 'golden';
|
||||
$isPlatinum = $mock === 'platinum';
|
||||
}
|
||||
|
||||
$userId = 'UID ' . $userId;
|
||||
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
|
||||
$imagePath = $isGolden ? 'back-golden.png' : ($isPlatinum ? 'back-platinum.png' : 'back.png');
|
||||
|
||||
$baseImage = new \Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $imagePath);
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId);
|
||||
|
||||
$text = new \ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont(__DIR__ . '/../../../public/fonts/SourceCodePro-Regular.ttf');
|
||||
$text->setFillColor(new \ImagickPixel($isGolden ? '#664A1E' : ($isPlatinum ? '#555555' : '#E8E9F0')));
|
||||
$text->setFontSize(28);
|
||||
$text->setFontWeight(400);
|
||||
$baseImage->annotateImage($text, 512, 596, 0, $userId);
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
});
|
||||
|
||||
App::get('/v1/cards/cloud-og')
|
||||
->desc('Get OG Image From Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud-og')
|
||||
->label('cache.resource', 'card-og/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long', 'normal-long-right', 'normal-long-middle', 'normal-bg2', 'normal-bg3', 'normal-right', 'normal-middle', 'platinum-right', 'platinum-middle', 'hero-middle', 'hero-right', 'contributor-right', 'employee-right', 'contributor-middle', 'employee-middle', 'employee-2digit-middle', 'employee-2digit-right']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 1024), 'Resize image card width, Pass an integer between 0 to 1024.', true)
|
||||
->param('height', 0, new Range(0, 1024), 'Resize image card height, Pass an integer between 0 to 1024.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$internalId = $user->getInternalId();
|
||||
$bgVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3');
|
||||
$cardVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3');
|
||||
|
||||
$name = $user->getAttribute('name', 'Anonymous');
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
$employeeNumber = $isEmployee ? $employees[$email]['spot'] : '';
|
||||
|
||||
if ($isHero) {
|
||||
$createdAt = new \DateTime($heroes[$email]['memberSince'] ?? '');
|
||||
} elseif ($isEmployee) {
|
||||
$createdAt = new \DateTime($employees[$email]['memberSince'] ?? '');
|
||||
}
|
||||
|
||||
if (!$isEmployee && !empty($githubName)) {
|
||||
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
|
||||
if (!empty($employeeGitHub)) {
|
||||
$isEmployee = true;
|
||||
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
|
||||
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$isPlatinum = $user->getInternalId() % 100 === 0;
|
||||
} else {
|
||||
$bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1');
|
||||
$cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1');
|
||||
$name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
|
||||
$createdAt = new \DateTime('now');
|
||||
$githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrian-junior' : 'walterobrian');
|
||||
$isHero = \str_starts_with($mock, 'hero');
|
||||
$isContributor = \str_starts_with($mock, 'contributor');
|
||||
$isEmployee = \str_starts_with($mock, 'employee');
|
||||
$employeeNumber = match ($mock) {
|
||||
'employee' => '1',
|
||||
'employee-right' => '1',
|
||||
'employee-middle' => '1',
|
||||
'employee-2digit' => '18',
|
||||
'employee-2digit-right' => '18',
|
||||
'employee-2digit-middle' => '18',
|
||||
default => ''
|
||||
};
|
||||
|
||||
$isPlatinum = \str_starts_with($mock, 'platinum');
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$isContributor = false;
|
||||
$isHero = false;
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$isContributor = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$isHero = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
$memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o'));
|
||||
|
||||
$baseImage = new \Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-background{$bgVariation}.png");
|
||||
|
||||
$cardType = $isGolden ? '-golden' : ($isPlatinum ? '-platinum' : '');
|
||||
|
||||
$image = new Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-card{$cardType}{$cardVariation}.png");
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 1008 / 2 - $image->getImageWidth() / 2, 1008 / 2 - $image->getImageHeight() / 2);
|
||||
|
||||
$imageLogo = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/og-background-logo.png');
|
||||
$imageShadow = new Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-shadow{$cardType}.png");
|
||||
if ($cardVariation === '1') {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -450, 700);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -20, 710);
|
||||
} else {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -135, 710);
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$file = $cardVariation === '3' ? 'employee-skew.png' : 'employee.png';
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
$hashtag = new \ImagickDraw();
|
||||
$hashtag->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$hashtag->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
|
||||
$hashtag->setFillColor(new \ImagickPixel('#FFFADF'));
|
||||
$hashtag->setFontSize(20);
|
||||
$hashtag->setFontWeight(700);
|
||||
|
||||
$text = new \ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
|
||||
$text->setFillColor(new \ImagickPixel('#FFFADF'));
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 1 ? 36 : 28);
|
||||
$text->setFontWeight(700);
|
||||
|
||||
if ($cardVariation === '3') {
|
||||
$hashtag->setFontSize(16);
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 1 ? 30 : 26);
|
||||
|
||||
$hashtag->skewY(20);
|
||||
$hashtag->skewX(20);
|
||||
$text->skewY(20);
|
||||
$text->skewX(20);
|
||||
}
|
||||
|
||||
$metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#');
|
||||
$metricsText = $baseImage->queryFontMetrics($text, $employeeNumber);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metricsHashtag['textWidth'] + 6 + $metricsText['textWidth'];
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$group->newImage($groupWidth, $metricsText['textHeight'], '#00000000');
|
||||
$group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#');
|
||||
$group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber);
|
||||
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), -22);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 660, 245);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 655, 247);
|
||||
}
|
||||
} elseif ($cardVariation === '2') {
|
||||
$group->newImage($groupWidth, $metricsText['textHeight'], '#00000000');
|
||||
$group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#');
|
||||
$group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber);
|
||||
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), 32);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 775, 465);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 767, 470);
|
||||
}
|
||||
} else {
|
||||
$group->newImage(300, 300, '#00000000');
|
||||
|
||||
$hashtag->annotation(0, $metricsText['textHeight'], '#');
|
||||
$text->annotation($metricsHashtag['textWidth'] + 2, $metricsText['textHeight'], $employeeNumber);
|
||||
|
||||
$group->drawImage($hashtag);
|
||||
$group->drawImage($text);
|
||||
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 670, 317);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 663, 322);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$file = $cardVariation === '3' ? 'contributor-skew.png' : 'contributor.png';
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
} else {
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$file = $cardVariation === '3' ? 'hero-skew.png' : 'hero.png';
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
} else {
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
}
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $name = \iconv("utf-8", "ascii//TRANSLIT", $name);
|
||||
// $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince);
|
||||
// $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName);
|
||||
|
||||
$textName = new \ImagickDraw();
|
||||
$textName->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$textName->setFont(__DIR__ . '/../../../public/fonts/Poppins-Bold.ttf');
|
||||
$textName->setFillColor(new \ImagickPixel('#FFFFFF'));
|
||||
|
||||
if (\strlen($name) > 32) {
|
||||
$name = \substr($name, 0, 32);
|
||||
}
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(54);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(36);
|
||||
}
|
||||
} elseif ($cardVariation === '2') {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(50);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(34);
|
||||
}
|
||||
} else {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(44);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(32);
|
||||
}
|
||||
}
|
||||
|
||||
$textName->setFontWeight(700);
|
||||
|
||||
$textMember = new \ImagickDraw();
|
||||
$textMember->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$textMember->setFont(__DIR__ . '/../../../public/fonts/Inter-Medium.ttf');
|
||||
$textMember->setFillColor(new \ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC'));
|
||||
$textMember->setFontWeight(500);
|
||||
$textMember->setTextKerning(1.12);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$textMember->setFontSize(21);
|
||||
|
||||
$baseImage->annotateImage($textName, 550, 600, -22, $name);
|
||||
$baseImage->annotateImage($textMember, 585, 635, -22, $memberSince);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$textMember->setFontSize(20);
|
||||
|
||||
$baseImage->annotateImage($textName, 435, 590, 31.37, $name);
|
||||
$baseImage->annotateImage($textMember, 412, 628, 31.37, $memberSince);
|
||||
} else {
|
||||
$textMember->setFontSize(16);
|
||||
|
||||
$textName->skewY(20);
|
||||
$textName->skewX(20);
|
||||
$textName->annotation(320, 700, $name);
|
||||
|
||||
$textMember->skewY(20);
|
||||
$textMember->skewX(20);
|
||||
$textMember->annotation(330, 735, $memberSince);
|
||||
|
||||
$baseImage->drawImage($textName);
|
||||
$baseImage->drawImage($textMember);
|
||||
}
|
||||
|
||||
if (!empty($githubName)) {
|
||||
$text = new \ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Regular.ttf');
|
||||
$text->setFillColor(new \ImagickPixel('#FFFFFF'));
|
||||
$text->setFontSize($scalingDown ? 16 : 20);
|
||||
$text->setFontWeight(400);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metrics['textWidth'] + 32 + 4;
|
||||
$group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000');
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
|
||||
$precisionFix = -1;
|
||||
|
||||
$group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0);
|
||||
$group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), -22);
|
||||
$x = 510 - $group->getImageWidth() / 2;
|
||||
$y = 530 - $group->getImageHeight() / 2;
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metrics['textWidth'] + 32 + 4;
|
||||
$group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000');
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
|
||||
$precisionFix = -1;
|
||||
|
||||
$group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0);
|
||||
$group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), 31.11);
|
||||
$x = 485 - $group->getImageWidth() / 2;
|
||||
$y = 530 - $group->getImageHeight() / 2;
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y);
|
||||
} else {
|
||||
$text->skewY(20);
|
||||
$text->skewX(20);
|
||||
$text->setTextAlignment(\Imagick::ALIGN_CENTER);
|
||||
|
||||
$text->annotation(320 + 15 + 2, 640, $githubName);
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github-skew.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2), 518 + \strlen($githubName) * 1.3);
|
||||
|
||||
$baseImage->drawImage($text);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
->file($image->getImageBlob())
|
||||
;
|
||||
});
|
||||
|
||||
@@ -179,7 +179,7 @@ App::post('/v1/databases')
|
||||
]));
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
$collections = (Config::getParam('collections', [])['databases'] ?? [])['collections'] ?? [];
|
||||
$collections = Config::getParam('collections', [])['collections'] ?? [];
|
||||
if (empty($collections)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.');
|
||||
}
|
||||
@@ -442,8 +442,8 @@ App::delete('/v1/databases/:databaseId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->action(function (string $databaseId, Response $response, Database $dbForProject, Event $events, Delete $deletes) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $databaseId, Response $response, Database $dbForProject, Event $events, Delete $queueForDeletes) {
|
||||
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
@@ -457,7 +457,7 @@ App::delete('/v1/databases/:databaseId')
|
||||
|
||||
$dbForProject->deleteCachedCollection('databases' . $database->getInternalId());
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($database)
|
||||
;
|
||||
@@ -814,8 +814,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, Event $events, Delete $deletes) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, Event $events, Delete $queueForDeletes) {
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
@@ -835,7 +835,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
||||
|
||||
$dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($collection)
|
||||
;
|
||||
@@ -2380,9 +2380,9 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->inject('queueForDeletes')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, Response $response, Database $dbForProject, Event $events, Delete $deletes, string $mode) {
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, Response $response, Database $dbForProject, Event $events, Delete $queueForDeletes, string $mode) {
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
@@ -2430,7 +2430,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
$document->setAttribute('$collectionId', $collectionId);
|
||||
$document->setAttribute('$databaseId', $databaseId);
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_AUDIT)
|
||||
->setDocument($document)
|
||||
;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::init()
|
||||
->groups(['edge'])
|
||||
->inject('request')
|
||||
->action(function (Request $request) {
|
||||
|
||||
$token = $request->getHeader('authorization');
|
||||
$token = str_replace(["Bearer"," "], "", $token);
|
||||
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 600, 10);
|
||||
try {
|
||||
$payload = $jwt->decode($token);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
App::post('/v1/edge/sync')
|
||||
->desc('Purge cache keys')
|
||||
->groups(['edge'])
|
||||
->label('scope', 'public')
|
||||
->param('keys', '', new ArrayList(new Text(9012), 600), 'Cache keys. an array containing alphanumerical cache keys')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('queueForEdgeSyncIn')
|
||||
->action(function (array $keys, Request $request, Response $response, Client $queueForEdgeSyncIn) {
|
||||
|
||||
if (empty($keys)) {
|
||||
throw new Exception(Exception::KEY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$originEdgeUrl = $request->getHeader('origin-edge-url');
|
||||
$time = DateTime::now();
|
||||
|
||||
var_dump('[' . $time . '] incoming request from:' . $originEdgeUrl);
|
||||
var_dump($keys);
|
||||
|
||||
foreach ($keys as $parts) {
|
||||
$key = json_decode($parts);
|
||||
$queueForEdgeSyncIn
|
||||
->enqueue([
|
||||
'type' => $key->type,
|
||||
'key' => $key->key
|
||||
]);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'keys' => $keys
|
||||
]), Response::MODEL_EDGE_SYNC);
|
||||
});
|
||||
@@ -84,6 +84,7 @@ App::post('/v1/functions')
|
||||
'deployment' => '',
|
||||
'events' => $events,
|
||||
'schedule' => $schedule,
|
||||
'scheduleUpdatedAt' => DateTime::now(),
|
||||
'timeout' => $timeout,
|
||||
'search' => implode(' ', [$functionId, $name, $runtime])
|
||||
]));
|
||||
@@ -462,6 +463,7 @@ App::put('/v1/functions/:functionId')
|
||||
'name' => $name,
|
||||
'events' => $events,
|
||||
'schedule' => $schedule,
|
||||
'scheduleUpdatedAt' => DateTime::now(),
|
||||
'timeout' => $timeout,
|
||||
'enabled' => $enabled,
|
||||
'search' => implode(' ', [$functionId, $name, $function->getAttribute('runtime')]),
|
||||
@@ -469,11 +471,18 @@ App::put('/v1/functions/:functionId')
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
|
||||
/**
|
||||
* In case we want to clear the schedule
|
||||
*/
|
||||
if (!empty($function->getAttribute('deployment'))) {
|
||||
$schedule->setAttribute('resourceUpdatedAt', $function->getAttribute('scheduleUpdatedAt'));
|
||||
}
|
||||
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
|
||||
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$eventsInstance->setParam('functionId', $function->getId());
|
||||
@@ -529,10 +538,15 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
])));
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
|
||||
$active = !empty($function->getAttribute('schedule'));
|
||||
|
||||
if ($active) {
|
||||
$schedule->setAttribute('resourceUpdatedAt', datetime::now());
|
||||
}
|
||||
|
||||
$schedule->setAttribute('active', $active);
|
||||
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$events
|
||||
@@ -558,11 +572,11 @@ App::delete('/v1/functions/:functionId')
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('deletes')
|
||||
->inject('queueForDeletes')
|
||||
->inject('events')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $deletes, Event $events, Document $project, Database $dbForConsole) {
|
||||
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $events, Document $project, Database $dbForConsole) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -583,7 +597,7 @@ App::delete('/v1/functions/:functionId')
|
||||
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($function);
|
||||
|
||||
@@ -772,6 +786,22 @@ App::post('/v1/functions/:functionId/deployments')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Should we update also the function collection with the scheduleUpdatedAt attr?
|
||||
*/
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
|
||||
$active = !empty($function->getAttribute('schedule'));
|
||||
|
||||
if ($active) {
|
||||
$schedule->setAttribute('resourceUpdatedAt', datetime::now());
|
||||
}
|
||||
|
||||
$schedule->setAttribute('active', $active);
|
||||
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$metadata = null;
|
||||
|
||||
$events
|
||||
@@ -909,10 +939,10 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('deletes')
|
||||
->inject('queueForDeletes')
|
||||
->inject('events')
|
||||
->inject('deviceFunctions')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $deletes, Event $events, Device $deviceFunctions) {
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $events, Device $deviceFunctions) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
if ($function->isEmpty()) {
|
||||
@@ -944,7 +974,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($deployment);
|
||||
|
||||
@@ -1167,7 +1197,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||
variables: $vars,
|
||||
timeout: $function->getAttribute('timeout', 0),
|
||||
image: $runtime['image'],
|
||||
source: $build->getAttribute('outputPath', ''),
|
||||
source: $build->getAttribute('path', ''),
|
||||
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
||||
);
|
||||
|
||||
@@ -1351,8 +1381,7 @@ App::post('/v1/functions/:functionId/variables')
|
||||
->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', false)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -1381,13 +1410,6 @@ App::post('/v1/functions/:functionId/variables')
|
||||
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$dbForProject->deleteCachedDocument('functions', $function->getId());
|
||||
|
||||
$response
|
||||
@@ -1475,8 +1497,7 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -1505,13 +1526,6 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$dbForProject->deleteCachedDocument('functions', $function->getId());
|
||||
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
@@ -1533,8 +1547,7 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -1551,14 +1564,6 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('variables', $variable->getId());
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$dbForProject->deleteCachedDocument('functions', $function->getId());
|
||||
|
||||
$response->noContent();
|
||||
|
||||
@@ -18,18 +18,17 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
@@ -90,81 +89,46 @@ App::post('/v1/projects')
|
||||
}
|
||||
|
||||
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
|
||||
|
||||
$backups['database_db_fra1_02'] = ['from' => '7:30', 'to' => '8:15'];
|
||||
$backups['database_db_fra1_03'] = ['from' => '10:30', 'to' => '11:15'];
|
||||
$backups['database_db_fra1_04'] = ['from' => '13:30', 'to' => '14:15'];
|
||||
$backups['database_db_fra1_05'] = ['from' => '4:30', 'to' => '5:15'];
|
||||
|
||||
$databases = Config::getParam('pools-database', []);
|
||||
|
||||
/**
|
||||
* Extract db from list while backing
|
||||
*/
|
||||
if (count($databases) > 1) {
|
||||
$now = new \DateTime();
|
||||
|
||||
foreach ($databases as $index => $database) {
|
||||
if (empty($backups[$database])) {
|
||||
continue;
|
||||
}
|
||||
$backup = $backups[$database];
|
||||
$from = \DateTime::createFromFormat('H:i', $backup['from']);
|
||||
$to = \DateTime::createFromFormat('H:i', $backup['to']);
|
||||
if ($now >= $from && $now <= $to) {
|
||||
unset($databases[$index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($index = array_search('database_db_fra1_05', $databases)) {
|
||||
$database = $databases[$index];
|
||||
} else {
|
||||
$database = $databases[array_rand($databases)];
|
||||
}
|
||||
$database = $databases[array_rand($databases)];
|
||||
|
||||
if ($projectId === 'console') {
|
||||
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
|
||||
}
|
||||
|
||||
try {
|
||||
$project = $dbForConsole->createDocument('projects', new Document([
|
||||
'$id' => $projectId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'name' => $name,
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'region' => $region,
|
||||
'description' => $description,
|
||||
'logo' => $logo,
|
||||
'url' => $url,
|
||||
'version' => APP_VERSION_STABLE,
|
||||
'legalName' => $legalName,
|
||||
'legalCountry' => $legalCountry,
|
||||
'legalState' => $legalState,
|
||||
'legalCity' => $legalCity,
|
||||
'legalAddress' => $legalAddress,
|
||||
'legalTaxId' => ID::custom($legalTaxId),
|
||||
'services' => new stdClass(),
|
||||
'platforms' => null,
|
||||
'authProviders' => [],
|
||||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'domains' => null,
|
||||
'auths' => $auths,
|
||||
'search' => implode(' ', [$projectId, $name]),
|
||||
'database' => $database
|
||||
]));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
|
||||
}
|
||||
$project = $dbForConsole->createDocument('projects', new Document([
|
||||
'$id' => $projectId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'name' => $name,
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'region' => $region,
|
||||
'description' => $description,
|
||||
'logo' => $logo,
|
||||
'url' => $url,
|
||||
'version' => APP_VERSION_STABLE,
|
||||
'legalName' => $legalName,
|
||||
'legalCountry' => $legalCountry,
|
||||
'legalState' => $legalState,
|
||||
'legalCity' => $legalCity,
|
||||
'legalAddress' => $legalAddress,
|
||||
'legalTaxId' => ID::custom($legalTaxId),
|
||||
'services' => new stdClass(),
|
||||
'platforms' => null,
|
||||
'authProviders' => [],
|
||||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'domains' => null,
|
||||
'auths' => $auths,
|
||||
'search' => implode(' ', [$projectId, $name]),
|
||||
'database' => $database,
|
||||
]));
|
||||
|
||||
$dbForProject = new Database($pools->get($database)->pop()->getResource(), $cache);
|
||||
$dbForProject->setNamespace("_{$project->getInternalId()}");
|
||||
@@ -177,7 +141,7 @@ App::post('/v1/projects')
|
||||
$adapter->setup();
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', [])['projects'] ?? [];
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
foreach ($collections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
@@ -519,8 +483,8 @@ App::delete('/v1/projects/:projectId')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForConsole')
|
||||
->inject('deletes')
|
||||
->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Delete $deletes) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Delete $queueForDeletes) {
|
||||
|
||||
if (!Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
|
||||
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
|
||||
@@ -532,7 +496,7 @@ App::delete('/v1/projects/:projectId')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($project)
|
||||
;
|
||||
@@ -1227,12 +1191,9 @@ App::post('/v1/projects/:projectId/domains')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($domain === App::getEnv('_APP_DOMAIN', '') || $domain === App::getEnv('_APP_DOMAIN_TARGET', '')) {
|
||||
throw new Exception(Exception::DOMAIN_FORBIDDEN);
|
||||
}
|
||||
|
||||
$document = $dbForConsole->findOne('domains', [
|
||||
Query::equal('domain', [$domain])
|
||||
Query::equal('domain', [$domain]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($document && !$document->isEmpty()) {
|
||||
@@ -1353,7 +1314,8 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
|
||||
->param('domainId', '', new UID(), 'Domain unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) {
|
||||
->inject('queueForCertificates')
|
||||
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, Certificate $queueForCertificates) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1391,8 +1353,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification')
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
|
||||
// Issue a TLS certificate when domain is verified
|
||||
$event = new Certificate();
|
||||
$event
|
||||
$queueForCertificates
|
||||
->setDomain($domain)
|
||||
->trigger();
|
||||
|
||||
@@ -1412,8 +1373,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
||||
->param('domainId', '', new UID(), 'Domain unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('deletes')
|
||||
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, Delete $deletes) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, Delete $queueForDeletes) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1430,15 +1391,11 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
||||
throw new Exception(Exception::DOMAIN_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($domain->getAttribute('domain') === App::getEnv('_APP_DOMAIN', '') || $domain->getAttribute('domain') === App::getEnv('_APP_DOMAIN_TARGET', '')) {
|
||||
throw new Exception(Exception::DOMAIN_FORBIDDEN);
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('domains', $domain->getId());
|
||||
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_CERTIFICATES)
|
||||
->setDocument($domain);
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ App::post('/v1/storage/buckets')
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
|
||||
try {
|
||||
$files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? [];
|
||||
$files = Config::getParam('collections', [])['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Files collection is not configured.');
|
||||
}
|
||||
@@ -300,9 +300,9 @@ App::delete('/v1/storage/buckets/:bucketId')
|
||||
->param('bucketId', '', new UID(), 'Bucket unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('deletes')
|
||||
->inject('queueForDeletes')
|
||||
->inject('events')
|
||||
->action(function (string $bucketId, Response $response, Database $dbForProject, Delete $deletes, Event $events) {
|
||||
->action(function (string $bucketId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $events) {
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
if ($bucket->isEmpty()) {
|
||||
@@ -313,7 +313,7 @@ App::delete('/v1/storage/buckets/:bucketId')
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove bucket from DB');
|
||||
}
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($bucket);
|
||||
|
||||
@@ -359,7 +359,8 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->inject('mode')
|
||||
->inject('deviceFiles')
|
||||
->inject('deviceLocal')
|
||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal, Delete $queueForDeletes) {
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
@@ -641,6 +642,11 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->setContext('bucket', $bucket)
|
||||
;
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_CACHE_BY_RESOURCE)
|
||||
->setResource('file/' . $file->getId())
|
||||
;
|
||||
|
||||
$metadata = null; // was causing leaks as it was passed by reference
|
||||
|
||||
$response
|
||||
@@ -1358,8 +1364,8 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->inject('events')
|
||||
->inject('mode')
|
||||
->inject('deviceFiles')
|
||||
->inject('deletes')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $events, string $mode, Device $deviceFiles, Delete $deletes) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $events, string $mode, Device $deviceFiles, Delete $queueForDeletes) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
@@ -1396,7 +1402,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
}
|
||||
|
||||
if ($deviceDeleted) {
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_CACHE_BY_RESOURCE)
|
||||
->setResource('file/' . $fileId)
|
||||
;
|
||||
|
||||
@@ -246,8 +246,8 @@ App::delete('/v1/teams/:teamId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject, Event $events, Delete $deletes) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject, Event $events, Delete $queueForDeletes) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -271,7 +271,7 @@ App::delete('/v1/teams/:teamId')
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
|
||||
}
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($team);
|
||||
|
||||
|
||||
@@ -1070,8 +1070,8 @@ App::delete('/v1/users/:userId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $events, Delete $deletes) {
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Event $events, Delete $queueForDeletes) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -1084,7 +1084,7 @@ App::delete('/v1/users/:userId')
|
||||
|
||||
$dbForProject->deleteDocument('users', $userId);
|
||||
|
||||
$deletes
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($clone);
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
App::init()
|
||||
->groups(['api', 'auth', 'avatars', 'functions', 'account', 'projects', 'storage', 'teams', 'users'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -51,7 +52,8 @@ App::init()
|
||||
->inject('locale')
|
||||
->inject('clients')
|
||||
->inject('servers')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients, array $servers) {
|
||||
->inject('queueForCertificates')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients, array $servers, Certificate $queueForCertificates) {
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
@@ -122,7 +124,7 @@ App::init()
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
|
||||
(new Certificate())
|
||||
$queueForCertificates
|
||||
->setDomain($domainDocument)
|
||||
->trigger();
|
||||
}
|
||||
@@ -425,7 +427,6 @@ App::error()
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('database', $project->getAttribute('database', 'console'));
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
@@ -438,7 +439,7 @@ App::error()
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
$log->addExtra('roles', Authorization::$roles);
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
@@ -581,7 +582,7 @@ App::get('/humans.txt')
|
||||
$response->text($template->render(false));
|
||||
});
|
||||
|
||||
App::get('/.well-known/acme-challenge/*')
|
||||
App::get('/.well-known/acme-challenge')
|
||||
->desc('SSL Verification')
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
|
||||
@@ -22,6 +22,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Queue\Client;
|
||||
|
||||
$parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) {
|
||||
preg_match_all('/{(.*?)}/', $label, $matches);
|
||||
@@ -101,11 +102,11 @@ App::init()
|
||||
->inject('audits')
|
||||
->inject('mails')
|
||||
->inject('usage')
|
||||
->inject('deletes')
|
||||
->inject('queueForDeletes')
|
||||
->inject('database')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) use ($databaseListener) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $queueForDeletes, EventDatabase $database, Database $dbForProject, string $mode) use ($databaseListener) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
@@ -199,7 +200,7 @@ App::init()
|
||||
->setParam('project.{scope}.network.inbound', 0)
|
||||
->setParam('project.{scope}.network.outbound', 0);
|
||||
|
||||
$deletes->setProject($project);
|
||||
$queueForDeletes->setProject($project);
|
||||
$database->setProject($project);
|
||||
|
||||
$dbForProject->on(Database::EVENT_DOCUMENT_CREATE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
||||
@@ -328,12 +329,13 @@ App::shutdown()
|
||||
->inject('events')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('deletes')
|
||||
->inject('queueForDeletes')
|
||||
->inject('database')
|
||||
->inject('mode')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForFunctions')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject, Func $queueForFunctions) use ($parseLabel) {
|
||||
->inject('queueForEdgeSyncOut')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject, Func $queueForFunctions, Client $queueForEdgeSyncOut) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -388,9 +390,32 @@ App::shutdown()
|
||||
'userId' => $events->getParam('userId')
|
||||
]
|
||||
);
|
||||
//Sync with other regions
|
||||
$queueForEdgeSyncOut->enqueue([
|
||||
'type' => 'realtime',
|
||||
'key' => [
|
||||
'projectId' => $target['projectId'] ?? $project->getId(),
|
||||
'payload' => $events->getPayload(),
|
||||
'events' => $allEvents,
|
||||
'channels' => $target['channels'],
|
||||
'roles' => $target['roles'],
|
||||
'options' => [
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
'userId' => $events->getParam('userId')
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// $queueForEdgeSyncOut->enqueue([
|
||||
// 'type' => 'certificate',
|
||||
// 'key' => [
|
||||
// 'domain' => 'appwrite.io',
|
||||
// 'contents' => base64_encode(file_get_contents(APP_STORAGE_CERTIFICATES . '/appwrite.io.tar.gz')),
|
||||
// ]
|
||||
// ]);
|
||||
|
||||
$route = $utopia->match($request);
|
||||
$requestParams = $route->getParamsValues();
|
||||
$user = $audits->getUser();
|
||||
@@ -465,7 +490,7 @@ App::shutdown()
|
||||
]) ;
|
||||
|
||||
$signature = md5($data);
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cacheLog = $dbForProject->getDocument('cache', $key);
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
@@ -511,6 +536,7 @@ App::shutdown()
|
||||
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
|
||||
if (!empty($file)) {
|
||||
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
|
||||
}
|
||||
|
||||
@@ -1,61 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
|
||||
App::get('/console/*')
|
||||
App::get('/console')
|
||||
->alias('/')
|
||||
->alias('auth/*')
|
||||
->alias('/invite')
|
||||
->alias('/login')
|
||||
->alias('/recover')
|
||||
->alias('/register/*')
|
||||
->alias('/register')
|
||||
->groups(['web'])
|
||||
->label('permission', 'public')
|
||||
->label('scope', 'home')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
->action(function (Response $response) {
|
||||
$fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
|
||||
|
||||
// Card SSR
|
||||
if (\str_starts_with($request->getURI(), '/card')) {
|
||||
$urlCunks = \explode('/', $request->getURI());
|
||||
$userId = $urlCunks[\count($urlCunks) - 1] ?? '';
|
||||
|
||||
$domain = $request->getProtocol() . '://' . $request->getHostname();
|
||||
|
||||
if (!empty($userId)) {
|
||||
$ogImageUrl = $domain . '/v1/cards/cloud-og?userId=' . $userId;
|
||||
} else {
|
||||
$ogImageUrl = $domain . '/v1/cards/cloud-og?mock=normal';
|
||||
}
|
||||
|
||||
$ogTags = [
|
||||
'<title>Appwrite Cloud Card</title>',
|
||||
'<meta name="description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
|
||||
'<meta property="og:url" content="' . $domain . $request->getURI() . '">',
|
||||
'<meta name="og:image:type" content="image/png">',
|
||||
'<meta name="og:image:width" content="1008">',
|
||||
'<meta name="og:image:height" content="1008">',
|
||||
'<meta property="og:type" content="website">',
|
||||
'<meta property="og:title" content="Appwrite Cloud Card">',
|
||||
'<meta property="og:description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
|
||||
'<meta property="og:image" content="' . $ogImageUrl . '">',
|
||||
'<meta name="twitter:card" content="summary_large_image">',
|
||||
'<meta property="twitter:domain" content="' . $request->getHostname() . '">',
|
||||
'<meta property="twitter:url" content="' . $domain . $request->getURI() . '">',
|
||||
'<meta name="twitter:title" content="Appwrite Cloud Card">',
|
||||
'<meta name="twitter:image:type" content="image/png">',
|
||||
'<meta name="twitter:image:width" content="1008">',
|
||||
'<meta name="twitter:image:height" content="1008">',
|
||||
'<meta name="twitter:description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
|
||||
'<meta name="twitter:image" content="' . $ogImageUrl . '">',
|
||||
];
|
||||
|
||||
$fallback = \str_replace('<!-- {{CLOUD_OG}} -->', \implode('', $ogTags), $fallback);
|
||||
}
|
||||
|
||||
$response->html($fallback);
|
||||
});
|
||||
|
||||
@@ -84,8 +84,12 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
|
||||
Console::success('[Setup] - Server database init started...');
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
try {
|
||||
$cache = $app->getResource('cache'); /** @var Utopia\Cache\Cache $cache */
|
||||
$cache->flush();
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create();
|
||||
} catch (\Exception $e) {
|
||||
@@ -102,10 +106,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
$adapter->setup();
|
||||
}
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
$consoleCollections = $collections['console'];
|
||||
foreach ($consoleCollections as $key => $collection) {
|
||||
foreach ($collections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
@@ -177,7 +178,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
$bucket = $dbForConsole->getDocument('buckets', 'default');
|
||||
|
||||
Console::success('[Setup] - Creating files collection for default bucket...');
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
$files = $collections['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ ini_set('display_startup_errors', 1);
|
||||
ini_set('default_socket_timeout', -1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\SMS\Adapter\Mock;
|
||||
@@ -39,6 +40,7 @@ use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Utopia\App;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\ID;
|
||||
@@ -532,12 +534,6 @@ $register->set('pools', function () {
|
||||
'multiple' => true,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'replica' => [
|
||||
'type' => 'database',
|
||||
'dsns' => App::getEnv('_APP_CONNECTIONS_DB_REPLICAS', $fallbackForDB),
|
||||
'multiple' => true,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'queue' => [
|
||||
'type' => 'queue',
|
||||
'dsns' => App::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis),
|
||||
@@ -863,9 +859,21 @@ App::setResource('messaging', fn() => new Phone());
|
||||
App::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
App::setResource('queueForDeletes', function (Connection $queue) {
|
||||
return new Delete($queue);
|
||||
}, ['queue']);
|
||||
App::setResource('queueForFunctions', function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
}, ['queue']);
|
||||
App::setResource('queueForCertificates', function (Connection $queue) {
|
||||
return new Certificate($queue);
|
||||
}, ['queue']);
|
||||
App::setResource('queueForEdgeSyncOut', function (Connection $queue) {
|
||||
return new Client('v1-sync-out', $queue);
|
||||
}, ['queue']);
|
||||
App::setResource('queueForEdgeSyncIn', function (Connection $queue) {
|
||||
return new Client('v1-sync-in', $queue);
|
||||
}, ['queue']);
|
||||
App::setResource('usage', function ($register) {
|
||||
return new Stats($register->get('statsd'));
|
||||
}, ['register']);
|
||||
@@ -1046,11 +1054,6 @@ App::setResource('console', function () {
|
||||
],
|
||||
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
|
||||
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
|
||||
'authProviders' => [
|
||||
'githubEnabled' => true,
|
||||
'githubSecret' => App::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
|
||||
'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
|
||||
],
|
||||
]);
|
||||
}, []);
|
||||
|
||||
@@ -1085,7 +1088,7 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
|
||||
return $database;
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
App::setResource('cache', function (Group $pools) {
|
||||
App::setResource('cache', function (Group $pools, Client $queueForEdgeSyncOut) {
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
@@ -1096,9 +1099,26 @@ App::setResource('cache', function (Group $pools) {
|
||||
->getResource()
|
||||
;
|
||||
}
|
||||
$cache = new Cache(new Sharding($adapters));
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['pools']);
|
||||
$cache->on(cache::EVENT_SAVE, function ($key) use ($queueForEdgeSyncOut) {
|
||||
$queueForEdgeSyncOut
|
||||
->enqueue([
|
||||
'type' => 'cache',
|
||||
'key' => $key
|
||||
]);
|
||||
});
|
||||
|
||||
$cache->on(cache::EVENT_PURGE, function ($key) use ($queueForEdgeSyncOut) {
|
||||
$queueForEdgeSyncOut
|
||||
->enqueue([
|
||||
'type' => 'cache',
|
||||
'key' => $key
|
||||
]);
|
||||
});
|
||||
|
||||
return $cache;
|
||||
}, ['pools', 'queueForEdgeSyncOut']);
|
||||
|
||||
App::setResource('deviceLocal', function () {
|
||||
return new Local();
|
||||
@@ -1138,21 +1158,14 @@ function getDevice($root): Device
|
||||
Console::error($e->getMessage() . 'Invalid DSN. Defaulting to Local device.');
|
||||
}
|
||||
|
||||
switch ($device) {
|
||||
case Storage::DEVICE_S3:
|
||||
return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
case STORAGE::DEVICE_DO_SPACES:
|
||||
return new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
case Storage::DEVICE_BACKBLAZE:
|
||||
return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
case Storage::DEVICE_LINODE:
|
||||
return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
case Storage::DEVICE_WASABI:
|
||||
return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl);
|
||||
case Storage::DEVICE_LOCAL:
|
||||
default:
|
||||
return new Local($root);
|
||||
}
|
||||
return match ($device) {
|
||||
Storage::DEVICE_S3 => new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl),
|
||||
STORAGE::DEVICE_DO_SPACES => new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl),
|
||||
Storage::DEVICE_BACKBLAZE => new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl),
|
||||
Storage::DEVICE_LINODE => new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl),
|
||||
Storage::DEVICE_WASABI => new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl),
|
||||
default => new Local($root),
|
||||
};
|
||||
}
|
||||
|
||||
App::setResource('mode', function ($request) {
|
||||
@@ -1197,21 +1210,3 @@ App::setResource('servers', function () {
|
||||
|
||||
return $languages;
|
||||
});
|
||||
|
||||
App::setResource('contributors', function () {
|
||||
$path = 'app/config/cloud/contributors.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
}, []);
|
||||
|
||||
App::setResource('employees', function () {
|
||||
$path = 'app/config/cloud/employees.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
}, []);
|
||||
|
||||
App::setResource('heroes', function () {
|
||||
$path = 'app/config/cloud/heroes.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
}, []);
|
||||
|
||||
@@ -195,6 +195,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
*/
|
||||
Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
|
||||
$payload = [];
|
||||
|
||||
foreach ($stats as $projectId => $value) {
|
||||
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Func;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\App;
|
||||
@@ -11,13 +12,16 @@ use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Queue\Adapter\Swoole;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Queue\Server;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Storage\Device;
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
@@ -30,8 +34,7 @@ Server::setResource('dbForConsole', function (Cache $cache, Registry $register)
|
||||
$database = $pools
|
||||
->get('console')
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
->getResource();
|
||||
|
||||
$adapter = new Database($database, $cache);
|
||||
$adapter->setNamespace('console');
|
||||
@@ -68,24 +71,27 @@ Server::setResource('cache', function (Registry $register) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
->pop()
|
||||
->getResource()
|
||||
;
|
||||
->getResource();
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('queueForFunctions', function (Registry $register) {
|
||||
$pools = $register->get('pools');
|
||||
return new Func(
|
||||
$pools
|
||||
->get('queue')
|
||||
->pop()
|
||||
->getResource()
|
||||
);
|
||||
}, ['register']);
|
||||
Server::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
|
||||
Server::setResource('log', fn() => new Log());
|
||||
Server::setResource('queueForFunctions', function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForCertificates', function (Connection $queue) {
|
||||
return new Certificate($queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('queueForEdgeSyncOut', function (Connection $queue) {
|
||||
return new Client('v1-sync-out', $queue);
|
||||
}, ['queue']);
|
||||
|
||||
Server::setResource('logger', function ($register) {
|
||||
return $register->get('logger');
|
||||
@@ -108,7 +114,7 @@ if (empty(App::getEnv('QUEUE'))) {
|
||||
}
|
||||
|
||||
$adapter = new Swoole($connection, $workerNumber, App::getEnv('QUEUE'));
|
||||
$server = new Server($adapter);
|
||||
$server = new Server($adapter);
|
||||
|
||||
$server
|
||||
->shutdown()
|
||||
@@ -121,15 +127,16 @@ $server
|
||||
->error()
|
||||
->inject('error')
|
||||
->inject('logger')
|
||||
->inject('log')
|
||||
->action(function (Throwable $error, Logger $logger, Log $log) {
|
||||
->action(function (Throwable $error, Logger $logger) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if ($error instanceof PDOException) {
|
||||
throw $error;
|
||||
}
|
||||
|
||||
if ($logger && ($error->getCode() >= 500 || $error->getCode() === 0)) {
|
||||
if ($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
$log = new Log();
|
||||
|
||||
$log->setNamespace("appwrite-worker");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
@@ -147,8 +154,7 @@ $server
|
||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Usage stats log pushed with status code: ' . $responseCode);
|
||||
$logger->addLog($log);
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
@@ -156,3 +162,111 @@ $server
|
||||
Console::error('[Error] File: ' . $error->getFile());
|
||||
Console::error('[Error] Line: ' . $error->getLine());
|
||||
});
|
||||
|
||||
/**
|
||||
* Get Console DB
|
||||
*
|
||||
* @returns Cache
|
||||
*/
|
||||
function getCache(): Cache
|
||||
{
|
||||
global $register;
|
||||
|
||||
$pools = $register->get('pools');
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
$adapters[] = $pools
|
||||
->get($value)
|
||||
->pop()
|
||||
->getResource();
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Console DB
|
||||
*
|
||||
* @returns Database
|
||||
*/
|
||||
function getConsoleDB(): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
$dbAdapter = $pools
|
||||
->get('console')
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
|
||||
$database->setNamespace('console');
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Project DB
|
||||
*
|
||||
* @param Document $project
|
||||
* @returns Database
|
||||
*/
|
||||
function getProjectDB(Document $project): Database
|
||||
{
|
||||
global $register;
|
||||
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return getConsoleDB();
|
||||
}
|
||||
|
||||
$dbAdapter = $pools
|
||||
->get($project->getAttribute('database'))
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$database = new Database($dbAdapter, getCache());
|
||||
$database->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Functions Storage Device
|
||||
* @param string $projectId of the project
|
||||
* @return Device
|
||||
*/
|
||||
function getFunctionsDevice(string $projectId): Device
|
||||
{
|
||||
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Files Storage Device
|
||||
* @param string $projectId of the project
|
||||
* @return Device
|
||||
*/
|
||||
function getFilesDevice(string $projectId): Device
|
||||
{
|
||||
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Builds Storage Device
|
||||
* @param string $projectId of the project
|
||||
* @return Device
|
||||
*/
|
||||
function getBuildsDevice(string $projectId): Device
|
||||
{
|
||||
return getDevice(APP_STORAGE_BUILDS . '/app-' . $projectId);
|
||||
}
|
||||
|
||||
@@ -99,13 +99,13 @@ class BuildsV1 extends Worker
|
||||
'startTime' => $startTime,
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'processing',
|
||||
'outputPath' => '',
|
||||
'path' => '',
|
||||
'size' => 0,
|
||||
'runtime' => $function->getAttribute('runtime'),
|
||||
'source' => $deployment->getAttribute('path'),
|
||||
'sourceType' => $device,
|
||||
'stdout' => '',
|
||||
'stderr' => '',
|
||||
'endTime' => null,
|
||||
'duration' => 0
|
||||
]));
|
||||
$deployment->setAttribute('buildId', $buildId);
|
||||
@@ -186,14 +186,12 @@ class BuildsV1 extends Worker
|
||||
]
|
||||
);
|
||||
|
||||
$endTime = new \DateTime();
|
||||
$endTime->setTimestamp($response['endTimeUnix']);
|
||||
|
||||
/** Update the build document */
|
||||
$build->setAttribute('endTime', DateTime::format($endTime));
|
||||
$build->setAttribute('startTime', DateTime::format((new \DateTime())->setTimestamp($response['startTime'])));
|
||||
$build->setAttribute('duration', \intval($response['duration']));
|
||||
$build->setAttribute('status', $response['status']);
|
||||
$build->setAttribute('outputPath', $response['outputPath']);
|
||||
$build->setAttribute('path', $response['path']);
|
||||
$build->setAttribute('size', $response['size']);
|
||||
$build->setAttribute('stderr', $response['stderr']);
|
||||
$build->setAttribute('stdout', $response['stdout']);
|
||||
|
||||
@@ -202,6 +200,8 @@ class BuildsV1 extends Worker
|
||||
|
||||
Console::success("Build id: $buildId created");
|
||||
|
||||
$function->setAttribute('scheduleUpdatedAt', DateTime::now());
|
||||
|
||||
/** Set auto deploy */
|
||||
if ($deployment->getAttribute('activate') === true) {
|
||||
$function->setAttribute('deployment', $deployment->getId());
|
||||
@@ -211,7 +211,7 @@ class BuildsV1 extends Worker
|
||||
/** Update function schedule */
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule->setAttribute('resourceUpdatedAt', DateTime::now());
|
||||
$schedule->setAttribute('resourceUpdatedAt', $function->getAttribute('scheduleUpdatedAt'));
|
||||
|
||||
$schedule
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
@@ -222,7 +222,7 @@ class BuildsV1 extends Worker
|
||||
} catch (\Throwable $th) {
|
||||
$endTime = DateTime::now();
|
||||
$interval = (new \DateTime($endTime))->diff(new \DateTime($startTime));
|
||||
$build->setAttribute('endTime', $endTime);
|
||||
|
||||
$build->setAttribute('duration', $interval->format('%s') + 0);
|
||||
$build->setAttribute('status', 'failed');
|
||||
$build->setAttribute('stderr', $th->getMessage());
|
||||
@@ -258,6 +258,7 @@ class BuildsV1 extends Worker
|
||||
->setParam('builds.{scope}.compute', 1)
|
||||
->setParam('buildStatus', $build->getAttribute('status', ''))
|
||||
->setParam('buildTime', $build->getAttribute('duration'))
|
||||
->setParam('buildSize', $build->getAttribute('size'))
|
||||
->setParam('networkRequestSize', 0)
|
||||
->setParam('networkResponseSize', 0)
|
||||
->submit();
|
||||
|
||||
@@ -1,42 +1,36 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../worker.php';
|
||||
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\Resque\Worker;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Queue\Server;
|
||||
use Utopia\Database\DateTime;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
Console::title('Certificates V1 Worker');
|
||||
Console::success(APP_NAME . ' certificates worker v1 has started');
|
||||
$database = null;
|
||||
|
||||
class CertificatesV1 extends Worker
|
||||
{
|
||||
/**
|
||||
* Database connection shared across all methods of this file
|
||||
*
|
||||
* @var Database
|
||||
*/
|
||||
private Database $dbForConsole;
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return "certificates";
|
||||
}
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
Server::setResource('execute', function () {
|
||||
return function (
|
||||
Database $dbForConsole,
|
||||
Document $document,
|
||||
Domain $domain,
|
||||
$queueForEdgeSyncOut,
|
||||
bool $skipRenewCheck = false,
|
||||
) {
|
||||
/**
|
||||
* 1. Read arguments and validate domain
|
||||
* 2. Get main domain
|
||||
@@ -67,14 +61,8 @@ class CertificatesV1 extends Worker
|
||||
* Note: Renewals are checked and scheduled from maintenence worker
|
||||
*/
|
||||
|
||||
$this->dbForConsole = $this->getConsoleDB();
|
||||
|
||||
$skipCheck = $this->args['skipRenewCheck'] ?? false; // If true, we won't double-check expiry from cert file
|
||||
$document = new Document($this->args['domain'] ?? []);
|
||||
$domain = new Domain($document->getAttribute('domain', ''));
|
||||
|
||||
// Get current certificate
|
||||
$certificate = $this->dbForConsole->findOne('certificates', [Query::equal('domain', [$domain->get()])]);
|
||||
$certificate = $dbForConsole->findOne('certificates', [Query::equal('domain', [$domain->get()])]);
|
||||
|
||||
// If we don't have certificate for domain yet, let's create new document. At the end we save it
|
||||
if (!$certificate) {
|
||||
@@ -90,14 +78,14 @@ class CertificatesV1 extends Worker
|
||||
}
|
||||
|
||||
// Validate domain and DNS records. Skip if job is forced
|
||||
if (!$skipCheck) {
|
||||
$mainDomain = $this->getMainDomain();
|
||||
if (!$skipRenewCheck) {
|
||||
$mainDomain = getMainDomain($dbForConsole);
|
||||
$isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain;
|
||||
$this->validateDomain($domain, $isMainDomain);
|
||||
validateDomain($domain, $isMainDomain);
|
||||
}
|
||||
|
||||
// If certificate exists already, double-check expiry date. Skip if job is forced
|
||||
if (!$skipCheck && !$this->isRenewRequired($domain->get())) {
|
||||
if (!$skipRenewCheck && !isRenewRequired($domain->get())) {
|
||||
throw new Exception('Renew isn\'t required.');
|
||||
}
|
||||
|
||||
@@ -105,7 +93,7 @@ class CertificatesV1 extends Worker
|
||||
$folder = ID::unique();
|
||||
|
||||
// Generate certificate files using Let's Encrypt
|
||||
$letsEncryptData = $this->issueCertificate($folder, $domain->get(), $email);
|
||||
$letsEncryptData = issueCertificate($folder, $domain->get(), $email);
|
||||
|
||||
// Command succeeded, store all data into document
|
||||
// We store stderr too, because it may include warnings
|
||||
@@ -115,12 +103,24 @@ class CertificatesV1 extends Worker
|
||||
]));
|
||||
|
||||
// Give certificates to Traefik
|
||||
$this->applyCertificateFiles($folder, $domain->get(), $letsEncryptData);
|
||||
applyCertificateFiles($folder, $domain->get(), $letsEncryptData);
|
||||
|
||||
// Update certificate info stored in database
|
||||
$certificate->setAttribute('renewDate', $this->getRenewDate($domain->get()));
|
||||
$certificate->setAttribute('renewDate', getRenewDate($domain->get()));
|
||||
$certificate->setAttribute('attempts', 0);
|
||||
$certificate->setAttribute('issueDate', DateTime::now());
|
||||
|
||||
// Enqueue certificate for regional sync
|
||||
$filename = APP_STORAGE_CERTIFICATES . '/' . $domain . '.tar.gz';
|
||||
if (file_exists($filename)) {
|
||||
$queueForEdgeSyncOut->enqueue([
|
||||
'type' => 'certificate',
|
||||
'key' => [
|
||||
'domain' => $domain,
|
||||
'contents' => base64_encode(file_get_contents($filename)),
|
||||
]
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// Set exception as log in certificate document
|
||||
$certificate->setAttribute('log', $e->getMessage());
|
||||
@@ -133,290 +133,320 @@ class CertificatesV1 extends Worker
|
||||
$certificate->setAttribute('renewDate', DateTime::now());
|
||||
|
||||
// Send email to security email
|
||||
$this->notifyError($domain->get(), $e->getMessage(), $attempts);
|
||||
notifyError($domain->get(), $e->getMessage(), $attempts);
|
||||
} finally {
|
||||
// All actions result in new updatedAt date
|
||||
$certificate->setAttribute('updated', DateTime::now());
|
||||
|
||||
// Save all changes we made to certificate document into database
|
||||
$this->saveCertificateDocument($domain->get(), $certificate);
|
||||
saveCertificateDocument($domain->get(), $certificate, $dbForConsole);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Save certificate data into database.
|
||||
*
|
||||
* @param string $domain Domain name that certificate is for
|
||||
* @param Document $certificate Certificate document that we need to save
|
||||
* @param Database $dbForConsole Database connection for console
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function saveCertificateDocument(string $domain, Document $certificate, Database $dbForConsole): void
|
||||
{
|
||||
// Check if update or insert required
|
||||
$certificateDocument = $dbForConsole->findOne('certificates', [Query::equal('domain', [$domain])]);
|
||||
if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) {
|
||||
// Merge new data with current data
|
||||
$certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy()));
|
||||
|
||||
$certificate = $dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate);
|
||||
} else {
|
||||
$certificate = $dbForConsole->createDocument('certificates', $certificate);
|
||||
}
|
||||
|
||||
$certificateId = $certificate->getId();
|
||||
updateDomainDocuments($certificateId, $domain, $dbForConsole);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main domain. Needed as we do different checks for main and non-main domains.
|
||||
*
|
||||
* @return null|string Returns main domain. If null, there is no main domain yet.
|
||||
*/
|
||||
function getMainDomain($dbForConsole): ?string
|
||||
{
|
||||
$envDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
return $envDomain;
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('domains', [Query::orderAsc('_id')]);
|
||||
if ($domainDocument) {
|
||||
return $domainDocument->getAttribute('domain');
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal domain validation functionality to prevent unnecessary attempts failed from Let's Encrypt side. We check:
|
||||
* - Domain needs to be public and valid (prevents NFT domains that are not supported by Let's Encrypt)
|
||||
* - Domain must have proper DNS record
|
||||
*
|
||||
* @param Domain $domain Domain which we validate
|
||||
* @param bool $isMainDomain In case of master domain, we look for different DNS configurations
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function validateDomain(Domain $domain, bool $isMainDomain): void
|
||||
{
|
||||
if (empty($domain->get())) {
|
||||
throw new Exception('Missing certificate domain.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save certificate data into database.
|
||||
*
|
||||
* @param string $domain Domain name that certificate is for
|
||||
* @param Document $certificate Certificate document that we need to save
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function saveCertificateDocument(string $domain, Document $certificate): void
|
||||
{
|
||||
// Check if update or insert required
|
||||
$certificateDocument = $this->dbForConsole->findOne('certificates', [Query::equal('domain', [$domain])]);
|
||||
if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) {
|
||||
// Merge new data with current data
|
||||
$certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy()));
|
||||
|
||||
$certificate = $this->dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate);
|
||||
} else {
|
||||
$certificate = $this->dbForConsole->createDocument('certificates', $certificate);
|
||||
}
|
||||
|
||||
$certificateId = $certificate->getId();
|
||||
$this->updateDomainDocuments($certificateId, $domain);
|
||||
if (!$domain->isKnown() || $domain->isTest()) {
|
||||
throw new Exception('Unknown public suffix for domain.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main domain. Needed as we do different checks for main and non-main domains.
|
||||
*
|
||||
* @return null|string Returns main domain. If null, there is no main domain yet.
|
||||
*/
|
||||
private function getMainDomain(): ?string
|
||||
{
|
||||
$envDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
return $envDomain;
|
||||
} else {
|
||||
$domainDocument = $this->dbForConsole->findOne('domains', [Query::orderAsc('_id')]);
|
||||
if ($domainDocument) {
|
||||
return $domainDocument->getAttribute('domain');
|
||||
}
|
||||
if (!$isMainDomain) {
|
||||
// TODO: Would be awesome to also support A/AAAA records here. Maybe dry run?
|
||||
// Validate if domain target is properly configured
|
||||
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
|
||||
if (!$target->isKnown() || $target->isTest()) {
|
||||
throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
|
||||
}
|
||||
|
||||
return null;
|
||||
// Verify domain with DNS records
|
||||
$validator = new CNAME($target->get());
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
throw new Exception('Failed to verify domain DNS records.');
|
||||
}
|
||||
} else {
|
||||
// Main domain validation
|
||||
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal domain validation functionality to prevent unnecessary attempts failed from Let's Encrypt side. We check:
|
||||
* - Domain needs to be public and valid (prevents NFT domains that are not supported by Let's Encrypt)
|
||||
* - Domain must have proper DNS record
|
||||
*
|
||||
* @param Domain $domain Domain which we validate
|
||||
* @param bool $isMainDomain In case of master domain, we look for different DNS configurations
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function validateDomain(Domain $domain, bool $isMainDomain): void
|
||||
{
|
||||
if (empty($domain->get())) {
|
||||
throw new Exception('Missing certificate domain.');
|
||||
}
|
||||
/**
|
||||
* Reads expiry date of certificate from file and decides if renewal is required or not.
|
||||
*
|
||||
* @param string $domain Domain for which we check certificate file
|
||||
*
|
||||
* @return bool True, if certificate needs to be renewed
|
||||
*/
|
||||
function isRenewRequired(string $domain): bool
|
||||
{
|
||||
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
|
||||
if (\file_exists($certPath)) {
|
||||
$validTo = null;
|
||||
|
||||
if (!$domain->isKnown() || $domain->isTest()) {
|
||||
throw new Exception('Unknown public suffix for domain.');
|
||||
}
|
||||
|
||||
if (!$isMainDomain) {
|
||||
// TODO: Would be awesome to also support A/AAAA records here. Maybe dry run?
|
||||
|
||||
// Validate if domain target is properly configured
|
||||
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
|
||||
if (!$target->isKnown() || $target->isTest()) {
|
||||
throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
|
||||
}
|
||||
|
||||
// Verify domain with DNS records
|
||||
$validator = new CNAME($target->get());
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
throw new Exception('Failed to verify domain DNS records.');
|
||||
}
|
||||
} else {
|
||||
// Main domain validation
|
||||
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads expiry date of certificate from file and decides if renewal is required or not.
|
||||
*
|
||||
* @param string $domain Domain for which we check certificate file
|
||||
*
|
||||
* @return bool True, if certificate needs to be renewed
|
||||
*/
|
||||
private function isRenewRequired(string $domain): bool
|
||||
{
|
||||
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
|
||||
if (\file_exists($certPath)) {
|
||||
$validTo = null;
|
||||
|
||||
$certData = openssl_x509_parse(file_get_contents($certPath));
|
||||
$validTo = $certData['validTo_time_t'] ?? 0;
|
||||
|
||||
if (empty($validTo)) {
|
||||
throw new Exception('Unable to read certificate file (cert.pem).');
|
||||
}
|
||||
|
||||
// LetsEncrypt allows renewal 30 days before expiry
|
||||
$expiryInAdvance = (60 * 60 * 24 * 30);
|
||||
if ($validTo - $expiryInAdvance > \time()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* LetsEncrypt communication to issue certificate (using certbot CLI)
|
||||
*
|
||||
* @param string $folder Folder into which certificates should be generated
|
||||
* @param string $domain Domain to generate certificate for
|
||||
*
|
||||
* @return array Named array with keys 'stdout' and 'stderr', both string
|
||||
*/
|
||||
private function issueCertificate(string $folder, string $domain, string $email): array
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
$staging = (App::isProduction()) ? '' : ' --dry-run';
|
||||
$exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
|
||||
. " --email " . $email
|
||||
. " --cert-name " . $folder
|
||||
. " -w " . APP_STORAGE_CERTIFICATES
|
||||
. " -d {$domain}", '', $stdout, $stderr);
|
||||
|
||||
// Unexpected error, usually 5XX, API limits, ...
|
||||
if ($exit !== 0) {
|
||||
throw new Exception('Failed to issue a certificate with message: ' . $stderr);
|
||||
}
|
||||
|
||||
return [
|
||||
'stdout' => $stdout,
|
||||
'stderr' => $stderr
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read new renew date from certificate file generated by Let's Encrypt
|
||||
*
|
||||
* @param string $domain Domain which certificate was generated for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getRenewDate(string $domain): string
|
||||
{
|
||||
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
|
||||
$certData = openssl_x509_parse(file_get_contents($certPath));
|
||||
$validTo = $certData['validTo_time_t'] ?? null;
|
||||
$dt = (new \DateTime())->setTimestamp($validTo);
|
||||
return DateTime::addSeconds($dt, -60 * 60 * 24 * 30); // -30 days
|
||||
}
|
||||
$validTo = $certData['validTo_time_t'] ?? 0;
|
||||
|
||||
/**
|
||||
* Method to take files from Let's Encrypt, and put it into Traefik.
|
||||
*
|
||||
* @param string $domain Domain which certificate was generated for
|
||||
* @param string $folder Folder in which certificates were generated
|
||||
* @param array $letsEncryptData Let's Encrypt logs to use for additional info when throwing error
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applyCertificateFiles(string $folder, string $domain, array $letsEncryptData): void
|
||||
{
|
||||
// Prepare folder in storage for domain
|
||||
$path = APP_STORAGE_CERTIFICATES . '/' . $domain;
|
||||
if (!\is_readable($path)) {
|
||||
if (!\mkdir($path, 0755, true)) {
|
||||
throw new Exception('Failed to create path for certificate.');
|
||||
}
|
||||
if (empty($validTo)) {
|
||||
throw new Exception('Unable to read certificate file (cert.pem).');
|
||||
}
|
||||
|
||||
// Move generated files
|
||||
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) {
|
||||
throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
|
||||
}
|
||||
|
||||
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) {
|
||||
throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
|
||||
}
|
||||
|
||||
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) {
|
||||
throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
|
||||
}
|
||||
|
||||
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) {
|
||||
throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
|
||||
}
|
||||
|
||||
$config = \implode(PHP_EOL, [
|
||||
"tls:",
|
||||
" certificates:",
|
||||
" - certFile: /storage/certificates/{$domain}/fullchain.pem",
|
||||
" keyFile: /storage/certificates/{$domain}/privkey.pem"
|
||||
]);
|
||||
|
||||
// Save configuration into Traefik using our new cert files
|
||||
if (!\file_put_contents(APP_STORAGE_CONFIG . '/' . $domain . '.yml', $config)) {
|
||||
throw new Exception('Failed to save Traefik configuration.');
|
||||
// LetsEncrypt allows renewal 30 days before expiry
|
||||
$expiryInAdvance = (60 * 60 * 24 * 30);
|
||||
if ($validTo - $expiryInAdvance > \time()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to make sure information about error is delivered to admnistrator.
|
||||
*
|
||||
* @param string $domain Domain that caused the error
|
||||
* @param string $errorMessage Verbose error message
|
||||
* @param int $attempt How many times it failed already
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function notifyError(string $domain, string $errorMessage, int $attempt): void
|
||||
{
|
||||
// Log error into console
|
||||
Console::warning('Cannot renew domain (' . $domain . ') on attempt no. ' . $attempt . ' certificate: ' . $errorMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send mail to administratore mail
|
||||
$mail = new Mail();
|
||||
$mail
|
||||
->setType(MAIL_TYPE_CERTIFICATE)
|
||||
->setRecipient(App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'))
|
||||
->setUrl('https://' . $domain)
|
||||
->setLocale(App::getEnv('_APP_LOCALE', 'en'))
|
||||
->setName('Appwrite Administrator')
|
||||
->setPayload([
|
||||
'domain' => $domain,
|
||||
'error' => $errorMessage,
|
||||
'attempt' => $attempt
|
||||
])
|
||||
->trigger();
|
||||
/**
|
||||
* LetsEncrypt communication to issue certificate (using certbot CLI)
|
||||
*
|
||||
* @param string $folder Folder into which certificates should be generated
|
||||
* @param string $domain Domain to generate certificate for
|
||||
*
|
||||
* @return array Named array with keys 'stdout' and 'stderr', both string
|
||||
*/
|
||||
function issueCertificate(string $folder, string $domain, string $email): array
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
$staging = (App::isProduction()) ? '' : ' --dry-run';
|
||||
$exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
|
||||
. " --email " . $email
|
||||
. " --cert-name " . $folder
|
||||
. " -w " . APP_STORAGE_CERTIFICATES
|
||||
. " -d {$domain}", '', $stdout, $stderr);
|
||||
|
||||
// Unexpected error, usually 5XX, API limits, ...
|
||||
if ($exit !== 0) {
|
||||
throw new Exception('Failed to issue a certificate with message: ' . $stderr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all existing domain documents so they have relation to correct certificate document.
|
||||
* This solved issues:
|
||||
* - when adding a domain for which there is already a certificate
|
||||
* - when renew creates new document? It might?
|
||||
* - overall makes it more reliable
|
||||
*
|
||||
* @param string $certificateId ID of a new or updated certificate document
|
||||
* @param string $domain Domain that is affected by new certificate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function updateDomainDocuments(string $certificateId, string $domain): void
|
||||
{
|
||||
$domains = $this->dbForConsole->find('domains', [
|
||||
Query::equal('domain', [$domain]),
|
||||
Query::limit(1000),
|
||||
]);
|
||||
return [
|
||||
'stdout' => $stdout,
|
||||
'stderr' => $stderr
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($domains as $domainDocument) {
|
||||
$domainDocument->setAttribute('updated', DateTime::now());
|
||||
$domainDocument->setAttribute('certificateId', $certificateId);
|
||||
/**
|
||||
* Read new renew date from certificate file generated by Let's Encrypt
|
||||
*
|
||||
* @param string $domain Domain which certificate was generated for
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getRenewDate(string $domain): string
|
||||
{
|
||||
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
|
||||
$certData = openssl_x509_parse(file_get_contents($certPath));
|
||||
$validTo = $certData['validTo_time_t'] ?? null;
|
||||
$dt = (new \DateTime())->setTimestamp($validTo);
|
||||
return DateTime::addSeconds($dt, -60 * 60 * 24 * 30); // -30 days
|
||||
}
|
||||
|
||||
$this->dbForConsole->updateDocument('domains', $domainDocument->getId(), $domainDocument);
|
||||
/**
|
||||
* Method to take files from Let's Encrypt, and put it into Traefik.
|
||||
*
|
||||
* @param string $domain Domain which certificate was generated for
|
||||
* @param string $folder Folder in which certificates were generated
|
||||
* @param array $letsEncryptData Let's Encrypt logs to use for additional info when throwing error
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function applyCertificateFiles(string $folder, string $domain, array $letsEncryptData): void
|
||||
{
|
||||
// Prepare folder in storage for domain
|
||||
$path = APP_STORAGE_CERTIFICATES . '/' . $domain;
|
||||
if (!\is_readable($path)) {
|
||||
if (!\mkdir($path, 0755, true)) {
|
||||
throw new Exception('Failed to create path for certificate.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($domainDocument->getAttribute('projectId')) {
|
||||
$this->dbForConsole->deleteCachedDocument('projects', $domainDocument->getAttribute('projectId'));
|
||||
}
|
||||
// Move generated files
|
||||
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) {
|
||||
throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
|
||||
}
|
||||
|
||||
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) {
|
||||
throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
|
||||
}
|
||||
|
||||
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) {
|
||||
throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
|
||||
}
|
||||
|
||||
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) {
|
||||
throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
|
||||
}
|
||||
|
||||
$config = \implode(PHP_EOL, [
|
||||
"tls:",
|
||||
" certificates:",
|
||||
" - certFile: /storage/certificates/{$domain}/fullchain.pem",
|
||||
" keyFile: /storage/certificates/{$domain}/privkey.pem"
|
||||
]);
|
||||
|
||||
// Save configuration into Traefik using our new cert files
|
||||
if (!\file_put_contents(APP_STORAGE_CONFIG . '/' . $domain . '.yml', $config)) {
|
||||
throw new Exception('Failed to save Traefik configuration.');
|
||||
}
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
Console::execute('cd ' . APP_STORAGE_CERTIFICATES . " && tar --exclude $domain.tar.gz -czf $domain.tar.gz .", '', $stdout, $stderr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to make sure information about error is delivered to admnistrator.
|
||||
*
|
||||
* @param string $domain Domain that caused the error
|
||||
* @param string $errorMessage Verbose error message
|
||||
* @param int $attempt How many times it failed already
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function notifyError(string $domain, string $errorMessage, int $attempt): void
|
||||
{
|
||||
// Log error into console
|
||||
Console::warning('Cannot renew domain (' . $domain . ') on attempt no. ' . $attempt . ' certificate: ' . $errorMessage);
|
||||
|
||||
// Send mail to administratore mail
|
||||
$mail = new Mail();
|
||||
$mail
|
||||
->setType(MAIL_TYPE_CERTIFICATE)
|
||||
->setRecipient(App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'))
|
||||
->setUrl('https://' . $domain)
|
||||
->setLocale(App::getEnv('_APP_LOCALE', 'en'))
|
||||
->setName('Appwrite Administrator')
|
||||
->setPayload([
|
||||
'domain' => $domain,
|
||||
'error' => $errorMessage,
|
||||
'attempt' => $attempt
|
||||
])
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all existing domain documents so they have relation to correct certificate document.
|
||||
* This solved issues:
|
||||
* - when adding a domain for which there is already a certificate
|
||||
* - when renew creates new document? It might?
|
||||
* - overall makes it more reliable
|
||||
*
|
||||
* @param string $certificateId ID of a new or updated certificate document
|
||||
* @param string $domain Domain that is affected by new certificate
|
||||
* @param Database $dbForConsole Database instance for console
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function updateDomainDocuments(string $certificateId, string $domain, Database $dbForConsole): void
|
||||
{
|
||||
$domains = $dbForConsole->find('domains', [
|
||||
Query::equal('domain', [$domain]),
|
||||
Query::limit(1000),
|
||||
]);
|
||||
|
||||
foreach ($domains as $domainDocument) {
|
||||
$domainDocument->setAttribute('updated', DateTime::now());
|
||||
$domainDocument->setAttribute('certificateId', $certificateId);
|
||||
|
||||
$dbForConsole->updateDocument('domains', $domainDocument->getId(), $domainDocument);
|
||||
|
||||
if ($domainDocument->getAttribute('projectId')) {
|
||||
$dbForConsole->deleteCachedDocument('projects', $domainDocument->getAttribute('projectId'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$server->job()
|
||||
->inject('message')
|
||||
->inject('dbForConsole')
|
||||
->inject('execute')
|
||||
->inject('queueForEdgeSyncOut')
|
||||
->action(function ($message, $dbForConsole, $execute, Client $queueForEdgeSyncOut) use ($server) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
$document = new Document($payload['domain'] ?? []);
|
||||
$domain = new Domain($document->getAttribute('domain', ''));
|
||||
$skipRenewCheck = $payload['skipRenewCheck'] ?? false;
|
||||
|
||||
$execute(
|
||||
dbForConsole: $dbForConsole,
|
||||
document: $document,
|
||||
domain: $domain,
|
||||
queueForEdgeSyncOut: $queueForEdgeSyncOut,
|
||||
skipRenewCheck: $skipRenewCheck,
|
||||
);
|
||||
});
|
||||
|
||||
$server->workerStart();
|
||||
$server->start();
|
||||
|
||||
@@ -20,7 +20,6 @@ use Utopia\Database\Permission;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Queue\Server;
|
||||
|
||||
Authorization::disable();
|
||||
@@ -28,7 +27,6 @@ Authorization::setDefaultStatus(false);
|
||||
|
||||
Server::setResource('execute', function () {
|
||||
return function (
|
||||
Log $log,
|
||||
Func $queueForFunctions,
|
||||
Database $dbForProject,
|
||||
Client $statsd,
|
||||
@@ -42,16 +40,10 @@ Server::setResource('execute', function () {
|
||||
string $eventData = null,
|
||||
string $executionId = null,
|
||||
) {
|
||||
$error = null; // Used to re-throw at the end to trigger Logger (Sentry)
|
||||
$errorCode = 0;
|
||||
|
||||
$user ??= new Document();
|
||||
$functionId = $function->getId();
|
||||
$deploymentId = $function->getAttribute('deployment', '');
|
||||
|
||||
$log->addTag('functionId', $functionId);
|
||||
$log->addTag('projectId', $project->getId());
|
||||
|
||||
/** Check if deployment exists */
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
@@ -141,7 +133,7 @@ Server::setResource('execute', function () {
|
||||
variables: $vars,
|
||||
timeout: $function->getAttribute('timeout', 0),
|
||||
image: $runtime['image'],
|
||||
source: $build->getAttribute('outputPath', ''),
|
||||
source: $build->getAttribute('path', ''),
|
||||
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
||||
);
|
||||
|
||||
@@ -161,8 +153,10 @@ Server::setResource('execute', function () {
|
||||
->setAttribute('statusCode', $th->getCode())
|
||||
->setAttribute('stderr', $th->getMessage());
|
||||
|
||||
$error = $th->getMessage();
|
||||
$errorCode = $th->getCode();
|
||||
Console::error($th->getTraceAsString());
|
||||
Console::error($th->getFile());
|
||||
Console::error($th->getLine());
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
@@ -223,10 +217,6 @@ Server::setResource('execute', function () {
|
||||
->setParam('networkResponseSize', 0)
|
||||
->submit();
|
||||
}
|
||||
|
||||
if (!empty($error)) {
|
||||
throw new Exception($error, $errorCode);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -236,8 +226,7 @@ $server->job()
|
||||
->inject('queueForFunctions')
|
||||
->inject('statsd')
|
||||
->inject('execute')
|
||||
->inject('log')
|
||||
->action(function (Message $message, Database $dbForProject, Func $queueForFunctions, Client $statsd, callable $execute, Log $log) {
|
||||
->action(function (Message $message, Database $dbForProject, Func $queueForFunctions, Client $statsd, callable $execute) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
@@ -265,7 +254,8 @@ $server->job()
|
||||
while ($sum >= $limit) {
|
||||
$functions = $dbForProject->find('functions', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset)
|
||||
Query::offset($offset),
|
||||
Query::orderAsc('name'),
|
||||
]);
|
||||
|
||||
$sum = \count($functions);
|
||||
@@ -277,17 +267,8 @@ $server->job()
|
||||
if (!array_intersect($events, $function->getAttribute('events', []))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** Skip if a function has been triggered by its own execution */
|
||||
$event = "functions.{$function->getId()}.executions.*";
|
||||
if (in_array($event, $events)) {
|
||||
Console::warning("Skipping function: {$function->getAttribute('name')} from project: {$project->getId()} triggered by self");
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::success("Iterating function: {$function->getAttribute('name')} from project: {$project->getId()}");
|
||||
Console::success('Iterating function: ' . $function->getAttribute('name'));
|
||||
$execute(
|
||||
log: $log,
|
||||
statsd: $statsd,
|
||||
dbForProject: $dbForProject,
|
||||
project: $project,
|
||||
@@ -316,7 +297,6 @@ $server->job()
|
||||
$execution = new Document($payload['execution'] ?? []);
|
||||
$user = new Document($payload['user'] ?? []);
|
||||
$execute(
|
||||
log: $log,
|
||||
project: $project,
|
||||
function: $function,
|
||||
dbForProject: $dbForProject,
|
||||
@@ -333,7 +313,6 @@ $server->job()
|
||||
break;
|
||||
case 'schedule':
|
||||
$execute(
|
||||
log: $log,
|
||||
project: $project,
|
||||
function: $function,
|
||||
dbForProject: $dbForProject,
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../worker.php';
|
||||
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Queue\Message;
|
||||
|
||||
$server->job()
|
||||
->inject('message')
|
||||
->inject('cache')
|
||||
->action(function (Message $message, Cache $cache) {
|
||||
$payload = $message->getPayload();
|
||||
$type = $payload['type'];
|
||||
$key = $payload['key'];
|
||||
$time = DateTime::now();
|
||||
|
||||
switch ($type) {
|
||||
case 'cache':
|
||||
$cache->setListenersStatus(false);
|
||||
Console::log("[{$time}] Purging cache key {$key}");
|
||||
$cache->purge($key);
|
||||
$cache->setListenersStatus(true);
|
||||
break;
|
||||
case 'realtime':
|
||||
Console::log("[{$time}] Sending realtime message");
|
||||
Realtime::send(
|
||||
projectId: $key['projectId'],
|
||||
payload: $key['payload'],
|
||||
events: $key['events'],
|
||||
channels: $key['channels'],
|
||||
roles: $key['roles'],
|
||||
options: $key['options']
|
||||
);
|
||||
break;
|
||||
case 'certificate':
|
||||
Console::log("[{$time}] Writing certificate for domain [{$key['domain']}]");
|
||||
|
||||
$path = APP_STORAGE_CERTIFICATES . '/__' . $key['domain'];
|
||||
$filename = $key['domain'] . 'tar.gz';
|
||||
if (!file_exists($path)) {
|
||||
mkdir($path, 0755, true);
|
||||
}
|
||||
|
||||
$result = file_put_contents($path . '/' . $filename, base64_decode($key['contents']));
|
||||
if (empty($result)) {
|
||||
Console::error('Can not write ' . $key['filename']);
|
||||
break;
|
||||
}
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$result = Console::execute('cd ' . $path . ' && tar xvzf ' . $filename, '', $stdout, $stderr);
|
||||
if ($result === 1) {
|
||||
Console::error('Can not open ' . $filename);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
$server
|
||||
->workerStart()
|
||||
->action(function () {
|
||||
Console::success("[" . App::getEnv('_APP_REGION', 'nyc1') . "] edge sync-in worker Started");
|
||||
});
|
||||
|
||||
$server->start();
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../worker.php';
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Timer;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Queue\Message;
|
||||
|
||||
$regions = array_filter(
|
||||
Config::getParam('regions', []),
|
||||
fn ($region) => App::getEnv('_APP_REGION') !== $region
|
||||
&& $region !== 'default',
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
$stack = [
|
||||
'regions' => $regions,
|
||||
'keys' => [],
|
||||
];
|
||||
$failures = [];
|
||||
|
||||
const CHUNK_MAX_KEYS = 500;
|
||||
const MAX_CURL_SEND_ATTEMPTS = 4;
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $token
|
||||
* @param array data
|
||||
* @return int
|
||||
*/
|
||||
function call(string $url, string $token, array $data): int
|
||||
{
|
||||
|
||||
$boundary = uniqid();
|
||||
$delimiter = '-------------' . $boundary;
|
||||
$payload = '';
|
||||
$eol = "\r\n";
|
||||
console::warning('Sending ' . count($data) . 'to ' . $url);
|
||||
var_dump($data);
|
||||
foreach ($data as $keys) {
|
||||
$payload .= "--" . $delimiter . $eol
|
||||
. 'Content-Disposition: form-data; name="keys[]"' . $eol . $eol
|
||||
. json_encode($keys) . $eol;
|
||||
}
|
||||
$payload .= "--" . $delimiter . "--" . $eol;
|
||||
|
||||
$status = 404;
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Origin-edge-url: ' . App::getEnv('_APP_REGION'),
|
||||
'Content-type: multipart/form-data; boundary=' . $delimiter,
|
||||
'Content-Length: ' . strlen($payload)
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
|
||||
|
||||
for ($attempts = 0; $attempts < MAX_CURL_SEND_ATTEMPTS; $attempts++) {
|
||||
curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
if ($status === 200) {
|
||||
return $status;
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
* @throws Exception|\Exception
|
||||
*/
|
||||
function handle($dbForConsole, $regions, $data): void
|
||||
{
|
||||
|
||||
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 600, 10);
|
||||
$token = $jwt->encode([]);
|
||||
|
||||
foreach ($regions as $code => $region) {
|
||||
$time = DateTime::now();
|
||||
$status = call($region['domain'] . '/v1/edge/sync', $token, $data);
|
||||
if ($status !== Response::STATUS_CODE_OK) {
|
||||
Console::error("[{$time}] Request to {$code} has failed");
|
||||
foreach ($data as $keys) {
|
||||
$dbForConsole->createDocument('syncs', new Document([
|
||||
'region' => App::getEnv('_APP_REGION'),
|
||||
'target' => $code,
|
||||
'type' => $keys['type'],
|
||||
'key' => ['key' => $keys['key']],
|
||||
'status' => $status,
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$server->job()
|
||||
->inject('message')
|
||||
->action(function (Message $message) use (&$stack, &$failures) {
|
||||
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
//Get failed requests
|
||||
if (!empty($payload['region']) && !empty($payload['keys'])) {
|
||||
$regions = array_filter(
|
||||
Config::getParam('regions', []),
|
||||
fn ($region) => $payload['region'] === $region,
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
$failures[] = [
|
||||
'regions' => $regions,
|
||||
'keys' => $payload['keys']
|
||||
];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($payload['type'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($payload['key'])) {
|
||||
$stack['keys'][] = [
|
||||
'type' => $payload['type'],
|
||||
'key' => $payload['key'],
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
$server
|
||||
->workerStart()
|
||||
->inject('dbForConsole')
|
||||
->action(function ($dbForConsole) use (&$stack, &$failures) {
|
||||
|
||||
Timer::tick(5000, function () use ($dbForConsole, &$stack, &$failures) {
|
||||
$time = DateTime::now();
|
||||
|
||||
if (empty($stack['keys']) && count($failures) === 0) {
|
||||
Console::info("[{$time}] Stack is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
//Send failed requests
|
||||
if (count($failures) > 0) {
|
||||
$i = 0;
|
||||
while ($i < count($failures)) {
|
||||
$failure = array_shift($failures);
|
||||
Console::info("[{$time}] ReSending " . count($failure['keys']) . " to " . key($failure['regions']));
|
||||
handle($dbForConsole, $failure['regions'], $failure['keys']);
|
||||
$i++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$chunk = array_slice($stack['keys'], 0, CHUNK_MAX_KEYS, true);
|
||||
array_splice($stack['keys'], 0, CHUNK_MAX_KEYS);
|
||||
Console::log("[{$time}] Sending " . count($chunk) . " remains " . count($stack['keys']));
|
||||
handle($dbForConsole, $stack['regions'], $chunk);
|
||||
});
|
||||
Console::success("[" . App::getEnv('_APP_REGION') . "] edge sync-out worker Started");
|
||||
});
|
||||
|
||||
$server->start();
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
/usr/src/code/vendor/bin/phpbench run --config /usr/src/code/phpbench.json --report appwrite $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php calc-tier-stats $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php calc-users-stats $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php clear-card-cache $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php backup $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php restore $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php edge-sync $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php hamster $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-create-missing-schedules $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-delete-project-collections $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-delete-schedule-updated-at-attribute $@
|
||||
@@ -1,10 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
|
||||
then
|
||||
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
else
|
||||
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
fi
|
||||
|
||||
INTERVAL=1 QUEUE='v1-certificates' APP_INCLUDE='/usr/src/code/app/workers/certificates.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
||||
QUEUE=v1-certificates php /usr/src/code/app/workers/certificates.php $@
|
||||
@@ -1,10 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
|
||||
then
|
||||
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
else
|
||||
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
fi
|
||||
|
||||
INTERVAL=1 QUEUE='v1-deletes' APP_INCLUDE='/usr/src/code/app/workers/deletes.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
||||
QUEUE=v1-deletes php /usr/src/code/app/workers/deletes.php $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
QUEUE=v1-sync-in php /usr/src/code/app/workers/sync-in.php $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
QUEUE=v1-sync-out php /usr/src/code/app/workers/sync-out.php $@
|
||||
@@ -12,7 +12,19 @@
|
||||
"scripts": {
|
||||
"test": "vendor/bin/phpunit",
|
||||
"lint": "vendor/bin/phpcs",
|
||||
"format": "vendor/bin/phpcbf"
|
||||
"format": "vendor/bin/phpcbf",
|
||||
"benchmark": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"vendor/bin/phpbench run --report=appwrite --"
|
||||
],
|
||||
"benchmark-tag": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"vendor/bin/phpbench run --report=appwrite --tag=baseline --"
|
||||
],
|
||||
"benchmark-compare": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"vendor/bin/phpbench run --report=appwrite --ref=baseline --"
|
||||
]
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -24,6 +36,7 @@
|
||||
"psr-4": {
|
||||
"Tests\\E2E\\": "tests/e2e",
|
||||
"Tests\\Unit\\": "tests/unit",
|
||||
"Tests\\Benchmarks\\": "tests/benchmarks",
|
||||
"Appwrite\\Tests\\": "tests/extensions"
|
||||
}
|
||||
},
|
||||
@@ -44,28 +57,29 @@
|
||||
"appwrite/php-clamav": "1.1.*",
|
||||
"appwrite/php-runtimes": "0.11.*",
|
||||
"utopia-php/abuse": "0.16.*",
|
||||
"utopia-php/analytics": "0.10.2",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
"utopia-php/audit": "0.17.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/cache": "dev-feat-redis-sync as 0.8.1",
|
||||
"utopia-php/cli": "0.14.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.28.*",
|
||||
"utopia-php/domains": "1.1.*",
|
||||
"utopia-php/dsn": "0.1.*",
|
||||
"utopia-php/framework": "0.26.*",
|
||||
"utopia-php/image": "0.5.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/messaging": "0.1.*",
|
||||
"utopia-php/queue": "0.5.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.3.*",
|
||||
"utopia-php/pools": "0.4.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.5.*",
|
||||
"utopia-php/domains": "1.1.*",
|
||||
"utopia-php/framework": "0.25.*",
|
||||
"utopia-php/image": "0.5.*",
|
||||
"utopia-php/dsn": "0.1.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/messaging": "0.1.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "dev-feat-transfer as 0.13.3",
|
||||
"utopia-php/storage": "0.13.*",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
"utopia-php/websocket": "0.1.0",
|
||||
"utopia-php/dsn": "0.1.0",
|
||||
"resque/php-resque": "1.3.6",
|
||||
"matomo/device-detector": "6.0.0",
|
||||
"dragonmantank/cron-expression": "3.3.1",
|
||||
@@ -73,8 +87,7 @@
|
||||
"phpmailer/phpmailer": "6.6.0",
|
||||
"chillerlan/php-qrcode": "4.3.3",
|
||||
"adhocore/jwt": "1.1.2",
|
||||
"slickdeals/statsd": "3.1.0",
|
||||
"league/csv": "9.7.1"
|
||||
"slickdeals/statsd": "3.1.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
@@ -88,7 +101,14 @@
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"swoole/ide-helper": "4.8.9",
|
||||
"textalk/websocket": "1.5.7"
|
||||
"textalk/websocket": "1.5.7",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
|
||||
"symfony/console": "^5.0",
|
||||
"symfony/filesystem": "^5.0",
|
||||
"symfony/finder": "^5.0",
|
||||
"symfony/options-resolver": "^5.0",
|
||||
"symfony/process": "^5.0"
|
||||
},
|
||||
"provide": {
|
||||
"ext-phpiredis": "*"
|
||||
|
||||
@@ -53,8 +53,8 @@ services:
|
||||
DEBUG: false
|
||||
TESTING: true
|
||||
VERSION: dev
|
||||
VITE_CONSOLE_MODE: cloud
|
||||
ports:
|
||||
VITE_CONSOLE_MODE: self-hosted
|
||||
ports:
|
||||
- 9501:80
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -85,7 +85,8 @@ services:
|
||||
- ./public:/usr/src/code/public
|
||||
- ./src:/usr/src/code/src
|
||||
- ./dev:/usr/local/dev
|
||||
|
||||
#- ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
|
||||
#- ./vendor/utopia-php/cache:/usr/src/code/vendor/utopia-php/cache
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
@@ -126,7 +127,6 @@ services:
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_DB_REPLICAS
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
@@ -136,7 +136,6 @@ services:
|
||||
- _APP_SMTP_SECURE
|
||||
- _APP_SMTP_USERNAME
|
||||
- _APP_SMTP_PASSWORD
|
||||
- _APP_USERS_STATS_RECIPIENTS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_INFLUXDB_HOST
|
||||
- _APP_INFLUXDB_PORT
|
||||
@@ -167,8 +166,6 @@ services:
|
||||
- _APP_SMS_PROVIDER
|
||||
- _APP_SMS_FROM
|
||||
- _APP_REGION
|
||||
- _APP_CONSOLE_GITHUB_APP_ID
|
||||
- _APP_CONSOLE_GITHUB_SECRET
|
||||
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
@@ -260,6 +257,71 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-sync-out:
|
||||
entrypoint: worker-sync-out
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-sync-out
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- appwrite-certificates:/storage/certificates:rw
|
||||
#- ./vendor/utopia-php/pools:/usr/src/code/vendor/utopia-php/pools
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_REGION
|
||||
|
||||
appwrite-worker-sync-in:
|
||||
entrypoint: worker-sync-in
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-sync-in
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- appwrite-certificates:/storage/certificates:rw
|
||||
#- ./vendor/utopia-php/cache:/usr/src/code/vendor/utopia-php/cache
|
||||
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_REGION
|
||||
|
||||
appwrite-worker-webhooks:
|
||||
entrypoint: worker-webhooks
|
||||
<<: *x-logging
|
||||
@@ -331,6 +393,7 @@ services:
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_EXECUTOR_SECRET
|
||||
- _APP_EXECUTOR_HOST
|
||||
- _APP_REGION
|
||||
|
||||
appwrite-worker-databases:
|
||||
entrypoint: worker-databases
|
||||
@@ -444,6 +507,7 @@ services:
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_REGION
|
||||
|
||||
appwrite-worker-functions:
|
||||
entrypoint: worker-functions
|
||||
@@ -484,8 +548,6 @@ services:
|
||||
- _APP_USAGE_STATS
|
||||
- DOCKERHUB_PULL_USERNAME
|
||||
- DOCKERHUB_PULL_PASSWORD
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_LOGGING_PROVIDER
|
||||
|
||||
appwrite-worker-mails:
|
||||
entrypoint: worker-mails
|
||||
@@ -549,40 +611,6 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-hamster:
|
||||
entrypoint: hamster
|
||||
<<: *x-logging
|
||||
container_name: appwrite-hamster
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_HAMSTER_INTERVAL
|
||||
- _APP_HAMSTER_TIME
|
||||
- _APP_MIXPANEL_TOKEN
|
||||
|
||||
appwrite-maintenance:
|
||||
entrypoint: maintenance
|
||||
<<: *x-logging
|
||||
@@ -594,6 +622,7 @@ services:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
@@ -614,6 +643,7 @@ services:
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_MAINTENANCE_INTERVAL
|
||||
- _APP_MAINTENANCE_RETENTION_EXECUTION
|
||||
@@ -623,6 +653,39 @@ services:
|
||||
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
|
||||
- _APP_MAINTENANCE_RETENTION_SCHEDULES
|
||||
|
||||
appwrite-edge-sync:
|
||||
entrypoint: edge-sync
|
||||
<<: *x-logging
|
||||
container_name: appwrite-edge-sync
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_SYNC_EDGE_INTERVAL
|
||||
- _APP_REGION
|
||||
|
||||
appwrite-usage:
|
||||
entrypoint: usage
|
||||
<<: *x-logging
|
||||
@@ -699,7 +762,7 @@ services:
|
||||
hostname: exc1
|
||||
<<: *x-logging
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/executor:0.1.6
|
||||
image: openruntimes/executor:0.2.0
|
||||
networks:
|
||||
- appwrite
|
||||
- runtimes
|
||||
@@ -722,7 +785,7 @@ services:
|
||||
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
|
||||
|
||||
mariadb:
|
||||
image: mysql:8.0.33 # 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
|
||||
<<: *x-logging
|
||||
networks:
|
||||
@@ -749,56 +812,6 @@ services:
|
||||
# - SMARTHOST_HOST=smtp
|
||||
# - SMARTHOST_PORT=587
|
||||
|
||||
xtrabackup:
|
||||
image: percona/percona-xtrabackup:latest
|
||||
container_name: xtrabackup
|
||||
command: sleep infinity
|
||||
volumes:
|
||||
- backup-data:/backups:rw
|
||||
- appwrite-mariadb:/var/lib/mysql:r
|
||||
networks:
|
||||
- appwrite
|
||||
user: 'root:root'
|
||||
|
||||
appwrite-backup:
|
||||
entrypoint: db-backup
|
||||
command:
|
||||
- --database=db_fra1_02
|
||||
<<: *x-logging
|
||||
container_name: appwrite-backup
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- backup-data:/backups:rw
|
||||
- appwrite-mariadb:/var/lib/mysql:r
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_CONNECTIONS_DB_REPLICAS
|
||||
- _APP_CONNECTIONS_BACKUPS_STORAGE
|
||||
|
||||
appwrite-restore:
|
||||
command: sleep infinity
|
||||
container_name: appwrite-backup-restore
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- backup-data:/backups:rw
|
||||
- appwrite-mariadb:/var/lib/mysql:rw
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_CONNECTIONS_DB_REPLICAS
|
||||
- _APP_CONNECTIONS_BACKUPS_STORAGE
|
||||
|
||||
redis:
|
||||
image: redis:7.0.4-alpine
|
||||
<<: *x-logging
|
||||
@@ -961,5 +974,4 @@ volumes:
|
||||
appwrite-config:
|
||||
appwrite-functions:
|
||||
appwrite-builds:
|
||||
backup-data:
|
||||
# appwrite-chronograf:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "./vendor/phpbench/phpbench/phpbench.schema.json",
|
||||
"runner.path": "tests/benchmarks",
|
||||
"runner.file_pattern": "*Bench.php",
|
||||
"runner.bootstrap": "app/init.php",
|
||||
"runner.revs": 1000,
|
||||
"runner.iterations": 3,
|
||||
"runner.retry_threshold": 5,
|
||||
"runner.warmup": 1,
|
||||
"report.generators": {
|
||||
"appwrite": {
|
||||
"extends": "aggregate",
|
||||
"cols": ["benchmark", "subject", "set" ,"revs", "its", "worst", "best", "mean"],
|
||||
"break": ["benchmark"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
>
|
||||
<extensions>
|
||||
<extension class="Appwrite\Tests\TestHook" />
|
||||
</extensions>
|
||||
|
||||
|
Before Width: | Height: | Size: 539 KiB |
|
Before Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 359 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 500 KiB |
|
Before Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 329 KiB |
|
Before Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 344 KiB |
|
Before Width: | Height: | Size: 311 KiB |
|
Before Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 263 KiB |
@@ -158,18 +158,6 @@ class Github extends OAuth2
|
||||
return $user['name'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserSlug(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['login'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
@@ -183,27 +171,13 @@ class Github extends OAuth2
|
||||
$emails = $this->request('GET', 'https://api.github.com/user/emails', ['Authorization: token ' . \urlencode($accessToken)]);
|
||||
|
||||
$emails = \json_decode($emails, true);
|
||||
|
||||
$verifiedEmail = null;
|
||||
$primaryEmail = null;
|
||||
|
||||
foreach ($emails as $email) {
|
||||
if (isset($email['verified']) && $email['verified'] === true) {
|
||||
$verifiedEmail = $email;
|
||||
|
||||
if (isset($email['primary']) && $email['primary'] === true) {
|
||||
$primaryEmail = $email;
|
||||
}
|
||||
$this->user['email'] = $email['email'];
|
||||
$this->user['verified'] = $email['verified'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($primaryEmail)) {
|
||||
$this->user['email'] = $primaryEmail['email'];
|
||||
$this->user['verified'] = $primaryEmail['verified'];
|
||||
} elseif (!empty($verifiedEmail)) {
|
||||
$this->user['email'] = $verifiedEmail['email'];
|
||||
$this->user['verified'] = $verifiedEmail['verified'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Resque;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
|
||||
class Certificate extends Event
|
||||
{
|
||||
protected bool $skipRenewCheck = false;
|
||||
protected ?Document $domain = null;
|
||||
|
||||
public function __construct()
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
parent::__construct(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME);
|
||||
}
|
||||
@@ -69,7 +70,9 @@ class Certificate extends Event
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
return Resque::enqueue($this->queue, $this->class, [
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
return $client->enqueue([
|
||||
'project' => $this->project,
|
||||
'domain' => $this->domain,
|
||||
'skipRenewCheck' => $this->skipRenewCheck
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Resque;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
|
||||
class Delete extends Event
|
||||
{
|
||||
@@ -14,7 +15,7 @@ class Delete extends Event
|
||||
protected ?string $hourlyUsageRetentionDatetime = null;
|
||||
|
||||
|
||||
public function __construct()
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
parent::__construct(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME);
|
||||
}
|
||||
@@ -121,7 +122,9 @@ class Delete extends Event
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
return Resque::enqueue($this->queue, $this->class, [
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
return $client->enqueue([
|
||||
'project' => $this->project,
|
||||
'type' => $this->type,
|
||||
'document' => $this->document,
|
||||
|
||||
@@ -7,11 +7,6 @@ use Utopia\Validator;
|
||||
|
||||
class Event extends Validator
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $message = 'Event is not valid.';
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
@@ -21,7 +16,7 @@ class Event extends Validator
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
return 'Event is not valid.';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,12 +40,6 @@ class Event extends Validator
|
||||
* Identify all sections of the pattern.
|
||||
*/
|
||||
$type = $parts[0] ?? false;
|
||||
|
||||
if ($type == 'functions') {
|
||||
$this->message = 'Triggering a function on a function event is not allowed.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$resource = $parts[1] ?? false;
|
||||
$hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false);
|
||||
$hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false);
|
||||
|
||||
@@ -50,7 +50,6 @@ class Exception extends \Exception
|
||||
public const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
|
||||
public const GENERAL_SERVER_ERROR = 'general_server_error';
|
||||
public const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
|
||||
public const GENERAL_CODES_DISABLED = 'general_codes_disabled';
|
||||
|
||||
/** Users */
|
||||
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
|
||||
@@ -61,7 +60,7 @@ class Exception extends \Exception
|
||||
public const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required';
|
||||
public const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted';
|
||||
public const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted';
|
||||
public const USER_INVALID_CODE = 'user_invalid_code';
|
||||
public const USER_CODE_INVALID = 'user_code_invalid';
|
||||
public const USER_INVALID_CREDENTIALS = 'user_invalid_credentials';
|
||||
public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited';
|
||||
public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
|
||||
@@ -156,7 +155,6 @@ class Exception extends \Exception
|
||||
public const PROJECT_UNKNOWN = 'project_unknown';
|
||||
public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
|
||||
public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
|
||||
public const PROJECT_ALREADY_EXISTS = 'project_already_exists';
|
||||
public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
|
||||
public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
|
||||
public const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
|
||||
@@ -178,11 +176,9 @@ class Exception extends \Exception
|
||||
/** Domain */
|
||||
public const DOMAIN_NOT_FOUND = 'domain_not_found';
|
||||
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
|
||||
public const DOMAIN_FORBIDDEN = 'domain_forbidden';
|
||||
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
|
||||
|
||||
protected $type = '';
|
||||
protected $errors = [];
|
||||
|
||||
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
|
||||
{
|
||||
|
||||