sync with master

and renaming attr
This commit is contained in:
shimon
2023-02-12 10:56:11 +02:00
parent edaa93936f
commit 3d1228ba13
24 changed files with 514 additions and 266 deletions
+5 -5
View File
@@ -3911,7 +3911,7 @@ $collections = [
'filters' => [],
],
[
'$id' => 'protocol',
'$id' => 'output',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -3924,9 +3924,9 @@ $collections = [
],
'indexes' => [
[
'$id' => '_key_video_protocol_profile',
'$id' => '_key_video_output_profile',
'type' => Database::INDEX_KEY,
'attributes' => ['videoId', 'protocol', 'profileId'],
'attributes' => ['videoId', 'output', 'profileId'],
'lengths' => [],
'orders' => [],
],
@@ -4075,7 +4075,7 @@ $collections = [
'filters' => [],
],
[
'$id' => 'protocol',
'$id' => 'output',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -4090,7 +4090,7 @@ $collections = [
[
'$id' => '_key_profile',
'type' => Database::INDEX_KEY,
'attributes' => ['protocol', 'width', 'height', 'videoBitrate', 'audioBitrate' ],
'attributes' => ['output', 'width', 'height', 'videoBitrate', 'audioBitrate' ],
'lengths' => [],
'orders' => [],
],
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
+3 -3
View File
@@ -8,7 +8,7 @@ return [
'audioBitrate' => 64, //audio bitrate in Kbps
'width' => 640, //width resolution in px
'height' => 360, //height resolution in px
'protocol' => 'dash'
'output' => 'dash'
],
[
'name' => '576p',
@@ -16,7 +16,7 @@ return [
'audioBitrate' => 128,
'width' => 1024,
'height' => 576,
'protocol' => 'dash'
'output' => 'dash'
],
[
'name' => '720p',
@@ -24,7 +24,7 @@ return [
'audioBitrate' => 128,
'width' => 1280,
'height' => 720,
'protocol' => 'dash'
'output' => 'dash'
],
];
+19 -85
View File
@@ -10,8 +10,8 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Phone as EventPhone;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\Host;
use Utopia\Validator\URL;
use Appwrite\Network\Validator\Host;
use Appwrite\Network\Validator\URL;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Template\Template;
use Appwrite\URL\URL as URLParser;
@@ -62,7 +62,7 @@ App::post('/v1/account')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->label('abuse-limit', 10)
->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('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('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@@ -140,7 +140,7 @@ App::post('/v1/account')
App::post('/v1/account/sessions/email')
->alias('/v1/account/sessions')
->desc('Create Email Session')
->groups(['api', 'account', 'auth', 'session'])
->groups(['api', 'account', 'auth'])
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('scope', 'public')
->label('auth.type', 'emailPassword')
@@ -365,7 +365,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
App::get('/v1/account/sessions/oauth2/:provider/redirect')
->desc('OAuth2 Redirect')
->groups(['api', 'account', 'session'])
->groups(['api', 'account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('scope', 'public')
@@ -621,7 +621,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 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('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('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')
@@ -713,33 +713,11 @@ 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
->setSubject($subject)
->setBody($body)
->setFrom($from)
->setType(MAIL_TYPE_MAGIC_SESSION)
->setRecipient($user->getAttribute('email'))
->setUrl($url)
->setLocale($locale->default)
->trigger()
;
@@ -761,7 +739,7 @@ App::post('/v1/account/sessions/magic-url')
App::put('/v1/account/sessions/magic-url')
->desc('Create Magic URL session (confirmation)')
->groups(['api', 'account', 'session'])
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('audits.event', 'session.update')
@@ -898,7 +876,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 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('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('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->inject('request')
->inject('response')
@@ -1003,7 +981,7 @@ App::post('/v1/account/sessions/phone')
App::put('/v1/account/sessions/phone')
->desc('Create Phone Session (confirmation)')
->groups(['api', 'account', 'session'])
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('usage.metric', 'sessions.{scope}.requests.create')
@@ -1118,7 +1096,7 @@ App::put('/v1/account/sessions/phone')
App::post('/v1/account/sessions/anonymous')
->desc('Create Anonymous Session')
->groups(['api', 'account', 'auth', 'session'])
->groups(['api', 'account', 'auth'])
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('scope', 'public')
->label('auth.type', 'anonymous')
@@ -2013,35 +1991,12 @@ 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();
;
@@ -2196,32 +2151,11 @@ 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
->setSubject($subject)
->setBody($body)
->setFrom($from)
->setType(MAIL_TYPE_VERIFICATION)
->setRecipient($user->getAttribute('email'))
->setUrl($url)
->setLocale($locale->default)
->setName($user->getAttribute('name'))
->trigger()
;
+3 -3
View File
@@ -1,7 +1,7 @@
<?php
use Appwrite\Extend\Exception;
use Utopia\Validator\URL;
use Appwrite\Network\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;
+28 -26
View File
@@ -33,8 +33,8 @@ use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Locale\Locale;
use Appwrite\Auth\Auth;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\IP;
use Utopia\Validator\URL;
use Appwrite\Network\Validator\IP;
use Appwrite\Network\Validator\URL;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Query\Limit;
use Appwrite\Utopia\Database\Validator\Query\Offset;
@@ -69,6 +69,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
$filters = $attribute->getAttribute('filters', []); // filters are hidden from the endpoint
$default = $attribute->getAttribute('default');
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
@@ -162,7 +163,7 @@ App::post('/v1/databases')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->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('databaseId', '', 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('name', '', new Text(128), 'Collection name. Max length: 128 chars.')
->inject('response')
->inject('dbForProject')
@@ -383,7 +384,7 @@ App::get('/v1/databases/:databaseId/logs')
App::put('/v1/databases/:databaseId')
->desc('Update Database')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('scope', 'databases.write')
->label('event', 'databases.[databaseId].update')
->label('audits.event', 'database.update')
@@ -397,7 +398,7 @@ App::put('/v1/databases/:databaseId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->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('name', null, new Text(128), 'Collection name. Max length: 128 chars.')
->inject('response')
->inject('dbForProject')
->inject('events')
@@ -426,7 +427,7 @@ App::put('/v1/databases/:databaseId')
App::delete('/v1/databases/:databaseId')
->desc('Delete Database')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('scope', 'databases.write')
->label('event', 'databases.[databaseId].delete')
->label('audits.event', 'database.delete')
@@ -488,9 +489,9 @@ App::post('/v1/databases/:databaseId/collections')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_COLLECTION)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', 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('collectionId', '', 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('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('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)
->inject('response')
->inject('dbForProject')
@@ -728,7 +729,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
App::put('/v1/databases/:databaseId/collections/:collectionId')
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
->desc('Update Collection')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('scope', 'collections.write')
->label('event', 'databases.[databaseId].collections.[collectionId].update')
->label('audits.event', 'collection.update')
@@ -745,7 +746,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default the current permission are inherited. [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')
@@ -796,7 +797,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
App::delete('/v1/databases/:databaseId/collections/:collectionId')
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
->desc('Delete Collection')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('scope', 'collections.write')
->label('event', 'databases.[databaseId].collections.[collectionId].delete')
->label('audits.event', 'collection.delete')
@@ -853,7 +854,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string')
->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default'])
->desc('Create String Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('audits.event', 'attribute.create')
@@ -903,7 +904,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default'])
->desc('Create Email Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('audits.event', 'attribute.create')
@@ -947,7 +948,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default'])
->desc('Create Enum Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('audits.event', 'attribute.create')
@@ -1007,7 +1008,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default'])
->desc('Create IP Address Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('audits.event', 'attribute.create')
@@ -1051,7 +1052,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default'])
->desc('Create URL Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('audits.event', 'attribute.create')
@@ -1095,7 +1096,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default'])
->desc('Create Integer Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('audits.event', 'attribute.create')
@@ -1168,7 +1169,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default'])
->desc('Create Float Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('audits.event', 'attribute.create')
@@ -1244,7 +1245,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default'])
->desc('Create Boolean Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('audits.event', 'attribute.create')
@@ -1443,7 +1444,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
->desc('Delete Attribute')
->groups(['api', 'database', 'schema'])
->groups(['api', 'database'])
->label('scope', 'collections.write')
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete')
->label('audits.event', 'attribute.delete')
@@ -1752,14 +1753,15 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$indexes = $collection->getAttribute('indexes');
// Search for index
$indexIndex = array_search($key, array_map(fn($idx) => $idx['key'], $indexes));
$indexIndex = array_search($key, array_column($indexes, 'key'));
if ($indexIndex === false) {
throw new Exception(Exception::INDEX_NOT_FOUND);
}
$index = $indexes[$indexIndex];
$index->setAttribute('collectionId', $database->getInternalId() . '_' . $collectionId);
$index = new Document([\array_merge($indexes[$indexIndex], [
'collectionId' => $database->getInternalId() . '_' . $collectionId,
])]);
$response->dynamic($index, Response::MODEL_INDEX);
});
@@ -1853,10 +1855,10 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('documentId', '', new CustomId(), 'Document 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('documentId', '', new CustomId(), 'Document 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('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current user is granted with all permissions. [Learn more about permissions](/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('user')
@@ -2244,7 +2246,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->param('collectionId', '', new UID(), 'Collection ID.')
->param('documentId', '', new UID(), 'Document ID.')
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('events')
+6 -37
View File
@@ -6,9 +6,9 @@ use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Validator\Event;
use Appwrite\Network\Validator\CNAME;
use Utopia\Validator\Domain as DomainValidator;
use Appwrite\Network\Validator\Domain as DomainValidator;
use Appwrite\Network\Validator\Origin;
use Utopia\Validator\URL;
use Appwrite\Network\Validator\URL;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Utopia\Abuse\Adapters\TimeLimit;
@@ -56,7 +56,7 @@ App::post('/v1/projects')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', 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('projectId', '', 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('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('teamId', '', new UID(), 'Team unique ID.')
->param('region', App::getEnv('_APP_REGION', 'default'), new Whitelist(array_keys(array_filter(Config::getParam('regions'), fn($config) => !$config['disabled']))), 'Project Region.', true)
@@ -81,7 +81,7 @@ App::post('/v1/projects')
}
$auth = Config::getParam('auth', []);
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
$auths = ['limit' => 0, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
foreach ($auth as $index => $method) {
$auths[$method['key'] ?? ''] = true;
}
@@ -179,7 +179,7 @@ App::post('/v1/projects')
'audioBitrate' => $profile['audioBitrate'],
'width' => $profile['width'],
'height' => $profile['height'],
'protocol' => $profile['protocol']
'output' => $profile['output']
]));
});
}
@@ -590,37 +590,6 @@ App::patch('/v1/projects/:projectId/auth/:method')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/max-sessions')
->desc('Update Project user sessions limit')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateAuthSessionsLimit')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('limit', false, new Range(1, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of users allowed in this project. Value allowed is between 1-' . APP_LIMIT_USER_SESSIONS_MAX . '. Default is ' . APP_LIMIT_USER_SESSIONS_DEFAULT)
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['maxSessions'] = $limit;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::delete('/v1/projects/:projectId')
->desc('Delete Project')
->groups(['api', 'projects'])
@@ -1129,7 +1098,7 @@ App::post('/v1/projects/:projectId/platforms')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PLATFORM)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.')
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.')
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
+9 -32
View File
@@ -7,7 +7,7 @@ use Appwrite\Event\Event;
use Appwrite\Event\Mail;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\Host;
use Appwrite\Network\Validator\Host;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries;
@@ -54,7 +54,7 @@ App::post('/v1/teams')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM)
->param('teamId', '', new CustomId(), 'Team 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('teamId', '', new CustomId(), 'Team 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('name', null, new Text(128), 'Team name. Max length: 128 chars.')
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true)
->inject('response')
@@ -434,37 +434,14 @@ App::post('/v1/teams/:teamId/memberships')
$url = Template::unParseURL($url);
if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode
$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 = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
$body->setParam('{{owner}}', $user->getAttribute('name'));
$body->setParam('{{team}}', $team->getAttribute('name'));
$body
->setParam('{{subject}}', $subject)
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
->setParam('{{name}}', $user->getAttribute('name'))
->setParam('{{body}}', $locale->getText("emails.invitation.body"))
->setParam('{{redirect}}', $url)
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
->setParam('{{signature}}', $locale->getText("emails.invitation.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
->setSubject($subject)
->setBody($body)
->setFrom($from)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name'))
->setType(MAIL_TYPE_INVITATION)
->setRecipient($email)
->setUrl($url)
->setName($name)
->setLocale($locale->default)
->setTeam($team)
->setUser($user)
->trigger()
;
}
+46 -46
View File
@@ -231,7 +231,6 @@ App::get('/v1/videos')
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$fileId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('videos', $fileId));
if ($cursorDocument->isEmpty()) {
@@ -328,11 +327,11 @@ App::patch('/v1/videos/:videoId/subtitles/:subtitleId')
}
$subtitle->setAttribute('videoId', $videoId)
->setAttribute('bucketId', $bucketId)
->setAttribute('fileId', $fileId)
->setAttribute('name', $name)
->setAttribute('code', $code)
->setAttribute('default', $default);
->setAttribute('bucketId', $bucketId)
->setAttribute('fileId', $fileId)
->setAttribute('name', $name)
->setAttribute('code', $code)
->setAttribute('default', $default);
$subtitle = Authorization::skip(fn() => $dbForProject->updateDocument('videos_subtitles', $subtitle->getId(), $subtitle));
@@ -578,7 +577,7 @@ App::get('/v1/videos/:videoId/renditions')
]), Response::MODEL_RENDITION_LIST);
});
App::get('/v1/videos/:videoId/protocols/:protocolId')
App::get('/v1/videos/:videoId/outputs/:output')
->desc('Get video master renditions manifest')
->groups(['api', 'video'])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
@@ -586,14 +585,15 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
->label('sdk.method', 'getMasterManifest')
->label('sdk.description', '/docs/references/videos/get-master-manifest.md') // TODO: Create markdown
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->label('scope', 'videos.read')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('protocolId', '', new WhiteList(['hls', 'dash']), 'protocol name')
->param('output', '', new WhiteList(['hls', 'dash']), 'protocol name')
->inject('response')
->inject('dbForProject')
->inject('mode')
->action(function (string $videoId, string $protocolId, Response $response, Database $dbForProject, string $mode) {
->action(function (string $videoId, string $output, Response $response, Database $dbForProject, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
@@ -606,21 +606,21 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
$renditions = Authorization::skip(fn () => $dbForProject->find('videos_renditions', [
Query::equal('videoId', [$video->getId()]),
Query::equal('status', ['ready']),
Query::equal('protocol', [$protocolId]),
Query::equal('output', [$output]),
]));
if (empty($renditions)) {
throw new Exception(Exception::VIDEO_RENDITION_NOT_FOUND);
}
$baseUrl = 'http://127.0.0.1/v1/videos/' . $videoId . '/protocols/' . $protocolId;
$baseUrl = 'http://127.0.0.1/v1/videos/' . $videoId . '/outputs/' . $output;
$subtitles = Authorization::skip(fn() => $dbForProject->find('videos_subtitles', [
Query::equal('videoId', [$video->getId()]),
]));
$_renditions = $_subtitles = [];
if ($protocolId === 'hls') {
if ($output === 'hls') {
foreach ($subtitles ?? [] as $subtitle) {
$_subtitles[] = [
'name' => $subtitle->getAttribute('name'),
@@ -650,10 +650,10 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
}
$_renditions[] = [
'bandwidth' => ($rendition->getAttribute('videoBitrate') + $rendition->getAttribute('audioBitrate')),
'bandwidth' => ($rendition->getAttribute('videoBitrate') + $rendition->getAttribute('audioBitrate')),
'resolution' => $rendition->getAttribute('width') . 'X' . $rendition->getAttribute('height'),
'name' => $rendition->getAttribute('name'),
'uri' => $uri,
'uri' => $uri,
'subs' => !empty($_subtitles) ? 'subs' : null,
'audio' => !empty($_audios) ? 'group_audio' : null,
];
@@ -738,7 +738,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
}
});
App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/streams/:streamId')
App::get('/v1/videos/:videoId/outputs/:output/renditions/:renditionId/streams/:streamId')
->desc('Get video rendition manifest')
->groups(['api', 'video'])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
@@ -746,17 +746,17 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/stre
->label('sdk.method', 'getManifest')
->label('sdk.description', '/docs/references/videos/get-manifest.md') // TODO: Create markdown
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
// TODO: Response model
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->label('scope', 'videos.read')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('protocolId', '', new WhiteList(['hls']), 'protocol name.')
->param('output', '', new WhiteList(['hls']), 'output name.')
->param('renditionId', '', new UID(), 'Rendition unique ID.')
->param('streamId', '', new Range(0, 10), 'Stream id.')
->inject('response')
->inject('dbForProject')
->inject('mode')
->action(function (string $videoId, string $protocolId, string $renditionId, string $streamId, Response $response, Database $dbForProject, string $mode) {
->action(function (string $videoId, string $output, string $renditionId, string $streamId, Response $response, Database $dbForProject, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
@@ -790,7 +790,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/stre
foreach ($segments as $segment) {
$_segments[] = [
'duration' => $segment->getAttribute('duration'),
'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/protocols/' . $protocolId . '/renditions/' . $renditionId . '/segments/' . $segment->getId(),
'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/outputs/' . $output . '/renditions/' . $renditionId . '/segments/' . $segment->getId(),
];
}
@@ -801,7 +801,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/stre
->send($template->render(false));
});
App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/segments/:segmentId')
App::get('/v1/videos/:videoId/outputs/:output/renditions/:renditionId/segments/:segmentId')
->desc('Get video rendition segment')
->groups(['api', 'video'])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
@@ -809,17 +809,17 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/segm
->label('sdk.method', 'getRenditionSegment')
->label('sdk.description', '/docs/references/videos/get-rendition-segment.md') // TODO: Create markdown
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
// TODO: Response model
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->label('scope', 'videos.read')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('protocolId', '', new WhiteList(['hls', 'dash']), 'Protocol name')
->param('output', '', new WhiteList(['hls', 'dash']), 'Output name')
->param('renditionId', '', new UID(), 'Rendition unique ID.')
->param('segmentId', '', new UID(), 'Segment unique ID.')
->inject('response')
->inject('dbForProject')
->inject('deviceVideos')
->action(function (string $videoId, string $protocolId, string $renditionId, string $segmentId, Response $response, Database $dbForProject, Device $deviceVideos) {
->action(function (string $videoId, string $output, string $renditionId, string $segmentId, Response $response, Database $dbForProject, Device $deviceVideos) {
$segment = Authorization::skip(fn() => $dbForProject->getDocument('videos_renditions_segments', $segmentId));
@@ -829,7 +829,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/segm
$output = $deviceVideos->read($segment->getAttribute('path') . $segment->getAttribute('fileName'));
if ($protocolId === 'hls') {
if ($output === 'hls') {
$response->setContentType('video/MP2T')
->send($output);
} else {
@@ -838,7 +838,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/segm
}
});
App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
App::get('/v1/videos/:videoId/outputs/:output/subtitles/:subtitleId')
->desc('Get video subtitle')
->groups(['api', 'video'])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
@@ -846,17 +846,17 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
->label('sdk.method', 'getSubtitle')
->label('sdk.description', '/docs/references/videos/get-subtitle.md') // TODO: Create markdown
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
// TODO: Response model
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->label('scope', 'videos.read')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('protocolId', '', new WhiteList(['hls', 'dash']), 'Protocol name')
->param('output', '', new WhiteList(['hls', 'dash']), 'Protocol name')
->param('subtitleId', '', new UID(), 'Subtitle unique ID.')
->inject('response')
->inject('dbForProject')
->inject('deviceVideos')
->inject('mode')
->action(function (string $videoId, string $protocolId, string $subtitleId, Response $response, Database $dbForProject, Device $deviceVideos, string $mode) {
->action(function (string $videoId, string $output, string $subtitleId, Response $response, Database $dbForProject, Device $deviceVideos, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
@@ -874,7 +874,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
throw new Exception(Exception::VIDEO_SUBTITLE_NOT_FOUND);
}
if ($protocolId == 'hls') {
if ($output == 'hls') {
$segments = Authorization::skip(fn () => $dbForProject->find('videos_subtitles_segments', [
Query::equal('subtitleId', [$subtitleId]),
]));
@@ -887,7 +887,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
foreach ($segments as $segment) {
$_segments[] = [
'duration' => $segment->getAttribute('duration'),
'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/protocols/' . $protocolId . '/subtitles/' . $subtitleId . '/segments/' . $segment->getId(),
'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/outputs/' . $output . '/subtitles/' . $subtitleId . '/segments/' . $segment->getId(),
];
}
@@ -903,7 +903,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
}
});
App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId/segments/:segmentId')
App::get('/v1/videos/:videoId/outputs/:output/subtitles/:subtitleId/segments/:segmentId')
->desc('Get video subtitle segment')
->groups(['api', 'video'])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
@@ -911,18 +911,18 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId/segmen
->label('sdk.method', 'getSubtitleSegment')
->label('sdk.description', '/docs/references/videos/get-subtitle-segment.md') // TODO: Create markdown
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
// TODO: Response model
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->label('scope', 'videos.read')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('protocolId', '', new WhiteList(['hls', 'dash']), 'Protocol name')
->param('output', '', new WhiteList(['hls', 'dash']), 'output name')
->param('subtitleId', '', new UID(), 'Subtitle unique ID.')
->param('segmentId', '', new UID(), 'Segment unique ID.')
->inject('response')
->inject('dbForProject')
->inject('deviceVideos')
->inject('mode')
->action(function (string $videoId, string $protocolId, string $subtitleId, string $segmentId, Response $response, Database $dbForProject, Device $deviceVideos, string $mode) {
->action(function (string $videoId, string $output, string $subtitleId, string $segmentId, Response $response, Database $dbForProject, Device $deviceVideos, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
@@ -959,19 +959,19 @@ App::post('/v1/videos/profiles')
->param('audioBitrate', '', new Range(32, 5000), 'Audio profile bit rate in Kbps.')
->param('width', '', new Range(6, 3000), 'Video profile width.')
->param('height', '', new Range(6, 3000), 'Video profile height.')
->param('protocol', false, new WhiteList(['hls', 'dash']), 'Video profile protocol.')
->param('output', false, new WhiteList(['hls', 'dash']), 'Video profile output.')
->inject('response')
->inject('dbForProject')
->action(action: function (string $name, string $videoBitrate, string $audioBitrate, string $width, string $height, string $protocol, Response $response, Database $dbForProject) {
->action(action: function (string $name, string $videoBitrate, string $audioBitrate, string $width, string $height, string $output, Response $response, Database $dbForProject) {
$profile = Authorization::skip(function () use ($dbForProject, $name, $videoBitrate, $audioBitrate, $width, $height, $protocol) {
$profile = Authorization::skip(function () use ($dbForProject, $name, $videoBitrate, $audioBitrate, $width, $height, $output) {
return $dbForProject->createDocument('videos_profiles', new Document([
'name' => $name,
'videoBitrate' => (int)$videoBitrate,
'audioBitrate' => (int)$audioBitrate,
'width' => (int)$width,
'height' => (int)$height,
'protocol' => $protocol,
'output' => $output,
]));
});
@@ -998,10 +998,10 @@ App::patch('/v1/videos/profiles/:profileId')
->param('audioBitrate', '', new Range(64, 4000), 'Audio profile bit rate in Kbps.')
->param('width', '', new Range(100, 2000), 'Video profile width.')
->param('height', '', new Range(100, 2000), 'Video profile height.')
->param('protocol', false, new WhiteList(['hls', 'dash']), 'Video profile protocol.')
->param('output', false, new WhiteList(['hls', 'dash']), 'Video profile output.')
->inject('response')
->inject('dbForProject')
->action(action: function (string $profileId, string $name, string $videoBitrate, string $audioBitrate, string $width, string $height, string $protocol, Response $response, Database $dbForProject) {
->action(action: function (string $profileId, string $name, string $videoBitrate, string $audioBitrate, string $width, string $height, string $output, Response $response, Database $dbForProject) {
$profile = Authorization::skip(fn() => $dbForProject->getDocument('videos_profiles', $profileId));
if ($profile->isEmpty()) {
@@ -1013,7 +1013,7 @@ App::patch('/v1/videos/profiles/:profileId')
->setAttribute('audioBitrate', (int)$audioBitrate)
->setAttribute('width', (int)$width)
->setAttribute('height', (int)$height)
->setAttribute('protocol', $protocol);
->setAttribute('output', $output);
$profile = Authorization::skip(fn() => $dbForProject->updateDocument('videos_profiles', $profile->getId(), $profile));
+1 -1
View File
@@ -4,7 +4,7 @@ global $utopia, $request, $response;
use Appwrite\Extend\Exception;
use Utopia\Database\Document;
use Utopia\Validator\Host;
use Appwrite\Network\Validator\Host;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
+1 -4
View File
@@ -888,11 +888,8 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Document $console */
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
$projectId = '63c41b7aed635ef2bab7';
$projectId = '63e8a33a1d98eedb152d';
if ($projectId === 'console') {
return $console;
}
+11 -11
View File
@@ -36,8 +36,8 @@ class TranscodingV1 extends Worker
private const STATUS_READY = 'ready';
private const STATUS_ERROR = 'error';
private const PROTOCOL_HLS = 'hls';
private const PROTOCOL_DASH = 'dash';
private const OUTPUT_HLS = 'hls';
private const OUTPUT_DASH = 'dash';
private string $basePath = '/tmp/';
//private string $basePath = '/usr/src/code/tests/tmp/';
@@ -176,7 +176,7 @@ class TranscodingV1 extends Worker
'name' => $this->getRenditionName(),
'startedAt' => DateTime::now(),
'status' => self::STATUS_START,
'protocol' => $this->profile->getAttribute('protocol'),
'output' => $this->profile->getAttribute('output'),
]));
$renditionRootPath = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/';
@@ -200,7 +200,7 @@ class TranscodingV1 extends Worker
}
});
$general = $this->transcode($this->profile->getAttribute('protocol'), $media, $format, $representation, $subs);
$general = $this->transcode($this->profile->getAttribute('output'), $media, $format, $representation, $subs);
if (!empty($general)) {
foreach ($general as $key => $value) {
$query->setAttribute($key, (string)$value);
@@ -211,7 +211,7 @@ class TranscodingV1 extends Worker
//var_dump($o);
//var_dump($v);
if ($this->profile->getAttribute('protocol') === self::PROTOCOL_HLS) {
if ($this->profile->getAttribute('output') === self::OUTPUT_HLS) {
$streams = $this->getHlsSegmentsUrls($this->outDir . 'master.m3u8');
foreach ($streams as $stream) {
$m3u8 = $this->getHlsSegments($this->outDir . $stream['path']);
@@ -254,7 +254,7 @@ class TranscodingV1 extends Worker
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
foreach ($subtitles ?? [] as $subtitle) {
if ($this->profile->getAttribute('protocol') === 'hls') {
if ($this->profile->getAttribute('output') === 'hls') {
$m3u8 = $this->getHlsSegments($this->outPath . '_subtitles_' . $subtitle['code'] . '.m3u8');
foreach ($m3u8['segments'] ?? [] as $segment) {
$this->database->createDocument('videos_subtitles_segments', new Document([
@@ -319,14 +319,14 @@ class TranscodingV1 extends Worker
}
/**
* @param string $protocol
* @param string $output
* @param $media Media
* @param $format StreamFormat
* @param $representation Representation
* @param array $subtitles
* @return string|array
*/
private function transcode(string $protocol, Media $media, StreamFormat $format, Representation $representation, array $subtitles): string | array
private function transcode(string $output, Media $media, StreamFormat $format, Representation $representation, array $subtitles): string | array
{
$additionalParams = [
@@ -342,7 +342,7 @@ class TranscodingV1 extends Worker
$segmentSize = 10;
if ($protocol === self::PROTOCOL_DASH) {
if ($output === self::OUTPUT_DASH) {
$dash = $media->dash()
->setFormat($format)
->setSegDuration($segmentSize)
@@ -351,7 +351,7 @@ class TranscodingV1 extends Worker
->save($this->outPath)
;
console::info(strtoupper($protocol) . ' rendition conversion ended');
console::info(strtoupper($output) . ' rendition conversion ended');
return $this->getVideoStreamInfo($dash->metadata()->export(), $representation);
}
@@ -373,7 +373,7 @@ class TranscodingV1 extends Worker
->save($this->outPath)
;
console::info(strtoupper($protocol) . ' rendition conversion ended');
console::info(strtoupper($output) . ' rendition conversion ended');
return $this->getVideoStreamInfo($hls->metadata()->export(), $representation);
}
+2 -1
View File
@@ -85,7 +85,8 @@ services:
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/local/dev
#- ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database
- ./tests/tmp:/tmp
- ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database
depends_on:
- mariadb
- redis
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace Appwrite\Network\Validator;
use Utopia\Validator;
/**
* Domain
*
* Validate that an variable is a valid domain address
*
* @package Utopia\Validator
*/
class Domain extends Validator
{
/**
* Get Description
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return 'Value must be a valid domain';
}
/**
* Is valid
*
* Validation will pass when $value is valid domain.
*
* Validates domain names against RFC 1034, RFC 1035, RFC 952, RFC 1123, RFC 2732, RFC 2181, and RFC 1123.
*
* @param mixed $value
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!is_string($value)) {
return false;
}
if (\filter_var($value, FILTER_VALIDATE_DOMAIN) === false) {
return false;
}
return true;
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
namespace Appwrite\Network\Validator;
use Utopia\Validator\Hostname;
use Utopia\Validator;
/**
* Host
*
* Validate that a host is allowed from given whitelisted hosts list
*
* @package Utopia\Validator
*/
class Host extends Validator
{
protected $whitelist = [];
/**
* @param array $whitelist
*/
public function __construct(array $whitelist)
{
$this->whitelist = $whitelist;
}
/**
* Get Description
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return 'URL host must be one of: ' . \implode(', ', $this->whitelist);
}
/**
* Is valid
*
* Validation will pass when $value starts with one of the given hosts
*
* @param mixed $value
* @return bool
*/
public function isValid($value): bool
{
// Check if value is valid URL
$urlValidator = new URL();
if (!$urlValidator->isValid($value)) {
return false;
}
$hostname = \parse_url($value, PHP_URL_HOST);
$hostnameValidator = new Hostname($this->whitelist);
return $hostnameValidator->isValid($hostname);
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
namespace Appwrite\Network\Validator;
use Exception;
use Utopia\Validator;
/**
* IP
*
* Validate that an variable is a valid IP address
*
* @package Utopia\Validator
*/
class IP extends Validator
{
public const ALL = 'all';
public const V4 = 'ipv4';
public const V6 = 'ipv6';
/**
* @var string
*/
protected $type = self::ALL;
/**
* Constructor
*
* Set a the type of IP check.
*
* @param string $type
*/
public function __construct(string $type = self::ALL)
{
if (!in_array($type, [self::ALL, self::V4, self::V6])) {
throw new Exception('Unsupported IP type');
}
$this->type = $type;
}
/**
* Get Description
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return 'Value must be a valid IP address';
}
/**
* Is valid
*
* Validation will pass when $value is valid IP address.
*
* @param mixed $value
* @return bool
*/
public function isValid($value): bool
{
switch ($this->type) {
case self::ALL:
if (\filter_var($value, FILTER_VALIDATE_IP)) {
return true;
}
break;
case self::V4:
if (\filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return true;
}
break;
case self::V6:
if (\filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return true;
}
break;
default:
return false;
break;
}
return false;
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace Appwrite\Network\Validator;
use Utopia\Validator;
/**
* URL
*
* Validate that an variable is a valid URL
*
* @package Appwrite\Network\Validator
*/
class URL extends Validator
{
protected array $allowedSchemes;
/**
* @param array $allowedSchemes
*/
public function __construct(array $allowedSchemes = [])
{
$this->allowedSchemes = $allowedSchemes;
}
/**
* Get Description
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
if (!empty($this->allowedSchemes)) {
return 'Value must be a valid URL with following schemes (' . \implode(', ', $this->allowedSchemes) . ')';
}
return 'Value must be a valid URL';
}
/**
* Is valid
*
* Validation will pass when $value is valid URL.
*
* @param mixed $value
* @return bool
*/
public function isValid($value): bool
{
$sanitizedURL = '';
foreach (str_split($value) as $character) {
$sanitizedURL .= (ord($character) > 127) ? rawurlencode($character) : $character;
}
if (\filter_var($sanitizedURL, FILTER_VALIDATE_URL) === false) {
return false;
}
if (!empty($this->allowedSchemes) && !\in_array(\parse_url($sanitizedURL, PHP_URL_SCHEME), $this->allowedSchemes)) {
return false;
}
return true;
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
@@ -537,7 +537,7 @@ class VideoCustomServerTest extends Scope
public function testProtocolWithSubs($videoId): string
{
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/protocols/hls', [
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/hls', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -585,7 +585,7 @@ class VideoCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1508, strlen($response['body']));
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/protocols/dash', [
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/dash', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -704,7 +704,7 @@ class VideoCustomServerTest extends Scope
public function testProtocolWithSubsAgain($videoId): string
{
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/protocols/hls', [
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/hls', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -774,7 +774,7 @@ class VideoCustomServerTest extends Scope
sleep(50);
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/protocols/hls', [
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/hls', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -803,7 +803,7 @@ class VideoCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(0, strlen($response['body']));
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/protocols/dash', [
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/dash', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],