mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03bfed4e25 |
@@ -23,15 +23,15 @@ _APP_DB_SCHEMA=appwrite
|
||||
_APP_DB_USER=user
|
||||
_APP_DB_PASS=password
|
||||
_APP_DB_ROOT_PASS=rootsecretpassword
|
||||
_APP_STORAGE_DEVICE=local
|
||||
_APP_STORAGE_DEVICE=Local
|
||||
_APP_STORAGE_S3_ACCESS_KEY=
|
||||
_APP_STORAGE_S3_SECRET=
|
||||
_APP_STORAGE_S3_REGION=
|
||||
_APP_STORAGE_S3_REGION=us-east-1
|
||||
_APP_STORAGE_S3_BUCKET=
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY=
|
||||
_APP_STORAGE_DO_SPACES_SECRET=
|
||||
_APP_STORAGE_DO_SPACES_REGION=fra1
|
||||
_APP_STORAGE_DO_SPACES_BUCKET=videos-test
|
||||
_APP_STORAGE_DO_SPACES_REGION=us-east-1
|
||||
_APP_STORAGE_DO_SPACES_BUCKET=
|
||||
_APP_STORAGE_BACKBLAZE_ACCESS_KEY=
|
||||
_APP_STORAGE_BACKBLAZE_SECRET=
|
||||
_APP_STORAGE_BACKBLAZE_REGION=us-west-004
|
||||
@@ -58,10 +58,8 @@ _APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_STORAGE_LIMIT=30000000000000
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
_APP_STORAGE_PREVIEW_LIMIT=20000000
|
||||
_APP_PHONE_PROVIDER=phone://mock
|
||||
_APP_PHONE_FROM=+123456789
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
||||
_APP_FUNCTIONS_TIMEOUT=900
|
||||
_APP_FUNCTIONS_BUILD_TIMEOUT=900
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Release
|
||||
name: Unit & E2E
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -37,9 +37,9 @@ jobs:
|
||||
with:
|
||||
images: appwrite/appwrite
|
||||
tags: |
|
||||
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
|
||||
@@ -13,7 +13,3 @@ debug/
|
||||
app/sdks
|
||||
dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
/tests/tmp/
|
||||
/tests/resources/disk-a/very-large-file-1.mov
|
||||
/tests/resources/disk-a/very-big-file-2.mov
|
||||
/tests/resources/disk-a/video-srt.*
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = video-scopes-support
|
||||
branch = 2.3.3
|
||||
|
||||
+2
-41
@@ -1,51 +1,12 @@
|
||||
# Version 1.3.7
|
||||
|
||||
## Bugs
|
||||
- Fix the routing for the default OAuth2 pages [#5640](https://github.com/appwrite/appwrite/pull/5640) [#5648](https://github.com/appwrite/appwrite/pull/5648)
|
||||
- Add support for trailing slashes in Routes and URLs [#5647](https://github.com/appwrite/appwrite/pull/5647) [#5648](https://github.com/appwrite/appwrite/pull/5648)
|
||||
|
||||
# Version 1.3.6
|
||||
|
||||
## Bugs
|
||||
|
||||
- Fix Console deep linking to result in a 404 [#5632](https://github.com/appwrite/appwrite/pull/5632)
|
||||
- Fix ACME HTTP Challenge [#5632](https://github.com/appwrite/appwrite/pull/5632)
|
||||
|
||||
# Version 1.3.5
|
||||
|
||||
## Bugs
|
||||
|
||||
- Fix minimum length for string attribute default values [#5606](https://github.com/appwrite/appwrite/pull/5606), [#5602](https://github.com/appwrite/appwrite/pull/5602)
|
||||
- Update framework to fix route mismatches [#5603](https://github.com/appwrite/appwrite/pull/5603)
|
||||
|
||||
# Version 1.3.4
|
||||
|
||||
## Bugs
|
||||
|
||||
- Update migration to properly migrate bucket permissions [#5497](https://github.com/appwrite/appwrite/pull/5497)
|
||||
|
||||
# Version 1.3.3
|
||||
|
||||
## Bugs
|
||||
- Fixed migration resetting some data [#5455](https://github.com/appwrite/appwrite/pull/5455)
|
||||
|
||||
# Version 1.3.2
|
||||
|
||||
## Bugs
|
||||
- Fixed auto-setting custom ID on nested documents [#5363](https://github.com/appwrite/appwrite/pull/5363)
|
||||
- Fixed listDocuments not returning all the documents [#5395](https://github.com/appwrite/appwrite/pull/5395)
|
||||
- Fixed deleting keys, webhooks, platforms and domains after deleting project [#5395](https://github.com/appwrite/appwrite/pull/5395)
|
||||
- Fixed empty team prefs returning as JSON object rather array [#5361](https://github.com/appwrite/appwrite/pull/5361)
|
||||
|
||||
# Version 1.3.1
|
||||
|
||||
## Bugs
|
||||
- Fixed Migration issue regarding 500 error [#5356](https://github.com/appwrite/appwrite/pull/5356)
|
||||
- Fixed Migration issue regarding 500 error [4906](https://github.com/appwrite/appwrite/pull/5356)
|
||||
|
||||
# Version 1.3.0
|
||||
|
||||
## Features
|
||||
- Password dictionary setting allows to compare user's password against command password database [#4906](https://github.com/appwrite/appwrite/pull/4906)
|
||||
- Password dictionary setting allows to compare user's password against command password database [4906](https://github.com/appwrite/appwrite/pull/4906)
|
||||
- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured. Minimum is 0 which means disabled. [#4866](https://github.com/appwrite/appwrite/pull/4866)
|
||||
- Update APIs to check X-Appwrite-Timestamp header [#5024](https://github.com/appwrite/appwrite/pull/5024)
|
||||
- Database relationships [#5238](https://github.com/appwrite/appwrite/pull/5238)
|
||||
|
||||
+1
-1
@@ -321,7 +321,7 @@ The Runtimes for all supported cloud functions (multicore builds) can be found a
|
||||
|
||||
The following steps are used to generate a new console SDK:
|
||||
|
||||
1. Update the console spec file located at `app/config/specs/swagger2-<version-number>.console.json` using Appwrite Tasks. Run the `php app/cli.php specs version=<version-number> mode=normal` command in a running `appwrite/appwrite` container.
|
||||
1. Update the console spec file located at `app/config/specs/swagger2-<version-number>.console.json` using Appwrite Tasks. Run the `php app/cli.php specs <version-number> normal` command in a running `appwrite/appwrite` container.
|
||||
2. Generate a new SDK using the command `php app/cli.php sdks`.
|
||||
3. Change your working dir using `cd app/sdks/console-web`.
|
||||
4. Build the new SDK `npm run build`.
|
||||
|
||||
-10
@@ -124,13 +124,6 @@ ENV _APP_SERVER=swoole \
|
||||
_APP_LOGGING_PROVIDER= \
|
||||
_APP_LOGGING_CONFIG=
|
||||
|
||||
RUN \
|
||||
apk update \
|
||||
&& apk add --no-cache\
|
||||
mediainfo \
|
||||
ffmpeg \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN \
|
||||
if [ "$DEBUG" == "true" ]; then \
|
||||
apk add boost boost-dev; \
|
||||
@@ -149,14 +142,12 @@ COPY ./src /usr/src/code/src
|
||||
|
||||
# Set Volumes
|
||||
RUN mkdir -p /storage/uploads && \
|
||||
mkdir -p /storage/videos && \
|
||||
mkdir -p /storage/cache && \
|
||||
mkdir -p /storage/config && \
|
||||
mkdir -p /storage/certificates && \
|
||||
mkdir -p /storage/functions && \
|
||||
mkdir -p /storage/debug && \
|
||||
chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
|
||||
chown -Rf www-data.www-data /storage/videos && chmod -Rf 0755 /storage/videos && \
|
||||
chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
|
||||
chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \
|
||||
chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
|
||||
@@ -185,7 +176,6 @@ 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-videos && \
|
||||
chmod +x /usr/local/bin/worker-webhooks
|
||||
|
||||
# Letsencrypt Permissions
|
||||
|
||||
+6
-5
@@ -1,4 +1,5 @@
|
||||
> 好消息!Appwrite 云现已进入公开测试版!立即访问 cloud.appwrite.io 注册,体验无忧的托管服务。今天就加入我们的云端吧!☁️🎉
|
||||
> 准备好迎接狂风暴'云'了吗? 🌩 ☂️
|
||||
> Appwrite Cloud即将到来!你能够通过https://appwrite.io/cloud了解更多的资讯, 注册即可领取试用额度哦
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
@@ -23,7 +24,7 @@
|
||||
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
[**Appwrite 云公开测试版!立即注册!**](https://cloud.appwrite.io)
|
||||
[**宣布带有数据库关系的 Appwrite 1.3!了解最新消息!**](https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc)
|
||||
|
||||
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面简化了从零开始编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
|
||||
@@ -66,7 +67,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.3.7
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -78,7 +79,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.3.7
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -88,7 +89,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.3.7
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
> Great news! Appwrite Cloud is now in public beta! Sign up at [cloud.appwrite.io](https://cloud.appwrite.io) for a hassle-free, hosted experience. Join us in the Cloud today! ☁️🎉
|
||||
> It's going to get cloudy! 🌩 ☂️
|
||||
> The Appwrite Cloud is coming soon! You can learn more about our upcoming hosted solution and signup for free credits at: https://appwrite.io/cloud
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img width="260" height="39" src="https://appwrite.io/images/appwrite.svg" alt="Appwrite Logo"></a>
|
||||
<br />
|
||||
<b>Appwrite is a backend platform for developing Web, Mobile, and Flutter applications. Built with the open source community and optimized for developer experience in the coding languages you love.</b>
|
||||
<br />
|
||||
<b>A complete backend solution for your [Flutter / Vue / Angular / React / iOS / Android / *ANY OTHER*] app</b>
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
@@ -24,7 +26,7 @@
|
||||
|
||||
English | [简体中文](README-CN.md)
|
||||
|
||||
[**Announcing Appwrite Cloud Public Beta! Sign up today!**](https://cloud.appwrite.io)
|
||||
[**Announcing Appwrite 1.3 with Database Relationships! Learn what's new!**](https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc)
|
||||
|
||||
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker<nobr> microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
|
||||
|
||||
@@ -49,7 +51,6 @@ Table of Contents:
|
||||
- [CMD](#cmd)
|
||||
- [PowerShell](#powershell)
|
||||
- [Upgrade from an Older Version](#upgrade-from-an-older-version)
|
||||
- [One-Click Setups](#one-click-setups)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Services](#services)
|
||||
- [SDKs](#sdks)
|
||||
@@ -75,7 +76,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.3.7
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -87,7 +88,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.3.7
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -97,7 +98,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.3.7
|
||||
appwrite/appwrite:1.3.1
|
||||
```
|
||||
|
||||
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
|
||||
|
||||
@@ -32,17 +32,6 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('enabled'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -3206,28 +3195,6 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceType',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 255,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('mimeType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 255, // https://tools.ietf.org/html/rfc4288#section-4.2
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'accessedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
@@ -3284,17 +3251,6 @@ $collections = [
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('bucketInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -3624,890 +3580,6 @@ $collections = [
|
||||
],
|
||||
],
|
||||
],
|
||||
'videos' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'videos',
|
||||
'$name' => 'Videos',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'bucketId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'bucketInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'size',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'format',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'duration',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'width',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'height',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'aspectRatio',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoFormat',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoFormatProfile',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoBitRate',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoFrameRateMode',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoFrameRate',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'audioFormat',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'audioBitRate',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'audioSampleRate',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_bucket_file',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['bucketId', 'fileId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
]
|
||||
],
|
||||
'videos_previews' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'videos_previews',
|
||||
'$name' => 'Videos_previews',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'videoId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'type',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'path',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'second',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_video_type',
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['videoId', 'type', 'name'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
]
|
||||
],
|
||||
'videos_renditions' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'videos_renditions',
|
||||
'$name' => 'Videos_renditions',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'videoId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'profileId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'profileInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'startedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => 'endedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => 'path',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'targetDuration',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'width',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'height',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoBitRate',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'audioBitRate',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'metadata',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 4096,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => 'status',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'progress',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'output',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_video_output_profile',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['videoId', 'output', 'profileId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
]
|
||||
],
|
||||
'videos_renditions_segments' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'videos_rendition_segments',
|
||||
'$name' => 'Videos_rendition_segments',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'renditionId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'renditionInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileName',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'path',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'duration',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'streamId',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'isInit',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_renditionId',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['renditionId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
]
|
||||
],
|
||||
'videos_profiles' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'videos_profiles',
|
||||
'$name' => 'Videos_profiles',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoBitRate',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'audioBitRate',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'width',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'height',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_profile',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['width', 'height', 'videoBitRate', 'audioBitRate'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
]
|
||||
],
|
||||
'videos_subtitles' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'videos_subtitles',
|
||||
'$name' => 'Videos_subtitles',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'videoId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'bucketId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'bucketInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'path',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'code',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'default',
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'targetDuration',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'status',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
|
||||
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_bucket_video_subtitle',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['videoId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
]
|
||||
],
|
||||
'videos_subtitles_segments' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'videos_subtitles_segments',
|
||||
'$name' => 'Videos_subtitles_segments',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'subtitleId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'subtitleInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileName',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'path',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'duration',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_subtitleId',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['subtitleId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
return $collections;
|
||||
|
||||
+3
-78
@@ -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_USAGE_DISABLED => [
|
||||
'name' => Exception::GENERAL_USAGE_DISABLED,
|
||||
'description' => 'Usage stats is not configured. Please check the value of the _APP_USAGE_STATS environment variable of your Appwrite server.',
|
||||
'code' => 501,
|
||||
],
|
||||
|
||||
/** User Errors */
|
||||
Exception::USER_COUNT_EXCEEDED => [
|
||||
@@ -107,12 +102,12 @@ return [
|
||||
],
|
||||
Exception::USER_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_ALREADY_EXISTS,
|
||||
'description' => 'A user with the same id, email, or phone already exists in your project.',
|
||||
'description' => 'A user with the same email already exists in your project.',
|
||||
'code' => 409,
|
||||
],
|
||||
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 => [
|
||||
@@ -403,14 +398,9 @@ return [
|
||||
'description' => 'The document structure is invalid. Please ensure the attributes match the collection definition.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::DOCUMENT_MISSING_DATA => [
|
||||
'name' => Exception::DOCUMENT_MISSING_DATA,
|
||||
'description' => 'The document data is missing. You must provide the document data.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::DOCUMENT_MISSING_PAYLOAD => [
|
||||
'name' => Exception::DOCUMENT_MISSING_PAYLOAD,
|
||||
'description' => 'The document data and permissions are missing. You must provide either the document data or permissions to be updated.',
|
||||
'description' => 'The document payload is missing.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::DOCUMENT_ALREADY_EXISTS => [
|
||||
@@ -499,11 +489,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.',
|
||||
@@ -579,66 +564,6 @@ return [
|
||||
'description' => 'Domain verification for the requested domain has failed.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::VIDEO_PROFILE_NOT_FOUND => [
|
||||
'name' => Exception::VIDEO_PROFILE_NOT_FOUND,
|
||||
'description' => 'Video profile not found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::VIDEO_NOT_VALID => [
|
||||
'name' => Exception::VIDEO_NOT_VALID,
|
||||
'description' => 'Video not valid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::VIDEO_RENDITION_NOT_FOUND => [
|
||||
'name' => Exception::VIDEO_RENDITION_NOT_FOUND,
|
||||
'description' => 'Video rendition not found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::VIDEO_SUBTITLE_NOT_FOUND => [
|
||||
'name' => Exception::VIDEO_SUBTITLE_NOT_FOUND,
|
||||
'description' => 'Video subtitle not found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::VIDEO_LANGUAGE_CODE_NOT_VALID => [
|
||||
'name' => Exception::VIDEO_LANGUAGE_CODE_NOT_VALID,
|
||||
'description' => 'Video language code not valid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::VIDEO_SUBTITLE_NOT_VALID => [
|
||||
'name' => Exception::VIDEO_SUBTITLE_NOT_VALID,
|
||||
'description' => 'Video subtitle not valid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::VIDEO_SECOND_OUT_OF_RANGE => [
|
||||
'name' => Exception::VIDEO_SECOND_OUT_OF_RANGE,
|
||||
'description' => 'Video second out of duration range.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::VIDEO_NOT_FOUND => [
|
||||
'name' => Exception::VIDEO_NOT_FOUND,
|
||||
'description' => 'Video not found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::VIDEO_PREVIEW_NOT_FOUND => [
|
||||
'name' => Exception::VIDEO_PREVIEW_NOT_FOUND,
|
||||
'description' => 'Video preview not found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::VIDEO_TIMELINE_NOT_FOUND => [
|
||||
'name' => Exception::VIDEO_TIMELINE_NOT_FOUND,
|
||||
'description' => 'Video timeline not found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND => [
|
||||
'name' => Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND,
|
||||
'description' => 'Video rendition segment not found',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND => [
|
||||
'name' => Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND,
|
||||
'description' => 'Video subtitle segment not found',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::DOMAIN_TARGET_INVALID => [
|
||||
'name' => Exception::DOMAIN_TARGET_INVALID,
|
||||
'description' => 'Your Appwrite instance is not publicly accessible. Please check the _APP_DOMAIN_TARGET environment variable of your Appwrite server.',
|
||||
|
||||
+1
-29
@@ -236,33 +236,5 @@ return [
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a function is updated.',
|
||||
]
|
||||
],
|
||||
'videos' => [
|
||||
'$model' => Response::MODEL_VIDEO,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any videos event.',
|
||||
'renditions' => [
|
||||
'$model' => Response::MODEL_RENDITION,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any renditions event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a rendition is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a rendition is deleted.'
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a rendition is updated.'
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a video is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a video is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a video is updated.',
|
||||
]
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+22
-22
@@ -28,7 +28,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
|
||||
'gitRepoName' => 'sdk-for-web',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
'demos' => [
|
||||
[
|
||||
'icon' => 'react.svg',
|
||||
@@ -76,7 +76,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-flutter.git',
|
||||
'gitRepoName' => 'sdk-for-flutter',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'apple',
|
||||
@@ -94,7 +94,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-apple.git',
|
||||
'gitRepoName' => 'sdk-for-apple',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'objective-c',
|
||||
@@ -111,7 +111,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-objective-c.git',
|
||||
'gitRepoName' => 'sdk-for-objective-c',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'android',
|
||||
@@ -129,7 +129,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-android.git',
|
||||
'gitRepoName' => 'sdk-for-android',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'main',
|
||||
'docDirectories' => [
|
||||
'Kotlin' => 'kotlin',
|
||||
'Java' => 'java',
|
||||
@@ -196,14 +196,14 @@ return [
|
||||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'main',
|
||||
'gitRepoName' => 'sdk-for-console',
|
||||
'gitUserName' => 'appwrite',
|
||||
],
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '2.0.2',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -216,7 +216,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-cli.git',
|
||||
'gitRepoName' => 'sdk-for-cli',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -244,7 +244,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-node.git',
|
||||
'gitRepoName' => 'sdk-for-node',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'deno',
|
||||
@@ -262,7 +262,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-deno.git',
|
||||
'gitRepoName' => 'sdk-for-deno',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'php',
|
||||
@@ -280,7 +280,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-php.git',
|
||||
'gitRepoName' => 'sdk-for-php',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'python',
|
||||
@@ -298,7 +298,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-python.git',
|
||||
'gitRepoName' => 'sdk-for-python',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'ruby',
|
||||
@@ -316,7 +316,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-ruby.git',
|
||||
'gitRepoName' => 'sdk-for-ruby',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'go',
|
||||
@@ -334,7 +334,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-go.git',
|
||||
'gitRepoName' => 'sdk-for-go',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'java',
|
||||
@@ -352,17 +352,17 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-java.git',
|
||||
'gitRepoName' => 'sdk-for-java',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.4.0',
|
||||
'version' => '2.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
'enabled' => false,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'dev' => true,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'csharp',
|
||||
@@ -370,7 +370,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-dotnet.git',
|
||||
'gitRepoName' => 'sdk-for-dotnet',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'main',
|
||||
],
|
||||
[
|
||||
'key' => 'dart',
|
||||
@@ -388,7 +388,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-dart.git',
|
||||
'gitRepoName' => 'sdk-for-dart',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'master',
|
||||
],
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
@@ -406,7 +406,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-kotlin.git',
|
||||
'gitRepoName' => 'sdk-for-kotlin',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'main',
|
||||
'docDirectories' => [
|
||||
'Kotlin' => 'kotlin',
|
||||
'Java' => 'java',
|
||||
@@ -428,7 +428,7 @@ return [
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-swift.git',
|
||||
'gitRepoName' => 'sdk-for-swift',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'gitBranch' => 'main',
|
||||
],
|
||||
[
|
||||
'key' => 'graphql',
|
||||
|
||||
@@ -20,8 +20,6 @@ $member = [
|
||||
'avatars.read',
|
||||
'execution.read',
|
||||
'execution.write',
|
||||
'videos.read',
|
||||
'videos.write',
|
||||
];
|
||||
|
||||
$admins = [
|
||||
@@ -53,8 +51,6 @@ $admins = [
|
||||
'functions.write',
|
||||
'execution.read',
|
||||
'execution.write',
|
||||
'videos.read',
|
||||
'videos.write',
|
||||
];
|
||||
|
||||
return [
|
||||
@@ -72,7 +68,6 @@ return [
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'execution.write',
|
||||
'videos.read',
|
||||
],
|
||||
],
|
||||
Auth::USER_ROLE_USERS => [
|
||||
|
||||
@@ -76,10 +76,4 @@ return [ // List of publicly visible scopes
|
||||
'health.read' => [
|
||||
'description' => 'Access to read your project\'s health status',
|
||||
],
|
||||
'videos.read' => [
|
||||
'description' => 'Access to read your project\'s video content',
|
||||
],
|
||||
'videos.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s video content',
|
||||
]
|
||||
];
|
||||
|
||||
@@ -199,17 +199,4 @@ return [
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'videos' => [
|
||||
'key' => 'videos',
|
||||
'name' => 'Videos',
|
||||
'subtitle' => 'Appwrite\'s Videos Endpoint',
|
||||
'description' => 'Videos Endpoint',
|
||||
'controller' => 'api/videos.php',
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
];
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -152,7 +152,7 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_LOGGING_PROVIDER',
|
||||
'description' => 'This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, to enable the logger set the value to one of \'sentry\', \'raygun\', \'appSignal\', \'logOwl\'',
|
||||
'description' => 'This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, to enable the logger set the value to one of \'sentry\', \'raygun\', \'appsignal\', \'logowl\'',
|
||||
'introduction' => '0.12.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
|
||||
'name' => '360p',
|
||||
'videoBitRate' => 890, //video BitRate in Kbps
|
||||
'audioBitRate' => 64, //audio BitRate in Kbps
|
||||
'width' => 640, //width resolution in px
|
||||
'height' => 360, //height resolution in px
|
||||
],
|
||||
[
|
||||
|
||||
'name' => '480p',
|
||||
'videoBitRate' => 2100,
|
||||
'audioBitRate' => 64,
|
||||
'width' => 854,
|
||||
'height' => 480,
|
||||
],
|
||||
[
|
||||
'name' => '576p',
|
||||
'videoBitRate' => 2538,
|
||||
'audioBitRate' => 128,
|
||||
'width' => 1024,
|
||||
'height' => 576,
|
||||
],
|
||||
[
|
||||
'name' => '720p',
|
||||
'videoBitRate' => 3551,
|
||||
'audioBitRate' => 128,
|
||||
'width' => 1280,
|
||||
'height' => 720,
|
||||
],
|
||||
[
|
||||
'name' => '1080p',
|
||||
'videoBitRate' => 4800,
|
||||
'audioBitRate' => 128,
|
||||
'width' => 1920,
|
||||
'height' => 1080,
|
||||
],
|
||||
[
|
||||
'name' => '2160p',
|
||||
'videoBitRate' => 16000,
|
||||
'audioBitRate' => 356,
|
||||
'width' => 4096,
|
||||
'height' => 2160,
|
||||
],
|
||||
];
|
||||
+1
-1
Submodule app/console updated: 9174d8f8cb...ac4181aea4
@@ -10,8 +10,8 @@ use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\URL;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\URL\URL as URLParser;
|
||||
@@ -64,7 +64,7 @@ App::post('/v1/account')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('abuse-limit', 10)
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
@@ -144,7 +144,7 @@ App::post('/v1/account')
|
||||
App::post('/v1/account/sessions/email')
|
||||
->alias('/v1/account/sessions')
|
||||
->desc('Create Email Session')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->groups(['api', 'account', 'auth', 'session'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('scope', 'public')
|
||||
->label('auth.type', 'emailPassword')
|
||||
@@ -375,7 +375,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
|
||||
|
||||
App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->desc('OAuth2 Redirect')
|
||||
->groups(['api', 'account'])
|
||||
->groups(['api', 'account', 'session'])
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('scope', 'public')
|
||||
@@ -641,7 +641,7 @@ App::post('/v1/account/sessions/magic-url')
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},email:{param-email}')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->inject('request')
|
||||
@@ -733,11 +733,33 @@ App::post('/v1/account/sessions/magic-url')
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $loginSecret, 'expire' => $expire, 'project' => $project->getId()]);
|
||||
$url = Template::unParseURL($url);
|
||||
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $project->getAttribute('name'));
|
||||
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.magicSession.subject");
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.magicSession.hello"))
|
||||
->setParam('{{name}}', '')
|
||||
->setParam('{{body}}', $locale->getText("emails.magicSession.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.magicSession.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.magicSession.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.magicSession.signature"))
|
||||
->setParam('{{project}}', $project->getAttribute('name'))
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
$mails
|
||||
->setType(MAIL_TYPE_MAGIC_SESSION)
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->setUrl($url)
|
||||
->setLocale($locale->default)
|
||||
->trigger()
|
||||
;
|
||||
|
||||
@@ -759,7 +781,7 @@ App::post('/v1/account/sessions/magic-url')
|
||||
|
||||
App::put('/v1/account/sessions/magic-url')
|
||||
->desc('Create Magic URL session (confirmation)')
|
||||
->groups(['api', 'account'])
|
||||
->groups(['api', 'account', 'session'])
|
||||
->label('scope', 'public')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('audits.event', 'session.update')
|
||||
@@ -896,7 +918,7 @@ App::post('/v1/account/sessions/phone')
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},email:{param-email}')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -1001,7 +1023,7 @@ App::post('/v1/account/sessions/phone')
|
||||
|
||||
App::put('/v1/account/sessions/phone')
|
||||
->desc('Create Phone Session (confirmation)')
|
||||
->groups(['api', 'account'])
|
||||
->groups(['api', 'account', 'session'])
|
||||
->label('scope', 'public')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('usage.metric', 'sessions.{scope}.requests.create')
|
||||
@@ -1116,7 +1138,7 @@ App::put('/v1/account/sessions/phone')
|
||||
|
||||
App::post('/v1/account/sessions/anonymous')
|
||||
->desc('Create Anonymous Session')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->groups(['api', 'account', 'auth', 'session'])
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
->label('scope', 'public')
|
||||
->label('auth.type', 'anonymous')
|
||||
@@ -1741,12 +1763,11 @@ App::patch('/v1/account/status')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->inject('requestTimestamp')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$user->setAttribute('status', false);
|
||||
|
||||
@@ -1760,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);
|
||||
});
|
||||
|
||||
@@ -2074,12 +2089,35 @@ App::post('/v1/account/recovery')
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
|
||||
$url = Template::unParseURL($url);
|
||||
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.recovery.subject");
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.recovery.hello"))
|
||||
->setParam('{{name}}', $profile->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.recovery.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.recovery.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.recovery.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
|
||||
$mails
|
||||
->setType(MAIL_TYPE_RECOVERY)
|
||||
->setRecipient($profile->getAttribute('email', ''))
|
||||
->setUrl($url)
|
||||
->setLocale($locale->default)
|
||||
->setName($profile->getAttribute('name'))
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setSubject($subject)
|
||||
->trigger();
|
||||
;
|
||||
|
||||
@@ -2235,11 +2273,32 @@ App::post('/v1/account/verification')
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]);
|
||||
$url = Template::unParseURL($url);
|
||||
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.verification.subject");
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.verification.hello"))
|
||||
->setParam('{{name}}', $user->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.verification.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.verification.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.verification.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.verification.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
$mails
|
||||
->setType(MAIL_TYPE_VERIFICATION)
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->setUrl($url)
|
||||
->setLocale($locale->default)
|
||||
->setName($user->getAttribute('name'))
|
||||
->trigger()
|
||||
;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Utopia\Validator\URL;
|
||||
use Appwrite\URL\URL as URLParse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
@@ -242,8 +242,8 @@ App::get('/v1/avatars/favicon')
|
||||
case 'jpeg':
|
||||
$size = \explode('x', \strtolower($sizes));
|
||||
|
||||
$sizeWidth = (int) $size[0] ?? 0;
|
||||
$sizeHeight = (int) $size[1] ?? 0;
|
||||
$sizeWidth = (int) ($size[0] ?? 0);
|
||||
$sizeHeight = (int) ($size[1] ?? 0);
|
||||
|
||||
if (($sizeWidth * $sizeHeight) >= $space) {
|
||||
$space = $sizeWidth * $sizeHeight;
|
||||
|
||||
+155
-163
@@ -384,12 +384,11 @@ App::post('/v1/databases')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_DATABASE) // Model for database needs to be created
|
||||
->param('databaseId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Database name. Max length: 128 chars.')
|
||||
->param('enabled', true, new Boolean(), 'Is database enabled?', true)
|
||||
->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $databaseId, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId;
|
||||
|
||||
@@ -397,7 +396,6 @@ App::post('/v1/databases')
|
||||
$dbForProject->createDocument('databases', new Document([
|
||||
'$id' => $databaseId,
|
||||
'name' => $name,
|
||||
'enabled' => $enabled,
|
||||
'search' => implode(' ', [$databaseId, $name]),
|
||||
]));
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
@@ -509,7 +507,7 @@ App::get('/v1/databases/:databaseId')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $databaseId, Response $response, Database $dbForProject) {
|
||||
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -621,13 +619,12 @@ App::put('/v1/databases/:databaseId')
|
||||
->label('sdk.response.model', Response::MODEL_DATABASE)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('name', null, new Text(128), 'Database name. Max length: 128 chars.')
|
||||
->param('enabled', true, new Boolean(), 'Is database enabled?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $databaseId, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -636,7 +633,6 @@ App::put('/v1/databases/:databaseId')
|
||||
try {
|
||||
$database = $dbForProject->updateDocument('databases', $databaseId, $database
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', implode(' ', [$databaseId, $name])));
|
||||
} catch (AuthorizationException $exception) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
@@ -718,16 +714,14 @@ App::post('/v1/databases/:databaseId/collections')
|
||||
->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(), 'Is collection enabled?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $events) {
|
||||
->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -743,13 +737,13 @@ App::post('/v1/databases/:databaseId/collections')
|
||||
'databaseId' => $databaseId,
|
||||
'$permissions' => $permissions ?? [],
|
||||
'documentSecurity' => $documentSecurity,
|
||||
'enabled' => $enabled,
|
||||
'enabled' => true,
|
||||
'name' => $name,
|
||||
'search' => implode(' ', [$collectionId, $name]),
|
||||
]));
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
|
||||
|
||||
$dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), permissions: $permissions ?? [], documentSecurity: $documentSecurity);
|
||||
$dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::COLLECTION_ALREADY_EXISTS);
|
||||
} catch (LimitException) {
|
||||
@@ -785,12 +779,11 @@ App::get('/v1/databases/:databaseId/collections')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) {
|
||||
->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -841,12 +834,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode) {
|
||||
->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -882,7 +874,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
|
||||
->inject('geodb')
|
||||
->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -980,13 +972,12 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->param('enabled', true, new Boolean(), 'Is collection enabled?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $events) {
|
||||
->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -1010,7 +1001,6 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->setAttribute('documentSecurity', $documentSecurity)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', implode(' ', [$collectionId, $name])));
|
||||
$dbForProject->updateCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $permissions, $documentSecurity);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (StructureException $exception) {
|
||||
@@ -1045,14 +1035,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode, Event $events, Delete $deletes) {
|
||||
->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, Event $events, Delete $deletes) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -1070,13 +1059,15 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
||||
|
||||
$deletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($collection);
|
||||
->setDocument($collection)
|
||||
;
|
||||
|
||||
$events
|
||||
->setContext('database', $database)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setPayload($response->output($collection, Response::MODEL_COLLECTION));
|
||||
->setPayload($response->output($collection, Response::MODEL_COLLECTION))
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
@@ -1103,7 +1094,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Text(0, 0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
|
||||
->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -1112,7 +1103,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $database, Event $events) {
|
||||
|
||||
// Ensure attribute default is within required size
|
||||
$validator = new Text($size, 0);
|
||||
$validator = new Text($size);
|
||||
if (!is_null($default) && !$validator->isValid($default)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
|
||||
}
|
||||
@@ -1601,7 +1592,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati
|
||||
Database $dbForProject,
|
||||
EventDatabase $database,
|
||||
Event $events
|
||||
) {
|
||||
) {
|
||||
$key ??= $relatedCollectionId;
|
||||
$twoWayKey ??= $collectionId;
|
||||
|
||||
@@ -1661,7 +1652,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -1712,7 +1703,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -1777,7 +1768,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('default', null, new Nullable(new Text(0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
@@ -2185,7 +2176,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
|
||||
$events,
|
||||
type: Database::VAR_RELATIONSHIP,
|
||||
required: false,
|
||||
options: [
|
||||
options : [
|
||||
'onDelete' => $onDelete
|
||||
]
|
||||
);
|
||||
@@ -2226,7 +2217,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $database, Event $events) {
|
||||
|
||||
$db = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -2279,7 +2270,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
|
||||
->setType(DATABASE_TYPE_DELETE_ATTRIBUTE)
|
||||
->setCollection($collection)
|
||||
->setDatabase($db)
|
||||
->setDocument($attribute);
|
||||
->setDocument($attribute)
|
||||
;
|
||||
|
||||
// Select response model based on type and format
|
||||
$type = $attribute->getAttribute('type');
|
||||
@@ -2307,7 +2299,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
|
||||
->setParam('attributeId', $attribute->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $db)
|
||||
->setPayload($response->output($attribute, $model));
|
||||
->setPayload($response->output($attribute, $model))
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
@@ -2341,7 +2334,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, Response $response, Database $dbForProject, EventDatabase $database, Event $events) {
|
||||
|
||||
$db = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -2364,7 +2357,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
}
|
||||
|
||||
// Convert Document[] to array of attribute metadata
|
||||
$oldAttributes = \array_map(fn($a) => $a->getArrayCopy(), $collection->getAttribute('attributes'));
|
||||
$oldAttributes = \array_map(fn ($a) => $a->getArrayCopy(), $collection->getAttribute('attributes'));
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$id',
|
||||
@@ -2406,7 +2399,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
$attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key'));
|
||||
|
||||
if ($attributeIndex === false) {
|
||||
throw new Exception(Exception::ATTRIBUTE_UNKNOWN, 'Unknown attribute: ' . $attribute);
|
||||
throw new Exception(Exception::ATTRIBUTE_UNKNOWN, 'Unknown attribute: ' . $attribute . '. Verify the attribute name or create the attribute.");
|
||||
}
|
||||
|
||||
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
|
||||
@@ -2450,14 +2443,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
->setType(DATABASE_TYPE_CREATE_INDEX)
|
||||
->setDatabase($db)
|
||||
->setCollection($collection)
|
||||
->setDocument($index);
|
||||
->setDocument($index)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('indexId', $index->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $db);
|
||||
->setContext('database', $db)
|
||||
;
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -2484,7 +2479,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -2524,7 +2519,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -2575,7 +2570,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $database, Event $events) {
|
||||
|
||||
$db = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -2603,7 +2598,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
||||
->setType(DATABASE_TYPE_DELETE_INDEX)
|
||||
->setDatabase($db)
|
||||
->setCollection($collection)
|
||||
->setDocument($index);
|
||||
->setDocument($index)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('databaseId', $databaseId)
|
||||
@@ -2611,7 +2607,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
||||
->setParam('indexId', $index->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $db)
|
||||
->setPayload($response->output($index, Response::MODEL_INDEX));
|
||||
->setPayload($response->output($index, Response::MODEL_INDEX))
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
@@ -2653,7 +2650,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
if (empty($data)) {
|
||||
throw new Exception(Exception::DOCUMENT_MISSING_DATA);
|
||||
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
|
||||
}
|
||||
|
||||
if (isset($data['$id'])) {
|
||||
@@ -2662,7 +2659,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -2737,7 +2734,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
@@ -2746,13 +2743,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
if (empty($related)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isList = \is_array($related) && \array_values($related) === $related;
|
||||
|
||||
if ($isList) {
|
||||
$relations = $related;
|
||||
} else {
|
||||
$relations = [$related];
|
||||
if (!\is_array($related)) {
|
||||
$related = [$related];
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
@@ -2760,15 +2752,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
|
||||
);
|
||||
|
||||
foreach ($relations as &$relation) {
|
||||
if (
|
||||
\is_array($relation)
|
||||
&& \array_values($relation) !== $relation
|
||||
&& !isset($relation['$id'])
|
||||
) {
|
||||
$relation['$id'] = ID::unique();
|
||||
$relation = new Document($relation);
|
||||
}
|
||||
foreach ($related as $relation) {
|
||||
if ($relation instanceof Document) {
|
||||
$current = Authorization::skip(
|
||||
fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), $relation->getId())
|
||||
@@ -2777,7 +2761,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
if ($current->isEmpty()) {
|
||||
$type = Database::PERMISSION_CREATE;
|
||||
|
||||
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
|
||||
if (!isset($relation['$id']) || $relation['$id'] === 'unique()') {
|
||||
$relation['$id'] = ID::unique();
|
||||
}
|
||||
} else {
|
||||
@@ -2790,12 +2774,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$checkPermissions($relatedCollection, $relation, $type);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isList) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
|
||||
} else {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2816,7 +2794,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
@@ -2849,7 +2827,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database);
|
||||
->setContext('database', $database)
|
||||
;
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -2879,25 +2858,18 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
|
||||
|
||||
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
|
||||
if (!$collection->getAttribute('documentSecurity', false)) {
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
if (!$validator->isValid($collection->getRead())) {
|
||||
$collection = new Document();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate queries
|
||||
@@ -2926,22 +2898,40 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
|
||||
$documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries));
|
||||
|
||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($collection->getRead());
|
||||
if (!$valid) {
|
||||
$total = $documentSecurity
|
||||
? $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT)
|
||||
: 0;
|
||||
} else {
|
||||
$total = Authorization::skip(fn() => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
|
||||
}
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool {
|
||||
if ($document->isEmpty()) {
|
||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
|
||||
$valid = $validator->isValid($collection->getRead());
|
||||
if (!$documentSecurity && !$valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$valid = $valid || $validator->isValid($document->getRead());
|
||||
if ($documentSecurity && !$valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$document->removeAttribute('$collection');
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
$document->setAttribute('$collectionId', $collection->getId());
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
@@ -2957,7 +2947,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
$relatedCollection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId));
|
||||
$relatedCollection = Authorization::skip(fn() =>
|
||||
$dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId));
|
||||
|
||||
foreach ($relations as $index => $doc) {
|
||||
if ($doc instanceof Document) {
|
||||
@@ -2977,11 +2968,19 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
return true;
|
||||
};
|
||||
|
||||
// The linter is forcing this indentation
|
||||
foreach ($documents as $document) {
|
||||
$processDocument($collection, $document);
|
||||
// The linter is forcing this indentation
|
||||
foreach ($documents as $index => $document) {
|
||||
if (!$processDocument($collection, $document)) {
|
||||
unset($documents[$index]);
|
||||
|
||||
if ($valid) {
|
||||
$total--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$documents = \array_values($documents);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $total,
|
||||
'documents' => $documents,
|
||||
@@ -3013,9 +3012,9 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -3036,7 +3035,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries);
|
||||
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries));
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
@@ -3044,8 +3043,17 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
|
||||
if ($document->isEmpty()) {
|
||||
return;
|
||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
|
||||
$valid = $validator->isValid($collection->getRead());
|
||||
if (!$documentSecurity && !$valid) {
|
||||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$valid = $valid || $validator->isValid($document->getRead());
|
||||
if ($documentSecurity && !$valid) {
|
||||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
@@ -3053,7 +3061,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
@@ -3108,7 +3116,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
->inject('geodb')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
@@ -3225,9 +3233,9 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -3279,7 +3287,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
$permissions = $document->getPermissions() ?? [];
|
||||
}
|
||||
|
||||
$data = \array_merge($document->getArrayCopy(), $data); // Merge existing data with new data
|
||||
$data = \array_merge($document->getArrayCopy(), $data);
|
||||
$data['$collection'] = $collection->getId(); // Make sure user doesn't switch collectionID
|
||||
$data['$createdAt'] = $document->getCreatedAt(); // Make sure user doesn't switch createdAt
|
||||
$data['$id'] = $document->getId(); // Make sure user doesn't switch document unique ID
|
||||
@@ -3304,7 +3312,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
@@ -3313,13 +3321,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
if (empty($related)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isList = \is_array($related) && \array_values($related) === $related;
|
||||
|
||||
if ($isList) {
|
||||
$relations = $related;
|
||||
} else {
|
||||
$relations = [$related];
|
||||
if (!\is_array($related)) {
|
||||
$related = [$related];
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
@@ -3327,15 +3330,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
|
||||
);
|
||||
|
||||
foreach ($relations as &$relation) {
|
||||
if (
|
||||
\is_array($relation)
|
||||
&& \array_values($relation) !== $relation
|
||||
&& !isset($relation['$id'])
|
||||
) {
|
||||
$relation['$id'] = ID::unique();
|
||||
$relation = new Document($relation);
|
||||
}
|
||||
foreach ($related as $relation) {
|
||||
if ($relation instanceof Document) {
|
||||
$oldDocument = Authorization::skip(fn() => $dbForProject->getDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(),
|
||||
@@ -3345,7 +3340,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
if ($oldDocument->isEmpty()) {
|
||||
$type = Database::PERMISSION_CREATE;
|
||||
|
||||
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
|
||||
if (!isset($relation['$id']) || $relation['$id'] === 'unique()') {
|
||||
$relation['$id'] = ID::unique();
|
||||
}
|
||||
} else {
|
||||
@@ -3358,26 +3353,20 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
$checkPermissions($relatedCollection, $relation, $oldDocument, $type);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isList) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
|
||||
} else {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$checkPermissions($collection, $newDocument, $document, Database::PERMISSION_UPDATE);
|
||||
|
||||
try {
|
||||
$document = $dbForProject->withRequestTimestamp(
|
||||
$document = Authorization::skip(fn() => $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
fn() => $dbForProject->updateDocument(
|
||||
fn () => $dbForProject->updateDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
$document->getId(),
|
||||
$newDocument
|
||||
)
|
||||
);
|
||||
));
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (DuplicateException) {
|
||||
@@ -3393,7 +3382,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
@@ -3426,7 +3415,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('documentId', $document->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database);
|
||||
->setContext('database', $database)
|
||||
;
|
||||
|
||||
$response->dynamic($document, Response::MODEL_DOCUMENT);
|
||||
});
|
||||
@@ -3463,9 +3453,9 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $events, Delete $deletes, string $mode) {
|
||||
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -3500,7 +3490,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
@@ -3531,7 +3521,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
|
||||
$checkPermissions($collection, $document);
|
||||
|
||||
Authorization::skip(fn() => $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
|
||||
Authorization::skip(fn () => $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
|
||||
try {
|
||||
$dbForProject->deleteDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
@@ -3554,7 +3544,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
);
|
||||
|
||||
foreach ($relationships as $relationship) {
|
||||
@@ -3584,7 +3574,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
|
||||
$deletes
|
||||
->setType(DELETE_TYPE_AUDIT)
|
||||
->setDocument($document);
|
||||
->setDocument($document)
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('databaseId', $databaseId)
|
||||
@@ -3592,14 +3583,15 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
->setParam('documentId', $document->getId())
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->output($document, Response::MODEL_DOCUMENT));
|
||||
->setPayload($response->output($document, Response::MODEL_DOCUMENT))
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/databases/usage')
|
||||
->desc('Get usage stats for the database')
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'databases')
|
||||
@@ -3697,16 +3689,16 @@ App::get('/v1/databases/usage')
|
||||
'databasesCount' => $stats['databases.$all.count.total'] ?? [],
|
||||
'documentsCount' => $stats['documents.$all.count.total'] ?? [],
|
||||
'collectionsCount' => $stats['collections.$all.count.total'] ?? [],
|
||||
'documentsCreate' => $stats['documents.$all.requests.create'] ?? [],
|
||||
'documentsRead' => $stats['documents.$all.requests.read'] ?? [],
|
||||
'documentsCreate' => $stats['documents.$all.requests.create'] ?? [],
|
||||
'documentsRead' => $stats['documents.$all.requests.read'] ?? [],
|
||||
'documentsUpdate' => $stats['documents.$all.requests.update'] ?? [],
|
||||
'documentsDelete' => $stats['documents.$all.requests.delete'] ?? [],
|
||||
'collectionsCreate' => $stats['collections.$all.requests.create'] ?? [],
|
||||
'collectionsRead' => $stats['collections.$all.requests.read'] ?? [],
|
||||
'collectionsRead' => $stats['collections.$all.requests.read'] ?? [],
|
||||
'collectionsUpdate' => $stats['collections.$all.requests.update'] ?? [],
|
||||
'collectionsDelete' => $stats['collections.$all.requests.delete'] ?? [],
|
||||
'databasesCreate' => $stats['databases.$all.requests.create'] ?? [],
|
||||
'databasesRead' => $stats['databases.$all.requests.read'] ?? [],
|
||||
'databasesRead' => $stats['databases.$all.requests.read'] ?? [],
|
||||
'databasesUpdate' => $stats['databases.$all.requests.update'] ?? [],
|
||||
'databasesDelete' => $stats['databases.$all.requests.delete'] ?? [],
|
||||
]);
|
||||
@@ -3717,7 +3709,7 @@ App::get('/v1/databases/usage')
|
||||
|
||||
App::get('/v1/databases/:databaseId/usage')
|
||||
->desc('Get usage stats for the database')
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'databases')
|
||||
@@ -3810,12 +3802,12 @@ App::get('/v1/databases/:databaseId/usage')
|
||||
'range' => $range,
|
||||
'collectionsCount' => $stats["collections.{$databaseId}.count.total"] ?? [],
|
||||
'collectionsCreate' => $stats["collections.{$databaseId}.requests.create"] ?? [],
|
||||
'collectionsRead' => $stats["collections.{$databaseId}.requests.read"] ?? [],
|
||||
'collectionsRead' => $stats["collections.{$databaseId}.requests.read"] ?? [],
|
||||
'collectionsUpdate' => $stats["collections.{$databaseId}.requests.update"] ?? [],
|
||||
'collectionsDelete' => $stats["collections.{$databaseId}.requests.delete"] ?? [],
|
||||
'documentsCount' => $stats["documents.{$databaseId}.count.total"] ?? [],
|
||||
'documentsCreate' => $stats["documents.{$databaseId}.requests.create"] ?? [],
|
||||
'documentsRead' => $stats["documents.{$databaseId}.requests.read"] ?? [],
|
||||
'documentsCreate' => $stats["documents.{$databaseId}.requests.create"] ?? [],
|
||||
'documentsRead' => $stats["documents.{$databaseId}.requests.read"] ?? [],
|
||||
'documentsUpdate' => $stats["documents.{$databaseId}.requests.update"] ?? [],
|
||||
'documentsDelete' => $stats["documents.{$databaseId}.requests.delete"] ?? [],
|
||||
]);
|
||||
@@ -3827,7 +3819,7 @@ App::get('/v1/databases/:databaseId/usage')
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
|
||||
->alias('/v1/database/:collectionId/usage', ['databaseId' => 'default'])
|
||||
->desc('Get usage stats for a collection')
|
||||
->groups(['api', 'database', 'usage'])
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'databases')
|
||||
@@ -3925,8 +3917,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
|
||||
'documentsCount' => $stats["documents.{$databaseId}/{$collectionId}.count.total"] ?? [],
|
||||
'documentsCreate' => $stats["documents.{$databaseId}/{$collectionId}.requests.create"] ?? [],
|
||||
'documentsRead' => $stats["documents.{$databaseId}/{$collectionId}.requests.read"] ?? [],
|
||||
'documentsUpdate' => $stats["documents.{$databaseId}/{$collectionId}.requests.update"] ?? [],
|
||||
'documentsDelete' => $stats["documents.{$databaseId}/{$collectionId}.requests.delete" ?? []]
|
||||
'documentsUpdate' => $stats["documents.{$databaseId}/{$collectionId}.requests.update"] ?? [],
|
||||
'documentsDelete' => $stats["documents.{$databaseId}/{$collectionId}.requests.delete" ?? []]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ App::get('/v1/functions/:functionId')
|
||||
|
||||
App::get('/v1/functions/:functionId/usage')
|
||||
->desc('Get Function Usage')
|
||||
->groups(['api', 'functions', 'usage'])
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'functions')
|
||||
@@ -321,7 +321,7 @@ App::get('/v1/functions/:functionId/usage')
|
||||
|
||||
App::get('/v1/functions/usage')
|
||||
->desc('Get Functions Usage')
|
||||
->groups(['api', 'functions', 'usage'])
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'functions')
|
||||
|
||||
@@ -29,7 +29,6 @@ use Utopia\Domains\Domain;
|
||||
use Utopia\Registry\Registry;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
@@ -92,43 +91,38 @@ App::post('/v1/projects')
|
||||
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]),
|
||||
]));
|
||||
} 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]),
|
||||
]));
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
@@ -175,22 +169,9 @@ App::post('/v1/projects')
|
||||
$dbForProject->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
|
||||
if ($dbForProject->exists($dbForProject->getDefaultDatabase(), 'videos_profiles')) {
|
||||
foreach (Config::getParam('videos-profiles', []) as $profile) {
|
||||
Authorization::skip(function () use ($project, $profile, $dbForProject) {
|
||||
return $dbForProject->createDocument('videos_profiles', new Document([
|
||||
'name' => $profile['name'],
|
||||
'videoBitRate' => $profile['videoBitRate'],
|
||||
'audioBitRate' => $profile['audioBitRate'],
|
||||
'width' => $profile['width'],
|
||||
'height' => $profile['height']
|
||||
]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::get('/v1/projects')
|
||||
@@ -264,7 +245,7 @@ App::get('/v1/projects/:projectId')
|
||||
|
||||
App::get('/v1/projects/:projectId/usage')
|
||||
->desc('Get usage stats for a project')
|
||||
->groups(['api', 'projects', 'usage'])
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
@@ -1256,7 +1237,7 @@ App::get('/v1/projects/:projectId/platforms')
|
||||
}
|
||||
|
||||
$platforms = $dbForConsole->find('platforms', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ App::post('/v1/storage/buckets')
|
||||
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity);
|
||||
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -274,7 +274,6 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
->setAttribute('encryption', $encryption)
|
||||
->setAttribute('compression', $compression)
|
||||
->setAttribute('antivirus', $antivirus));
|
||||
$dbForProject->updateCollection('bucket_' . $bucket->getInternalId(), $permissions, $fileSecurity);
|
||||
|
||||
$events
|
||||
->setParam('bucketId', $bucket->getId())
|
||||
@@ -515,7 +514,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
}
|
||||
|
||||
$mimeType = $deviceFiles->getFileMimeType($path); // Get mime-type before compression and encryption
|
||||
$fileHash = $deviceFiles->getFileHash($path); // Get file hash before compression and encryption
|
||||
$data = '';
|
||||
// Compression
|
||||
$algorithm = $bucket->getAttribute('compression', COMPRESSION_TYPE_NONE);
|
||||
@@ -549,6 +547,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
}
|
||||
|
||||
$sizeActual = $deviceFiles->getFileSize($path);
|
||||
$fileHash = $deviceFiles->getFileHash($path);
|
||||
|
||||
$openSSLVersion = null;
|
||||
$openSSLCipher = null;
|
||||
@@ -568,7 +567,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
'$id' => $fileId,
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => $fileHash,
|
||||
@@ -618,7 +616,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
'$id' => ID::custom($fileId),
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => '',
|
||||
@@ -1450,7 +1447,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
|
||||
App::get('/v1/storage/usage')
|
||||
->desc('Get usage stats for storage')
|
||||
->groups(['api', 'storage', 'usage'])
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'storage')
|
||||
@@ -1560,7 +1557,7 @@ App::get('/v1/storage/usage')
|
||||
|
||||
App::get('/v1/storage/:bucketId/usage')
|
||||
->desc('Get usage stats for a storage bucket')
|
||||
->groups(['api', 'storage', 'usage'])
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'storage')
|
||||
|
||||
@@ -1129,7 +1129,7 @@ App::delete('/v1/users/:userId')
|
||||
|
||||
App::get('/v1/users/usage')
|
||||
->desc('Get usage stats for the users API')
|
||||
->groups(['api', 'users', 'usage'])
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -262,6 +262,7 @@ App::init()
|
||||
$role = ($user->isEmpty())
|
||||
? Role::guests()->toString()
|
||||
: Role::users()->toString();
|
||||
|
||||
// Add user roles
|
||||
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
|
||||
|
||||
@@ -436,7 +437,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);
|
||||
@@ -583,7 +584,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)
|
||||
|
||||
@@ -4,7 +4,7 @@ global $utopia, $request, $response;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Document;
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use Utopia\Validator\Host;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
@@ -195,25 +195,28 @@ App::init()
|
||||
$database->setProject($project);
|
||||
|
||||
$dbForProject->on(Database::EVENT_DOCUMENT_CREATE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
||||
|
||||
$dbForProject->on(Database::EVENT_DOCUMENT_DELETE, fn ($event, Document $document) => $databaseListener($event, $document, $usage));
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
|
||||
if ($useCache) {
|
||||
$key = md5($request->getURI() . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER;
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cache = new Cache(
|
||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
|
||||
);
|
||||
$timestamp = 60 * 60 * 24 * 30;
|
||||
$data = $cache->load($key, $timestamp);
|
||||
|
||||
if (!empty($data) && !$cacheLog->isEmpty()) {
|
||||
$parts = explode('/', $cacheLog->getAttribute('resourceType'));
|
||||
if (!empty($data)) {
|
||||
$data = json_decode($data, true);
|
||||
$parts = explode('/', $data['resourceType']);
|
||||
$type = $parts[0] ?? null;
|
||||
|
||||
if ($type === 'bucket') {
|
||||
$bucketId = $parts[1] ?? null;
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -222,12 +225,11 @@ App::init()
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$parts = explode('/', $cacheLog->getAttribute('resource'));
|
||||
$parts = explode('/', $data['resource']);
|
||||
$fileId = $parts[1] ?? null;
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
@@ -244,8 +246,8 @@ App::init()
|
||||
$response
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT')
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->setContentType($cacheLog->getAttribute('mimeType'))
|
||||
->send($data)
|
||||
->setContentType($data['contentType'])
|
||||
->send(base64_decode($data['payload']))
|
||||
;
|
||||
|
||||
$route->setIsActive(false);
|
||||
@@ -473,6 +475,7 @@ App::shutdown()
|
||||
if ($useCache) {
|
||||
$resource = $resourceType = null;
|
||||
$data = $response->getPayload();
|
||||
|
||||
if (!empty($data['payload'])) {
|
||||
$pattern = $route->getLabel('cache.resource', null);
|
||||
if (!empty($pattern)) {
|
||||
@@ -485,17 +488,21 @@ App::shutdown()
|
||||
}
|
||||
|
||||
$key = md5($request->getURI() . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER;
|
||||
$data = json_encode([
|
||||
'resourceType' => $resourceType,
|
||||
'resource' => $resource,
|
||||
'contentType' => $response->getContentType(),
|
||||
'payload' => base64_encode($data['payload']),
|
||||
]) ;
|
||||
|
||||
$signature = md5($data['payload']);
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$signature = md5($data);
|
||||
$cacheLog = $dbForProject->getDocument('cache', $key);
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'resourceType' => $resourceType,
|
||||
'mimeType' => $response->getContentType(),
|
||||
'accessedAt' => $now,
|
||||
'signature' => $signature,
|
||||
])));
|
||||
@@ -508,7 +515,7 @@ App::shutdown()
|
||||
$cache = new Cache(
|
||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
|
||||
);
|
||||
$cache->save($key, $data['payload']);
|
||||
$cache->save($key, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -545,11 +552,3 @@ App::shutdown()
|
||||
->submit();
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
->groups(['usage'])
|
||||
->action(function () {
|
||||
if (App::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
|
||||
throw new Exception(Exception::GENERAL_USAGE_DISABLED);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
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')
|
||||
|
||||
@@ -115,6 +115,12 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||
if (!$dbForConsole->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Skip to prevent 0.16 migration issues.
|
||||
*/
|
||||
if (in_array($key, ['cache', 'variables']) && $dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), 'bucket_1')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
|
||||
|
||||
|
||||
+27
-34
@@ -100,8 +100,8 @@ const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate pe
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 506;
|
||||
const APP_VERSION_STABLE = '1.3.7';
|
||||
const APP_CACHE_BUSTER = 503;
|
||||
const APP_VERSION_STABLE = '1.3.1';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
@@ -114,7 +114,6 @@ const APP_STORAGE_UPLOADS = '/storage/uploads';
|
||||
const APP_STORAGE_FUNCTIONS = '/storage/functions';
|
||||
const APP_STORAGE_BUILDS = '/storage/builds';
|
||||
const APP_STORAGE_CACHE = '/storage/cache';
|
||||
const APP_STORAGE_VIDEOS = '/storage/videos';
|
||||
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
|
||||
const APP_STORAGE_CONFIG = '/storage/config';
|
||||
const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT`
|
||||
@@ -163,7 +162,6 @@ const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
|
||||
const COMPRESSION_TYPE_NONE = 'none';
|
||||
const COMPRESSION_TYPE_GZIP = 'gzip';
|
||||
const COMPRESSION_TYPE_ZSTD = 'zstd';
|
||||
const DELETE_TYPE_VIDEOS = 'videos';
|
||||
// Mail Types
|
||||
const MAIL_TYPE_VERIFICATION = 'verification';
|
||||
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
|
||||
@@ -177,7 +175,7 @@ const APP_AUTH_TYPE_KEY = 'Key';
|
||||
const APP_AUTH_TYPE_ADMIN = 'Admin';
|
||||
// Response related
|
||||
const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
|
||||
const TMP_HOST = 'http://127.0.0.1/';
|
||||
|
||||
$register = new Registry();
|
||||
|
||||
App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
|
||||
@@ -211,7 +209,6 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
|
||||
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
|
||||
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
|
||||
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
|
||||
Config::load('videos-profiles', __DIR__ . '/config/videos-profiles.php');
|
||||
|
||||
$user = App::getEnv('_APP_REDIS_USER', '');
|
||||
$pass = App::getEnv('_APP_REDIS_PASS', '');
|
||||
@@ -517,7 +514,7 @@ $register->set('logger', function () {
|
||||
return new Logger($adapter);
|
||||
});
|
||||
$register->set('dbPool', function () {
|
||||
// Register DB connection
|
||||
// Register DB connection
|
||||
$dbHost = App::getEnv('_APP_DB_HOST', '');
|
||||
$dbPort = App::getEnv('_APP_DB_PORT', '');
|
||||
$dbUser = App::getEnv('_APP_DB_USER', '');
|
||||
@@ -526,20 +523,20 @@ $register->set('dbPool', function () {
|
||||
|
||||
$pool = new PDOPool(
|
||||
(new PDOConfig())
|
||||
->withHost($dbHost)
|
||||
->withPort($dbPort)
|
||||
->withDbName($dbScheme)
|
||||
->withCharset('utf8mb4')
|
||||
->withUsername($dbUser)
|
||||
->withPassword($dbPass)
|
||||
->withOptions([
|
||||
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
|
||||
PDO::ATTR_TIMEOUT => 3, // Seconds
|
||||
PDO::ATTR_PERSISTENT => true,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => true,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => true,
|
||||
]),
|
||||
->withHost($dbHost)
|
||||
->withPort($dbPort)
|
||||
->withDbName($dbScheme)
|
||||
->withCharset('utf8mb4')
|
||||
->withUsername($dbUser)
|
||||
->withPassword($dbPass)
|
||||
->withOptions([
|
||||
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
|
||||
PDO::ATTR_TIMEOUT => 3, // Seconds
|
||||
PDO::ATTR_PERSISTENT => true,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => true,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => true,
|
||||
]),
|
||||
64
|
||||
);
|
||||
|
||||
@@ -558,17 +555,17 @@ $register->set('redisPool', function () {
|
||||
|
||||
$pool = new RedisPool(
|
||||
(new RedisConfig())
|
||||
->withHost($redisHost)
|
||||
->withPort($redisPort)
|
||||
->withAuth($redisAuth)
|
||||
->withDbIndex(0),
|
||||
->withHost($redisHost)
|
||||
->withPort($redisPort)
|
||||
->withAuth($redisAuth)
|
||||
->withDbIndex(0),
|
||||
64
|
||||
);
|
||||
|
||||
return $pool;
|
||||
});
|
||||
$register->set('influxdb', function () {
|
||||
// Register DB connection
|
||||
// Register DB connection
|
||||
$host = App::getEnv('_APP_INFLUXDB_HOST', '');
|
||||
$port = App::getEnv('_APP_INFLUXDB_PORT', '');
|
||||
|
||||
@@ -582,7 +579,7 @@ $register->set('influxdb', function () {
|
||||
return $client;
|
||||
});
|
||||
$register->set('statsd', function () {
|
||||
// Register DB connection
|
||||
// Register DB connection
|
||||
$host = App::getEnv('_APP_STATSD_HOST', 'telegraf');
|
||||
$port = App::getEnv('_APP_STATSD_PORT', 8125);
|
||||
|
||||
@@ -629,7 +626,7 @@ $register->set('passwordsDictionary', function () {
|
||||
return $content;
|
||||
});
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
// This is usually for our workers or CLI commands scope
|
||||
$dbHost = App::getEnv('_APP_DB_HOST', '');
|
||||
$dbPort = App::getEnv('_APP_DB_PORT', '');
|
||||
$dbUser = App::getEnv('_APP_DB_USER', '');
|
||||
@@ -648,7 +645,7 @@ $register->set('db', function () {
|
||||
return $pdo;
|
||||
});
|
||||
$register->set('cache', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
// This is usually for our workers or CLI commands scope
|
||||
$redis = new Redis();
|
||||
$redis->pconnect(App::getEnv('_APP_REDIS_HOST', ''), App::getEnv('_APP_REDIS_PORT', ''));
|
||||
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
|
||||
@@ -907,7 +904,6 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
|
||||
/** @var Utopia\Database\Document $console */
|
||||
|
||||
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
|
||||
//$projectId = '64635cdd5f809d98d3ad';
|
||||
|
||||
if ($projectId === 'console') {
|
||||
return $console;
|
||||
@@ -954,6 +950,7 @@ App::setResource('console', function () {
|
||||
|
||||
App::setResource('dbForProject', function ($db, $cache, Document $project) {
|
||||
$cache = new Cache(new RedisCache($cache));
|
||||
|
||||
$database = new Database(new MariaDB($db), $cache);
|
||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$database->setNamespace("_{$project->getInternalId()}");
|
||||
@@ -980,10 +977,6 @@ App::setResource('deviceFiles', function ($project) {
|
||||
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
App::setResource('deviceVideos', function ($project) {
|
||||
return getDevice(APP_STORAGE_VIDEOS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
App::setResource('deviceFunctions', function ($project) {
|
||||
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
+2
-5
@@ -34,11 +34,8 @@ foreach (
|
||||
realpath(__DIR__ . '/../vendor/matomo'),
|
||||
realpath(__DIR__ . '/../vendor/symfony'),
|
||||
realpath(__DIR__ . '/../vendor/mongodb'),
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/websocket'),
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/cache'),
|
||||
realpath(__DIR__ . '/../vendor/alchemy'),
|
||||
realpath(__DIR__ . '/../vendor/php-ffmpeg'),
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/storage'),
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/websocket'), // TODO: remove workerman autoload
|
||||
realpath(__DIR__ . '/../vendor/utopia-php/cache'), // TODO: remove memcache autoload
|
||||
] as $key => $value
|
||||
) {
|
||||
if ($value !== false) {
|
||||
|
||||
@@ -95,9 +95,9 @@ $cli
|
||||
if (is_null($value)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($vars as $i => $var) {
|
||||
foreach ($vars as &$var) {
|
||||
if ($var['name'] === $key) {
|
||||
$vars[$i]['default'] = $value;
|
||||
$var['default'] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,9 +114,9 @@ $cli
|
||||
if (is_null($value)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($vars as $i => $var) {
|
||||
foreach ($vars as &$var) {
|
||||
if ($var['name'] === $key) {
|
||||
$vars[$i]['default'] = $value;
|
||||
$var['default'] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ $cli
|
||||
|
||||
$input = [];
|
||||
|
||||
foreach ($vars as $var) {
|
||||
foreach ($vars as $key => $var) {
|
||||
if (!empty($var['filter']) && ($interactive !== 'Y' || !Console::isInteractive())) {
|
||||
if ($data && $var['default'] !== null) {
|
||||
$input[$var['name']] = $var['default'];
|
||||
@@ -224,9 +224,9 @@ $cli
|
||||
}
|
||||
}
|
||||
|
||||
Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\"");
|
||||
Console::log("Running \"docker compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes\"");
|
||||
|
||||
$exit = Console::execute("${env} docker compose --project-directory {$path} up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr);
|
||||
$exit = Console::execute("${env} docker compose -f {$path}/docker-compose.yml up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr);
|
||||
|
||||
if ($exit !== 0) {
|
||||
$message = 'Failed to install Appwrite dockers';
|
||||
|
||||
+2
-2
@@ -239,8 +239,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
cd ' . $target . ' && \
|
||||
git init --initial-branch=' . $gitBranch . ' && \
|
||||
git remote add origin ' . $gitUrl . ' && \
|
||||
git fetch origin ' . $gitBranch . ' && \
|
||||
git pull origin ' . $gitBranch . ' && \
|
||||
git fetch && \
|
||||
git pull ' . $gitUrl . ' && \
|
||||
rm -rf ' . $target . '/* && \
|
||||
cp -r ' . $result . '/* ' . $target . '/ && \
|
||||
git add . && \
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
$mpd = $this->getParam('mpd', []);
|
||||
echo '<?xml version="1.0" encoding="utf-8"?>'; ?>
|
||||
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
||||
profiles="<?php if (isset($mpd['profiles'])): echo ($mpd['profiles']); endif;?>" type="<?php if (isset($mpd['type'])): echo ($mpd['type']); endif;?>" mediaPresentationDuration="<?php if (isset($mpd['maxSegmentDuration'])): echo ($mpd['mediaPresentationDuration']); endif;?>" maxSegmentDuration="<?php if (isset($mpd['maxSegmentDuration'])): echo ($mpd['maxSegmentDuration']); endif;?>" minBufferTime="<?php if (isset($mpd['minBufferTime'])): echo ($mpd['minBufferTime']); endif;?>">
|
||||
<ProgramInformation></ProgramInformation>
|
||||
<ServiceDescription id="0"></ServiceDescription>
|
||||
<Period id="0" start="PT0.0S">
|
||||
<?php foreach ($this->getParam('renditions', []) as $adaptation):
|
||||
$representation = $adaptation['representation'];
|
||||
?>
|
||||
<AdaptationSet <?php if (isset($adaptation['id'])):?> id="<?php echo ($adaptation['id']); endif;?>"<?php if (isset($adaptation['attributes']['contentType'])): ?> contentType="<?php echo $adaptation['attributes']['contentType']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['startWithSAP'])): ?> startWithSAP="<?php echo $adaptation['attributes']['startWithSAP']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['segmentAlignment'])): ?> segmentAlignment="<?php echo $adaptation['attributes']['segmentAlignment']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['bitstreamSwitching'])): ?> bitstreamSwitching="<?php echo $adaptation['attributes']['bitstreamSwitching']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['frameRate'])): ?> frameRate="<?php echo $adaptation['attributes']['frameRate']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['maxWidth'])): ?> maxWidth="<?php echo $adaptation['attributes']['maxWidth']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['par'])): ?> par="<?php echo $adaptation['attributes']['par']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['lang'])): ?> lang="<?php echo $adaptation['attributes']['lang']; ?>"<?php endif;?>>
|
||||
<Representation <?php if (isset($representation['attributes']['id'])):?> id="<?php echo $representation['attributes']['id']; ?>"<?php endif;?><?php if (isset($representation['attributes']['mimeType'])):?> mimeType="<?php echo $representation['attributes']['mimeType']; ?>"<?php endif;?><?php if (isset($representation['attributes']['codecs'])):?> codecs="<?php echo $representation['attributes']['codecs']; ?>"<?php endif;?><?php if (isset($representation['attributes']['bandwidth'])):?> bandwidth="<?php echo $representation['attributes']['bandwidth']; ?>"<?php endif;?><?php if (isset($representation['attributes']['width'])):?> width="<?php echo $representation['attributes']['width']; ?>"<?php endif;?><?php if (isset($representation['attributes']['height'])):?> height="<?php echo $representation['attributes']['height']; ?>"<?php endif;?><?php if (isset($representation['attributes']['sar'])):?> sar="<?php echo $representation['attributes']['sar']; ?>"<?php endif;?><?php if (isset($representation['attributes']['audioSamplingRate'])):?> audioSamplingRate="<?php echo $representation['attributes']['audioSamplingRate']; ?>"<?php endif;?>>
|
||||
<BaseURL><?php if (isset($adaptation['baseUrl'])): echo $this->escape($adaptation['baseUrl']); endif;?></BaseURL>
|
||||
<?php if (isset($adaptation['attributes']['contentType']) && $adaptation['attributes']['contentType'] === 'audio' ):?>
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
|
||||
<?php endif;?>
|
||||
<SegmentList<?php if (isset($representation['segmentList']['attributes']['timescale'])): ?> timescale="<?php echo $representation['segmentList']['attributes']['timescale']; ?>"<?php endif;?><?php if (isset($representation['segmentList']['attributes']['duration'])): ?> duration="<?php echo $representation['segmentList']['attributes']['duration']; ?>"<?php endif;?><?php if (isset($representation['segmentList']['attributes']['startNumber'])): ?> startNumber="<?php echo $representation['segmentList']['attributes']['startNumber']; ?>"<?php endif;?>>
|
||||
<Initialization sourceURL="<?php if (isset($representation['segmentList']['init'])):?><?php echo $representation['segmentList']['init']; ?><?php endif;?>"/>
|
||||
<?php if (isset($representation['segmentList']['media'])):?>
|
||||
<?php foreach ($representation['segmentList']['media'] as $segment): ?>
|
||||
<SegmentURL media="<?php if (isset($segment)):?><?php echo $segment; ?><?php endif;?>"/>
|
||||
<?php endforeach; ?>
|
||||
<?php endif;?>
|
||||
</SegmentList>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<?php endforeach; ?>
|
||||
<?php foreach ($this->getParam('subtitles', []) as $subtitle):?>
|
||||
<AdaptationSet id="<?php if (isset($subtitle['id'])): echo $subtitle['id']; endif;?>" lang="<?php if (isset($subtitle['name'])): echo $this->escape($subtitle['name']); endif;?>" mimeType="text/vtt">
|
||||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle" />
|
||||
<Representation>
|
||||
<BaseURL><?php if (isset($subtitle['baseUrl'])): echo $this->escape($subtitle['baseUrl']); endif;?></BaseURL>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<?php endforeach; ?>
|
||||
</Period>
|
||||
</MPD>
|
||||
@@ -1,12 +0,0 @@
|
||||
#EXTM3U
|
||||
#EXT-X-VERSION:4
|
||||
<?php foreach ($this->getParam('audios', []) as $audio):?>
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_audio"<?php if($audio['name'] !== null):?>,NAME="<?php echo $audio['name'];?>"<?php endif;?>,AUTOSELECT=YES<?php if($audio['language'] !== null):?>,LANGUAGE="<?php echo $audio['language'];?>"<?php endif;?><?php if($audio['uri'] !== null):?>,URI="<?php echo $audio['uri'];?>"<?php endif;?><?php echo PHP_EOL?>
|
||||
<?php endforeach;?><?php echo PHP_EOL?>
|
||||
<?php foreach ($this->getParam('subtitles', []) as $subtitle):?>
|
||||
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="<?php echo $this->escape($subtitle['name']); ?>",DEFAULT=<?php echo $subtitle['default']; ?>,AUTOSELECT=YES,FORCED=NO,LANGUAGE="<?php echo $this->escape($subtitle['code']); ?>",URI="<?php echo $subtitle['uri']; ?>"<?php echo PHP_EOL?>
|
||||
<?php endforeach;?><?php echo PHP_EOL?>
|
||||
<?php foreach ($this->getParam('renditions', []) as $rendition):?>
|
||||
#EXT-X-STREAM-INF:BANDWIDTH="<?php echo $rendition['bandwidth']; ?>",RESOLUTION="<?php echo $rendition['resolution']; ?>",NAME="<?php echo $rendition['name']; ?>"<?php if($rendition['codecs'] !== null):?>,CODECS="<?php echo $rendition['codecs']; ?>"<?php endif;?><?php if($rendition['subs'] !== null):?>,SUBTITLES="<?php echo $rendition['subs'];?>"<?php endif;?><?php if($rendition['audio'] !== null):?>,AUDIO="<?php echo $rendition['audio'];?>"<?php endif;?> <?php echo PHP_EOL?>
|
||||
<?php echo $rendition['uri'];?><?php echo PHP_EOL?>
|
||||
<?php endforeach;?>
|
||||
@@ -1,10 +0,0 @@
|
||||
#EXTM3U
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:1
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-TARGETDURATION:<?php echo $this->getParam('targetDuration', 0)?><?php echo PHP_EOL?>
|
||||
#EXT-X-MEDIA-SEQUENCE:0
|
||||
<?php foreach ($this->getParam('segments', []) as $segment): ?>
|
||||
#EXTINF:<?php echo $segment['duration']; ?><?php echo PHP_EOL?>
|
||||
<?php echo $segment['url']; ?><?php echo PHP_EOL?>
|
||||
<?php endforeach; ?>
|
||||
@@ -1,10 +0,0 @@
|
||||
#EXTM3U
|
||||
#EXT-X-VERSION:4
|
||||
#EXT-X-ALLOW-CACHE:YES
|
||||
#EXT-X-TARGETDURATION:<?php echo $this->getParam('targetDuration', 0)?><?php echo PHP_EOL?>
|
||||
#EXT-X-MEDIA-SEQUENCE:0
|
||||
<?php foreach ($this->getParam('segments', []) as $segment): ?>
|
||||
#EXTINF:<?php echo $segment['duration']; ?>,<?php echo PHP_EOL?>
|
||||
<?php echo $segment['url']; ?><?php echo PHP_EOL?>
|
||||
<?php endforeach; ?>
|
||||
#EXT-X-ENDLIST
|
||||
+5
-77
@@ -9,6 +9,7 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Appwrite\Resque\Worker;
|
||||
use Executor\Executor;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\CLI\Console;
|
||||
@@ -64,9 +65,6 @@ class DeletesV1 extends Worker
|
||||
case DELETE_TYPE_BUCKETS:
|
||||
$this->deleteBucket($document, $project->getId());
|
||||
break;
|
||||
case DELETE_TYPE_VIDEOS:
|
||||
$this->deleteVideo($document, $project->getId());
|
||||
break;
|
||||
default:
|
||||
if (\str_starts_with($document->getCollection(), 'database_')) {
|
||||
$this->deleteCollection($document, $project->getId());
|
||||
@@ -293,13 +291,12 @@ class DeletesV1 extends Worker
|
||||
protected function deleteProject(Document $document): void
|
||||
{
|
||||
$projectId = $document->getId();
|
||||
$projectInternalId = $document->getInternalId();
|
||||
|
||||
// Delete project certificates
|
||||
// Delete project domains and certificates
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
|
||||
$domains = $dbForConsole->find('domains', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
Query::equal('projectInternalId', [$document->getInternalId()])
|
||||
]);
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
@@ -321,26 +318,6 @@ class DeletesV1 extends Worker
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Platforms
|
||||
$this->deleteByGroup('platforms', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete Domains
|
||||
$this->deleteByGroup('domains', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete Keys
|
||||
$this->deleteByGroup('keys', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete Webhooks
|
||||
$this->deleteByGroup('webhooks', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete metadata tables
|
||||
try {
|
||||
$dbForProject->deleteCollection('_metadata');
|
||||
@@ -779,58 +756,9 @@ class DeletesV1 extends Worker
|
||||
{
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
$dbForProject->deleteCollection('bucket_' . $document->getInternalId());
|
||||
|
||||
$device = $this->getFilesDevice($projectId);
|
||||
|
||||
$device->deletePath($document->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $document database document
|
||||
* @param string $projectId
|
||||
*/
|
||||
protected function deleteVideo(Document $document, string $projectId): void
|
||||
{
|
||||
|
||||
$videoId = $document->getId();
|
||||
$dbForProject = $this->getProjectDB($projectId);
|
||||
|
||||
$previews = $dbForProject->find('videos_previews', [
|
||||
new Query('videoId', Query::TYPE_EQUAL, [$videoId])
|
||||
]);
|
||||
foreach ($previews as $preview) {
|
||||
$dbForProject->deleteDocument('videos_previews', $preview->getId());
|
||||
}
|
||||
|
||||
$renditions = $dbForProject->find('videos_renditions', [
|
||||
new Query('videoId', Query::TYPE_EQUAL, [$videoId])
|
||||
]);
|
||||
foreach ($renditions as $rendition) {
|
||||
$this->deleteByGroup('videos_renditions_segments', [
|
||||
new Query('renditionId', Query::TYPE_EQUAL, [$rendition->getId()])
|
||||
], $dbForProject);
|
||||
|
||||
$dbForProject->deleteDocument('videos_renditions', $rendition->getId());
|
||||
}
|
||||
|
||||
$subtitles = $dbForProject->find('videos_subtitles', [
|
||||
new Query('videoId', Query::TYPE_EQUAL, [$videoId])
|
||||
]);
|
||||
|
||||
foreach ($subtitles as $subtitle) {
|
||||
$segments = $dbForProject->find('videos_subtitles_segments', [
|
||||
new Query('subtitleId', Query::TYPE_EQUAL, [$subtitle->getId()])
|
||||
]);
|
||||
foreach ($segments as $segment) {
|
||||
$dbForProject->deleteDocument('videos_subtitles_segments', $segment->getId());
|
||||
}
|
||||
$dbForProject->deleteDocument('videos_subtitles', $subtitle->getId());
|
||||
}
|
||||
|
||||
$videosDevice = $this->getVideoDevice($projectId);
|
||||
$videosPath = $videosDevice->getPath($videoId);
|
||||
if ($videosDevice->deletePath($videosPath)) {
|
||||
Console::success('Deleted video directory: ' . $videosPath);
|
||||
} else {
|
||||
Console::error('Failed to delete video directory: ' . $videosPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-15
@@ -82,22 +82,18 @@ class FunctionsV1 extends Worker
|
||||
|
||||
Console::success('Iterating function: ' . $function->getAttribute('name'));
|
||||
|
||||
try {
|
||||
$this->execute(
|
||||
project: $project,
|
||||
function: $function,
|
||||
dbForProject: $database,
|
||||
trigger: 'event',
|
||||
// Pass first, most verbose event pattern
|
||||
event: $events[0],
|
||||
eventData: $payload,
|
||||
user: $user
|
||||
);
|
||||
$this->execute(
|
||||
project: $project,
|
||||
function: $function,
|
||||
dbForProject: $database,
|
||||
trigger: 'event',
|
||||
// Pass first, most verbose event pattern
|
||||
event: $events[0],
|
||||
eventData: $payload,
|
||||
user: $user
|
||||
);
|
||||
|
||||
Console::success('Triggered function: ' . $events[0]);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error("Failed to execute " . $function->getId() . " with error: " . $th->getMessage());
|
||||
}
|
||||
Console::success('Triggered function: ' . $events[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,820 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Resque\Worker;
|
||||
use Streaming\FFMpeg;
|
||||
use FFMpeg\FFProbe;
|
||||
use Streaming\Format\StreamFormat;
|
||||
use Streaming\HLSSubtitle;
|
||||
use Streaming\Media;
|
||||
use Streaming\Representation;
|
||||
use Mhor\MediaInfo\MediaInfo;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Storage\Compression\Algorithms\GZIP;
|
||||
use Utopia\Storage\Compression\Algorithms\Zstd;
|
||||
use Captioning\Format\SubripFile;
|
||||
use Utopia\Storage\Device\Local;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
Console::title('Video V1 Worker');
|
||||
Console::success(APP_NAME . ' video worker v1 has started');
|
||||
|
||||
class VideosV1 extends Worker
|
||||
{
|
||||
/**
|
||||
* Rendition Status
|
||||
*/
|
||||
private const STATUS_START = 'started';
|
||||
private const STATUS_END = 'ended';
|
||||
private const STATUS_UPLOADING = 'uploading';
|
||||
private const STATUS_READY = 'ready';
|
||||
private const STATUS_ERROR = 'error';
|
||||
|
||||
private const OUTPUT_HLS = 'hls';
|
||||
private const OUTPUT_DASH = 'dash';
|
||||
|
||||
private string $basePath = '/tmp/';
|
||||
//private string $basePath = '/usr/src/code/tests/tmp/';
|
||||
private string $inDir;
|
||||
private string $inPath;
|
||||
private string $outDir;
|
||||
private string $outPath;
|
||||
private string $renditionName;
|
||||
private Database $database;
|
||||
private Document $video;
|
||||
private Document $subtitle;
|
||||
private string $action;
|
||||
private Document $profile;
|
||||
private Document $project;
|
||||
private Document $file;
|
||||
private Document $bucket;
|
||||
private FFProbe $ffprobe;
|
||||
private FFMpeg $ffmpeg;
|
||||
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return "Video v1";
|
||||
}
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$this->video = new Document($this->args['video'] ?? []);
|
||||
$this->profile = new Document($this->args['profile'] ?? []);
|
||||
$this->project = new Document($this->args['project'] ?? []);
|
||||
$this->subtitle = new Document($this->args['subtitle'] ?? []);
|
||||
|
||||
$this->action = $this->args['action'];
|
||||
$this->basePath .= uniqid();
|
||||
$this->inDir = $this->basePath . '/in/';
|
||||
$this->outDir = $this->basePath . '/out/';
|
||||
@mkdir($this->inDir, 0755, true);
|
||||
@mkdir($this->outDir, 0755, true);
|
||||
$this->outPath = $this->outDir . $this->video->getId();
|
||||
}
|
||||
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
|
||||
$this->database = $this->getProjectDB($this->project->getId());
|
||||
$this->bucket = $this->database->getDocument('buckets', $this->video->getAttribute('bucketId'));
|
||||
$this->file = $this->database->getDocument('bucket_' . $this->bucket->getInternalId(), $this->video->getAttribute('fileId'));
|
||||
$path = basename($this->file->getAttribute('path'));
|
||||
$this->inPath = $this->inDir . $path;
|
||||
|
||||
match ($this->action) {
|
||||
'timeline' => $this->createTimeLine(),
|
||||
'subtitle' => $this->createSubtitle(),
|
||||
default => $this->createOutput(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create timeline
|
||||
*
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function createTimeLine(): void
|
||||
{
|
||||
|
||||
/*
|
||||
* Title: PlayerJS Thumbnails & WebVTT Creator
|
||||
* URI: https://playerjs.com/docs/q=thumbnailsphpwebvtt
|
||||
* Version: 1.0
|
||||
* Author: Playerjs.com
|
||||
* Author URI: https://playerjs.com
|
||||
* License: GPL-2.0+
|
||||
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
||||
* Text Domain: playerjs
|
||||
*/
|
||||
|
||||
$this->download($this->project, $this->file);
|
||||
|
||||
if (empty($this->video->getAttribute('duration'))) {
|
||||
$this->createInfo();
|
||||
}
|
||||
|
||||
if (empty($this->video->getAttribute('videoBitRate'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$interval = 2;
|
||||
$ranges = [
|
||||
['from' => 120, 'to' => 600, 'interval' => 5],
|
||||
['from' => 600, 'to' => 1800, 'interval' => 10],
|
||||
['from' => 1800, 'to' => 3600, 'interval' => 20],
|
||||
['from' => 3600, 'to' => 99999, 'interval' => 30],
|
||||
];
|
||||
|
||||
foreach ($ranges as $range) {
|
||||
if (
|
||||
$this->video->getAttribute('duration') > $range['from'] &&
|
||||
$this->video->getAttribute('duration') <= $range['to']
|
||||
) {
|
||||
$interval = $range['interval'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$timeline['aspect'] = $this->video->getAttribute('width') / $this->video->getAttribute('height');
|
||||
$timeline['width'] = 160;
|
||||
$timeline['height'] = round($timeline['width'] / $timeline['aspect']);
|
||||
$timeline['size'] = '5x5';
|
||||
|
||||
$cmd = [
|
||||
'/usr/bin/ffmpeg',
|
||||
'-i ' . $this->inPath,
|
||||
'-hide_banner',
|
||||
'-loglevel error',
|
||||
'-vsync vfr',
|
||||
'-vf \'select=isnan(prev_selected_t)+gte(t-prev_selected_t\,' . $interval . '),scale=' . $timeline['width'] . ':' . $timeline['height'] . ',tile=' . $timeline['size'] . '\'',
|
||||
'-qscale:v 3',
|
||||
$this->outDir . 'sprite%d.jpg',
|
||||
];
|
||||
$result = shell_exec(implode(" ", $cmd));
|
||||
|
||||
/**
|
||||
* Vtt creation*
|
||||
*/
|
||||
if ($result !== false) {
|
||||
$size = explode('x', $timeline['size']);
|
||||
$counter = 0;
|
||||
$images = ceil((($this->video->getAttribute('duration') / 1000) / $interval) / ($size[0] * $size[1]));
|
||||
$data = "WEBVTT";
|
||||
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/timeline/';
|
||||
for ($image = 1; $image <= $images; $image++) {
|
||||
$sprite = $this->database->createDocument('videos_previews', new Document([
|
||||
'videoId' => $this->video->getId(),
|
||||
'videoInternalId' => $this->video->getInternalId(),
|
||||
'type' => 'sprite',
|
||||
'name' => 'sprite' . $image . '.jpg',
|
||||
'path' => $path,
|
||||
]));
|
||||
|
||||
$url = TMP_HOST . 'v1/videos/' . $this->video->getId() . '/preview/' . $sprite->getId() . '/';
|
||||
for ($col = 0; $col < $size[0]; $col++) {
|
||||
for ($row = 0; $row < $size[1]; $row++) {
|
||||
$data .= "\n" . gmdate("H:i:s", $counter * $interval) . " --> " . gmdate("H:i:s", ($counter + 1) * $interval) . "\n" . $url . "#xywh=" . ($row * $timeline['width']) . "," . ($col * $timeline['height']) . "," . $timeline['width'] . "," . $timeline['height'];
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($counter > 0) {
|
||||
/**
|
||||
* Upload vtt
|
||||
*/
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$this->getVideoDevice($this->project->getId())->getPath($this->video->getId() . '/timeline/timeline.vtt'),
|
||||
$data,
|
||||
'text/vtt'
|
||||
);
|
||||
|
||||
console::info('Uploading timeline vtt');
|
||||
|
||||
/**
|
||||
* Upload sprites
|
||||
*/
|
||||
$dir = new DirectoryIterator($this->outDir);
|
||||
foreach ($dir as $fileinfo) {
|
||||
if (!$fileinfo->isDot()) {
|
||||
console::info('Uploading ' . $fileinfo->getFilename());
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$path . $fileinfo->getFilename(),
|
||||
(new Local('/'))->read($this->outDir . $fileinfo->getFilename()),
|
||||
mime_content_type($this->outDir . $fileinfo->getFilename())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create subtitle
|
||||
*
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function createSubtitle(): void
|
||||
{
|
||||
|
||||
if (empty($this->video->getAttribute('duration'))) {
|
||||
$this->download($this->project, $this->file);
|
||||
$this->createInfo();
|
||||
}
|
||||
|
||||
$segments = $this->database->find('videos_subtitles_segments', [
|
||||
Query::equal('subtitleInternalId', [$this->subtitle->getInternalId()]),
|
||||
]);
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
$this->database->deleteDocument('videos_subtitles_segments', $segment->getId());
|
||||
}
|
||||
|
||||
$this->subtitle->setAttribute('status', self::STATUS_START);
|
||||
$this->database->updateDocument('videos_subtitles', $this->subtitle->getId(), $this->subtitle);
|
||||
|
||||
$bucket = $this->database->getDocument('buckets', $this->subtitle->getAttribute('bucketId'));
|
||||
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $this->subtitle->getAttribute('fileId'));
|
||||
$path = basename($file->getAttribute('path'));
|
||||
|
||||
$this->download($this->project, $file);
|
||||
|
||||
$ext = pathinfo($path, PATHINFO_EXTENSION);
|
||||
$subtitlePath = $this->inDir . $this->subtitle->getId() . '.vtt';
|
||||
|
||||
if ($ext === 'srt') {
|
||||
$srt = new SubripFile($this->inDir . $path);
|
||||
$srt->convertTo('webvtt')->save($subtitlePath);
|
||||
}
|
||||
|
||||
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/subtitles/';
|
||||
$this->database->createDocument('videos_subtitles_segments', new Document([
|
||||
'subtitleId' => $this->subtitle->getId(),
|
||||
'subtitleInternalId' => $this->subtitle->getInternalId(),
|
||||
'fileName' => $this->subtitle->getId() . '.vtt',
|
||||
'path' => $path,
|
||||
'duration' => (string)number_format(($this->video->getAttribute('duration') / 1000), 1),
|
||||
]));
|
||||
|
||||
/**
|
||||
* Upload subtitle
|
||||
*/
|
||||
console::info('Uploading ' . $this->subtitle->getId() . '.vtt');
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$path . $this->subtitle->getId() . '.vtt',
|
||||
(new Local('/'))->read($this->inDir . $this->subtitle->getId() . '.vtt'),
|
||||
'text/vtt'
|
||||
);
|
||||
|
||||
$this->subtitle->setAttribute('targetDuration', (string)number_format(($this->video->getAttribute('duration') / 1000), 1));
|
||||
$this->subtitle->setAttribute('status', self::STATUS_READY);
|
||||
$this->subtitle->setAttribute('path', $path);
|
||||
$this->database->updateDocument('videos_subtitles', $this->subtitle->getId(), $this->subtitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create output
|
||||
*
|
||||
* @throws Authorization
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
* @throws Structure
|
||||
*/
|
||||
private function createOutput(): void
|
||||
{
|
||||
|
||||
$this->download($this->project, $this->file);
|
||||
if (empty($this->video->getAttribute('duration'))) {
|
||||
$this->createInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* FFMpeg init
|
||||
*/
|
||||
$this->ffprobe = FFProbe::create();
|
||||
$this->ffmpeg = FFMpeg::create([
|
||||
'timeout' => 0,
|
||||
'ffmpeg.threads' => 4
|
||||
]);
|
||||
|
||||
if (!$this->ffprobe->isValid($this->inPath)) {
|
||||
console::error('Not an valid Video file "' . $this->inPath . '"');
|
||||
}
|
||||
|
||||
$media = $this->ffmpeg->open($this->inPath);
|
||||
|
||||
$this->renditionName = $this->profile->getAttribute('width')
|
||||
. 'X' . $this->profile->getAttribute('height')
|
||||
. '@' . ($this->profile->getAttribute('videoBitRate') + $this->profile->getAttribute('audioBitRate'));
|
||||
|
||||
|
||||
$query = $this->database->createDocument('videos_renditions', new Document([
|
||||
'videoId' => $this->video->getId(),
|
||||
'videoInternalId' => $this->video->getInternalId(),
|
||||
'profileId' => $this->profile->getId(),
|
||||
'profileInternalId' => $this->profile->getInternalId(),
|
||||
'name' => $this->renditionName,
|
||||
'startedAt' => DateTime::now(),
|
||||
'status' => self::STATUS_START,
|
||||
'width' => $this->profile->getAttribute('width'),
|
||||
'height' => $this->profile->getAttribute('height'),
|
||||
'videoBitRate' => $this->profile->getAttribute('videoBitRate'),
|
||||
'audioBitRate' => $this->profile->getAttribute('audioBitRate'),
|
||||
'output' => $this->args['output'],
|
||||
]));
|
||||
|
||||
$this->send($query);
|
||||
|
||||
try {
|
||||
$representation = (new Representation())
|
||||
->setKiloBitRate($this->profile->getAttribute('videoBitRate'))
|
||||
->setAudioKiloBitRate($this->profile->getAttribute('audioBitRate'))
|
||||
->setResize($this->profile->getAttribute('width'), $this->profile->getAttribute('height'));
|
||||
|
||||
console::info('Output video id:' . $this->video->getId() . PHP_EOL .
|
||||
'Output name: ' . $this->file->getAttribute('name') . PHP_EOL .
|
||||
'Output width: ' . $this->profile->getAttribute('width') . PHP_EOL .
|
||||
'Output height: ' . $this->profile->getAttribute('height') . PHP_EOL .
|
||||
'Output video BitRate:' . $this->profile->getAttribute('videoBitRate') . PHP_EOL .
|
||||
'Output audio BitRate:' . $this->profile->getAttribute('audioBitRate') . PHP_EOL .
|
||||
'Output:' . $this->args['output'] . PHP_EOL);
|
||||
|
||||
$format = new Streaming\Format\X264();
|
||||
$format->on('progress', function ($media, $format, $percentage) use ($query) {
|
||||
if ($percentage % 3 === 0) {
|
||||
$query->setAttribute('progress', (string)$percentage);
|
||||
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
|
||||
$this->send($query, 'update');
|
||||
}
|
||||
});
|
||||
|
||||
$this->transcode($media, $format, $representation);
|
||||
|
||||
unset($media);
|
||||
|
||||
//exec('/usr/bin/ffmpeg -y -i /usr/src/code/tests/tmp/637f59c88f9ff0fe3b1f/637e1b82aeab8980400e/in/637f59ab5bce0e36d05e.mp4 -c:v libx264 -c:a aac -bf 1 -keyint_min 25 -g 250 -sc_threshold 40 -use_timeline 0 -use_template 0 -seg_duration 10 -hls_playlist 0 -f dash -dn -sn -vf scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1 -b_strategy 1 -bf 3 -force_key_frames "expr:gte(t,n_forced*2)" -map 0 -s:v:0 1024x576 -b:v:0 2538k -b:a:0 128k -strict -2 -threads 12 /usr/src/code/tests/tmp/637f59c88f9ff0fe3b1f/637e1b82aeab8980400e/out/637f59c88f9ff0fe3b1f.mpd2>&1', $o, $v);
|
||||
//var_dump($o);
|
||||
//var_dump($v);
|
||||
|
||||
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/' . $this->renditionName . '-' . $query->getId() . '/';
|
||||
|
||||
if ($this->args['output'] === self::OUTPUT_HLS) {
|
||||
$streams = $this->getHlsSegmentsUrls($this->outDir . 'master.m3u8');
|
||||
foreach ($streams as $stream) {
|
||||
$m3u8 = $this->getSegments($this->outDir . $stream['path']);
|
||||
if (!empty($m3u8['segments'])) {
|
||||
foreach ($m3u8['segments'] as $segment) {
|
||||
$this->database->createDocument('videos_renditions_segments', new Document([
|
||||
'renditionId' => $query->getId(),
|
||||
'renditionInternalId' => $query->getInternalId(),
|
||||
'streamId' => (int)$stream['id'],
|
||||
'fileName' => $segment['fileName'],
|
||||
'path' => $path,
|
||||
'duration' => $segment['duration'],
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
$query->setAttribute('metadata', json_encode(['hls' => $streams]));
|
||||
$query->setAttribute('targetDuration', $m3u8['targetDuration']);
|
||||
}
|
||||
} else {
|
||||
$mpd = $this->getSegments($this->outPath . '.mpd');
|
||||
|
||||
if (!empty($mpd['segments'])) {
|
||||
foreach ($mpd['segments'] as $segment) {
|
||||
$this->database->createDocument('videos_renditions_segments', new Document([
|
||||
'renditionId' => $query->getId(),
|
||||
'renditionInternalId' => $query->getInternalId(),
|
||||
'streamId' => $segment['streamId'],
|
||||
'fileName' => $segment['fileName'],
|
||||
'path' => $path,
|
||||
'isInit' => $segment['isInit'],
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($mpd['metadata'])) {
|
||||
$query->setAttribute('metadata', json_encode(['mpd' => $mpd['metadata']]));
|
||||
}
|
||||
}
|
||||
|
||||
$query->setAttribute('status', self::STATUS_END);
|
||||
$query->setAttribute('endedAt', DateTime::now());
|
||||
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
|
||||
$this->send($query, 'update');
|
||||
|
||||
console::info('Rendition ' . $query->getId() . ' conversion, done');
|
||||
|
||||
/**
|
||||
* Upload
|
||||
*/
|
||||
$dir = new DirectoryIterator($this->outDir);
|
||||
foreach ($dir as $fileinfo) {
|
||||
if (!$fileinfo->isDot()) {
|
||||
$data = (new Local('/'))->read($this->outDir . $fileinfo->getFilename());
|
||||
|
||||
console::info('Uploading ' . $fileinfo->getFilename());
|
||||
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$path . $fileinfo->getFilename(),
|
||||
$data,
|
||||
mime_content_type($this->outDir . $fileinfo->getFilename())
|
||||
);
|
||||
|
||||
if ($fileinfo->key() === 0) {
|
||||
$query->setAttribute('progress', '100');
|
||||
$query->setAttribute('status', self::STATUS_UPLOADING);
|
||||
$query->setAttribute('path', $path);
|
||||
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
|
||||
$this->send($query, 'update');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$query->setAttribute('status', self::STATUS_READY);
|
||||
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
|
||||
$this->send($query, 'update');
|
||||
} catch (\Throwable $th) {
|
||||
$query->setAttribute('metadata', json_encode([
|
||||
'code' => $th->getCode(),
|
||||
'message' => substr($th->getMessage(), 0, 255),
|
||||
]));
|
||||
|
||||
$query->setAttribute('status', self::STATUS_ERROR);
|
||||
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
|
||||
$this->send($query, 'update');
|
||||
|
||||
console::error('Error video id:' . $this->video->getId() . PHP_EOL
|
||||
. 'Message: ' . $th->getMessage() . PHP_EOL
|
||||
. 'File: ' . $th->getFile() . PHP_EOL
|
||||
. 'line: ' . $th->getLine() . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create media Info
|
||||
*
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function createInfo(): void
|
||||
{
|
||||
|
||||
$mediaInfo = new MediaInfo();
|
||||
$mediaInfoContainer = $mediaInfo->getInfo($this->inPath);
|
||||
$general = $mediaInfoContainer->getGeneral();
|
||||
$this->video
|
||||
->setAttribute('duration', $general->has('duration') ? $general->get('duration')->getMilliseconds() : 0)
|
||||
->setAttribute('format', $general->has('format') ? $general->get('format')->getShortName() : '');
|
||||
|
||||
foreach ($mediaInfoContainer->getVideos() ?? [] as $video) {
|
||||
$this->video
|
||||
->setAttribute('height', $video->has('height') ? $video->get('height')->getAbsoluteValue() : 0)
|
||||
->setAttribute('width', $video->has('width') ? $video->get('width')->getAbsoluteValue() : 0)
|
||||
->setAttribute('aspectRatio', $video->has('display_aspect_ratio') ? $video->get('display_aspect_ratio')->getTextValue() : '')
|
||||
->setAttribute('videoFormat', $video->has('format') ? $video->get('format')->getShortName() : '')
|
||||
->setAttribute('videoFormatProfile', $video->has('format_profile') ? $video->get('format_profile') : '')
|
||||
->setAttribute('videoFrameRate', $video->has('frame_rate') ? strval($video->get('frame_rate')->getAbsoluteValue()) : '')
|
||||
->setAttribute('videoFrameRateMode', $video->has('frame_rate_mode') ? $video->get('frame_rate_mode')->getFullName() : '')
|
||||
->setAttribute('videoBitRate', $video->has('bit_rate') ? $video->get('bit_rate')->getAbsoluteValue() : 0);
|
||||
}
|
||||
|
||||
foreach ($mediaInfoContainer->getAudios() ?? [] as $audio) {
|
||||
$this->video
|
||||
->setAttribute('audioFormat', $audio->has('format') ? strval($audio->get('format')->getShortName()) : '')
|
||||
->setAttribute('audioSampleRate', $audio->has('sampling_rate') ? strval($audio->get('sampling_rate')->getAbsoluteValue()) : '')
|
||||
->setAttribute('audioBitRate', $audio->has('bit_rate') ? $audio->get('bit_rate')->getAbsoluteValue() : 0);
|
||||
}
|
||||
|
||||
console::info('Input video id: ' . $this->video->getId() . PHP_EOL .
|
||||
'Input name: ' . $this->file->getAttribute('name') . PHP_EOL .
|
||||
'Input width: ' . $this->video->getAttribute('width') . ' px' . PHP_EOL .
|
||||
'Input height: ' . $this->video->getAttribute('height') . ' px' . PHP_EOL .
|
||||
'Input duration: ' . ($this->video->getAttribute('duration') / 1000) . ' Sec' . PHP_EOL .
|
||||
'Input size: ' . ($this->video->getAttribute('size') / 1024 / 1024) . ' MiB' . PHP_EOL .
|
||||
'Input videoBitRate: ' . ($this->video->getAttribute('videoBitRate') / 1000) . ' kb/s' . PHP_EOL .
|
||||
'Input audioBitRate: ' . ($this->video->getAttribute('audioBitRate') / 1000) . ' kb/s' . PHP_EOL);
|
||||
|
||||
$this->database->updateDocument(
|
||||
'videos',
|
||||
$this->video->getId(),
|
||||
new document(
|
||||
array_filter((array)$this->video, fn($value) => !is_null($value))
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcode
|
||||
*
|
||||
* @param $media Media
|
||||
* @param $format StreamFormat
|
||||
* @param $representation Representation
|
||||
* @return void
|
||||
*/
|
||||
private function transcode(Media $media, StreamFormat $format, Representation $representation): void
|
||||
{
|
||||
|
||||
$additionalParams = [
|
||||
'-dn',
|
||||
'-sn',
|
||||
'-vf', 'scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1', //keep frame size
|
||||
'-crf', '22', // constant rate factor
|
||||
'-bf', '3', // bframe
|
||||
'-force_key_frames', 'expr:gte(t,n_forced*2)' //enforce strict key frame
|
||||
];
|
||||
|
||||
$segmentSize = 6;
|
||||
if ($this->args['output'] === self::OUTPUT_DASH) {
|
||||
$media->dash()
|
||||
->setFormat($format)
|
||||
->setSegDuration($segmentSize)
|
||||
->addRepresentation($representation)
|
||||
->setAdditionalParams($additionalParams)
|
||||
->save($this->outPath);
|
||||
return;
|
||||
}
|
||||
|
||||
$media->hls()
|
||||
->setFormat($format)
|
||||
->setHlsTime($segmentSize)
|
||||
->addRepresentation($representation)
|
||||
->setAdditionalParams($additionalParams)
|
||||
->save($this->outPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HLS|DASH segments
|
||||
*
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
private function getSegments(string $path): array
|
||||
{
|
||||
|
||||
$segments = [];
|
||||
if ($this->args['output'] === self::OUTPUT_DASH) {
|
||||
$metadata = null;
|
||||
$handle = fopen($path, "r");
|
||||
if ($handle) {
|
||||
$streamId = -1;
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = str_replace([",", "\r", "\n"], "", $line);
|
||||
if (str_contains($line, "<AdaptationSet")) {
|
||||
$streamId++;
|
||||
}
|
||||
|
||||
if (!str_contains($line, "SegmentURL") && !str_contains($line, "Initialization")) {
|
||||
$metadata .= $line . PHP_EOL;
|
||||
} else {
|
||||
$segments[] = [
|
||||
'isInit' => str_contains($line, "Initialization") ? 1 : 0,
|
||||
'streamId' => $streamId,
|
||||
'fileName' => trim(str_replace(["<SegmentURL media=\"", "<Initialization sourceURL=\"", "\"/>", "\" />"], "", $line)),
|
||||
];
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return [
|
||||
'metadata' => $metadata,
|
||||
'segments' => $segments
|
||||
];
|
||||
}
|
||||
|
||||
$targetDuration = 0;
|
||||
$handle = fopen($path, "r");
|
||||
if ($handle) {
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = str_replace([",","\r","\n"], "", $line);
|
||||
if (str_contains($line, "#EXT-X-TARGETDURATION")) {
|
||||
$targetDuration = str_replace(["#EXT-X-TARGETDURATION:"], "", $line);
|
||||
}
|
||||
if (str_contains($line, "#EXTINF")) {
|
||||
$duration = str_replace(["#EXTINF:"], "", $line);
|
||||
}
|
||||
if (str_contains($line, ".ts") || str_contains($line, ".vtt")) {
|
||||
if (!empty($duration)) {
|
||||
$segments[] = [
|
||||
'fileName' => $line,
|
||||
'duration' => $duration
|
||||
];
|
||||
$duration = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
return [
|
||||
'targetDuration' => $targetDuration,
|
||||
'segments' => $segments
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get HLS segments urls
|
||||
*
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
private function getHlsSegmentsUrls(string $path): array
|
||||
{
|
||||
|
||||
$files = [];
|
||||
$handle = fopen($path, "r");
|
||||
if ($handle) {
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$tmp = [];
|
||||
$line = str_replace(['"'], '', $line);
|
||||
$attributes = explode(',', $line);
|
||||
foreach ($attributes as $key => $attribute) {
|
||||
$parts = explode('=', $attribute);
|
||||
switch (true) {
|
||||
case str_contains($parts[0], 'LANGUAGE'):
|
||||
$attr['language'] = $parts[1];
|
||||
break;
|
||||
case str_contains($parts[0], 'BANDWIDTH'):
|
||||
$attr['bandwidth'] = $parts[1];
|
||||
break;
|
||||
case str_contains($parts[0], 'RESOLUTION'):
|
||||
$attr['resolution'] = $parts[1];
|
||||
break;
|
||||
case str_contains($parts[0], 'CODECS'):
|
||||
$attr['codecs'] = trim($parts[1], " \n\r\t\v\x00");
|
||||
|
||||
if (!empty($attributes[$key + 1])) {
|
||||
$attr['codecs'] .= ',' . trim($attributes[$key + 1], " \n\r\t\v\x00");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$end = strpos($line, 'm3u8');
|
||||
if ($end !== false) {
|
||||
$start = strpos($line, $this->video->getId());
|
||||
if ($start !== false) {
|
||||
$path = substr($line, $start, ($end - $start) + 4);
|
||||
$parts = explode('_', $path);
|
||||
$tmp = [
|
||||
'id' => $parts[1],
|
||||
'path' => $path
|
||||
];
|
||||
if (str_contains($line, "TYPE=AUDIO")) {
|
||||
$tmp['type'] = 'audio';
|
||||
if (!empty($attr['language'])) {
|
||||
$tmp['language'] = $attr['language'];
|
||||
}
|
||||
} else {
|
||||
$tmp['type'] = 'video';
|
||||
if (!empty($attr['resolution'])) {
|
||||
$tmp['resolution'] = $attr['resolution'];
|
||||
}
|
||||
if (!empty($attr['bandwidth'])) {
|
||||
$tmp['bandwidth'] = $attr['bandwidth'];
|
||||
}
|
||||
if (!empty($attr['codecs'])) {
|
||||
$tmp['codecs'] = $attr['codecs'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$files[] = $tmp;
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send
|
||||
*
|
||||
* @param string $action
|
||||
* @param Document $payload
|
||||
* @throws Exception|\Exception
|
||||
*/
|
||||
private function send(Document $payload, string $action = 'create')
|
||||
{
|
||||
unset($payload['metadata']);
|
||||
|
||||
$allEvents = Event::generateEvents('videos.[videoId].renditions.[renditionId].' . $action, [
|
||||
'videoId' => $payload['videoId'],
|
||||
'renditionId' => $payload->getId()
|
||||
]);
|
||||
|
||||
$payload->setAttribute('$permissions', $this->bucket->getAttribute('fileSecurity', false)
|
||||
? \array_merge($this->bucket->getAttribute('$permissions'), $this->file->getAttribute('$permissions'))
|
||||
: $this->bucket->getAttribute('$permissions'));
|
||||
|
||||
$target = Realtime::fromPayload(
|
||||
event: $allEvents[0],
|
||||
payload: $payload
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
projectId: 'console',
|
||||
payload: $payload->getArrayCopy(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download
|
||||
*
|
||||
* @param $project Document
|
||||
* @param $file Document
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function download(Document $project, Document $file)
|
||||
{
|
||||
|
||||
$fullPath = $file->getAttribute('path');
|
||||
$path = basename($file->getAttribute('path'));
|
||||
|
||||
console::info('Downloading file: ' . $path . ' to local storage ' . $this->inDir);
|
||||
|
||||
$local = new Local('/');
|
||||
try {
|
||||
if (
|
||||
!empty($file->getAttribute('openSSLCipher')) ||
|
||||
$file->getAttribute('algorithm', 'none') !== 'none'
|
||||
) {
|
||||
$data = $this->getFilesDevice($project->getId())->read($fullPath);
|
||||
if (!empty($file->getAttribute('openSSLCipher'))) {
|
||||
$data = OpenSSL::decrypt(
|
||||
$data,
|
||||
$file->getAttribute('openSSLCipher'),
|
||||
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
|
||||
0,
|
||||
\hex2bin($file->getAttribute('openSSLIV')),
|
||||
\hex2bin($file->getAttribute('openSSLTag'))
|
||||
);
|
||||
}
|
||||
|
||||
$algorithm = $file->getAttribute('algorithm', 'none');
|
||||
switch ($algorithm) {
|
||||
case 'zstd':
|
||||
$compressor = new Zstd();
|
||||
$data = $compressor->decompress($data);
|
||||
break;
|
||||
case 'gzip':
|
||||
$compressor = new GZIP();
|
||||
$data = $compressor->decompress($data);
|
||||
break;
|
||||
}
|
||||
$local->
|
||||
write($this->inDir . $path, $data, $file->getAttribute('mimeType'));
|
||||
} else {
|
||||
$this->getFilesDevice(
|
||||
$project->getId()
|
||||
)->transfer($fullPath, $this->inDir . $path, $local);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
console::error($th->getMessage() . ' on file: ' . $th->getFile() . ' line: ' . $th->getLine);
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanup(): bool
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$stdin = '';
|
||||
|
||||
return Console::execute("rm -rf {$this->basePath}", $stdin, $stdout, $stderr, 3) === 0;
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
{
|
||||
$result = $this->cleanup();
|
||||
if (!$result) {
|
||||
Console::error('Failed Removing files from [' . $this->basePath . ']');
|
||||
}
|
||||
Console::info('Removing files from [' . $this->basePath . ']');
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/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=0.1 QUEUE='v1-videos' APP_INCLUDE='/usr/src/code/app/workers/videos.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
||||
+7
-15
@@ -41,16 +41,15 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-zlib": "*",
|
||||
"ext-sockets": "*",
|
||||
"ext-fileinfo": "*",
|
||||
"appwrite/php-clamav": "1.1.*",
|
||||
"appwrite/php-runtimes": "0.11.*",
|
||||
"utopia-php/abuse": "0.25.*",
|
||||
"utopia-php/abuse": "0.24.*",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
"utopia-php/audit": "0.26.*",
|
||||
"utopia-php/audit": "0.25.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cli": "0.13.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.36.*",
|
||||
"utopia-php/database": "0.35.*",
|
||||
"utopia-php/domains": "1.1.*",
|
||||
"utopia-php/framework": "0.28.*",
|
||||
"utopia-php/image": "0.5.*",
|
||||
@@ -60,7 +59,7 @@
|
||||
"utopia-php/orchestration": "0.6.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "dev-feat-transfer as 0.13.2",
|
||||
"utopia-php/storage": "0.14.*",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
"resque/php-resque": "1.3.6",
|
||||
@@ -71,24 +70,17 @@
|
||||
"chillerlan/php-qrcode": "4.3.3",
|
||||
"adhocore/jwt": "1.1.2",
|
||||
"slickdeals/statsd": "3.1.0",
|
||||
"aminyazdanpanah/php-ffmpeg-video-streaming": "dev-master",
|
||||
"captioning/captioning": "2.*",
|
||||
"ext-simplexml": "*",
|
||||
"webonyx/graphql-php": "14.11.*",
|
||||
"mhor/php-mediainfo": "5.4.3"
|
||||
"webonyx/graphql-php": "14.11.*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"url": "https://github.com/appwrite/runtimes.git",
|
||||
"type": "git"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/shimonewman/PHP-FFmpeg-video-streaming.git",
|
||||
"type": "git"
|
||||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "dev-shimon-video-test",
|
||||
"appwrite/sdk-generator": "0.32.*",
|
||||
"ext-fileinfo": "*",
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"swoole/ide-helper": "4.8.9",
|
||||
|
||||
Generated
+265
-1263
File diff suppressed because it is too large
Load Diff
@@ -73,7 +73,6 @@ services:
|
||||
- traefik.http.routers.appwrite_api_https.tls=true
|
||||
volumes:
|
||||
- appwrite-uploads:/storage/uploads:rw
|
||||
- appwrite-videos:/storage/videos:rw
|
||||
- appwrite-cache:/storage/cache:rw
|
||||
- appwrite-config:/storage/config:rw
|
||||
- appwrite-certificates:/storage/certificates:rw
|
||||
@@ -85,9 +84,6 @@ services:
|
||||
- ./public:/usr/src/code/public
|
||||
- ./src:/usr/src/code/src
|
||||
- ./dev:/usr/src/code/dev
|
||||
- ./dev:/usr/local/dev
|
||||
- ./tests/tmp:/tmp
|
||||
- ./vendor/utopia-php/storage:/usr/src/code/vendor/utopia-php/storage
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
@@ -229,65 +225,6 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-videos:
|
||||
entrypoint: worker-videos
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-videos
|
||||
build:
|
||||
context: .
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- appwrite-uploads:/storage/uploads:rw
|
||||
- appwrite-videos:/storage/videos:rw
|
||||
#- ./tests:/usr/src/code/tests #TODO remove when done!!
|
||||
- ./vendor/aminyazdanpanah/php-ffmpeg-video-streaming:/usr/src/code/vendor/aminyazdanpanah/php-ffmpeg-video-streaming #TODO remove when done!!
|
||||
- ./vendor/php-ffmpeg/php-ffmpeg:/usr/src/code/vendor/php-ffmpeg/php-ffmpeg #TODO remove when done!!
|
||||
- ./vendor/alchemy/binary-driver:/usr/src/code/vendor/alchemy/binary-driver
|
||||
- ./vendor/utopia-php/storage:/usr/src/code/vendor/utopia-php/storage
|
||||
#- ./vendor/symfony/process:/usr/src/code/vendor/symfony/process
|
||||
|
||||
depends_on:
|
||||
- redis
|
||||
- mariadb
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_STORAGE_DEVICE
|
||||
- _APP_STORAGE_S3_ACCESS_KEY
|
||||
- _APP_STORAGE_S3_SECRET
|
||||
- _APP_STORAGE_S3_REGION
|
||||
- _APP_STORAGE_S3_BUCKET
|
||||
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
|
||||
- _APP_STORAGE_DO_SPACES_SECRET
|
||||
- _APP_STORAGE_DO_SPACES_REGION
|
||||
- _APP_STORAGE_DO_SPACES_BUCKET
|
||||
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
|
||||
- _APP_STORAGE_BACKBLAZE_SECRET
|
||||
- _APP_STORAGE_BACKBLAZE_REGION
|
||||
- _APP_STORAGE_BACKBLAZE_BUCKET
|
||||
- _APP_STORAGE_LINODE_ACCESS_KEY
|
||||
- _APP_STORAGE_LINODE_SECRET
|
||||
- _APP_STORAGE_LINODE_REGION
|
||||
- _APP_STORAGE_LINODE_BUCKET
|
||||
- _APP_STORAGE_WASABI_ACCESS_KEY
|
||||
- _APP_STORAGE_WASABI_SECRET
|
||||
- _APP_STORAGE_WASABI_REGION
|
||||
- _APP_STORAGE_WASABI_BUCKET
|
||||
- _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_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-audits:
|
||||
entrypoint: worker-audits
|
||||
<<: *x-logging
|
||||
@@ -353,7 +290,6 @@ services:
|
||||
- mariadb
|
||||
volumes:
|
||||
- appwrite-uploads:/storage/uploads:rw
|
||||
- appwrite-videos:/storage/videos:rw
|
||||
- appwrite-cache:/storage/cache:rw
|
||||
- appwrite-functions:/storage/functions:rw
|
||||
- appwrite-builds:/storage/builds:rw
|
||||
@@ -910,7 +846,6 @@ volumes:
|
||||
appwrite-redis:
|
||||
appwrite-cache:
|
||||
appwrite-uploads:
|
||||
appwrite-videos:
|
||||
appwrite-certificates:
|
||||
appwrite-functions:
|
||||
appwrite-builds:
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Avatars avatars = new Avatars(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Avatars avatars = new Avatars(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Avatars avatars = new Avatars(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Avatars avatars = new Avatars(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Avatars avatars = new Avatars(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Avatars avatars = new Avatars(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Avatars avatars = new Avatars(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Databases databases = new Databases(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Databases databases = new Databases(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Databases databases = new Databases(client);
|
||||
|
||||
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Client client = new Client(getApplicationContext())
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
|
||||
.setProject("5df5acd0d48c2"); // Your project ID
|
||||
|
||||
Databases databases = new Databases(client);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user