Merge branch 'master' into release-1.2.1

This commit is contained in:
Steven Nguyen
2023-02-13 15:46:30 -08:00
197 changed files with 1581 additions and 1498 deletions
+27 -55
View File
@@ -8,11 +8,10 @@ use Utopia\Database\Document;
class Mail extends Event
{
protected string $recipient = '';
protected string $url = '';
protected string $type = '';
protected string $from = '';
protected string $name = '';
protected string $locale = '';
protected ?Document $team = null;
protected string $subject = '';
protected string $body = '';
public function __construct()
{
@@ -20,14 +19,14 @@ class Mail extends Event
}
/**
* Sets team for the mail event.
* Sets subject for the mail event.
*
* @param Document $team
* @param string $subject
* @return self
*/
public function setTeam(Document $team): self
public function setSubject(string $subject): self
{
$this->team = $team;
$this->subject = $subject;
return $this;
}
@@ -35,11 +34,11 @@ class Mail extends Event
/**
* Returns set team for the mail event.
*
* @return null|Document
* @return string
*/
public function getTeam(): ?Document
public function getSubject(): string
{
return $this->team;
return $this->subject;
}
/**
@@ -66,49 +65,49 @@ class Mail extends Event
}
/**
* Sets url for the mail event.
* Sets from for the mail event.
*
* @param string $url
* @param string $from
* @return self
*/
public function setUrl(string $url): self
public function setFrom(string $from): self
{
$this->url = $url;
$this->from = $from;
return $this;
}
/**
* Returns set url for the mail event.
* Returns from for mail event.
*
* @return string
*/
public function getURL(): string
public function getFrom(): string
{
return $this->url;
return $this->from;
}
/**
* Sets type for the mail event (use the constants starting with MAIL_TYPE_*).
* Sets body for the mail event.
*
* @param string $type
* @param string $body
* @return self
*/
public function setType(string $type): self
public function setBody(string $body): self
{
$this->type = $type;
$this->body = $body;
return $this;
}
/**
* Returns set type for the mail event.
* Returns body for the mail event.
*
* @return string
*/
public function getType(): string
public function getBody(): string
{
return $this->type;
return $this->body;
}
/**
@@ -134,29 +133,6 @@ class Mail extends Event
return $this->name;
}
/**
* Sets locale for the mail event.
*
* @param string $locale
* @return self
*/
public function setLocale(string $locale): self
{
$this->locale = $locale;
return $this;
}
/**
* Returns set locale for the mail event.
*
* @return string
*/
public function getLocale(): string
{
return $this->locale;
}
/**
* Executes the event and sends it to the mails worker.
*
@@ -166,15 +142,11 @@ class Mail extends Event
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'payload' => $this->payload,
'from' => $this->from,
'recipient' => $this->recipient,
'url' => $this->url,
'locale' => $this->locale,
'type' => $this->type,
'name' => $this->name,
'team' => $this->team,
'subject' => $this->subject,
'body' => $this->body,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
]);
}
+5 -5
View File
@@ -228,18 +228,18 @@ class Mapper
case 'Appwrite\Network\Validator\CNAME':
case 'Appwrite\Task\Validator\Cron':
case 'Appwrite\Utopia\Database\Validator\CustomId':
case 'Appwrite\Network\Validator\Domain':
case 'Utopia\Validator\Domain':
case 'Appwrite\Network\Validator\Email':
case 'Appwrite\Event\Validator\Event':
case 'Utopia\Validator\HexColor':
case 'Appwrite\Network\Validator\Host':
case 'Appwrite\Network\Validator\IP':
case 'Utopia\Validator\Host':
case 'Utopia\Validator\IP':
case 'Utopia\Database\Validator\Key':
case 'Appwrite\Network\Validator\Origin':
case 'Utopia\Validator\Origin':
case 'Appwrite\Auth\Validator\Password':
case 'Utopia\Validator\Text':
case 'Utopia\Database\Validator\UID':
case 'Appwrite\Network\Validator\URL':
case 'Utopia\Validator\URL':
case 'Utopia\Validator\WhiteList':
default:
$type = Type::string();
+1 -1
View File
@@ -149,7 +149,7 @@ class Realtime extends Adapter
'data' => [
'events' => $events,
'channels' => $channels,
'timestamp' => DateTime::now(),
'timestamp' => DateTime::formatTz(DateTime::now()),
'payload' => $payload
]
]));
+4
View File
@@ -46,6 +46,10 @@ class CNAME extends Validator
return false;
}
if (!$records) {
return false;
}
foreach ($records as $record) {
if (isset($record['target']) && $record['target'] === $this->target) {
return true;
-78
View File
@@ -1,78 +0,0 @@
<?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
@@ -1,84 +0,0 @@
<?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
@@ -1,114 +0,0 @@
<?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;
}
}
@@ -14,6 +14,7 @@ class Origin extends Validator
public const CLIENT_TYPE_FLUTTER_MACOS = 'flutter-macos';
public const CLIENT_TYPE_FLUTTER_WINDOWS = 'flutter-windows';
public const CLIENT_TYPE_FLUTTER_LINUX = 'flutter-linux';
public const CLIENT_TYPE_FLUTTER_WEB = 'flutter-web';
public const CLIENT_TYPE_APPLE_IOS = 'apple-ios';
public const CLIENT_TYPE_APPLE_MACOS = 'apple-macos';
public const CLIENT_TYPE_APPLE_WATCHOS = 'apple-watchos';
@@ -73,6 +74,7 @@ class Origin extends Validator
switch ($type) {
case self::CLIENT_TYPE_WEB:
case self::CLIENT_TYPE_FLUTTER_WEB:
$this->clients[] = (isset($platform['hostname'])) ? $platform['hostname'] : '';
break;
-92
View File
@@ -1,92 +0,0 @@
<?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;
}
}
+64 -40
View File
@@ -2,22 +2,23 @@
namespace Appwrite\Resque;
use Exception;
use Utopia\App;
use Utopia\Cache\Cache;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Storage\Device;
use Utopia\Storage\Storage;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Device\Backblaze;
use Utopia\Storage\Device\DOSpaces;
use Utopia\Storage\Device\Linode;
use Utopia\Storage\Device\Wasabi;
use Utopia\Storage\Device\Backblaze;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Device\S3;
use Exception;
use Utopia\Database\Validator\Authorization;
use Utopia\Storage\Device\Wasabi;
use Utopia\Storage\Storage;
abstract class Worker
{
@@ -53,7 +54,7 @@ abstract class Worker
* @return void
* @throws \Exception|\Throwable
*/
public function init()
public function init(): void
{
throw new Exception("Please implement init method in worker");
}
@@ -65,7 +66,7 @@ abstract class Worker
* @return void
* @throws \Exception|\Throwable
*/
public function run()
public function run(): void
{
throw new Exception("Please implement run method in worker");
}
@@ -77,7 +78,7 @@ abstract class Worker
* @return void
* @throws \Exception|\Throwable
*/
public function shutdown()
public function shutdown(): void
{
throw new Exception("Please implement shutdown method in worker");
}
@@ -151,35 +152,39 @@ abstract class Worker
/**
* Register callback. Will be executed when error occurs.
* @param callable $callback
* @param Throwable $error
* @return self
* @return void
*/
public static function error(callable $callback): void
{
\array_push(self::$errorCallbacks, $callback);
self::$errorCallbacks[] = $callback;
}
/**
* Get internal project database
* @param string $projectId
* @return Database
* @throws Exception
*/
protected function getProjectDB(string $projectId): Database
protected function getProjectDB(string $projectId, ?Document $project = null): Database
{
$consoleDB = $this->getConsoleDB();
if ($project === null) {
$consoleDB = $this->getConsoleDB();
if ($projectId === 'console') {
return $consoleDB;
if ($projectId === 'console') {
return $consoleDB;
}
/** @var Document $project */
$project = Authorization::skip(fn() => $consoleDB->getDocument('projects', $projectId));
}
/** @var Document $project */
$project = Authorization::skip(fn() => $consoleDB->getDocument('projects', $projectId));
return $this->getDB(self::DATABASE_PROJECT, $projectId, $project->getInternalId());
return $this->getDB(self::DATABASE_PROJECT, $projectId, $project->getInternalId(), $project);
}
/**
* Get console database
* @return Database
* @throws Exception
*/
protected function getConsoleDB(): Database
{
@@ -187,24 +192,35 @@ abstract class Worker
}
/**
* Get console database
* @param string $type One of (internal, external, console)
* @param string $projectId of internal or external DB
* Get database
* @param string $type One of (project, console)
* @param string $projectId of project or console DB
* @param string $projectInternalId
* @param Document|null $project
* @return Database
* @throws Exception
*/
private function getDB(string $type, string $projectId = '', string $projectInternalId = ''): Database
{
private function getDB(
string $type,
string $projectId = '',
string $projectInternalId = '',
?Document $project = null
): Database {
global $register;
$namespace = '';
$sleep = DATABASE_RECONNECT_SLEEP; // overwritten when necessary
if ($project !== null) {
$projectId = $project->getId();
$projectInternalId = $project->getInternalId();
}
switch ($type) {
case self::DATABASE_PROJECT:
if (!$projectId) {
throw new \Exception('ProjectID not provided - cannot get database');
}
$namespace = "_{$projectInternalId}";
$namespace = "_$projectInternalId";
break;
case self::DATABASE_CONSOLE:
$namespace = "_console";
@@ -212,12 +228,11 @@ abstract class Worker
break;
default:
throw new \Exception('Unknown database type: ' . $type);
break;
}
$attempts = 0;
do {
while (true) {
try {
$attempts++;
$cache = new Cache(new RedisCache($register->get('cache')));
@@ -225,8 +240,12 @@ abstract class Worker
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace($namespace); // Main DB
if (!empty($projectId) && !$database->getDocument('projects', $projectId)->isEmpty()) {
throw new \Exception("Project does not exist: {$projectId}");
if (
$project === null
&& !empty($projectId)
&& !$database->getDocument('projects', $projectId)->isEmpty()
) {
throw new \Exception("Project does not exist: $projectId");
}
if ($type === self::DATABASE_CONSOLE && !$database->exists($database->getDefaultDatabase(), Database::METADATA)) {
@@ -235,13 +254,13 @@ abstract class Worker
break; // leave loop if successful
} catch (\Exception $e) {
Console::warning("Database not ready. Retrying connection ({$attempts})...");
Console::warning("Database not ready. Retrying connection ($attempts)...");
if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) {
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
}
sleep($sleep);
}
} while ($attempts < DATABASE_RECONNECT_MAX_ATTEMPTS);
}
return $database;
}
@@ -251,7 +270,7 @@ abstract class Worker
* @param string $projectId of the project
* @return Device
*/
protected function getFunctionsDevice($projectId): Device
protected function getFunctionsDevice(string $projectId): Device
{
return $this->getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $projectId);
}
@@ -261,7 +280,7 @@ abstract class Worker
* @param string $projectId of the project
* @return Device
*/
protected function getFilesDevice($projectId): Device
protected function getFilesDevice(string $projectId): Device
{
return $this->getDevice(APP_STORAGE_UPLOADS . '/app-' . $projectId);
}
@@ -272,19 +291,24 @@ abstract class Worker
* @param string $projectId of the project
* @return Device
*/
protected function getBuildsDevice($projectId): Device
protected function getBuildsDevice(string $projectId): Device
{
return $this->getDevice(APP_STORAGE_BUILDS . '/app-' . $projectId);
}
protected function getCacheDevice(string $projectId): Device
{
return $this->getDevice(APP_STORAGE_CACHE . '/app-' . $projectId);
}
/**
* Get Device based on selected storage environment
* @param string $root path of the device
* @return Device
*/
public function getDevice($root): Device
public function getDevice(string $root): Device
{
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
switch (strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL))) {
case Storage::DEVICE_LOCAL:
default:
return new Local($root);
@@ -311,7 +311,7 @@ class OpenAPI3 extends Format
$node['schema']['format'] = 'email';
$node['schema']['x-example'] = 'email@example.com';
break;
case 'Appwrite\Network\Validator\URL':
case 'Utopia\Validator\URL':
$node['schema']['type'] = $validator->getType();
$node['schema']['format'] = 'url';
$node['schema']['x-example'] = 'https://example.com';
@@ -391,7 +391,7 @@ class OpenAPI3 extends Format
case 'Utopia\Validator\Length':
$node['schema']['type'] = $validator->getType();
break;
case 'Appwrite\Network\Validator\Host':
case 'Utopia\Validator\Host':
$node['schema']['type'] = $validator->getType();
$node['schema']['format'] = 'url';
$node['schema']['x-example'] = 'https://example.com';
@@ -312,7 +312,7 @@ class Swagger2 extends Format
$node['format'] = 'email';
$node['x-example'] = 'email@example.com';
break;
case 'Appwrite\Network\Validator\URL':
case 'Utopia\Validator\URL':
$node['type'] = $validator->getType();
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
@@ -393,7 +393,7 @@ class Swagger2 extends Format
case 'Utopia\Validator\Length':
$node['type'] = $validator->getType();
break;
case 'Appwrite\Network\Validator\Host':
case 'Utopia\Validator\Host':
$node['type'] = $validator->getType();
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
+1 -1
View File
@@ -97,7 +97,7 @@ class Func extends Model
'type' => self::TYPE_INTEGER,
'description' => 'Function execution timeout in seconds.',
'default' => 15,
'example' => 1592981237,
'example' => 15,
])
;
}
@@ -42,7 +42,7 @@ class Locale extends Model
])
->addRule('eu', [
'type' => self::TYPE_BOOLEAN,
'description' => 'True if country is part of the Europian Union.',
'description' => 'True if country is part of the European Union.',
'default' => false,
'example' => false,
])
@@ -41,9 +41,9 @@ class Platform extends Model
])
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Platform type. Possible values are: web, flutter-ios, flutter-android, ios, android, and unity.',
'description' => 'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.',
'default' => '',
'example' => 'My Web App',
'example' => 'web',
])
->addRule('key', [
'type' => self::TYPE_STRING,