small fix

This commit is contained in:
shimon
2022-07-05 11:55:49 +03:00
parent c2e599222e
commit e48e1dae2c
16 changed files with 1006 additions and 556 deletions
+2 -2
View File
@@ -266,14 +266,14 @@ COPY ./src /usr/src/code/src
# Set Volumes
RUN mkdir -p /storage/uploads && \
mkdir -p /storage/video && \
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/video && chmod -Rf 0755 /storage/video && \
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 && \
+282 -154
View File
@@ -16,10 +16,10 @@ $auth = Config::getParam('auth', []);
*/
$collections = [
'collections' => [
'databases' => [
'$collection' => Database::METADATA,
'$id' => 'collections',
'name' => 'Collections',
'$id' => 'databases',
'name' => 'Databases',
'attributes' => [
[
'$id' => 'name',
@@ -31,27 +31,63 @@ $collections = [
'filters' => [],
],
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'$id' => 'search',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 0,
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => 'dateUpdated',
'type' => Database::VAR_INTEGER,
'$id' => '_fulltext_search',
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
],
],
'collections' => [
'$collection' => 'databases',
'$id' => 'collections',
'name' => 'Collections',
'attributes' => [
[
'$id' => 'databaseInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 0,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'databaseId',
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => 'name',
'type' => Database::VAR_STRING,
'size' => 256,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => 'enabled',
'type' => Database::VAR_BOOLEAN,
@@ -120,7 +156,7 @@ $collections = [
'name' => 'Attributes',
'attributes' => [
[
'$id' => 'collectionId',
'$id' => 'databaseInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -130,6 +166,39 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'databaseId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'collectionInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'collectionId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'key',
'type' => Database::VAR_STRING,
@@ -249,11 +318,11 @@ $collections = [
],
'indexes' => [
[
'$id' => '_key_collection',
'$id' => '_key_db_collection',
'type' => Database::INDEX_KEY,
'attributes' => ['collectionId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
'attributes' => ['databaseInternalId', 'collectionInternalId'],
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
],
],
@@ -264,7 +333,7 @@ $collections = [
'name' => 'Indexes',
'attributes' => [
[
'$id' => 'collectionId',
'$id' => 'databaseInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -274,6 +343,39 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'databaseId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'collectionInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'collectionId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'key',
'type' => Database::VAR_STRING,
@@ -343,11 +445,11 @@ $collections = [
],
'indexes' => [
[
'$id' => '_key_collection',
'$id' => '_key_db_collection',
'type' => Database::INDEX_KEY,
'attributes' => ['collectionId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
'attributes' => ['databaseInternalId', 'collectionInternalId'],
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
],
],
@@ -357,6 +459,17 @@ $collections = [
'$id' => 'projects',
'name' => 'Projects',
'attributes' => [
[
'$id' => 'teamInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'teamId',
'type' => Database::VAR_STRING,
@@ -594,6 +707,17 @@ $collections = [
'$id' => 'platforms',
'name' => 'platforms',
'attributes' => [
[
'$id' => 'projectInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'projectId',
'type' => Database::VAR_STRING,
@@ -659,35 +783,13 @@ $collections = [
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'dateUpdated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
]
],
'indexes' => [
[
'$id' => '_key_project',
'type' => Database::INDEX_KEY,
'attributes' => ['projectId'],
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
@@ -699,6 +801,17 @@ $collections = [
'$id' => 'domains',
'name' => 'domains',
'attributes' => [
[
'$id' => 'projectInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'projectId',
'type' => Database::VAR_STRING,
@@ -781,7 +894,7 @@ $collections = [
[
'$id' => '_key_project',
'type' => Database::INDEX_KEY,
'attributes' => ['projectId'],
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
@@ -794,7 +907,7 @@ $collections = [
'name' => 'keys',
'attributes' => [
[
'$id' => 'projectId',
'$id' => 'projectInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -804,6 +917,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'projectId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'name',
'type' => Database::VAR_STRING,
@@ -837,12 +961,23 @@ $collections = [
'array' => false,
'filters' => ['encrypt'],
],
[
'$id' => 'expire',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_project',
'type' => Database::INDEX_KEY,
'attributes' => ['projectId'],
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
@@ -854,6 +989,17 @@ $collections = [
'$id' => 'webhooks',
'name' => 'webhooks',
'attributes' => [
[
'$id' => 'projectInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'projectId',
'type' => Database::VAR_STRING,
@@ -947,10 +1093,10 @@ $collections = [
[
'$id' => '_key_project',
'type' => Database::INDEX_KEY,
'attributes' => ['projectId'],
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
]
],
],
@@ -981,6 +1127,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'phone',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16, // leading '+' and 15 digitts maximum by E.164 format
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'status',
'type' => Database::VAR_BOOLEAN,
@@ -1047,6 +1204,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'phoneVerification',
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'reset',
'type' => Database::VAR_BOOLEAN,
@@ -1111,6 +1279,13 @@ $collections = [
'lengths' => [320],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_phone',
'type' => Database::INDEX_UNIQUE,
'attributes' => ['phone'],
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_search',
'type' => Database::INDEX_FULLTEXT,
@@ -1126,6 +1301,17 @@ $collections = [
'$id' => 'tokens',
'name' => 'Tokens',
'attributes' => [
[
'$id' => 'userInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'userId',
'type' => Database::VAR_STRING,
@@ -1197,7 +1383,7 @@ $collections = [
[
'$id' => '_key_user',
'type' => Database::INDEX_KEY,
'attributes' => ['userId'],
'attributes' => ['userInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
@@ -1209,6 +1395,17 @@ $collections = [
'$id' => 'sessions',
'name' => 'Sessions',
'attributes' => [
[
'$id' => 'userInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'userId',
'type' => Database::VAR_STRING,
@@ -1474,7 +1671,7 @@ $collections = [
[
'$id' => '_key_user',
'type' => Database::INDEX_KEY,
'attributes' => ['userId'],
'attributes' => ['userInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
@@ -1497,17 +1694,6 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'total',
'type' => Database::VAR_INTEGER,
@@ -1548,7 +1734,7 @@ $collections = [
'name' => 'Memberships',
'attributes' => [
[
'$id' => 'teamId',
'$id' => 'userInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -1569,6 +1755,28 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'teamInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'teamId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'roles',
'type' => Database::VAR_STRING,
@@ -1640,21 +1848,21 @@ $collections = [
[
'$id' => '_key_unique',
'type' => Database::INDEX_UNIQUE,
'attributes' => ['teamId', 'userId'],
'attributes' => ['teamInternalId', 'userInternalId'],
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
[
'$id' => '_key_team',
'$id' => '_key_user',
'type' => Database::INDEX_KEY,
'attributes' => ['teamId'],
'attributes' => ['userInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_user',
'$id' => '_key_team',
'type' => Database::INDEX_KEY,
'attributes' => ['userId'],
'attributes' => ['teamInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
@@ -1695,28 +1903,6 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'dateUpdated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'array' => false,
'$id' => 'status',
@@ -1726,7 +1912,6 @@ $collections = [
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
@@ -1845,17 +2030,6 @@ $collections = [
'$id' => 'deployments',
'name' => 'Deployments',
'attributes' => [
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'resourceId',
'type' => Database::VAR_STRING,
@@ -1898,7 +2072,6 @@ $collections = [
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
@@ -2147,17 +2320,6 @@ $collections = [
'$id' => 'executions',
'name' => 'Executions',
'attributes' => [
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'functionId',
'type' => Database::VAR_STRING,
@@ -2189,7 +2351,6 @@ $collections = [
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
@@ -2368,26 +2529,6 @@ $collections = [
'$id' => 'buckets',
'name' => 'Buckets',
'attributes' => [
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'signed' => false,
'size' => 0,
'required' => false,
'array' => false,
'filters' => [],
],
[
'$id' => 'dateUpdated',
'type' => Database::VAR_INTEGER,
'size' => 0,
'format' => '',
'signed' => false,
'required' => false,
'array' => false,
'filters' => [],
],
[
'$id' => 'enabled',
'type' => Database::VAR_BOOLEAN,
@@ -2628,17 +2769,6 @@ $collections = [
'$id' => 'files',
'$name' => 'Files',
'attributes' => [
[
'$id' => 'dateCreated',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'array' => false,
'$id' => 'bucketId',
@@ -2648,7 +2778,6 @@ $collections = [
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
@@ -3077,10 +3206,10 @@ $collections = [
],
[
'$id' => 'width',
'type' => Database::VAR_STRING,
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
@@ -3088,10 +3217,10 @@ $collections = [
],
[
'$id' => 'height',
'type' => Database::VAR_STRING,
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
@@ -3300,7 +3429,6 @@ $collections = [
],
]
],
];
return $collections;
-29
View File
@@ -1,29 +0,0 @@
<?php
return [
[
'id' => 1,
'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
],
[
'id' => 2,
'name' => '576p',
'videoBitrate' => 2538,
'audioBitrate' => 128,
'width' => 1024,
'height' => 576,
],
[
'id' => 3,
'name' => '720p',
'videoBitrate' => 3551,
'audioBitrate' => 128,
'width' => 1280,
'height' => 720,
],
];
@@ -2,7 +2,7 @@
return [
[
'id' => 1,
'name' => '360p',
'videoBitrate' => 890, //video bitrate in Kbps
'audioBitrate' => 64, //audio bitrate in Kbps
@@ -10,7 +10,6 @@ return [
'height' => 360, //height resolution in px
],
[
'id' => 2,
'name' => '576p',
'videoBitrate' => 2538,
'audioBitrate' => 128,
@@ -18,7 +17,6 @@ return [
'height' => 576,
],
[
'id' => 3,
'name' => '720p',
'videoBitrate' => 3551,
'audioBitrate' => 128,
+1 -1
View File
@@ -153,7 +153,7 @@ App::post('/v1/projects')
}
if($dbForProject->exists($dbForProject->getDefaultDatabase(), 'video_profiles')) {
foreach (Config::getParam('profiles', []) as $profile) {
foreach (Config::getParam('video-profiles', []) as $profile) {
Authorization::skip(function () use ($project, $profile, $dbForProject) {
return $dbForProject->createDocument('video_profiles', new Document([
'name' => $profile['name'],
+107 -56
View File
@@ -55,7 +55,7 @@ App::get('/v1/video/profiles')
$profiles = Authorization::skip(fn () => $dbForProject->find('video_profiles', [], 12, 0, [], ['ASC']));
if(empty($profiles)) {
if (empty($profiles)) {
throw new Exception('Video profiles where not found', 404, Exception::PROFILES_NOT_FOUND);
}
@@ -63,7 +63,7 @@ App::get('/v1/video/profiles')
'total' => $dbForProject->count('video_profiles', [], APP_LIMIT_COUNT),
'profiles' => $profiles,
]), Response::MODEL_VIDEO_PROFILE_LIST);
});
});
App::post('/v1/video/buckets/:bucketId/files/:fileId')
@@ -135,7 +135,7 @@ App::post('/v1/video/buckets/:bucketId/files/:fileId')
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
try {
$video = Authorization::skip(function () use ($dbForProject, $bucket, $file) {
$video = Authorization::skip(function () use ($dbForProject, $bucket, $file) {
return $dbForProject->createDocument('videos', new Document([
'bucketId' => $bucket->getId(),
'fileId' => $file->getId(),
@@ -156,13 +156,13 @@ App::post('/v1/video/buckets/:bucketId/files/:fileId')
App::post('/v1/video/:videoId/rendition/:profileId')
->alias('/v1/video/files', ['bucketId' => 'default'])
->alias('/v1/video/:videoId/rendition/:profileId', [])
->desc('Start transcoding video rendition')
->groups(['api', 'storage'])
->label('scope', 'files.write')
// ->label('event', 'buckets.[bucketId].files.[fileId].create')
->param('videoId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('profileId', '', new CustomId(), 'File ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('profileId', '', new CustomId(), 'Profile unique ID.')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->inject('request')
@@ -182,7 +182,7 @@ App::post('/v1/video/:videoId/rendition/:profileId')
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if($video->isEmpty()){
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
}
@@ -232,7 +232,7 @@ App::post('/v1/video/:videoId/rendition/:profileId')
$profile = Authorization::skip(fn() => $dbForProject->findOne('video_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$profileId])]));
if(!$profile){
if (!$profile) {
throw new Exception('Video profile not found', 400, Exception::PROFILES_NOT_FOUND);
}
@@ -248,27 +248,31 @@ App::post('/v1/video/:videoId/rendition/:profileId')
});
App::get('/v1/video/buckets/:bucketId/files/:fileId/renditions')
->alias('/v1/storage/files/:fileId/renditions', ['bucketId' => 'default'])
App::get('/v1/video/:videoId/:stream/renditions')
->alias('/v1/video/:videoId/renditions', [])
->desc('Get File renditions')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFile')
->label('sdk.description', '/docs/references/storage/get-file.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('stream', '', new WhiteList(['hls', 'mpeg-dash']), 'stream protocol name')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('mode')
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Stats $usage, string $mode) {
->action(function (string $videoId, string $stream, ?array $read, ?array $write, Response $response, Database $dbForProject, Stats $usage, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
}
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $video['bucketId']));
if (
$bucket->isEmpty()
@@ -278,52 +282,81 @@ App::get('/v1/video/buckets/:bucketId/files/:fileId/renditions')
}
// Check bucket permissions when enforced
if ($bucket->getAttribute('permission') === 'bucket') {
$validator = new Authorization('read');
if (!$validator->isValid($bucket->getRead())) {
$permissionBucket = $bucket->getAttribute('permission') === 'bucket';
if ($permissionBucket) {
$validator = new Authorization('write');
if (!$validator->isValid($bucket->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
}
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
// Users can only add their roles to files, API keys and Admin users can add any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($read as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
}
}
foreach ($write as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
}
}
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
if ($bucket->getAttribute('permission') === 'bucket') {
// skip authorization
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']);
}
$queries = [
new Query('bucketId', Query::TYPE_EQUAL, [$bucketId]),
new Query('fileId', Query::TYPE_EQUAL, [$fileId]),
new Query('stream', Query::TYPE_EQUAL, ['dash']),
new Query('videoId', Query::TYPE_EQUAL, [$video->getId()]),
new Query('endedAt', Query::TYPE_GREATER, [0]),
new Query('status', Query::TYPE_EQUAL, ['ready']),
new Query('stream', Query::TYPE_EQUAL, [$stream]),
];
$renditions = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId() . '_video_renditions', $queries, 12, 0, [], ['ASC']));
$renditions = Authorization::skip(fn () => $dbForProject->find('video_renditions', $queries, 12, 0, [], ['ASC']));
$response->dynamic(new Document([
'total' => $dbForProject->count('bucket_' . $bucket->getInternalId() . '_video_renditions', $queries, APP_LIMIT_COUNT),
'total' => $dbForProject->count('video_renditions', $queries, APP_LIMIT_COUNT),
'renditions' => $renditions,
]), Response::MODEL_FILE_RENDITIONS_LIST);
]), Response::MODEL_VIDEO_RENDITIONS_LIST);
});
App::get('/v1/video/buckets/:bucketId/files/:stream/:fileId')
App::get('/v1/video/:videoId/:stream/:namespace/:fileName')
->alias('/v1/video/buckets/:bucketId/files/:stream/:fileId', [])
->desc('Get video playlist manifest')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('stream', '', new WhiteList(['hls', 'dash']), 'stream protocol name')
->param('fileId', '', new UID(), 'File ID.')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('stream', '', new WhiteList(['hls', 'mpeg-dash']), 'stream protocol name')
->param('namespace', '', new WhiteList(['master']), 'stream protocol name')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('mode')
->action(function (string $bucketId, string $stream, string $fileId, Response $response, Database $dbForProject, Stats $usage, string $mode) {
->action(function (string $videoId, string $stream, string $namespace, ?array $read, ?array $write, Response $response, Database $dbForProject, Stats $usage, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
}
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $video['bucketId']));
if (
$bucket->isEmpty()
@@ -333,32 +366,48 @@ App::get('/v1/video/buckets/:bucketId/files/:stream/:fileId')
}
// Check bucket permissions when enforced
if ($bucket->getAttribute('permission') === 'bucket') {
$validator = new Authorization('read');
if (!$validator->isValid($bucket->getRead())) {
$permissionBucket = $bucket->getAttribute('permission') === 'bucket';
if ($permissionBucket) {
$validator = new Authorization('write');
if (!$validator->isValid($bucket->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
}
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
// Users can only add their roles to files, API keys and Admin users can add any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($read as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
}
}
foreach ($write as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
}
}
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
if ($bucket->getAttribute('permission') === 'bucket') {
// skip authorization
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']);
}
$queries = [
new Query('bucketId', Query::TYPE_EQUAL, [$bucketId]),
new Query('fileId', Query::TYPE_EQUAL, [$fileId]),
new Query('stream', Query::TYPE_EQUAL, [$stream]),
new Query('videoId', Query::TYPE_EQUAL, [$video->getId()]),
new Query('endedAt', Query::TYPE_GREATER, [0]),
new Query('status', Query::TYPE_EQUAL, ['ready'])
new Query('status', Query::TYPE_EQUAL, ['ready']),
new Query('stream', Query::TYPE_EQUAL, ['stream']),
];
$renditions = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId() . '_video_renditions', $queries, 12, 0, [], ['ASC']));
$renditions = Authorization::skip(fn () => $dbForProject->find('video_renditions', $queries, 12, 0, [], ['ASC']));
if (empty($renditions)) {
throw new Exception('Renditions not found');
@@ -379,7 +428,9 @@ App::get('/v1/video/buckets/:bucketId/files/:stream/:fileId')
$template->setParam('params', $params);
$response->setContentType('application/x-mpegurl');
$response->send($template->render());
} else {
$adaptations = [];
foreach ($renditions as $rendition) {
$metadata = $rendition->getAttribute('metadata');
+11 -2
View File
@@ -19,6 +19,7 @@ use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Response\Filters\V11 as ResponseV11;
use Appwrite\Utopia\Response\Filters\V12 as ResponseV12;
use Appwrite\Utopia\Response\Filters\V13 as ResponseV13;
use Appwrite\Utopia\Response\Filters\V14 as ResponseV14;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -177,6 +178,9 @@ App::init(function (App $utopia, Request $request, Response $response, Document
case version_compare($responseFormat, '0.13.4', '<='):
Response::setFilter(new ResponseV13());
break;
case version_compare($responseFormat, '0.14.0', '<='):
Response::setFilter(new ResponseV14());
break;
default:
Response::setFilter(null);
}
@@ -279,6 +283,12 @@ App::init(function (App $utopia, Request $request, Response $response, Document
$role = Auth::USER_ROLE_APP;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
$expire = $key->getAttribute('expire', 0);
if (!empty($expire) && $expire < \time()) {
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
}
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
}
@@ -292,11 +302,10 @@ App::init(function (App $utopia, Request $request, Response $response, Document
$service = $route->getLabel('sdk.namespace', '');
if (!empty($service)) {
$roles = Authorization::getRoles();
if (
array_key_exists($service, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$service]
&& !(Auth::isPrivilegedUser($roles) || Auth::isAppUser($roles))
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
}
+53 -44
View File
@@ -23,11 +23,17 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Extend\Exception;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Phone\Mock;
use Appwrite\Auth\Phone\Telesign;
use Appwrite\Auth\Phone\TextMagic;
use Appwrite\Auth\Phone\Twilio;
use Appwrite\DSN\DSN;
use Appwrite\Event\Audit;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Mail;
use Appwrite\Event\Phone;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\IP;
use Appwrite\Network\Validator\URL;
@@ -78,9 +84,10 @@ const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
const APP_LIMIT_ENCRYPTION = 20000000; //20MB
const APP_LIMIT_COMPRESSION = 20000000; //20MB
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
const APP_LIMIT_SUBQUERY = 1000;
const APP_CACHE_BUSTER = 305;
const APP_VERSION_STABLE = '0.14.2';
const APP_CACHE_BUSTER = 401;
const APP_VERSION_STABLE = '0.15.1';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -89,9 +96,9 @@ const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1073741824; // 2^32 bits / 4 bits per char
const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_VIDEO = '/storage/video';
const APP_STORAGE_FUNCTIONS = '/storage/functions';
const APP_STORAGE_BUILDS = '/storage/builds';
const APP_STORAGE_VIDEO = '/storage/videos';
const APP_STORAGE_CACHE = '/storage/cache';
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
const APP_STORAGE_CONFIG = '/storage/config';
@@ -119,6 +126,7 @@ const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
const BUILD_TYPE_DEPLOYMENT = 'deployment';
const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_PROJECTS = 'projects';
@@ -133,6 +141,7 @@ const DELETE_TYPE_CERTIFICATES = 'certificates';
const DELETE_TYPE_USAGE = 'usage';
const DELETE_TYPE_REALTIME = 'realtime';
const DELETE_TYPE_BUCKETS = 'buckets';
const DELETE_TYPE_SESSIONS = 'sessions';
// Mail Types
const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
@@ -154,32 +163,6 @@ App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
/*
* ENV vars
*/
Config::load('profiles', __DIR__ . '/config/profiles.php');
Config::load('events', __DIR__ . '/config/events.php');
Config::load('auth', __DIR__ . '/config/auth.php');
Config::load('errors', __DIR__ . '/config/errors.php');
Config::load('providers', __DIR__ . '/config/providers.php');
Config::load('platforms', __DIR__ . '/config/platforms.php');
Config::load('collections', __DIR__ . '/config/collections.php');
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
Config::load('services', __DIR__ . '/config/services.php'); // List of services
Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');
Config::load('locale-codes', __DIR__ . '/config/locale/codes.php');
Config::load('locale-currencies', __DIR__ . '/config/locale/currencies.php');
Config::load('locale-eu', __DIR__ . '/config/locale/eu.php');
Config::load('locale-languages', __DIR__ . '/config/locale/languages.php');
Config::load('locale-phones', __DIR__ . '/config/locale/phones.php');
Config::load('locale-countries', __DIR__ . '/config/locale/countries.php');
Config::load('locale-continents', __DIR__ . '/config/locale/continents.php');
Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
Config::load('events', __DIR__ . '/config/events.php');
Config::load('auth', __DIR__ . '/config/auth.php');
Config::load('errors', __DIR__ . '/config/errors.php');
@@ -191,6 +174,7 @@ Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
Config::load('services', __DIR__ . '/config/services.php'); // List of services
Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
Config::load('video-profiles', __DIR__ . '/config/video-profiles.php');
Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');
@@ -206,6 +190,7 @@ 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');
$user = App::getEnv('_APP_REDIS_USER', '');
$pass = App::getEnv('_APP_REDIS_PASS', '');
if (!empty($user) || !empty($pass)) {
@@ -283,8 +268,9 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('attributes', [
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
], $database->getAttributeLimit());
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
], $database->getAttributeLimit(), 0, []);
}
);
@@ -296,7 +282,8 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('indexes', [
new Query('collectionId', Query::TYPE_EQUAL, [$document->getId()])
new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]),
new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')])
], 64);
}
);
@@ -309,7 +296,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('platforms', [
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
], APP_LIMIT_SUBQUERY);
}
);
@@ -322,7 +309,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('domains', [
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
], APP_LIMIT_SUBQUERY);
}
);
@@ -335,7 +322,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('keys', [
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
], APP_LIMIT_SUBQUERY);
}
);
@@ -348,7 +335,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('webhooks', [
new Query('projectId', Query::TYPE_EQUAL, [$document->getId()])
new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
], APP_LIMIT_SUBQUERY);
}
);
@@ -360,7 +347,7 @@ Database::addFilter(
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database->find('sessions', [
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
], APP_LIMIT_SUBQUERY));
}
);
@@ -373,7 +360,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn() => $database
->find('tokens', [
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
], APP_LIMIT_SUBQUERY));
}
);
@@ -386,7 +373,7 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn() => $database
->find('memberships', [
new Query('userId', Query::TYPE_EQUAL, [$document->getId()])
new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()])
], APP_LIMIT_SUBQUERY));
}
);
@@ -453,7 +440,7 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
* Registry
*/
$register->set('logger', function () {
// Register error logger
// Register error logger
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
@@ -487,6 +474,11 @@ $register->set('dbPool', function () {
->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
);
@@ -568,7 +560,7 @@ $register->set('smtp', function () {
return $mail;
});
$register->set('geodb', function () {
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-03.mmdb');
return new Reader(__DIR__ . '/db/DBIP/dbip-country-lite-2022-06.mmdb');
});
$register->set('db', function () {
// This is usually for our workers or CLI commands scope
@@ -579,11 +571,12 @@ $register->set('db', function () {
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
$pdo = new PDO("mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true,
));
return $pdo;
@@ -712,6 +705,7 @@ App::setResource('audits', fn() => new Audit());
App::setResource('mails', fn() => new Mail());
App::setResource('deletes', fn() => new Delete());
App::setResource('database', fn() => new EventDatabase());
App::setResource('messaging', fn() => new Phone());
App::setResource('usage', function ($register) {
return new Stats($register->get('statsd'));
}, ['register']);
@@ -862,6 +856,7 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
App::setResource('console', function () {
return new Document([
'$id' => 'console',
'$internalId' => 'console',
'name' => 'Appwrite',
'$collection' => 'projects',
'description' => 'Appwrite core engine',
@@ -891,12 +886,12 @@ App::setResource('console', function () {
]);
}, []);
App::setResource('dbForProject', function ($db, $cache, $project) {
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->getId()}");
$database->setNamespace("_{$project->getInternalId()}");
return $database;
}, ['db', 'cache', 'project']);
@@ -987,3 +982,17 @@ App::setResource('geodb', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('geodb');
}, ['register']);
App::setResource('phone', function () {
$dsn = new DSN(App::getEnv('_APP_PHONE_PROVIDER'));
$user = $dsn->getUser();
$secret = $dsn->getPassword();
return match ($dsn->getHost()) {
'mock' => new Mock('', ''), // used for tests
'twilio' => new Twilio($user, $secret),
'text-magic' => new TextMagic($user, $secret),
'telesign' => new Telesign($user, $secret),
default => null
};
});
+154 -149
View File
@@ -5,6 +5,7 @@ use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Resque\Worker;
use Streaming\Format\StreamFormat;
use Streaming\Media;
use Streaming\Metadata;
use Streaming\Representation;
use Utopia\App;
use Utopia\CLI\Console;
@@ -34,7 +35,8 @@ class TranscodingV1 extends Worker
const HLS_BASE_URL = '';
protected string $basePath = '/tmp/';
//protected string $basePath = '/tmp/';
protected string $basePath = '/usr/src/code/tests/tmp/';
protected string $inDir;
@@ -68,20 +70,20 @@ class TranscodingV1 extends Worker
$this->database = $this->getProjectDB($project->getId());
$sourceVideo = Authorization::skip(fn() => $this->database->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$this->args['videoId']])]));
if(empty($sourceVideo)){
if (empty($sourceVideo)) {
throw new Exception('Video not found');
}
$profile = Authorization::skip(fn() => $this->database->findOne('video_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$this->args['profileId']])]));
if(empty($profile)){
if (empty($profile)) {
throw new Exception('profile not found');
}
}
$user = new Document($this->args['user'] ?? []);
$bucket = Authorization::skip(fn() => $this->database->getDocument('buckets', $sourceVideo['bucketId']));
$bucket = Authorization::skip(fn() => $this->database->getDocument('buckets', $sourceVideo['bucketId']));
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(fn() => $this->database->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']));
$file = Authorization::skip(fn() => $this->database->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']));
} else {
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $sourceVideo['fileId']);
}
@@ -127,25 +129,24 @@ class TranscodingV1 extends Worker
if (!empty($rendition)) {
Authorization::skip(fn() => $this->database->deleteDocument($collection, $rendition->getId()));
$deviceFiles = $this->getVideoDevice($project->getId());
if(!empty($rendition['path'])) {
if (!empty($rendition['path'])) {
$deviceFiles->deletePath($rendition['path']);
}
}
$stream = !empty($rendition['stream']) ? $rendition['stream'] : 'hls';
//$general = $this->getMetadataExport($ffprobe->streams($inPath));
//var_dump($general);
// if(!empty($general)) {
// foreach ($general as $key => $value) {
// $sourceVideo->setAttribute($key, $value);
// }
//
// Authorization::skip(fn() => $this->database->updateDocument(
// 'videos',
// $sourceVideo->getId(),
// $sourceVideo
// ));
// }
$general = $this->getVideoInfo($ffprobe->streams($inPath));
if(!empty($general)) {
foreach ($general as $key => $value) {
$sourceVideo->setAttribute($key, $value);
}
Authorization::skip(fn() => $this->database->updateDocument(
'videos',
$sourceVideo->getId(),
$sourceVideo
));
}
$video = $ffmpeg->open($inPath);
@@ -159,142 +160,108 @@ class TranscodingV1 extends Worker
'status' => self::STATUS_TRANSCODE_START,
'stream' => $stream,
]));
});
});
try {
$representation = (new Representation())->
setKiloBitrate($profile->getAttribute('videoBitrate'))->
setAudioKiloBitrate($profile->getAttribute('audioBitrate'))->
setResize( $profile->getAttribute('width'), $profile->getAttribute('height'));
try {
$representation = (new Representation())->
setKiloBitrate($profile->getAttribute('videoBitrate'))->
setAudioKiloBitrate($profile->getAttribute('audioBitrate'))->
setResize($profile->getAttribute('width'), $profile->getAttribute('height'));
$format = new Streaming\Format\X264();
$format->on('progress', function ($video, $format, $percentage) use ($query, $collection) {
if ($percentage % 3 === 0) {
$query->setAttribute('progress', (string)$percentage);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
}
});
list($general, $metadata) = $this->transcode($stream, $video, $format, $representation);
if (!empty($metadata)) {
$query->setAttribute('metadata', json_encode($metadata));
}
if(!empty($general)) {
foreach ($general as $key => $value) {
$query->setAttribute($key, $value);
}
}
$query->setAttribute('status', self::STATUS_TRANSCODE_END);
$query->setAttribute('endedAt', time());
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
/** Upload & remove files **/
$start = 0;
$fileNames = scandir($this->outDir);
foreach ($fileNames as $fileName) {
if (
$fileName === '.' ||
$fileName === '..' ||
str_contains($fileName, '.json')
) {
continue;
}
$deviceFiles = $this->getVideoDevice($project->getId());
$devicePath = $deviceFiles->getPath($this->args['videoId']);
$devicePath = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $devicePath);
$data = $this->getFilesDevice($project->getId())->read($this->outDir . $fileName);
$renditionDir = $profile->getAttribute('width') . 'X' . $profile->getAttribute('height') . '@' .$profile->getAttribute('videoBitrate');
$renditionPath = $devicePath . DIRECTORY_SEPARATOR . $renditionDir;
$this->getVideoDevice($project->getId())->write($renditionPath . DIRECTORY_SEPARATOR . $fileName, $data, \mime_content_type($this->outDir . $fileName));
if ($start === 0) {
$query->setAttribute('status', self::STATUS_UPLOADING);
$query->setAttribute('path', $renditionPath);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
$start = 1;
}
//$metadata=[];
//$chunksUploaded = $deviceFiles->upload($file, $path, -1, 1, $metadata);
//var_dump($chunksUploaded);
// if (empty($chunksUploaded)) {
// throw new Exception('Failed uploading file', 500, Exception::GENERAL_SERVER_ERROR);
//}
// }
@unlink($this->outDir . $fileName);
}
$query->setAttribute('status', self::STATUS_PACKAGE_END);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
} catch (\Throwable $th) {
$query->setAttribute('metadata', json_encode([
'code' => $th->getCode(),
'message' => $th->getMessage(),
]));
$query->setAttribute('status', self::STATUS_ERROR);
$format = new Streaming\Format\X264();
$format->on('progress', function ($video, $format, $percentage) use ($query, $collection) {
if ($percentage % 3 === 0) {
$query->setAttribute('progress', (string)$percentage);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
}
}
});
/**
* @param $metadata array
* @return array
*/
private function getMetadataExport(array $metadata): array
{
$info = [];
if (!empty($metadata['stream']['resolutions'][0])) {
$general = $metadata['stream']['resolutions'][0];
$parts = explode("X", $general['dimension']);
$info['width'] = $parts['0'];
$info['height'] = $parts['1'];
}
list($general, $metadata) = $this->transcode($stream, $video, $format, $representation);
if (!empty($metadata['video']['streams'])) {
foreach ($metadata['video']['streams'] as $streams) {
if ($streams['codec_type'] === 'video') {
$info['duration'] = $streams['duration'];
$info['videoCodec'] = $streams['codec_name'] . ',' . $streams['codec_tag_string'];
$info['videoBitrate'] = $streams['bit_rate'];
$info['videoFramerate'] = $streams['avg_frame_rate'];
} elseif ($streams['codec_type'] === 'audio') {
$info['audioCodec'] = $streams['codec_name'] . ',' . $streams['codec_tag_string'];
$info['audioBitrate'] = $streams['sample_rate'];
$info['audioSamplerate'] = $streams['bit_rate'];
if (!empty($metadata)) {
$query->setAttribute('metadata', json_encode($metadata));
}
if (!empty($general)) {
foreach ($general as $key => $value) {
$query->setAttribute($key, $value);
}
}
}
return $info;
$query->setAttribute('status', self::STATUS_TRANSCODE_END);
$query->setAttribute('endedAt', time());
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
/** Upload & remove files **/
$start = 0;
$fileNames = scandir($this->outDir);
foreach ($fileNames as $fileName) {
if (
$fileName === '.' ||
$fileName === '..' ||
str_contains($fileName, '.json')
) {
continue;
}
$deviceFiles = $this->getVideoDevice($project->getId());
$devicePath = $deviceFiles->getPath($this->args['videoId']);
$data = $this->getFilesDevice($project->getId())->read($this->outDir . $fileName);
$renditionDir = $profile->getAttribute('width') . 'X' . $profile->getAttribute('height') . '@' . $profile->getAttribute('videoBitrate');
$renditionPath = $devicePath . DIRECTORY_SEPARATOR . $renditionDir;
$this->getVideoDevice($project->getId())->write($renditionPath . DIRECTORY_SEPARATOR . $fileName, $data, \mime_content_type($this->outDir . $fileName));
if ($start === 0) {
$query->setAttribute('status', self::STATUS_UPLOADING);
$query->setAttribute('path', $renditionPath);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
$start = 1;
}
//$metadata=[];
//$chunksUploaded = $deviceFiles->upload($file, $path, -1, 1, $metadata);
//var_dump($chunksUploaded);
// if (empty($chunksUploaded)) {
// throw new Exception('Failed uploading file', 500, Exception::GENERAL_SERVER_ERROR);
//}
// }
//@unlink($this->outDir . $fileName);
}
$query->setAttribute('status', self::STATUS_PACKAGE_END);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
} catch (\Throwable $th) {
$query->setAttribute('metadata', json_encode([
'code' => $th->getCode(),
'message' => $th->getMessage(),
]));
$query->setAttribute('status', self::STATUS_ERROR);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
}
}
/**
@@ -321,13 +288,16 @@ class TranscodingV1 extends Worker
->addRepresentation($representation)
->setAdditionalParams($additionalParams)
->save($this->outPath);
var_dump($this->outPath);
$xml = simplexml_load_string(
file_get_contents($this->outDir . $this->args['fileId'] . '.mpd')
);
$metadata = $this->getVideoInfo($dash->metadata());
$metadata['width'] = $representation->getWidth();
$metadata['height'] = $representation->getHeight();
return [
$this->getMetadataExport($dash->metadata()->export()),
$metadata,
['mpeg-dash' => !empty($xml) ? json_decode(json_encode((array)$xml), true) : []],
];
}
@@ -340,12 +310,33 @@ class TranscodingV1 extends Worker
->setAdditionalParams($additionalParams)
->setHlsBaseUrl(self::HLS_BASE_URL)
->save($this->outPath);
var_dump($this->outPath);
$metadata = $this->getVideoInfo($hls->metadata()->getVideoStreams());
$metadata['width'] = $representation->getWidth();
$metadata['height'] = $representation->getHeight();
$this->rewriteHlsRefs($this->outPath . '_' . $representation->getHeight() . 'P.m3u8');
return [
$this->getMetadataExport($hls->metadata()->export()), []
$metadata, []
];
}
private function rewriteHlsRefs($path)
{
$handle = fopen($path, "r");
$destination = fopen($path . '_tmp', "w");
if ($handle) {
while (($line = fgets($handle)) !== false) {
if (str_contains($line, ".ts")) {
//$return['data'][$i]['url'] = str_replace(array("\r","\n"),"",$line);
}
fwrite($destination, str_replace(array("\r","\n"), "", $line) . PHP_EOL);
}
fclose($handle);
fclose($destination);
}
}
/**
* @param $streams StreamCollection
@@ -353,13 +344,27 @@ class TranscodingV1 extends Worker
*/
private function getVideoInfo(StreamCollection $streams): array
{
var_dump([
'duration' => $streams->videos()->first()->get('duration'),
'height' => $streams->videos()->first()->get('height'),
'width' => $streams->videos()->first()->get('width'),
'videoCodec' => $streams->videos()->first()->get('codec_name') . ',' . $streams->videos()->first()->get('codec_tag_string'),
'videoFramerate' => $streams->videos()->first()->get('avg_frame_rate'),
'videoBitrate' => (int)$streams->videos()->first()->get('bit_rate'),
'audioCodec' => $streams->audios()->first()->get('codec_name') . ',' . $streams->audios()->first()->get('codec_tag_string'),
'audioSamplerate' => (int)$streams->audios()->first()->get('sample_rate'),
'audioBitrate' => (int)$streams->audios()->first()->get('bit_rate'),
]);
return [
'duration' => $streams->videos()->first()->get('duration'),
'height' => $streams->videos()->first()->get('height'),
'width' => $streams->videos()->first()->get('width'),
'frameRate' => $streams->videos()->first()->get('r_frame_rate'),
'bitrateKb' => $streams->videos()->first()->get('bit_rate') / 1000,
'bitrateMb' => $streams->videos()->first()->get('bit_rate') / 1000 / 1000,
'videoCodec' => $streams->videos()->first()->get('codec_name') . ',' . $streams->videos()->first()->get('codec_tag_string'),
'videoFramerate' => $streams->videos()->first()->get('avg_frame_rate'),
'videoBitrate' => (int)$streams->videos()->first()->get('bit_rate'),
'audioCodec' => $streams->audios()->first()->get('codec_name') . ',' . $streams->audios()->first()->get('codec_tag_string'),
'audioSamplerate' => (int)$streams->audios()->first()->get('sample_rate'),
'audioBitrate' => (int)$streams->audios()->first()->get('bit_rate'),
];
}
+84 -37
View File
@@ -7,14 +7,38 @@ x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: "5"
max-size: 10m
max-file: '5'
max-size: '10m'
x-env-storage: &x-env-storage |-
_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_DO_SPACES_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
version: '3'
services:
traefik:
image: traefik:2.5
image: traefik:2.7
<<: *x-logging
container_name: appwrite-traefik
command:
@@ -72,7 +96,7 @@ services:
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-video:/storage/video:rw
- appwrite-videos:/storage/videos:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -81,6 +105,7 @@ services:
- ./tests:/usr/src/code/tests
- ./app:/usr/src/code/app
- ./vendor:/usr/src/code/vendor #TODO remove when done!!
# - ./vendor/utopia/database:/usr/src/code/vendor/utopia/database
- ./docs:/usr/src/code/docs
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
@@ -129,18 +154,11 @@ services:
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
- _APP_STORAGE_ANTIVIRUS
- _APP_STORAGE_ANTIVIRUS_HOST
- _APP_STORAGE_ANTIVIRUS_PORT
- _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
- *x-env-storage
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
@@ -150,6 +168,7 @@ services:
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_RUNTIMES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STATSD_HOST
@@ -158,6 +177,8 @@ services:
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_PHONE_PROVIDER
- _APP_PHONE_SECRET
appwrite-realtime:
entrypoint: realtime
@@ -220,7 +241,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- appwrite-uploads:/storage/uploads:rw
- appwrite-video:/storage/video:rw
- appwrite-videos:/storage/videos:rw
- ./tests:/usr/src/code/tests #TODO remove when done!!
- ./vendor:/usr/src/code/vendor #TODO remove when done!!
@@ -310,7 +331,7 @@ services:
- mariadb
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-video:/storage/video:rw
- appwrite-videos:/storage/videos:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
@@ -329,23 +350,16 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _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
- *x-env-storage
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
appwrite-worker-database:
entrypoint: worker-database
appwrite-worker-databases:
entrypoint: worker-databases
<<: *x-logging
container_name: appwrite-worker-database
container_name: appwrite-worker-databases
build:
context: .
networks:
@@ -390,6 +404,7 @@ services:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -421,6 +436,7 @@ services:
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
@@ -464,6 +480,7 @@ services:
- _APP_DB_PASS
- _APP_FUNCTIONS_TIMEOUT
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_USAGE_STATS
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
@@ -510,15 +527,7 @@ services:
- OPEN_RUNTIMES_NETWORK
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _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
- *x-env-storage
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
@@ -554,6 +563,30 @@ services:
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-messaging:
entrypoint: worker-messaging
<<: *x-logging
container_name: appwrite-worker-messaging
build:
context: .
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
environment:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_PHONE_PROVIDER
- _APP_PHONE_FROM
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-maintenance:
entrypoint: maintenance
<<: *x-logging
@@ -569,11 +602,18 @@ services:
- redis
environment:
- _APP_ENV
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_OPENSSL_KEY_V1
- _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_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
@@ -611,6 +651,8 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-schedule:
entrypoint: schedule
@@ -666,6 +708,11 @@ services:
image: redis:6.2-alpine
<<: *x-logging
container_name: appwrite-redis
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
ports:
- "6379:6379"
networks:
@@ -802,7 +849,7 @@ volumes:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
appwrite-video:
appwrite-videos:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
+69 -15
View File
@@ -32,17 +32,17 @@ class Event
public const BUILDS_QUEUE_NAME = 'v1-builds';
public const BUILDS_CLASS_NAME = 'BuildsV1';
const TRANSCODING_QUEUE_NAME = 'v1-transcoding';
const TRANSCODING_CLASS_NAME = 'TranscodingV1';
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
public const MESSAGING_CLASS_NAME = 'MessagingV1';
protected string $queue = '';
protected string $class = '';
protected string $event = '';
protected array $params = [];
protected array $payload = [];
protected array $context = [];
protected ?Document $project = null;
protected ?Document $user = null;
protected ?Document $context = null;
/**
* @param string $queue
@@ -172,12 +172,13 @@ class Event
/**
* Set context for this event.
*
* @param string $key
* @param Document $context
* @return self
*/
public function setContext(Document $context): self
public function setContext(string $key, Document $context): self
{
$this->context = $context;
$this->context[$key] = $context;
return $this;
}
@@ -185,11 +186,13 @@ class Event
/**
* Get context for this event.
*
* @param string $key
*
* @return null|Document
*/
public function getContext(): ?Document
public function getContext(string $key): ?Document
{
return $this->context;
return $this->context[$key] ?? null;
}
/**
@@ -295,14 +298,28 @@ class Event
$type = $parts[0] ?? false;
$resource = $parts[1] ?? false;
$hasSubResource = $count > 3 && \str_starts_with($parts[3], '[');
$hasSubSubResource = $count > 5 && \str_starts_with($parts[5], '[') && $hasSubResource;
if ($hasSubResource) {
$subType = $parts[2];
$subResource = $parts[3];
}
if ($hasSubSubResource) {
$subSubType = $parts[4];
$subSubResource = $parts[5];
if ($count == 8) {
$attribute = $parts[7];
}
}
if ($hasSubResource && !$hasSubSubResource) {
if ($count === 6) {
$attribute = $parts[5];
}
} else {
}
if (!$hasSubResource) {
if ($count === 4) {
$attribute = $parts[3];
}
@@ -310,18 +327,25 @@ class Event
$subType ??= false;
$subResource ??= false;
$subSubType ??= false;
$subSubResource ??= false;
$attribute ??= false;
$action = match (true) {
!$hasSubResource && $count > 2 => $parts[2],
$hasSubSubResource => $parts[6] ?? false,
$hasSubResource && $count > 4 => $parts[4],
default => false
};
return [
'type' => $type,
'resource' => $resource,
'subType' => $subType,
'subResource' => $subResource,
'subSubType' => $subSubType,
'subSubResource' => $subSubResource,
'action' => $action,
'attribute' => $attribute,
];
@@ -348,6 +372,8 @@ class Event
$resource = $parsed['resource'];
$subType = $parsed['subType'];
$subResource = $parsed['subResource'];
$subSubType = $parsed['subSubType'];
$subSubResource = $parsed['subSubResource'];
$action = $parsed['action'];
$attribute = $parsed['attribute'];
@@ -359,11 +385,21 @@ class Event
throw new InvalidArgumentException("{$subResource} is missing from the params.");
}
if ($subSubResource && !\in_array(\trim($subSubResource, "\[\]"), $paramKeys)) {
throw new InvalidArgumentException("{$subSubResource} is missing from the params.");
}
/**
* Create all possible patterns including placeholders.
*/
if ($action) {
if ($subResource) {
if ($subSubResource) {
if ($attribute) {
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource, $action, $attribute]);
}
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource, $action]);
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource]);
} elseif ($subResource) {
if ($attribute) {
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action, $attribute]);
}
@@ -376,6 +412,9 @@ class Event
$patterns[] = \implode('.', [$type, $resource, $action, $attribute]);
}
}
if ($subSubResource) {
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $subSubType, $subSubResource]);
}
if ($subResource) {
$patterns[] = \implode('.', [$type, $resource, $subType, $subResource]);
}
@@ -395,12 +434,24 @@ class Event
$events[] = \str_replace($paramKeys, '*', $eventPattern);
foreach ($paramKeys as $key) {
foreach ($paramKeys as $current) {
if ($current === $key) {
continue;
if ($subSubResource) {
foreach ($paramKeys as $subCurrent) {
if ($subCurrent === $current || $subCurrent === $key) {
continue;
}
$filtered1 = \array_filter($paramKeys, fn(string $k) => $k === $subCurrent);
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered1, '*', $eventPattern));
$filtered2 = \array_filter($paramKeys, fn(string $k) => $k === $current);
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered2, '*', \str_replace($filtered1, '*', $eventPattern)));
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered2, '*', $eventPattern));
}
} else {
if ($current === $key) {
continue;
}
$filtered = \array_filter($paramKeys, fn(string $k) => $k === $current);
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered, '*', $eventPattern));
}
$filtered = \array_filter($paramKeys, fn(string $k) => $k === $current);
$events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered, '*', $eventPattern));
}
}
}
@@ -411,6 +462,9 @@ class Event
$events = \array_map(fn (string $event) => \str_replace(['[', ']'], '', $event), $events);
$events = \array_unique($events);
return $events;
/**
* Force a non-assoc array.
*/
return \array_values($events);
}
}
+13 -6
View File
@@ -22,11 +22,11 @@ use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Collection;
use Appwrite\Utopia\Response\Model\Database;
use Appwrite\Utopia\Response\Model\Continent;
use Appwrite\Utopia\Response\Model\Country;
use Appwrite\Utopia\Response\Model\Currency;
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
use Appwrite\Utopia\Response\Model\DocumentList;
use Appwrite\Utopia\Response\Model\Domain;
use Appwrite\Utopia\Response\Model\Error;
use Appwrite\Utopia\Response\Model\ErrorDev;
@@ -65,11 +65,12 @@ use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
use Appwrite\Utopia\Response\Model\UsageDatabase;
use Appwrite\Utopia\Response\Model\UsageDatabases;
use Appwrite\Utopia\Response\Model\UsageFunctions;
use Appwrite\Utopia\Response\Model\UsageProject;
use Appwrite\Utopia\Response\Model\UsageStorage;
use Appwrite\Utopia\Response\Model\UsageUsers;
use Appwrite\Utopia\Response\Model\FileRendition;
use Appwrite\Utopia\Response\Model\VideoRendition;
use Appwrite\Utopia\Response\Model\Video;
use Appwrite\Utopia\Response\Model\VideoProfile;
@@ -88,6 +89,7 @@ class Response extends SwooleResponse
public const MODEL_METRIC_LIST = 'metricList';
public const MODEL_ERROR_DEV = 'errorDev';
public const MODEL_BASE_LIST = 'baseList';
public const MODEL_USAGE_DATABASES = 'usageDatabases';
public const MODEL_USAGE_DATABASE = 'usageDatabase';
public const MODEL_USAGE_COLLECTION = 'usageCollection';
public const MODEL_USAGE_USERS = 'usageUsers';
@@ -97,6 +99,8 @@ class Response extends SwooleResponse
public const MODEL_USAGE_PROJECT = 'usageProject';
// Database
public const MODEL_DATABASE = 'database';
public const MODEL_DATABASE_LIST = 'databaseList';
public const MODEL_COLLECTION = 'collection';
public const MODEL_COLLECTION_LIST = 'collectionList';
public const MODEL_INDEX = 'index';
@@ -135,8 +139,8 @@ class Response extends SwooleResponse
public const MODEL_VIDEO = 'video';
public const MODEL_VIDEO_PROFILE = 'videoProfile';
public const MODEL_VIDEO_PROFILE_LIST = 'videoProfileList';
public const MODEL_FILE_RENDITION = 'fileRendition';
public const MODEL_FILE_RENDITIONS_LIST = 'fileRenditionsList';
public const MODEL_VIDEO_RENDITION = 'videoRendition';
public const MODEL_VIDEO_RENDITIONS_LIST = 'videoRenditionsList';
// Locale
public const MODEL_LOCALE = 'locale';
@@ -223,6 +227,7 @@ class Response extends SwooleResponse
// Lists
->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT))
->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION))
->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE))
->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX))
->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
@@ -247,10 +252,11 @@ class Response extends SwooleResponse
->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY))
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
->setModel(new BaseList('File Renditions List', self::MODEL_FILE_RENDITIONS_LIST, 'renditions', self::MODEL_FILE_RENDITION))
->setModel(new BaseList('Video Renditions List', self::MODEL_VIDEO_RENDITIONS_LIST, 'renditions', self::MODEL_VIDEO_RENDITION))
->setModel(new BaseList('video profile List', self::MODEL_VIDEO_PROFILE_LIST, 'profiles', self::MODEL_VIDEO_PROFILE))
// Entities
->setModel(new Database())
->setModel(new Collection())
->setModel(new Attribute())
->setModel(new AttributeList())
@@ -296,6 +302,7 @@ class Response extends SwooleResponse
->setModel(new HealthTime())
->setModel(new HealthVersion())
->setModel(new Metric())
->setModel(new UsageDatabases())
->setModel(new UsageDatabase())
->setModel(new UsageCollection())
->setModel(new UsageUsers())
@@ -303,7 +310,7 @@ class Response extends SwooleResponse
->setModel(new UsageBuckets())
->setModel(new UsageFunctions())
->setModel(new UsageProject())
->setModel(new FileRendition())
->setModel(new VideoRendition())
->setModel(new Video())
->setModel(new VideoProfile())
@@ -0,0 +1,47 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Database extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Database ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Database name.',
'default' => '',
'example' => 'My Database',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Database';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_DATABASE;
}
}
@@ -0,0 +1,146 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class UsageDatabases extends Model
{
public function __construct()
{
$this
->addRule('range', [
'type' => self::TYPE_STRING,
'description' => 'The time range of the usage stats.',
'default' => '',
'example' => '30d',
])
->addRule('databasesCount', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of documents.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('documentsCount', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of documents.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('collectionsCount', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of collections.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('databasesCreate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents created.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('databasesRead', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents read.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('databasesUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents updated.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('databasesDelete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for total number of collections.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('documentsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents created.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('documentsRead', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents read.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('documentsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents updated.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('documentsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for documents deleted.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('collectionsCreate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for collections created.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('collectionsRead', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for collections read.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('collectionsUpdate', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for collections updated.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
->addRule('collectionsDelete', [
'type' => Response::MODEL_METRIC_LIST,
'description' => 'Aggregated stats for collections delete.',
'default' => [],
'example' => new \stdClass(),
'array' => true
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'UsageDatabases';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_USAGE_DATABASES;
}
}
@@ -5,7 +5,7 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class FileRendition extends Model
class VideoRendition extends Model
{
public function __construct()
{
@@ -16,25 +16,19 @@ class FileRendition extends Model
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('bucketId', [
->addRule('videoId', [
'type' => self::TYPE_STRING,
'description' => 'Bucket ID.',
'description' => 'Video ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('fileId', [
->addRule('profileId', [
'type' => self::TYPE_STRING,
'description' => 'File ID.',
'description' => 'profile ID.',
'default' => '',
'example' => 'd5fg5ehg1c168g7c',
])
->addRule('renditionId', [
'type' => self::TYPE_INTEGER,
'description' => 'Rendition ID.',
'default' => '',
'example' => 3,
])
->addRule('renditionName', [
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Rendition name.',
'default' => '',
@@ -64,6 +58,12 @@ class FileRendition extends Model
'default' => 0,
'example' => 88,
])
->addRule('stream', [
'type' => self::TYPE_STRING,
'description' => 'Rendition trascoding stream protocol type',
'default' => 0,
'example' => 88,
])
;
}
@@ -82,7 +82,7 @@ class VideoCustomServerTest extends Scope
], [
'read' => ['role:all'],
'write' => ['role:all']
]);
]);
$videoId = $response['body']['$id'];
@@ -104,29 +104,8 @@ class VideoCustomServerTest extends Scope
'write' => ['role:all']
]);
var_dump($response['body']);
// var_dump($pid);
// var_dump($key);
// var_dump($fid);
// var_dump($bid);
//
////
//// $pid = '62b1956c92e0e39ef577';
//// $key = 'f9b8dd3800b93a3dd513cf7cbcbd436cd61850b2c101662210e8ee2f052f796b3d4c7a08149634ce90da6037f6164d7faa36b32b91b568524e6720014e149b83f7a970c28de1a14a97a69010be325d142d51ca51f0f1b29783a7c1f4689d1b90a42cf19a7b55ec9ea6dc51974a1740b67e71de9f80009c2d91c6f3c686aa616c';
//// $fid = '62b1956e4f03f57a0f74';
//// $bid = '62b1956d0c600d70c8f7';
////
// $video = $this->client->call(Client::METHOD_POST, '/video/buckets/' . $bid . '/files/' . $fid, [
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $pid,
// 'x-appwrite-key' => $key,
// ], [
// 'read' => ['role:all'],
// 'write' => ['role:all']
// ]);
// var_dump($video);
$this->assertNotEmpty($profileId);
return [
'videoId' => $videoId,
@@ -135,40 +114,39 @@ class VideoCustomServerTest extends Scope
}
/**
* @depends testCreateBucketFile
* @depends testTranscodingRendition
*/
public function testRenditions(): void
public function testGetRenditions($data): void
{
$pid = '62b1956c92e0e39ef577';
$key = 'f9b8dd3800b93a3dd513cf7cbcbd436cd61850b2c101662210e8ee2f052f796b3d4c7a08149634ce90da6037f6164d7faa36b32b91b568524e6720014e149b83f7a970c28de1a14a97a69010be325d142d51ca51f0f1b29783a7c1f4689d1b90a42cf19a7b55ec9ea6dc51974a1740b67e71de9f80009c2d91c6f3c686aa616c';
$fid = '62b1956e4f03f57a0f74';
$bid = '62b1956d0c600d70c8f7';
sleep(30);
$renditions = $this->client->call(Client::METHOD_GET, '/video/buckets/' . $bid . '/files/' . $fid . '/renditions', [
$response = $this->client->call(Client::METHOD_GET, '/video/' . $data['videoId'] . '/hls/renditions', [
'content-type' => 'application/json',
'x-appwrite-project' => $pid,
'x-appwrite-key' => $key,
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
],[
'read' => ['role:all'],
'write' => ['role:all']
]);
var_dump($renditions['body']);
var_dump($response['body']);
}
public function testPlaylist(): void
/**
* @depends testTranscodingRendition
*/
public function testPlaylist($data): void
{
sleep(60);
$pid = '62b1956c92e0e39ef577';
$key = 'f9b8dd3800b93a3dd513cf7cbcbd436cd61850b2c101662210e8ee2f052f796b3d4c7a08149634ce90da6037f6164d7faa36b32b91b568524e6720014e149b83f7a970c28de1a14a97a69010be325d142d51ca51f0f1b29783a7c1f4689d1b90a42cf19a7b55ec9ea6dc51974a1740b67e71de9f80009c2d91c6f3c686aa616c';
$fid = '62b1956e4f03f57a0f74';
$bid = '62b1956d0c600d70c8f7';
$stream = 'hls';
$renditions = $this->client->call(Client::METHOD_GET, '/video/buckets/' . $bid . '/files/' . $stream . '/' . $fid, [
$response = $this->client->call(Client::METHOD_GET, '/video/' . $data['videoId'] . '/master/hls/' . $data['videoId'].'.m3u8', [
'content-type' => 'application/json',
'x-appwrite-project' => $pid,
'x-appwrite-key' => $key,
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
],[
'read' => ['role:all'],
'write' => ['role:all']
]);
var_dump($renditions['body']);
}
}