mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'refs/heads/1.6.x' into feat-extract-team-deletion
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
/**
|
||||
* MockNumber.
|
||||
*
|
||||
* Validates if a given object represents a valid phone and OTP pair
|
||||
*/
|
||||
class MockNumber extends Validator
|
||||
{
|
||||
private $message = '';
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!\is_array($value) || !isset($value['phone']) || !isset($value['otp'])) {
|
||||
$this->message = 'Invalid payload structure. Please check the "phone" and "otp" fields';
|
||||
return false;
|
||||
}
|
||||
|
||||
$phone = new Phone();
|
||||
if (!$phone->isValid($value['phone'])) {
|
||||
$this->message = $phone->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
$otp = new Text(6, 6, Text::NUMBERS);
|
||||
if (!$otp->isValid($value['otp'])) {
|
||||
$this->message = 'Invalid OTP. Please make sure the OTP is a 6 digit number';
|
||||
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_OBJECT;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ class Func extends Event
|
||||
protected string $path = '';
|
||||
protected string $method = '';
|
||||
protected array $headers = [];
|
||||
protected ?string $functionId = null;
|
||||
protected ?Document $function = null;
|
||||
protected ?Document $execution = null;
|
||||
|
||||
@@ -49,6 +50,28 @@ class Func extends Event
|
||||
return $this->function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets function id for the function event.
|
||||
*
|
||||
* @param string $functionId
|
||||
*/
|
||||
public function setFunctionId(string $functionId): self
|
||||
{
|
||||
$this->functionId = $functionId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set function id for the function event.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFunctionId(): ?string
|
||||
{
|
||||
return $this->functionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets execution for the function event.
|
||||
*
|
||||
@@ -200,6 +223,7 @@ class Func extends Event
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'function' => $this->function,
|
||||
'functionId' => $this->functionId,
|
||||
'execution' => $this->execution,
|
||||
'type' => $this->type,
|
||||
'jwt' => $this->jwt,
|
||||
|
||||
@@ -156,6 +156,7 @@ class Exception extends \Exception
|
||||
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
|
||||
public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing';
|
||||
public const FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout';
|
||||
public const FUNCTION_TEMPLATE_NOT_FOUND = 'function_template_not_found';
|
||||
|
||||
/** Deployments */
|
||||
public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found';
|
||||
@@ -168,6 +169,7 @@ class Exception extends \Exception
|
||||
|
||||
/** Execution */
|
||||
public const EXECUTION_NOT_FOUND = 'execution_not_found';
|
||||
public const EXECUTION_IN_PROGRESS = 'execution_in_progress';
|
||||
|
||||
/** Databases */
|
||||
public const DATABASE_NOT_FOUND = 'database_not_found';
|
||||
|
||||
@@ -86,6 +86,7 @@ abstract class Migration
|
||||
'1.5.5' => 'V20',
|
||||
'1.5.6' => 'V20',
|
||||
'1.5.7' => 'V20',
|
||||
'1.6.0' => 'V21'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -239,7 +240,7 @@ abstract class Migration
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates colletion from the config collection.
|
||||
* Creates collection from the config collection.
|
||||
*
|
||||
* @param string $id
|
||||
* @param string|null $name
|
||||
@@ -266,6 +267,7 @@ abstract class Migration
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
|
||||
@@ -336,6 +336,32 @@ class V20 extends Migration
|
||||
Console::warning("Purge cache from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'topics':
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'emailTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'pushTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'smsTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->purgeCachedCollection($id);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Purge cache from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class V21 extends Migration
|
||||
{
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
/**
|
||||
* Disable SubQueries for Performance.
|
||||
*/
|
||||
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
fn () => []
|
||||
);
|
||||
}
|
||||
|
||||
Console::log('Migrating Project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
|
||||
$this->projectDB->setNamespace("_{$this->project->getInternalId()}");
|
||||
|
||||
Console::info('Migrating Collections');
|
||||
$this->migrateCollections();
|
||||
|
||||
Console::info('Migrating Documents');
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Collections.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|Throwable
|
||||
*/
|
||||
private function migrateCollections(): void
|
||||
{
|
||||
$internalProjectId = $this->project->getInternalId();
|
||||
$collectionType = match ($internalProjectId) {
|
||||
'console' => 'console',
|
||||
default => 'projects',
|
||||
};
|
||||
|
||||
$collections = $this->collections[$collectionType];
|
||||
foreach ($collections as $collection) {
|
||||
$id = $collection['$id'];
|
||||
|
||||
Console::log("Migrating Collection \"{$id}\"");
|
||||
|
||||
$this->projectDB->setNamespace("_$internalProjectId");
|
||||
|
||||
switch ($id) {
|
||||
case 'projects':
|
||||
// Create accessedAt attribute
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'accessedAt');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'accessedAt' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
case 'schedules':
|
||||
// Create data attribute
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'data');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'data' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'functions':
|
||||
// Create scopes attribute
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'scopes');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'scopes' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
// Create size attribute
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'size');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'size' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'executions':
|
||||
// Create requestMethod index
|
||||
try {
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_requestMethod');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_requestMethod' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
// Create requestPath index
|
||||
try {
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_requestPath');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_requestPath' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
// Create deployment index
|
||||
try {
|
||||
$this->createIndexFromCollection($this->projectDB, $id, '_key_deployment');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'_key_deployment' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Create 'scheduledAt' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Create 'scheduleInternalId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Create 'scheduleId' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId');
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'scheduleId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix run on each document
|
||||
*
|
||||
* @param Document $document
|
||||
* @return Document
|
||||
*/
|
||||
protected function fixDocument(Document $document): Document
|
||||
{
|
||||
switch ($document->getCollection()) {
|
||||
case 'projects':
|
||||
/**
|
||||
* Bump version number.
|
||||
*/
|
||||
$document->setAttribute('version', '1.6.0');
|
||||
|
||||
// Add accessedAt attribute
|
||||
$document->setAttribute('accessedAt', DateTime::now());
|
||||
break;
|
||||
case 'functions':
|
||||
// Add scopes attribute
|
||||
$document->setAttribute('scopes', []);
|
||||
|
||||
// Add size attribute
|
||||
$document->setAttribute('size', 's-1vcpu-512m');
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use Appwrite\Platform\Tasks\Maintenance;
|
||||
use Appwrite\Platform\Tasks\Migrate;
|
||||
use Appwrite\Platform\Tasks\QueueCount;
|
||||
use Appwrite\Platform\Tasks\QueueRetry;
|
||||
use Appwrite\Platform\Tasks\ScheduleExecutions;
|
||||
use Appwrite\Platform\Tasks\ScheduleFunctions;
|
||||
use Appwrite\Platform\Tasks\ScheduleMessages;
|
||||
use Appwrite\Platform\Tasks\SDKs;
|
||||
@@ -33,6 +34,7 @@ class Tasks extends Service
|
||||
->addAction(SDKs::getName(), new SDKs())
|
||||
->addAction(SSL::getName(), new SSL())
|
||||
->addAction(ScheduleFunctions::getName(), new ScheduleFunctions())
|
||||
->addAction(ScheduleExecutions::getName(), new ScheduleExecutions())
|
||||
->addAction(ScheduleMessages::getName(), new ScheduleMessages())
|
||||
->addAction(Specs::getName(), new Specs())
|
||||
->addAction(Upgrade::getName(), new Upgrade())
|
||||
|
||||
@@ -7,6 +7,7 @@ use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
@@ -50,7 +50,7 @@ class SDKs extends Action
|
||||
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
|
||||
$message = ($git) ? Console::confirm('Please enter your commit message:') : '';
|
||||
|
||||
if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', '0.15.x', '1.0.x', '1.1.x', '1.2.x', '1.3.x', '1.4.x', '1.5.x', 'latest'])) {
|
||||
if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', '0.15.x', '1.0.x', '1.1.x', '1.2.x', '1.3.x', '1.4.x', '1.5.x', '1.6.x', 'latest'])) {
|
||||
throw new \Exception('Unknown version given');
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
->setTwitter(APP_SOCIAL_TWITTER_HANDLE)
|
||||
->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD)
|
||||
->setDefaultHeaders([
|
||||
'X-Appwrite-Response-Format' => '1.5.0',
|
||||
'X-Appwrite-Response-Format' => '1.6.0',
|
||||
]);
|
||||
|
||||
// Make sure we have a clean slate.
|
||||
@@ -252,6 +252,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
$gitUrl = $language['gitUrl'];
|
||||
$gitBranch = $language['gitBranch'];
|
||||
|
||||
// TEMPORARY
|
||||
$gitBranch = '1.6.x';
|
||||
|
||||
if (!$production) {
|
||||
$gitUrl = 'git@github.com:aw-tests/' . $language['gitRepoName'] . '.git';
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ abstract class ScheduleBase extends Action
|
||||
|
||||
$collectionId = match ($schedule->getAttribute('resourceType')) {
|
||||
'function' => 'functions',
|
||||
'message' => 'messages'
|
||||
'message' => 'messages',
|
||||
'execution' => 'executions'
|
||||
};
|
||||
|
||||
$resource = $getProjectDB($project)->getDocument(
|
||||
@@ -113,7 +114,8 @@ abstract class ScheduleBase extends Action
|
||||
} catch (\Throwable $th) {
|
||||
$collectionId = match ($document->getAttribute('resourceType')) {
|
||||
'function' => 'functions',
|
||||
'message' => 'messages'
|
||||
'message' => 'messages',
|
||||
'execution' => 'executions'
|
||||
};
|
||||
|
||||
Console::error("Failed to load schedule for project {$document['projectId']} {$collectionId} {$document['resourceId']}");
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Func;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Pools\Group;
|
||||
|
||||
class ScheduleExecutions extends ScheduleBase
|
||||
{
|
||||
public const UPDATE_TIMER = 3; // seconds
|
||||
public const ENQUEUE_TIMER = 4; // seconds
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'schedule-executions';
|
||||
}
|
||||
|
||||
public static function getSupportedResource(): string
|
||||
{
|
||||
return 'execution';
|
||||
}
|
||||
|
||||
protected function enqueueResources(Group $pools, Database $dbForConsole): void
|
||||
{
|
||||
$queue = $pools->get('queue')->pop();
|
||||
$connection = $queue->getResource();
|
||||
$queueForFunctions = new Func($connection);
|
||||
|
||||
foreach ($this->schedules as $schedule) {
|
||||
if (!$schedule['active']) {
|
||||
$dbForConsole->deleteDocument(
|
||||
'schedules',
|
||||
$schedule['$id'],
|
||||
);
|
||||
|
||||
unset($this->schedules[$schedule['resourceId']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$now = new \DateTime();
|
||||
$scheduledAt = new \DateTime($schedule['schedule']);
|
||||
|
||||
if ($scheduledAt > $now) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$queueForFunctions
|
||||
->setType('schedule')
|
||||
// Set functionId instead of function as we don't have $dbForProject
|
||||
// TODO: Refactor to use function instead of functionId
|
||||
->setFunctionId($schedule['resource']['functionId'])
|
||||
->setExecution($schedule['resource'])
|
||||
->setMethod($schedule['data']['method'] ?? 'POST')
|
||||
->setPath($schedule['data']['path'] ?? '/')
|
||||
->setHeaders($schedule['data']['headers'] ?? [])
|
||||
->setBody($schedule['data']['body'] ?? '')
|
||||
->setProject($schedule['project'])
|
||||
->trigger();
|
||||
|
||||
$dbForConsole->deleteDocument(
|
||||
'schedules',
|
||||
$schedule['$id'],
|
||||
);
|
||||
|
||||
unset($this->schedules[$schedule['resourceId']]);
|
||||
}
|
||||
|
||||
$queue->reclaim();
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class ScheduleMessages extends ScheduleBase
|
||||
continue;
|
||||
}
|
||||
|
||||
\go(function () use ($now, $schedule, $pools, $dbForConsole) {
|
||||
\go(function () use ($schedule, $pools, $dbForConsole) {
|
||||
$queue = $pools->get('queue')->pop();
|
||||
$connection = $queue->getResource();
|
||||
$queueForMessaging = new Messaging($connection);
|
||||
|
||||
@@ -333,6 +333,7 @@ class Builds extends Action
|
||||
$source = $path;
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source));
|
||||
|
||||
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole);
|
||||
}
|
||||
@@ -400,6 +401,8 @@ class Builds extends Action
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
|
||||
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
|
||||
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
|
||||
'APPWRITE_VERSION' => APP_VERSION_STABLE,
|
||||
'APPWRITE_REGION' => $project->getAttribute('region'),
|
||||
]);
|
||||
|
||||
$command = $deployment->getAttribute('commands', '');
|
||||
@@ -490,6 +493,11 @@ class Builds extends Action
|
||||
$endTime = DateTime::now();
|
||||
$durationEnd = \microtime(true);
|
||||
|
||||
$buildSizeLimit = (int) System::getEnv('_APP_FUNCTIONS_BUILD_SIZE_LIMIT', '2000000000');
|
||||
if ($response['size'] > $buildSizeLimit) {
|
||||
throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / 1048576, 2) . ' MBs.');
|
||||
}
|
||||
|
||||
/** Update the build document */
|
||||
$build->setAttribute('startTime', DateTime::format((new \DateTime())->setTimestamp(floor($response['startTime']))));
|
||||
$build->setAttribute('endTime', $endTime);
|
||||
|
||||
@@ -439,40 +439,24 @@ class Certificates extends Action
|
||||
|
||||
$locale = new Locale(System::getEnv('_APP_LOCALE', 'en'));
|
||||
|
||||
// Send mail to administratore mail
|
||||
// Send mail to administrator mail
|
||||
$template = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-certificate-failed.tpl');
|
||||
$template->setParam('{{domain}}', $domain);
|
||||
$template->setParam('{{error}}', \nl2br($errorMessage));
|
||||
$template->setParam('{{attempts}}', $attempt);
|
||||
|
||||
// TODO: Use setbodyTemplate once #7307 is merged
|
||||
$subject = 'Certificate failed to generate';
|
||||
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl');
|
||||
|
||||
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{body}}', $locale->getText("emails.certificate.body"), escapeHtml: false)
|
||||
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.certificate.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.certificate.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"));
|
||||
$body = $message->render();
|
||||
$body = $template->render();
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'domain' => $domain,
|
||||
'error' => '<br><pre>' . $errorMessage . '</pre>',
|
||||
'attempt' => $attempt,
|
||||
'project' => 'Console',
|
||||
'redirect' => 'https://' . $domain,
|
||||
];
|
||||
|
||||
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setName('Appwrite Administrator')
|
||||
->setbodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
|
||||
->setVariables($emailVariables)
|
||||
->setRecipient(System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')))
|
||||
->trigger();
|
||||
|
||||
@@ -83,6 +83,7 @@ class Functions extends Action
|
||||
$eventData = $payload['payload'] ?? '';
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
$function = new Document($payload['function'] ?? []);
|
||||
$functionId = $payload['functionId'] ?? '';
|
||||
$user = new Document($payload['user'] ?? []);
|
||||
$method = $payload['method'] ?? 'POST';
|
||||
$headers = $payload['headers'] ?? [];
|
||||
@@ -92,6 +93,10 @@ class Functions extends Action
|
||||
return;
|
||||
}
|
||||
|
||||
if ($function->isEmpty() && !empty($functionId)) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
}
|
||||
|
||||
$log->addTag('functionId', $function->getId());
|
||||
$log->addTag('projectId', $project->getId());
|
||||
$log->addTag('type', $type);
|
||||
@@ -176,6 +181,7 @@ class Functions extends Action
|
||||
);
|
||||
break;
|
||||
case 'schedule':
|
||||
$execution = new Document($payload['execution'] ?? []);
|
||||
$this->execute(
|
||||
log: $log,
|
||||
dbForProject: $dbForProject,
|
||||
@@ -193,7 +199,7 @@ class Functions extends Action
|
||||
jwt: null,
|
||||
event: null,
|
||||
eventData: null,
|
||||
executionId: null,
|
||||
executionId: $execution->getId() ?? null
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -367,6 +373,9 @@ class Functions extends Action
|
||||
$headers['x-appwrite-event'] = $event ?? '';
|
||||
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
|
||||
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
|
||||
$headers['x-appwrite-country-code'] = '';
|
||||
$headers['x-appwrite-continent-code'] = '';
|
||||
$headers['x-appwrite-continent-eu'] = 'false';
|
||||
|
||||
/** Create execution or update execution status */
|
||||
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
|
||||
@@ -399,9 +408,7 @@ class Functions extends Action
|
||||
'search' => implode(' ', [$functionId, $executionId]),
|
||||
]);
|
||||
|
||||
if ($function->getAttribute('logging')) {
|
||||
$execution = $dbForProject->createDocument('executions', $execution);
|
||||
}
|
||||
$execution = $dbForProject->createDocument('executions', $execution);
|
||||
|
||||
// TODO: @Meldiron Trigger executions.create event here
|
||||
|
||||
@@ -413,9 +420,7 @@ class Functions extends Action
|
||||
if ($execution->getAttribute('status') !== 'processing') {
|
||||
$execution->setAttribute('status', 'processing');
|
||||
|
||||
if ($function->getAttribute('logging')) {
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
}
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
}
|
||||
|
||||
$durationStart = \microtime(true);
|
||||
@@ -462,6 +467,8 @@ class Functions extends Action
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
|
||||
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
|
||||
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
|
||||
'APPWRITE_VERSION' => APP_VERSION_STABLE,
|
||||
'APPWRITE_REGION' => $project->getAttribute('region'),
|
||||
]);
|
||||
|
||||
/** Execute function */
|
||||
@@ -483,7 +490,8 @@ class Functions extends Action
|
||||
path: $path,
|
||||
method: $method,
|
||||
headers: $headers,
|
||||
runtimeEntrypoint: $command
|
||||
runtimeEntrypoint: $command,
|
||||
logging: $function->getAttribute('logging', true),
|
||||
);
|
||||
|
||||
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
|
||||
@@ -525,9 +533,9 @@ class Functions extends Action
|
||||
;
|
||||
}
|
||||
|
||||
if ($function->getAttribute('logging')) {
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
}
|
||||
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
|
||||
/** Trigger Webhook */
|
||||
$executionModel = new Execution();
|
||||
$queueForEvents
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Fetch;
|
||||
|
||||
class BodyMultipart
|
||||
{
|
||||
/**
|
||||
* @var array<string, mixed> $parts
|
||||
*/
|
||||
private array $parts = [];
|
||||
private string $boundary = "";
|
||||
|
||||
public function __construct(string $boundary = null)
|
||||
{
|
||||
if (is_null($boundary)) {
|
||||
$this->boundary = self::generateBoundary();
|
||||
} else {
|
||||
$this->boundary = $boundary;
|
||||
}
|
||||
}
|
||||
|
||||
public static function generateBoundary(): string
|
||||
{
|
||||
return '-----------------------------' . \uniqid();
|
||||
}
|
||||
|
||||
public function load(string $body): self
|
||||
{
|
||||
$eol = "\r\n";
|
||||
|
||||
$sections = \explode('--' . $this->boundary, $body);
|
||||
|
||||
foreach ($sections as $section) {
|
||||
if (empty($section)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($section, $eol) === 0) {
|
||||
$section = substr($section, \strlen($eol));
|
||||
}
|
||||
|
||||
if (substr($section, -2) === $eol) {
|
||||
$section = substr($section, 0, -1 * \strlen($eol));
|
||||
}
|
||||
|
||||
if ($section == '--') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$partChunks = \explode($eol . $eol, $section, 2);
|
||||
|
||||
if (\count($partChunks) < 2) {
|
||||
continue; // Broken part
|
||||
}
|
||||
|
||||
[ $partHeaders, $partBody ] = $partChunks;
|
||||
$partHeaders = \explode($eol, $partHeaders);
|
||||
|
||||
$partName = "";
|
||||
foreach ($partHeaders as $partHeader) {
|
||||
if (!empty($partName)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$partHeaderArray = \explode(':', $partHeader, 2);
|
||||
|
||||
$partHeaderName = \strtolower($partHeaderArray[0] ?? '');
|
||||
$partHeaderValue = $partHeaderArray[1] ?? '';
|
||||
if ($partHeaderName == "content-disposition") {
|
||||
$dispositionChunks = \explode("; ", $partHeaderValue);
|
||||
foreach ($dispositionChunks as $dispositionChunk) {
|
||||
$dispositionChunkValues = \explode("=", $dispositionChunk, 2);
|
||||
if (\count($dispositionChunkValues) >= 2) {
|
||||
if ($dispositionChunkValues[0] === "name") {
|
||||
$partName = \trim($dispositionChunkValues[1], "\"");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($partName)) {
|
||||
$this->parts[$partName] = $partBody;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getParts(): array
|
||||
{
|
||||
return $this->parts ?? [];
|
||||
}
|
||||
|
||||
public function getPart(string $key, mixed $default = ''): mixed
|
||||
{
|
||||
return $this->parts[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function setPart(string $key, mixed $value): self
|
||||
{
|
||||
$this->parts[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBoundary(): string
|
||||
{
|
||||
return $this->boundary;
|
||||
}
|
||||
|
||||
public function setBoundary(string $boundary): self
|
||||
{
|
||||
$this->boundary = $boundary;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function exportHeader(): string
|
||||
{
|
||||
return 'multipart/form-data; boundary=' . $this->boundary;
|
||||
}
|
||||
|
||||
public function exportBody(): string
|
||||
{
|
||||
$eol = "\r\n";
|
||||
$query = '--' . $this->boundary;
|
||||
|
||||
foreach ($this->parts as $key => $value) {
|
||||
$query .= $eol . 'Content-Disposition: form-data; name="' . $key . '"';
|
||||
|
||||
if (\is_array($value)) {
|
||||
$query .= $eol . 'Content-Type: application/json';
|
||||
$value = \json_encode($value);
|
||||
}
|
||||
|
||||
$query .= $eol . $eol;
|
||||
if ($value === false) {
|
||||
$query .= 0 . $eol;
|
||||
} else {
|
||||
$query .= $value . $eol;
|
||||
}
|
||||
$query .= '--' . $this->boundary;
|
||||
}
|
||||
|
||||
$query .= "--" . $eol;
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Request\Filters;
|
||||
|
||||
use Appwrite\Utopia\Request\Filter;
|
||||
|
||||
class V18 extends Filter
|
||||
{
|
||||
// Convert 1.5 params to 1.6
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
switch ($model) {
|
||||
case 'account.deleteMfaAuthenticator':
|
||||
unset($content['otp']);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ use Appwrite\Utopia\Response\Model\Migration;
|
||||
use Appwrite\Utopia\Response\Model\MigrationFirebaseProject;
|
||||
use Appwrite\Utopia\Response\Model\MigrationReport;
|
||||
use Appwrite\Utopia\Response\Model\Mock;
|
||||
use Appwrite\Utopia\Response\Model\MockNumber;
|
||||
use Appwrite\Utopia\Response\Model\None;
|
||||
use Appwrite\Utopia\Response\Model\Phone;
|
||||
use Appwrite\Utopia\Response\Model\Platform;
|
||||
@@ -86,7 +87,10 @@ use Appwrite\Utopia\Response\Model\Subscriber;
|
||||
use Appwrite\Utopia\Response\Model\Target;
|
||||
use Appwrite\Utopia\Response\Model\Team;
|
||||
use Appwrite\Utopia\Response\Model\TemplateEmail;
|
||||
use Appwrite\Utopia\Response\Model\TemplateFunction;
|
||||
use Appwrite\Utopia\Response\Model\TemplateRuntime;
|
||||
use Appwrite\Utopia\Response\Model\TemplateSMS;
|
||||
use Appwrite\Utopia\Response\Model\TemplateVariable;
|
||||
use Appwrite\Utopia\Response\Model\Token;
|
||||
use Appwrite\Utopia\Response\Model\Topic;
|
||||
use Appwrite\Utopia\Response\Model\UsageBuckets;
|
||||
@@ -250,6 +254,10 @@ class Response extends SwooleResponse
|
||||
public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
|
||||
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
|
||||
public const MODEL_HEADERS = 'headers';
|
||||
public const MODEL_TEMPLATE_FUNCTION = 'templateFunction';
|
||||
public const MODEL_TEMPLATE_FUNCTION_LIST = 'templateFunctionList';
|
||||
public const MODEL_TEMPLATE_RUNTIME = 'templateRuntime';
|
||||
public const MODEL_TEMPLATE_VARIABLE = 'templateVariable';
|
||||
|
||||
// Proxy
|
||||
public const MODEL_PROXY_RULE = 'proxyRule';
|
||||
@@ -269,6 +277,7 @@ class Response extends SwooleResponse
|
||||
public const MODEL_WEBHOOK_LIST = 'webhookList';
|
||||
public const MODEL_KEY = 'key';
|
||||
public const MODEL_KEY_LIST = 'keyList';
|
||||
public const MODEL_MOCK_NUMBER = 'mockNumber';
|
||||
public const MODEL_AUTH_PROVIDER = 'authProvider';
|
||||
public const MODEL_AUTH_PROVIDER_LIST = 'authProviderList';
|
||||
public const MODEL_PLATFORM = 'platform';
|
||||
@@ -338,6 +347,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM))
|
||||
->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP))
|
||||
->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION))
|
||||
->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION))
|
||||
->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION))
|
||||
->setModel(new BaseList('Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_LIST, 'providerRepositories', self::MODEL_PROVIDER_REPOSITORY))
|
||||
->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH))
|
||||
@@ -407,6 +417,9 @@ class Response extends SwooleResponse
|
||||
->setModel(new Team())
|
||||
->setModel(new Membership())
|
||||
->setModel(new Func())
|
||||
->setModel(new TemplateFunction())
|
||||
->setModel(new TemplateRuntime())
|
||||
->setModel(new TemplateVariable())
|
||||
->setModel(new Installation())
|
||||
->setModel(new ProviderRepository())
|
||||
->setModel(new Detection())
|
||||
@@ -419,6 +432,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new Project())
|
||||
->setModel(new Webhook())
|
||||
->setModel(new Key())
|
||||
->setModel(new MockNumber())
|
||||
->setModel(new AuthProvider())
|
||||
->setModel(new Platform())
|
||||
->setModel(new Variable())
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
|
||||
class V18 extends Filter
|
||||
{
|
||||
// Convert 1.6 Data format to 1.5 format
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
$parsedResponse = $content;
|
||||
|
||||
$parsedResponse = match($model) {
|
||||
Response::MODEL_FUNCTION => $this->parseFunction($content),
|
||||
Response::MODEL_EXECUTION => $this->parseExecution($content),
|
||||
Response::MODEL_PROJECT => $this->parseProject($content),
|
||||
default => $parsedResponse,
|
||||
};
|
||||
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
protected function parseExecution(array $content)
|
||||
{
|
||||
unset($content['scheduledAt']);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseFunction(array $content)
|
||||
{
|
||||
unset($content['scopes']);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseProject(array $content)
|
||||
{
|
||||
unset($content['authMockNumbers']);
|
||||
unset($content['authSessionAlerts']);
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
class Execution extends Model
|
||||
@@ -110,6 +111,13 @@ class Execution extends Model
|
||||
'default' => 0,
|
||||
'example' => 0.400,
|
||||
])
|
||||
->addRule('scheduledAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The scheduled time for execution. If left empty, execution will be queued immediately.',
|
||||
'required' => false,
|
||||
'default' => DateTime::now(),
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class Func extends Model
|
||||
->addRule('version', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Version of Open Runtimes used for the function.',
|
||||
'default' => 'v3',
|
||||
'default' => 'v4',
|
||||
'example' => 'v2',
|
||||
])
|
||||
->addRule('installationId', [
|
||||
|
||||
@@ -60,7 +60,7 @@ class Key extends Model
|
||||
])
|
||||
->addRule('accessedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_KEY_ACCCESS / 60 / 60 . ' hours.',
|
||||
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_KEY_ACCESS / 60 / 60 . ' hours.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE
|
||||
])
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class MockNumber extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('phone', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Mock phone number for testing phone authentication. Useful for testing phone authentication without sending an SMS.',
|
||||
'default' => '',
|
||||
'example' => '+1612842323',
|
||||
])
|
||||
->addRule('otp', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Mock OTP for the number. ',
|
||||
'default' => '',
|
||||
'example' => '123456',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Mock Number';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_MOCK_NUMBER;
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,19 @@ class Project extends Model
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authMockNumbers', [
|
||||
'type' => Response::MODEL_MOCK_NUMBER,
|
||||
'description' => 'An array of mock numbers and their corresponding verification codes (OTPs).',
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'example' => [new \stdClass()],
|
||||
])
|
||||
->addRule('authSessionAlerts', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to send session alert emails to users.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('oAuthProviders', [
|
||||
'type' => Response::MODEL_AUTH_PROVIDER,
|
||||
'description' => 'List of Auth Providers.',
|
||||
@@ -321,6 +334,8 @@ class Project extends Model
|
||||
$document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0);
|
||||
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
|
||||
$document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false);
|
||||
$document->setAttribute('authMockNumbers', $authValues['mockNumbers'] ?? []);
|
||||
$document->setAttribute('authSessionAlerts', $authValues['sessionAlerts'] ?? false);
|
||||
|
||||
foreach ($auth as $index => $method) {
|
||||
$key = $method['key'];
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class TemplateFunction extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('icon', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function Template Icon.',
|
||||
'default' => '',
|
||||
'example' => 'icon-lightning-bolt',
|
||||
])
|
||||
->addRule('id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function Template ID.',
|
||||
'default' => '',
|
||||
'example' => 'starter',
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function Template Name.',
|
||||
'default' => '',
|
||||
'example' => 'Starter function',
|
||||
])
|
||||
->addRule('tagline', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function Template Tagline.',
|
||||
'default' => '',
|
||||
'example' => 'A simple function to get started.',
|
||||
])
|
||||
->addRule('permissions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Execution permissions.',
|
||||
'default' => [],
|
||||
'example' => 'any',
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('events', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function trigger events.',
|
||||
'default' => [],
|
||||
'example' => 'account.create',
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('cron', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function execution schedult in CRON format.',
|
||||
'default' => '',
|
||||
'example' => '0 0 * * *',
|
||||
])
|
||||
->addRule('timeout', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Function execution timeout in seconds.',
|
||||
'default' => 15,
|
||||
'example' => 300,
|
||||
])
|
||||
->addRule('useCases', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function use cases.',
|
||||
'default' => [],
|
||||
'example' => 'Starter',
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('runtimes', [
|
||||
'type' => Response::MODEL_TEMPLATE_RUNTIME,
|
||||
'description' => 'List of runtimes that can be used with this template.',
|
||||
'default' => [],
|
||||
'example' => [],
|
||||
'array' => true
|
||||
])
|
||||
->addRule('instructions', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function Template Instructions.',
|
||||
'default' => '',
|
||||
'example' => 'For documentation and instructions check out <link>.',
|
||||
])
|
||||
->addRule('vcsProvider', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'VCS (Version Control System) Provider.',
|
||||
'default' => '',
|
||||
'example' => 'github',
|
||||
])
|
||||
->addRule('providerRepositoryId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'VCS (Version Control System) Repository ID',
|
||||
'default' => '',
|
||||
'example' => 'templates',
|
||||
])
|
||||
->addRule('providerOwner', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'VCS (Version Control System) Owner.',
|
||||
'default' => '',
|
||||
'example' => 'appwrite',
|
||||
])
|
||||
->addRule('providerBranch', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'VCS (Version Control System) branch name',
|
||||
'default' => '',
|
||||
'example' => 'main',
|
||||
])
|
||||
->addRule('variables', [
|
||||
'type' => Response::MODEL_TEMPLATE_VARIABLE,
|
||||
'description' => 'Function variables.',
|
||||
'default' => [],
|
||||
'example' => [],
|
||||
'array' => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Template Function';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_TEMPLATE_FUNCTION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class TemplateRuntime extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Runtime Name.',
|
||||
'default' => '',
|
||||
'example' => 'node-19.0',
|
||||
])
|
||||
->addRule('commands', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The build command used to build the deployment.',
|
||||
'default' => '',
|
||||
'example' => 'npm install',
|
||||
])
|
||||
->addRule('entrypoint', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The entrypoint file used to execute the deployment.',
|
||||
'default' => '',
|
||||
'example' => 'index.js',
|
||||
])
|
||||
->addRule('providerRootDirectory', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Path to function in VCS (Version Control System) repository',
|
||||
'default' => '',
|
||||
'example' => 'node/starter',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Template Runtime';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_TEMPLATE_RUNTIME;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class TemplateVariable extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Name.',
|
||||
'default' => '',
|
||||
'example' => 'APPWRITE_DATABASE_ID',
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Description.',
|
||||
'default' => '',
|
||||
'example' => 'The ID of the Appwrite database that contains the collection to sync.',
|
||||
])
|
||||
->addRule('value', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Value.',
|
||||
'default' => '',
|
||||
'example' => '512',
|
||||
])
|
||||
->addRule('placeholder', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Placeholder.',
|
||||
'default' => '',
|
||||
'example' => '64a55...7b912',
|
||||
])
|
||||
->addRule('required', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Is the variable required?',
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Type.',
|
||||
'default' => '',
|
||||
'example' => 'password',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Template Variable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_TEMPLATE_VARIABLE;
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ class User extends Model
|
||||
])
|
||||
->addRule('accessedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_USER_ACCCESS / 60 / 60 . ' hours.',
|
||||
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_USER_ACCESS / 60 / 60 . ' hours.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Executor;
|
||||
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Utopia\Fetch\BodyMultipart;
|
||||
use Exception;
|
||||
use Utopia\System\System;
|
||||
|
||||
@@ -74,6 +75,12 @@ class Executor
|
||||
$runtimeId = "$projectId-$deploymentId-build";
|
||||
$route = "/runtimes";
|
||||
$timeout = (int) System::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
|
||||
|
||||
// Remove after migration
|
||||
if ($version == 'v3') {
|
||||
$version = 'v4';
|
||||
}
|
||||
|
||||
$params = [
|
||||
'runtimeId' => $runtimeId,
|
||||
'source' => $source,
|
||||
@@ -161,6 +168,7 @@ class Executor
|
||||
* @param string $source
|
||||
* @param string $entrypoint
|
||||
* @param string $runtimeEntrypoint
|
||||
* @param bool $logging
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -178,6 +186,7 @@ class Executor
|
||||
string $method,
|
||||
array $headers,
|
||||
string $runtimeEntrypoint = null,
|
||||
bool $logging,
|
||||
int $requestTimeout = null
|
||||
) {
|
||||
if (empty($headers['host'])) {
|
||||
@@ -185,11 +194,16 @@ class Executor
|
||||
}
|
||||
|
||||
$runtimeId = "$projectId-$deploymentId";
|
||||
$route = '/runtimes/' . $runtimeId . '/execution';
|
||||
$route = '/runtimes/' . $runtimeId . '/executions';
|
||||
|
||||
// Remove after migration
|
||||
if ($version == 'v3') {
|
||||
$version = 'v4';
|
||||
}
|
||||
|
||||
$params = [
|
||||
'runtimeId' => $runtimeId,
|
||||
'variables' => $variables,
|
||||
'body' => $body,
|
||||
'timeout' => $timeout,
|
||||
'path' => $path,
|
||||
'method' => $method,
|
||||
@@ -201,15 +215,21 @@ class Executor
|
||||
'memory' => $this->memory,
|
||||
'version' => $version,
|
||||
'runtimeEntrypoint' => $runtimeEntrypoint,
|
||||
'logging' => $logging,
|
||||
'restartPolicy' => 'always' // Once utopia/orchestration has it, use DockerAPI::ALWAYS (0.13+)
|
||||
];
|
||||
|
||||
if(!empty($body)) {
|
||||
$params['body'] = $body;
|
||||
}
|
||||
|
||||
// Safety timeout. Executor has timeout, and open runtime has soft timeout.
|
||||
// This one shouldn't really happen, but prevents from unexpected networking behaviours.
|
||||
if ($requestTimeout == null) {
|
||||
$requestTimeout = $timeout + 15;
|
||||
}
|
||||
|
||||
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $requestTimeout);
|
||||
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout);
|
||||
|
||||
$status = $response['headers']['status-code'];
|
||||
if ($status >= 400) {
|
||||
@@ -217,6 +237,11 @@ class Executor
|
||||
throw new \Exception($message, $status);
|
||||
}
|
||||
|
||||
$response['body']['headers'] = \json_decode($response['body']['headers'] ?? '{}', true);
|
||||
$response['body']['statusCode'] = \intval($response['body']['statusCode'] ?? 500);
|
||||
$response['body']['duration'] = \floatval($response['body']['duration'] ?? 0);
|
||||
$response['body']['startTime'] = \floatval($response['body']['startTime'] ?? \microtime(true));
|
||||
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
@@ -248,7 +273,13 @@ class Executor
|
||||
break;
|
||||
|
||||
case 'multipart/form-data':
|
||||
$query = $this->flatten($params);
|
||||
$multipart = new BodyMultipart();
|
||||
foreach ($params as $key => $value) {
|
||||
$multipart->setPart($key, $value);
|
||||
}
|
||||
|
||||
$headers['content-type'] = $multipart->exportHeader();
|
||||
$query = $multipart->exportBody();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -315,7 +346,16 @@ class Executor
|
||||
$curlErrorMessage = curl_error($ch);
|
||||
|
||||
if ($decode) {
|
||||
switch (substr($responseType, 0, strpos($responseType, ';'))) {
|
||||
$strpos = strpos($responseType, ';');
|
||||
$strpos = \is_bool($strpos) ? \strlen($responseType) : $strpos;
|
||||
switch (substr($responseType, 0, $strpos)) {
|
||||
case 'multipart/form-data':
|
||||
$boundary = \explode('boundary=', $responseHeaders['content-type'] ?? '')[1] ?? '';
|
||||
$multipartResponse = new BodyMultipart($boundary);
|
||||
$multipartResponse->load(\is_bool($responseBody) ? '' : $responseBody);
|
||||
|
||||
$responseBody = $multipartResponse->getParts();
|
||||
break;
|
||||
case 'application/json':
|
||||
$json = json_decode($responseBody, true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user