1
0
Fork 0
mirror of synced 2024-09-29 17:01:37 +13:00

Merge remote-tracking branch 'origin/1.5.x' into json-parsing

# Conflicts:
#	composer.lock
This commit is contained in:
Jake Barnby 2024-01-09 13:56:01 +13:00
commit 51b2019ab6
No known key found for this signature in database
GPG key ID: C437A8CC85B96E9C
21 changed files with 473 additions and 120 deletions

2
.gitmodules vendored
View file

@ -1,4 +1,4 @@
[submodule "app/console"] [submodule "app/console"]
path = app/console path = app/console
url = https://github.com/appwrite/console url = https://github.com/appwrite/console
branch = 3.2.15 branch = 3.2.16

View file

@ -3,30 +3,36 @@
"settings.locale": "tr", "settings.locale": "tr",
"settings.direction": "ltr", "settings.direction": "ltr",
"emails.sender": "%s Takımı", "emails.sender": "%s Takımı",
"emails.verification.subject": "", "emails.verification.subject": "Hesabını Doğrula",
"emails.verification.hello": "", "emails.verification.hello": "Merhaba {{user}}",
"emails.verification.body": "", "emails.verification.body": "Eposta adresini doğrulamak için bu bağlantıyı kullanın.",
"emails.verification.footer": "", "emails.verification.footer": "Eğer bu eposta adresini doğrulamak isteyen siz değilseniz devam etmeyin.",
"emails.verification.thanks": "", "emails.verification.thanks": "Teşekkürler",
"emails.verification.signature": "", "emails.verification.signature": "{{project}} takımı",
"emails.magicSession.subject": "", "emails.magicSession.subject": "Giriş",
"emails.magicSession.hello": "", "emails.magicSession.hello": "Merhaba,",
"emails.magicSession.body": "", "emails.magicSession.body": "Giriş yapmak için tıklayın.",
"emails.magicSession.footer": "", "emails.magicSession.footer": "Eğer bu eposta adresini kullanarak giriş yapmak istemediyseniz devam etmeyin.",
"emails.magicSession.thanks": "", "emails.magicSession.thanks": "Teşekkürler",
"emails.magicSession.signature": "", "emails.magicSession.signature": "{{project}} takımı",
"emails.recovery.subject": "", "emails.recovery.subject": "Şifremi Sıfırla",
"emails.recovery.hello": "", "emails.recovery.hello": "Merhaba {{user}}",
"emails.recovery.body": "", "emails.recovery.body": "{{project}} şifrenizi sıfırlamak için bu bağlantıyı kullanın.",
"emails.recovery.footer": "", "emails.recovery.footer": "Eğer şifre sıfırlama talebinde bulunmadıysanız devam etmeyin.",
"emails.recovery.thanks": "", "emails.recovery.thanks": "Teşekkürler",
"emails.recovery.signature": "", "emails.recovery.signature": "{{project}} takımı",
"emails.invitation.subject": "", "emails.invitation.subject": "%s üzerinde %s Takımına Davet",
"emails.invitation.hello": "", "emails.invitation.hello": "Merhaba",
"emails.invitation.body": "", "emails.invitation.body": "Bu epostayı aldınız, çünkü {{owner}} sizi {{project}} üzerinde {{team}} takımının üyesi olmaya davet etti.",
"emails.invitation.footer": "", "emails.invitation.footer": "Eğer ilgilenmiyorsanız devam etmeyin.",
"emails.invitation.thanks": "", "emails.invitation.thanks": "Teşekkürler",
"emails.invitation.signature": "", "emails.invitation.signature": "{{project}} takımı",
"emails.certificate.subject": "%s için sertifika hatası",
"emails.certificate.hello": "Merhaba",
"emails.certificate.body": "Alan adınız '{{domain}}' için sertifika oluşturulamadı. Deneme sayısı {{attempt}} ve hata sebebi: {{error}}",
"emails.certificate.footer": "Geçmiş sertifikanız ilk denemeden sonra 30 gün daha geçerli kalacaktır. Bu konuyu araştırmanızı öneriyoruz, aksi taktirde alan adınız SSL sertifikasız kalacaktır.",
"emails.certificate.thanks": "Teşekkürler",
"emails.certificate.signature": "{{project}} takımı",
"locale.country.unknown": "Bilinmeyen", "locale.country.unknown": "Bilinmeyen",
"countries.af": "Afganistan", "countries.af": "Afganistan",
"countries.ao": "Angola", "countries.ao": "Angola",
@ -229,4 +235,4 @@
"continents.na": "Kuzey Amerika", "continents.na": "Kuzey Amerika",
"continents.oc": "Okyanusya", "continents.oc": "Okyanusya",
"continents.sa": "Güney Amerika" "continents.sa": "Güney Amerika"
} }

View file

@ -362,6 +362,16 @@ return [
'beta' => false, 'beta' => false,
'mock' => false, 'mock' => false,
], ],
'zoho' => [
'name' => 'Zoho',
'developers' => 'https://zoho.com/accounts/protocol/oauth.html',
'icon' => 'icon-zoho',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'zoom' => [ 'zoom' => [
'name' => 'Zoom', 'name' => 'Zoom',
'developers' => 'https://marketplace.zoom.us/docs/guides/auth/oauth/', 'developers' => 'https://marketplace.zoom.us/docs/guides/auth/oauth/',

@ -1 +1 @@
Subproject commit 94e4c1a73024b0e974fbe6077674281f6e973c9d Subproject commit 0a007a3b1b6eafc39dc19b7129f41643102f9676

View file

@ -69,7 +69,7 @@ App::post('/v1/account')
->label('abuse-limit', 10) ->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 a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.') ->param('email', '', new Email(), 'User email.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary']) ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('request') ->inject('request')
->inject('response') ->inject('response')
@ -566,11 +566,19 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
if (!$user->isEmpty()) { if (!$user->isEmpty()) {
$userId = $user->getId(); $userId = $user->getId();
$identitiesWithMatchingEmail = $dbForProject->find('identities', [ $identityWithMatchingEmail = $dbForProject->findOne('identities', [
Query::equal('providerEmail', [$email]), Query::equal('providerEmail', [$email]),
Query::notEqual('userId', $userId), Query::notEqual('userId', $userId),
]); ]);
if (!empty($identitiesWithMatchingEmail)) { if (!empty($identityWithMatchingEmail)) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
$userWithMatchingEmail = $dbForProject->find('users', [
Query::equal('email', [$email]),
Query::notEqual('$id', $userId),
]);
if (!empty($userWithMatchingEmail)) {
throw new Exception(Exception::USER_ALREADY_EXISTS); throw new Exception(Exception::USER_ALREADY_EXISTS);
} }
} }
@ -2725,8 +2733,8 @@ App::put('/v1/account/recovery')
->label('abuse-key', 'url:{url},userId:{param-userId}') ->label('abuse-key', 'url:{url},userId:{param-userId}')
->param('userId', '', new UID(), 'User ID.') ->param('userId', '', new UID(), 'User ID.')
->param('secret', '', new Text(256), 'Valid reset token.') ->param('secret', '', new Text(256), 'Valid reset token.')
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.') ->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
->param('passwordAgain', '', new Password(), 'Repeat new user password. Must be at least 8 chars.') ->param('passwordAgain', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Repeat new user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
->inject('response') ->inject('response')
->inject('user') ->inject('user')
->inject('dbForProject') ->inject('dbForProject')

View file

@ -44,6 +44,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList; use Utopia\Validator\WhiteList;
use Utopia\DSN\DSN; use Utopia\DSN\DSN;
use Utopia\Swoole\Request; use Utopia\Swoole\Request;
use Utopia\Storage\Compression\Compression;
App::post('/v1/storage/buckets') App::post('/v1/storage/buckets')
->desc('Create bucket') ->desc('Create bucket')
@ -67,7 +68,7 @@ App::post('/v1/storage/buckets')
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true) ->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true)
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true) ->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response') ->inject('response')
@ -241,7 +242,7 @@ App::put('/v1/storage/buckets/:bucketId')
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true) ->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true)
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true) ->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response') ->inject('response')
@ -538,19 +539,24 @@ App::post('/v1/storage/buckets/:bucketId/files')
$fileHash = $deviceFiles->getFileHash($path); // Get file hash before compression and encryption $fileHash = $deviceFiles->getFileHash($path); // Get file hash before compression and encryption
$data = ''; $data = '';
// Compression // Compression
$algorithm = $bucket->getAttribute('compression', COMPRESSION_TYPE_NONE); $algorithm = $bucket->getAttribute('compression', Compression::NONE);
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != COMPRESSION_TYPE_NONE) { if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != Compression::NONE) {
$data = $deviceFiles->read($path); $data = $deviceFiles->read($path);
switch ($algorithm) { switch ($algorithm) {
case COMPRESSION_TYPE_ZSTD: case Compression::ZSTD:
$compressor = new Zstd(); $compressor = new Zstd();
break; break;
case COMPRESSION_TYPE_GZIP: case Compression::GZIP:
default: default:
$compressor = new GZIP(); $compressor = new GZIP();
break; break;
} }
$data = $compressor->compress($data); $data = $compressor->compress($data);
} else {
// reset the algorithm to none as we do not compress the file
// if file size exceedes the APP_STORAGE_READ_BUFFER
// regardless the bucket compression algoorithm
$algorithm = Compression::NONE;
} }
if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) { if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) {
@ -622,7 +628,17 @@ App::post('/v1/storage/buckets/:bucketId/files')
->setAttribute('metadata', $metadata) ->setAttribute('metadata', $metadata)
->setAttribute('chunksUploaded', $chunksUploaded); ->setAttribute('chunksUploaded', $chunksUploaded);
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); /**
* Validate create permission and skip authorization in updateDocument
* Without this, the file creation will fail when user doesn't have update permission
* However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update
*/
$validator = new Authorization(Database::PERMISSION_CREATE);
if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
} }
} catch (AuthorizationException) { } catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED);
@ -659,7 +675,17 @@ App::post('/v1/storage/buckets/:bucketId/files')
->setAttribute('chunksUploaded', $chunksUploaded) ->setAttribute('chunksUploaded', $chunksUploaded)
->setAttribute('metadata', $metadata); ->setAttribute('metadata', $metadata);
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); /**
* Validate create permission and skip authorization in updateDocument
* Without this, the file creation will fail when user doesn't have update permission
* However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update
*/
$validator = new Authorization(Database::PERMISSION_CREATE);
if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
} }
} catch (AuthorizationException) { } catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED);
@ -872,14 +898,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception(Exception::USER_UNAUTHORIZED); throw new Exception(Exception::USER_UNAUTHORIZED);
} }
if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support
$output = 'jpg';
}
$inputs = Config::getParam('storage-inputs');
$outputs = Config::getParam('storage-outputs');
$fileLogos = Config::getParam('storage-logos');
if ($fileSecurity && !$valid) { if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else { } else {
@ -890,9 +908,17 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
} }
if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support
$output = 'jpg';
}
$inputs = Config::getParam('storage-inputs');
$outputs = Config::getParam('storage-outputs');
$fileLogos = Config::getParam('storage-logos');
$path = $file->getAttribute('path'); $path = $file->getAttribute('path');
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
$algorithm = $file->getAttribute('algorithm', 'none'); $algorithm = $file->getAttribute('algorithm', Compression::NONE);
$cipher = $file->getAttribute('openSSLCipher'); $cipher = $file->getAttribute('openSSLCipher');
$mime = $file->getAttribute('mimeType'); $mime = $file->getAttribute('mimeType');
if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) App::getEnv('_APP_STORAGE_PREVIEW_LIMIT', 20000000)) { if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) App::getEnv('_APP_STORAGE_PREVIEW_LIMIT', 20000000)) {
@ -903,7 +929,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$path = $fileLogos['default_image']; $path = $fileLogos['default_image'];
} }
$algorithm = 'none'; $algorithm = Compression::NONE;
$cipher = null; $cipher = null;
$background = (empty($background)) ? 'eceff1' : $background; $background = (empty($background)) ? 'eceff1' : $background;
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
@ -915,12 +941,17 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
} }
if (empty($output)) { if (empty($output)) {
// when file extension is provided but it's not one of our
// supported outputs we fallback to `jpg`
if (!empty($type) && !array_key_exists($type, $outputs)) {
$type = 'jpg';
}
// when file extension is not provided and the mime type is not one of our supported outputs // when file extension is not provided and the mime type is not one of our supported outputs
// we fallback to `jpg` output format // we fallback to `jpg` output format
$output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type; $output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type;
} }
$source = $deviceFiles->read($path); $source = $deviceFiles->read($path);
if (!empty($cipher)) { // Decrypt if (!empty($cipher)) { // Decrypt
@ -935,11 +966,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
} }
switch ($algorithm) { switch ($algorithm) {
case 'zstd': case Compression::ZSTD:
$compressor = new Zstd(); $compressor = new Zstd();
$source = $compressor->decompress($source); $source = $compressor->decompress($source);
break; break;
case 'gzip': case Compression::GZIP:
$compressor = new GZIP(); $compressor = new GZIP();
$source = $compressor->decompress($source); $source = $compressor->decompress($source);
break; break;
@ -1080,15 +1111,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
); );
} }
switch ($file->getAttribute('algorithm', 'none')) { switch ($file->getAttribute('algorithm', Compression::NONE)) {
case 'zstd': case Compression::ZSTD:
if (empty($source)) { if (empty($source)) {
$source = $deviceFiles->read($path); $source = $deviceFiles->read($path);
} }
$compressor = new Zstd(); $compressor = new Zstd();
$source = $compressor->decompress($source); $source = $compressor->decompress($source);
break; break;
case 'gzip': case Compression::GZIP:
if (empty($source)) { if (empty($source)) {
$source = $deviceFiles->read($path); $source = $deviceFiles->read($path);
} }
@ -1231,15 +1262,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
); );
} }
switch ($file->getAttribute('algorithm', 'none')) { switch ($file->getAttribute('algorithm', Compression::NONE)) {
case 'zstd': case Compression::ZSTD:
if (empty($source)) { if (empty($source)) {
$source = $deviceFiles->read($path); $source = $deviceFiles->read($path);
} }
$compressor = new Zstd(); $compressor = new Zstd();
$source = $compressor->decompress($source); $source = $compressor->decompress($source);
break; break;
case 'gzip': case Compression::GZIP:
if (empty($source)) { if (empty($source)) {
$source = $deviceFiles->read($path); $source = $deviceFiles->read($path);
} }
@ -1253,10 +1284,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$response->send(substr($source, $start, ($end - $start + 1))); $response->send(substr($source, $start, ($end - $start + 1)));
} }
$response->send($source); $response->send($source);
return;
} }
if (!empty($rangeHeader)) { if (!empty($rangeHeader)) {
$response->send($deviceFiles->read($path, $start, ($end - $start + 1))); $response->send($deviceFiles->read($path, $start, ($end - $start + 1)));
return;
} }
$size = $deviceFiles->getFileSize($path); $size = $deviceFiles->getFileSize($path);

View file

@ -556,6 +556,22 @@ App::shutdown()
->setParam('project.{scope}.network.outbound', $response->getSize()) ->setParam('project.{scope}.network.outbound', $response->getSize())
->submit(); ->submit();
} }
/**
* Update user last activity
*/
if (!$user->isEmpty()) {
$accessedAt = $user->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCCESS)) > $accessedAt) {
$user->setAttribute('accessedAt', DateTime::now());
if (APP_MODE_ADMIN !== $mode) {
$dbForProject->updateDocument('users', $user->getId(), $user);
} else {
$dbForConsole->updateDocument('users', $user->getId(), $user);
}
}
}
}); });
App::init() App::init()

View file

@ -171,10 +171,6 @@ const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource'; const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
const DELETE_TYPE_SCHEDULES = 'schedules'; const DELETE_TYPE_SCHEDULES = 'schedules';
const DELETE_TYPE_TOPIC = 'topic'; const DELETE_TYPE_TOPIC = 'topic';
// Compression type
const COMPRESSION_TYPE_NONE = 'none';
const COMPRESSION_TYPE_GZIP = 'gzip';
const COMPRESSION_TYPE_ZSTD = 'zstd';
// Mail Types // Mail Types
const MAIL_TYPE_VERIFICATION = 'verification'; const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession'; const MAIL_TYPE_MAGIC_SESSION = 'magicSession';

82
composer.lock generated
View file

@ -2476,16 +2476,16 @@
}, },
{ {
"name": "utopia-php/platform", "name": "utopia-php/platform",
"version": "0.5.0", "version": "0.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/utopia-php/platform.git", "url": "https://github.com/utopia-php/platform.git",
"reference": "229a7b1fa1f39afd1532f7a515326a6afc222a26" "reference": "3eceef0b6593fe0f7d2efd36d40402a395a4c285"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/229a7b1fa1f39afd1532f7a515326a6afc222a26", "url": "https://api.github.com/repos/utopia-php/platform/zipball/3eceef0b6593fe0f7d2efd36d40402a395a4c285",
"reference": "229a7b1fa1f39afd1532f7a515326a6afc222a26", "reference": "3eceef0b6593fe0f7d2efd36d40402a395a4c285",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2493,7 +2493,7 @@
"ext-redis": "*", "ext-redis": "*",
"php": ">=8.0", "php": ">=8.0",
"utopia-php/cli": "0.15.*", "utopia-php/cli": "0.15.*",
"utopia-php/framework": "0.31.*" "utopia-php/framework": "0.*.*"
}, },
"require-dev": { "require-dev": {
"laravel/pint": "1.2.*", "laravel/pint": "1.2.*",
@ -2519,9 +2519,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/utopia-php/platform/issues", "issues": "https://github.com/utopia-php/platform/issues",
"source": "https://github.com/utopia-php/platform/tree/0.5.0" "source": "https://github.com/utopia-php/platform/tree/0.5.1"
}, },
"time": "2023-10-16T20:28:49+00:00" "time": "2023-12-26T16:14:41+00:00"
}, },
{ {
"name": "utopia-php/pools", "name": "utopia-php/pools",
@ -2742,16 +2742,16 @@
}, },
{ {
"name": "utopia-php/storage", "name": "utopia-php/storage",
"version": "0.18.1", "version": "0.18.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/utopia-php/storage.git", "url": "https://github.com/utopia-php/storage.git",
"reference": "983e6dee137012f9f57f126d3c79aab54e4e8824" "reference": "faa0279519ac14f3501e8b138e0865ad9d12bff6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/983e6dee137012f9f57f126d3c79aab54e4e8824", "url": "https://api.github.com/repos/utopia-php/storage/zipball/faa0279519ac14f3501e8b138e0865ad9d12bff6",
"reference": "983e6dee137012f9f57f126d3c79aab54e4e8824", "reference": "faa0279519ac14f3501e8b138e0865ad9d12bff6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2791,9 +2791,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/utopia-php/storage/issues", "issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.18.1" "source": "https://github.com/utopia-php/storage/tree/0.18.3"
}, },
"time": "2023-10-24T14:44:19+00:00" "time": "2023-12-31T11:45:12+00:00"
}, },
{ {
"name": "utopia-php/swoole", "name": "utopia-php/swoole",
@ -2904,23 +2904,23 @@
}, },
{ {
"name": "utopia-php/vcs", "name": "utopia-php/vcs",
"version": "0.6.3", "version": "0.6.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/utopia-php/vcs.git", "url": "https://github.com/utopia-php/vcs.git",
"reference": "86c3f42a2624bcccb7a67b74dcd7bd3a31fc2e4b" "reference": "b2595a50a4897a8c88319240810055b7a96efd6d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/86c3f42a2624bcccb7a67b74dcd7bd3a31fc2e4b", "url": "https://api.github.com/repos/utopia-php/vcs/zipball/b2595a50a4897a8c88319240810055b7a96efd6d",
"reference": "86c3f42a2624bcccb7a67b74dcd7bd3a31fc2e4b", "reference": "b2595a50a4897a8c88319240810055b7a96efd6d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"adhocore/jwt": "^1.1", "adhocore/jwt": "^1.1",
"php": ">=8.0", "php": ">=8.0",
"utopia-php/cache": "^0.8.0", "utopia-php/cache": "^0.8.0",
"utopia-php/framework": "0.31.*" "utopia-php/framework": "0.*.*"
}, },
"require-dev": { "require-dev": {
"laravel/pint": "1.2.*", "laravel/pint": "1.2.*",
@ -2947,9 +2947,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/utopia-php/vcs/issues", "issues": "https://github.com/utopia-php/vcs/issues",
"source": "https://github.com/utopia-php/vcs/tree/0.6.3" "source": "https://github.com/utopia-php/vcs/tree/0.6.4"
}, },
"time": "2023-12-14T06:53:39+00:00" "time": "2023-12-26T15:38:19+00:00"
}, },
{ {
"name": "utopia-php/websocket", "name": "utopia-php/websocket",
@ -3938,23 +3938,23 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "9.2.29", "version": "9.2.30",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089",
"reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"nikic/php-parser": "^4.15", "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3", "php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3", "phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2", "phpunit/php-text-template": "^2.0.2",
@ -4004,7 +4004,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30"
}, },
"funding": [ "funding": [
{ {
@ -4012,7 +4012,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-09-19T04:57:46+00:00" "time": "2023-12-22T06:47:57+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -4651,20 +4651,20 @@
}, },
{ {
"name": "sebastian/complexity", "name": "sebastian/complexity",
"version": "2.0.2", "version": "2.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git", "url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "739b35e53379900cc9ac327b2147867b8b6efd88" "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
"reference": "739b35e53379900cc9ac327b2147867b8b6efd88", "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nikic/php-parser": "^4.7", "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
@ -4696,7 +4696,7 @@
"homepage": "https://github.com/sebastianbergmann/complexity", "homepage": "https://github.com/sebastianbergmann/complexity",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues", "issues": "https://github.com/sebastianbergmann/complexity/issues",
"source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
}, },
"funding": [ "funding": [
{ {
@ -4704,7 +4704,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2020-10-26T15:52:27+00:00" "time": "2023-12-22T06:19:30+00:00"
}, },
{ {
"name": "sebastian/diff", "name": "sebastian/diff",
@ -4978,20 +4978,20 @@
}, },
{ {
"name": "sebastian/lines-of-code", "name": "sebastian/lines-of-code",
"version": "1.0.3", "version": "1.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git", "url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
"reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nikic/php-parser": "^4.6", "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
@ -5023,7 +5023,7 @@
"homepage": "https://github.com/sebastianbergmann/lines-of-code", "homepage": "https://github.com/sebastianbergmann/lines-of-code",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
}, },
"funding": [ "funding": [
{ {
@ -5031,7 +5031,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2020-11-28T06:42:11+00:00" "time": "2023-12-22T06:20:34+00:00"
}, },
{ {
"name": "sebastian/object-enumerator", "name": "sebastian/object-enumerator",

View file

@ -0,0 +1,163 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://zoho.com/accounts/protocol/oauth.html
class Zoho extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://accounts.zoho.com';
/**
* @var array
*/
protected array $scopes = [
'email',
'profile',
];
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'zoho';
}
/**
* @return string
*/
public function getLoginURL(): string
{
$url = $this->endpoint . '/oauth/v2/auth?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback,
'scope' => \implode(' ', $this->getScopes())
]);
return $url;
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/v2/token',
["Content-Type: application/x-www-form-urlencoded"],
\http_build_query([
'grant_type' => 'authorization_code',
"client_id" => $this->appID,
"client_secret" => $this->appSecret,
"redirect_uri" => $this->callback,
'code' => $code,
'scope' => \implode(' ', $this->getScopes()),
])
), true);
$this->user = (isset($this->tokens['id_token'])) ? \explode('.', $this->tokens['id_token']) : [0 => '', 1 => ''];
$this->user = (isset($this->user[1])) ? \json_decode(\base64_decode($this->user[1]), true) : [];
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/v2/token',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
$this->user = (isset($this->tokens['id_token'])) ? \explode('.', $this->tokens['id_token']) : [0 => '', 1 => ''];
$this->user = (isset($this->user[1])) ? \json_decode(\base64_decode($this->user[1]), true) : [];
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
return $this->user['sub'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
return $this->user['email'] ?? '';
}
/**
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
if ($this->user['email_verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
return $this->user['name'] ?? '';
}
}

View file

@ -20,7 +20,7 @@ class Password extends Validator
*/ */
public function getDescription(): string public function getDescription(): string
{ {
return 'Password must be at least 8 characters'; return 'Password must be between 8 and 256 characters long.';
} }
/** /**
@ -40,6 +40,10 @@ class Password extends Validator
return false; return false;
} }
if (\strlen($value) > 256) {
return false;
}
return true; return true;
} }

View file

@ -27,7 +27,7 @@ class PasswordDictionary extends Password
*/ */
public function getDescription(): string public function getDescription(): string
{ {
return 'Password must be at least 8 characters and should not be one of the commonly used password.'; return 'Password must be between 8 and 265 characters long, and should not be one of the commonly used password.';
} }
/** /**

View file

@ -0,0 +1,27 @@
<?php
namespace Appwrite\Enum;
enum MessageStatus: string
{
/**
* Message that is not ready to be sent
*/
case Draft = 'draft';
/**
* Scheduled to be sent for a later time
*/
case Scheduled = 'scheduled';
/**
* Picked up by the worker and starting to send
*/
case Processing = 'processing';
/**
* Sent without errors
*/
case Sent = 'sent';
/**
* Sent with some errors
*/
case Failed = 'failed';
}

View file

@ -7,6 +7,7 @@ class Topics extends Base
public const ALLOWED_ATTRIBUTES = [ public const ALLOWED_ATTRIBUTES = [
'name', 'name',
'description', 'description',
'total'
]; ];
/** /**

View file

@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response; use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response\Model;
use Utopia\Storage\Compression\Compression;
class Bucket extends Model class Bucket extends Model
{ {
@ -68,7 +69,7 @@ class Bucket extends Model
]) ])
->addRule('compression', [ ->addRule('compression', [
'type' => self::TYPE_STRING, 'type' => self::TYPE_STRING,
'description' => 'Compression algorithm choosen for compression. Will be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd).', 'description' => 'Compression algorithm choosen for compression. Will be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd).',
'default' => '', 'default' => '',
'example' => 'gzip', 'example' => 'gzip',
'array' => false 'array' => false

View file

@ -95,6 +95,36 @@ trait AccountBase
$this->assertEquals($response['headers']['status-code'], 400); $this->assertEquals($response['headers']['status-code'], 400);
$shortPassword = 'short';
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'userId' => ID::unique(),
'email' => 'shortpass@appwrite.io',
'password' => $shortPassword
]);
$this->assertEquals($response['headers']['status-code'], 400);
$longPassword = '';
for ($i = 0; $i < 257; $i++) { // 256 is the limit
$longPassword .= 'p';
}
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'userId' => ID::unique(),
'email' => 'longpass@appwrite.io',
'password' => $longPassword,
]);
$this->assertEquals($response['headers']['status-code'], 400);
return [ return [
'id' => $id, 'id' => $id,
'email' => $email, 'email' => $email,

View file

@ -469,7 +469,7 @@ trait DatabasesBase
Query::equal('type', ['string'])->toString(), Query::equal('type', ['string'])->toString(),
Query::limit(2)->toString(), Query::limit(2)->toString(),
Query::cursorAfter(new Document(['$id' => 'title']))->toString() Query::cursorAfter(new Document(['$id' => 'title']))->toString()
], ],
]); ]);
$this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(2, \count($response['body']['attributes'])); $this->assertEquals(2, \count($response['body']['attributes']));

View file

@ -162,11 +162,11 @@ trait MessagingBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'], 'x-appwrite-key' => $this->getProject()['apiKey'],
], [ ], [
'name' => 'Mailgun2', 'name' => 'Mailgun2',
'apiKey' => 'my-apikey', 'apiKey' => 'my-apikey',
'domain' => 'my-domain', 'domain' => 'my-domain',
'isEuRegion' => true, 'isEuRegion' => true,
'enabled' => false, 'enabled' => false,
]); ]);
$this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('Mailgun2', $response['body']['name']); $this->assertEquals('Mailgun2', $response['body']['name']);
@ -273,6 +273,32 @@ trait MessagingBase
$this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, \count($response['body']['topics'])); $this->assertEquals(1, \count($response['body']['topics']));
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'queries' => [
'equal("total", [0])'
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, \count($response['body']['topics']));
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'queries' => [
'greaterThan("total", 0)'
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(0, \count($response['body']['topics']));
return $topicId; return $topicId;
} }

View file

@ -28,7 +28,7 @@ trait StorageBase
'name' => 'Test Bucket', 'name' => 'Test Bucket',
'fileSecurity' => true, 'fileSecurity' => true,
'maximumFileSize' => 2000000, //2MB 'maximumFileSize' => 2000000, //2MB
'allowedFileExtensions' => ["jpg", "png"], 'allowedFileExtensions' => ["jpg", "png", 'jfif'],
'permissions' => [ 'permissions' => [
Permission::read(Role::any()), Permission::read(Role::any()),
Permission::create(Role::any()), Permission::create(Role::any()),
@ -75,10 +75,7 @@ trait StorageBase
'name' => 'Test Bucket 2', 'name' => 'Test Bucket 2',
'fileSecurity' => true, 'fileSecurity' => true,
'permissions' => [ 'permissions' => [
Permission::read(Role::any()),
Permission::create(Role::any()), Permission::create(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
], ],
]); ]);
$this->assertEquals(201, $bucket2['headers']['status-code']); $this->assertEquals(201, $bucket2['headers']['status-code']);
@ -111,9 +108,7 @@ trait StorageBase
'fileId' => $fileId, 'fileId' => $fileId,
'file' => $curlFile, 'file' => $curlFile,
'permissions' => [ 'permissions' => [
Permission::read(Role::any()), Permission::read(Role::any())
Permission::update(Role::any()),
Permission::delete(Role::any()),
], ],
]); ]);
$counter++; $counter++;
@ -471,6 +466,32 @@ trait StorageBase
$this->assertEquals('image/png', $file2['headers']['content-type']); $this->assertEquals('image/png', $file2['headers']['content-type']);
$this->assertNotEmpty($file2['body']); $this->assertNotEmpty($file2['body']);
// upload JXL file for preview
$fileJfif = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'fileId' => ID::unique(),
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/disk-a/preview-test.jfif'), 'image/jxl', 'preview-test.jfif'),
'permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $fileJfif['headers']['status-code']);
$this->assertNotEmpty($fileJfif['body']['$id']);
// TEST preview JXL
$preview = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileJfif['body']['$id'] . '/preview', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $preview['headers']['status-code']);
$this->assertEquals('image/jpeg', $preview['headers']['content-type']);
$this->assertNotEmpty($preview['body']);
//new image preview features //new image preview features
$file3 = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $data['fileId'] . '/preview', array_merge([ $file3 = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $data['fileId'] . '/preview', array_merge([
'content-type' => 'application/json', 'content-type' => 'application/json',

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View file

@ -24,5 +24,16 @@ class PasswordDictionaryTest extends TestCase
$this->assertEquals($this->object->isValid('123456'), false); $this->assertEquals($this->object->isValid('123456'), false);
$this->assertEquals($this->object->isValid('password'), false); $this->assertEquals($this->object->isValid('password'), false);
$this->assertEquals($this->object->isValid('myPasswordIsRight'), true); $this->assertEquals($this->object->isValid('myPasswordIsRight'), true);
$pass = ''; // 256 chars
for ($i = 0; $i < 256; $i++) {
$pass .= 'p';
}
$this->assertEquals($this->object->isValid($pass), true);
$pass .= 'p'; // 257 chars
$this->assertEquals($this->object->isValid($pass), false);
} }
} }