mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.7.x' into update-tokens
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Deletes;
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Identities
|
||||
{
|
||||
public static function delete(Database $database, Query $query): void
|
||||
{
|
||||
$database->deleteDocuments(
|
||||
'identities',
|
||||
[
|
||||
$query,
|
||||
Query::orderAsc()
|
||||
],
|
||||
Database::DELETE_BATCH_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Deletes;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Targets
|
||||
{
|
||||
public static function delete(Database $database, Query $query): void
|
||||
{
|
||||
$database->deleteDocuments(
|
||||
'targets',
|
||||
[
|
||||
$query,
|
||||
Query::orderAsc()
|
||||
],
|
||||
Database::DELETE_BATCH_SIZE,
|
||||
fn (Document $target) => self::deleteSubscribers($database, $target)
|
||||
);
|
||||
}
|
||||
|
||||
public static function deleteSubscribers(Database $database, Document $target): void
|
||||
{
|
||||
$database->deleteDocuments(
|
||||
'subscribers',
|
||||
[
|
||||
Query::equal('targetInternalId', [$target->getInternalId()]),
|
||||
Query::orderAsc(),
|
||||
],
|
||||
Database::DELETE_BATCH_SIZE,
|
||||
function (Document $subscriber) use ($database, $target) {
|
||||
$topicId = $subscriber->getAttribute('topicId');
|
||||
$topicInternalId = $subscriber->getAttribute('topicInternalId');
|
||||
$topic = $database->getDocument('topics', $topicId);
|
||||
if (!$topic->isEmpty() && $topic->getInternalId() === $topicInternalId) {
|
||||
$totalAttribute = match ($target->getAttribute('providerType')) {
|
||||
MESSAGE_TYPE_EMAIL => 'emailTotal',
|
||||
MESSAGE_TYPE_SMS => 'smsTotal',
|
||||
MESSAGE_TYPE_PUSH => 'pushTotal',
|
||||
default => throw new Exception('Invalid target provider type'),
|
||||
};
|
||||
$database->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
min: 0
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -117,13 +117,19 @@ class Mapper
|
||||
$list = false;
|
||||
|
||||
foreach ($route->getParams() as $name => $parameter) {
|
||||
$methodParameters = $method->getParameters();
|
||||
$sdkParameters = $method->getParameters();
|
||||
|
||||
if (!empty($methodParameters)) {
|
||||
if (!array_key_exists($name, $methodParameters)) {
|
||||
if (!empty($sdkParameters)) {
|
||||
$sdkMethodParameters = [];
|
||||
foreach ($sdkParameters as $sdkParameter) {
|
||||
$sdkMethodParameters[$sdkParameter->getName()] = $sdkParameter;
|
||||
}
|
||||
|
||||
if (!\array_key_exists($name, $sdkMethodParameters)) {
|
||||
continue;
|
||||
}
|
||||
$optional = $methodParameters[$name]['optional'];
|
||||
|
||||
$optional = $sdkMethodParameters[$name]->getOptional();
|
||||
} else {
|
||||
$optional = $parameter['optional'];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\PDO;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\System\System;
|
||||
@@ -38,9 +39,9 @@ abstract class Migration
|
||||
protected Database $consoleDB;
|
||||
|
||||
/**
|
||||
* @var \PDO
|
||||
* @var PDO
|
||||
*/
|
||||
protected \PDO $pdo;
|
||||
protected PDO $pdo;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@@ -147,10 +148,10 @@ abstract class Migration
|
||||
/**
|
||||
* Set PDO for Migration.
|
||||
*
|
||||
* @param \PDO $pdo
|
||||
* @param PDO $pdo
|
||||
* @return \Appwrite\Migration\Migration
|
||||
*/
|
||||
public function setPDO(\PDO $pdo): self
|
||||
public function setPDO(PDO $pdo): self
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ class Specification extends Validator
|
||||
$allowedSpecifications = [];
|
||||
|
||||
foreach ($this->specifications as $size => $values) {
|
||||
if ($values['cpus'] <= $this->maxCpus && $values['memory'] <= $this->maxMemory) {
|
||||
if (!empty($this->plan) && array_key_exists('specifications', $this->plan)) {
|
||||
if (!\in_array($size, $this->plan['specifications'])) {
|
||||
if ((empty($this->maxCpus) || $values['cpus'] <= $this->maxCpus) && (empty($this->maxMemory) || $values['memory'] <= $this->maxMemory)) {
|
||||
if (!empty($this->plan) && array_key_exists('runtimeSpecifications', $this->plan)) {
|
||||
if (!\in_array($size, $this->plan['runtimeSpecifications'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -68,7 +69,7 @@ class Create extends Action
|
||||
model: Response::MODEL_DEPLOYMENT,
|
||||
)
|
||||
],
|
||||
requestType: 'multipart/form-data',
|
||||
requestType: ContentType::MULTIPART,
|
||||
type: MethodType::UPLOAD,
|
||||
packaging: true,
|
||||
))
|
||||
|
||||
@@ -75,7 +75,6 @@ class Create extends Base
|
||||
)
|
||||
],
|
||||
contentType: ContentType::MULTIPART,
|
||||
requestType: 'application/json',
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
|
||||
|
||||
@@ -14,7 +14,6 @@ use Appwrite\Task\Validator\Cron;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -89,8 +88,8 @@ class Create extends Base
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -131,7 +130,7 @@ class Create extends Base
|
||||
// Temporary abuse check
|
||||
$abuseCheck = function () use ($project, $timelimit, $response) {
|
||||
$abuseKey = "projectId:{projectId},url:{url}";
|
||||
$abuseLimit = App::getEnv('_APP_FUNCTIONS_CREATION_ABUSE_LIMIT', 50);
|
||||
$abuseLimit = System::getEnv('_APP_FUNCTIONS_CREATION_ABUSE_LIMIT', 50);
|
||||
$abuseTime = 86400; // 1 day
|
||||
|
||||
$timeLimit = $timelimit($abuseKey, $abuseLimit, $abuseTime);
|
||||
|
||||
@@ -14,7 +14,6 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Task\Validator\Cron;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -93,8 +92,8 @@ class Update extends Base
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
||||
@@ -59,12 +59,16 @@ class XList extends Base
|
||||
foreach ($allSpecs as $spec) {
|
||||
$spec['enabled'] = true;
|
||||
|
||||
if (array_key_exists('specifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['specifications']);
|
||||
if (array_key_exists('runtimeSpecifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
|
||||
}
|
||||
|
||||
$maxCpus = System::getEnv('_APP_FUNCTIONS_CPUS', 0);
|
||||
$maxMemory = System::getEnv('_APP_FUNCTIONS_MEMORY', 0);
|
||||
|
||||
// Only add specs that are within the limits set by environment variables
|
||||
if ($spec['cpus'] <= System::getEnv('_APP_COMPUTE_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_COMPUTE_MEMORY', 512)) {
|
||||
// Treat 0 as no limit
|
||||
if ((empty($maxCpus) || $spec['cpus'] <= $maxCpus) && (empty($maxMemory) || $spec['memory'] <= $maxMemory)) {
|
||||
$specs[] = $spec;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -65,7 +66,7 @@ class Create extends Action
|
||||
model: Response::MODEL_DEPLOYMENT,
|
||||
)
|
||||
],
|
||||
requestType: 'multipart/form-data',
|
||||
requestType: ContentType::MULTIPART,
|
||||
type: MethodType::UPLOAD,
|
||||
packaging: true,
|
||||
))
|
||||
|
||||
@@ -11,7 +11,6 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -82,8 +81,8 @@ class Create extends Base
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Framework specification for the site and builds.', true, ['plan'])
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
||||
@@ -12,7 +12,6 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -86,8 +85,8 @@ class Update extends Base
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Framework specification for the site and builds.', true, ['plan'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
||||
@@ -59,12 +59,16 @@ class XList extends Base
|
||||
foreach ($allSpecs as $spec) {
|
||||
$spec['enabled'] = true;
|
||||
|
||||
if (array_key_exists('specifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['specifications']);
|
||||
if (array_key_exists('runtimeSpecifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
|
||||
}
|
||||
|
||||
$maxCpus = System::getEnv('_APP_FUNCTIONS_CPUS', 0);
|
||||
$maxMemory = System::getEnv('_APP_FUNCTIONS_MEMORY', 0);
|
||||
|
||||
// Only add specs that are within the limits set by environment variables
|
||||
if ($spec['cpus'] <= System::getEnv('_APP_COMPUTE_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_COMPUTE_MEMORY', 512)) {
|
||||
// Treat 0 as no limit
|
||||
if ((empty($maxCpus) || $spec['cpus'] <= $maxCpus) && (empty($maxMemory) || $spec['memory'] <= $maxMemory)) {
|
||||
$specs[] = $spec;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,27 @@ class Maintenance extends Action
|
||||
Console::title('Maintenance V1');
|
||||
Console::success(APP_NAME . ' maintenance process v1 has started');
|
||||
|
||||
// # of days in seconds (1 day = 86400s)
|
||||
$interval = (int) System::getEnv('_APP_MAINTENANCE_INTERVAL', '86400');
|
||||
$delay = (int) System::getEnv('_APP_MAINTENANCE_DELAY', '0');
|
||||
$interval = (int) System::getEnv('_APP_MAINTENANCE_INTERVAL', '86400'); // 1 day
|
||||
$usageStatsRetentionHourly = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_HOURLY', '8640000'); //100 days
|
||||
$cacheRetention = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days
|
||||
$schedulesDeletionRetention = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_SCHEDULES', '86400'); // 1 Day
|
||||
$jobInitTime = System::getEnv('_APP_MAINTENANCE_START_TIME', '00:00'); // (hour:minutes)
|
||||
|
||||
$now = new \DateTime();
|
||||
$now->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
$next = new \DateTime($now->format("Y-m-d $jobInitTime"));
|
||||
$next->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
$delay = $next->getTimestamp() - $now->getTimestamp();
|
||||
|
||||
/**
|
||||
* If time passed for the target day.
|
||||
*/
|
||||
if ($delay <= 0) {
|
||||
$next->add(\DateInterval::createFromDateString('1 days'));
|
||||
$delay = $next->getTimestamp() - $now->getTimestamp();
|
||||
}
|
||||
|
||||
Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.');
|
||||
|
||||
Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
|
||||
$time = DateTime::now();
|
||||
|
||||
@@ -33,7 +33,7 @@ class Migrate extends Action
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('register')
|
||||
->callback([$this, 'action']);
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
private function clearProjectsCache(Document $project)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Swoole\Runtime;
|
||||
use Swoole\Timer;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
@@ -13,8 +14,9 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\System\System;
|
||||
|
||||
use function Swoole\Coroutine\run;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Telemetry\Gauge;
|
||||
use Utopia\Telemetry\Histogram;
|
||||
|
||||
abstract class ScheduleBase extends Action
|
||||
{
|
||||
@@ -23,6 +25,11 @@ abstract class ScheduleBase extends Action
|
||||
|
||||
protected array $schedules = [];
|
||||
|
||||
private ?Histogram $collectSchedulesTelemetryDuration = null;
|
||||
private ?Gauge $collectSchedulesTelemetryCount = null;
|
||||
private ?Gauge $scheduleTelemetryCount = null;
|
||||
private ?Histogram $enqueueDelayTelemetry = null;
|
||||
|
||||
abstract public static function getName(): string;
|
||||
abstract public static function getSupportedResource(): string;
|
||||
abstract public static function getCollectionId(): string;
|
||||
@@ -37,7 +44,8 @@ abstract class ScheduleBase extends Action
|
||||
->inject('pools')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->callback([$this, 'action']);
|
||||
->inject('telemetry')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
protected function updateProjectAccess(Document $project, Database $dbForPlatform): void
|
||||
@@ -56,11 +64,44 @@ abstract class ScheduleBase extends Action
|
||||
* 2. Create timer that sync all changes from 'schedules' collection to local copy. Only reading changes thanks to 'resourceUpdatedAt' attribute
|
||||
* 3. Create timer that prepares coroutines for soon-to-execute schedules. When it's ready, coroutine sleeps until exact time before sending request to worker.
|
||||
*/
|
||||
public function action(Group $pools, Database $dbForPlatform, callable $getProjectDB): void
|
||||
public function action(Group $pools, Database $dbForPlatform, callable $getProjectDB, Telemetry $telemetry): void
|
||||
{
|
||||
Runtime::enableCoroutine();
|
||||
|
||||
Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1');
|
||||
Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started');
|
||||
|
||||
$this->scheduleTelemetryCount = $telemetry->createGauge('task.schedule.count');
|
||||
$this->collectSchedulesTelemetryDuration = $telemetry->createHistogram('task.schedule.collect_schedules.duration', 's');
|
||||
$this->collectSchedulesTelemetryCount = $telemetry->createGauge('task.schedule.collect_schedules.count');
|
||||
$this->enqueueDelayTelemetry = $telemetry->createHistogram('task.schedule.enqueue_delay', 's');
|
||||
|
||||
// start with "0" to load all active documents.
|
||||
$lastSyncUpdate = "0";
|
||||
$this->collectSchedules($pools, $dbForPlatform, $getProjectDB, $lastSyncUpdate);
|
||||
|
||||
Console::success("Starting timers at " . DateTime::now());
|
||||
/**
|
||||
* The timer synchronize $schedules copy with database collection.
|
||||
*/
|
||||
Timer::tick(static::UPDATE_TIMER * 1000, function () use ($pools, $dbForPlatform, $getProjectDB, &$lastSyncUpdate) {
|
||||
$time = DateTime::now();
|
||||
Console::log("Sync tick: Running at $time");
|
||||
$this->collectSchedules($pools, $dbForPlatform, $getProjectDB, $lastSyncUpdate);
|
||||
});
|
||||
|
||||
while (true) {
|
||||
$this->enqueueResources($pools, $dbForPlatform, $getProjectDB);
|
||||
$this->scheduleTelemetryCount->record(count($this->schedules), ['resourceType' => static::getSupportedResource()]);
|
||||
sleep(static::ENQUEUE_TIMER);
|
||||
}
|
||||
}
|
||||
|
||||
private function collectSchedules(Group $pools, Database $dbForPlatform, callable $getProjectDB, ?string &$lastSyncUpdate): void
|
||||
{
|
||||
// If we haven't synced yet, load all active schedules
|
||||
$initialLoad = $lastSyncUpdate === "0";
|
||||
|
||||
/**
|
||||
* Extract only necessary attributes to lower memory used.
|
||||
*
|
||||
@@ -68,7 +109,7 @@ abstract class ScheduleBase extends Action
|
||||
* @throws Exception
|
||||
* @var Document $schedule
|
||||
*/
|
||||
$getSchedule = function (Document $schedule) use ($dbForPlatform, $getProjectDB): array {
|
||||
$getSchedule = function (Document $schedule) use ($pools, $dbForPlatform, $getProjectDB): array {
|
||||
$project = $dbForPlatform->getDocument('projects', $schedule->getAttribute('projectId'));
|
||||
|
||||
$resource = $getProjectDB($project)->getDocument(
|
||||
@@ -76,6 +117,8 @@ abstract class ScheduleBase extends Action
|
||||
$schedule->getAttribute('resourceId')
|
||||
);
|
||||
|
||||
$pools->reclaim();
|
||||
|
||||
return [
|
||||
'$internalId' => $schedule->getInternalId(),
|
||||
'$id' => $schedule->getId(),
|
||||
@@ -88,12 +131,12 @@ abstract class ScheduleBase extends Action
|
||||
];
|
||||
};
|
||||
|
||||
$lastSyncUpdate = DateTime::now();
|
||||
$loadStart = microtime(true);
|
||||
$time = DateTime::now();
|
||||
|
||||
$limit = 10_000;
|
||||
$sum = $limit;
|
||||
$total = 0;
|
||||
$loadStart = \microtime(true);
|
||||
$latestDocument = null;
|
||||
|
||||
while ($sum === $limit) {
|
||||
@@ -110,105 +153,59 @@ abstract class ScheduleBase extends Action
|
||||
$regions[] = 'default';
|
||||
}
|
||||
|
||||
$results = $dbForPlatform->find('schedules', \array_merge($paginationQueries, [
|
||||
$paginationQueries = [
|
||||
...$paginationQueries,
|
||||
Query::equal('region', $regions),
|
||||
Query::equal('resourceType', [static::getSupportedResource()]),
|
||||
Query::equal('active', [true]),
|
||||
]));
|
||||
];
|
||||
|
||||
$sum = \count($results);
|
||||
if ($initialLoad) {
|
||||
$paginationQueries[] = Query::equal('active', [true]);
|
||||
} else {
|
||||
$paginationQueries[] = Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate);
|
||||
}
|
||||
|
||||
$results = $dbForPlatform->find('schedules', $paginationQueries);
|
||||
|
||||
$sum = count($results);
|
||||
$total = $total + $sum;
|
||||
|
||||
foreach ($results as $document) {
|
||||
try {
|
||||
$this->schedules[$document->getInternalId()] = $getSchedule($document);
|
||||
} catch (\Throwable $th) {
|
||||
$collectionId = static::getCollectionId();
|
||||
Console::error("Failed to load schedule for project {$document['projectId']} {$collectionId} {$document['resourceId']}");
|
||||
Console::error($th->getMessage());
|
||||
$localDocument = $this->schedules[$document->getInternalId()] ?? null;
|
||||
|
||||
if ($localDocument !== null) {
|
||||
if (!$document['active']) {
|
||||
Console::info("Removing: {$document['resourceType']}::{$document['resourceId']}");
|
||||
unset($this->schedules[$document->getInternalId()]);
|
||||
} elseif (strtotime($localDocument['resourceUpdatedAt']) !== strtotime($document['resourceUpdatedAt'])) {
|
||||
Console::info("Updating: {$document['resourceType']}::{$document['resourceId']}");
|
||||
$this->schedules[$document->getInternalId()] = $getSchedule($document);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$this->schedules[$document->getInternalId()] = $getSchedule($document);
|
||||
} catch (\Throwable $th) {
|
||||
$collectionId = static::getCollectionId();
|
||||
Console::error("Failed to load schedule for project {$document['projectId']} {$collectionId} {$document['resourceId']}");
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$latestDocument = \end($results);
|
||||
}
|
||||
|
||||
$pools->reclaim();
|
||||
$lastSyncUpdate = $time;
|
||||
$duration = microtime(true) - $loadStart;
|
||||
$this->collectSchedulesTelemetryDuration->record($duration, ['initial' => $initialLoad, 'resourceType' => static::getSupportedResource()]);
|
||||
$this->collectSchedulesTelemetryCount->record($total, ['initial' => $initialLoad, 'resourceType' => static::getSupportedResource()]);
|
||||
Console::success("{$total} resources were loaded in " . $duration . " seconds");
|
||||
}
|
||||
|
||||
Console::success("{$total} resources were loaded in " . (\microtime(true) - $loadStart) . " seconds");
|
||||
|
||||
Console::success("Starting timers at " . DateTime::now());
|
||||
|
||||
run(function () use ($dbForPlatform, &$lastSyncUpdate, $getSchedule, $pools, $getProjectDB) {
|
||||
/**
|
||||
* The timer synchronize $schedules copy with database collection.
|
||||
*/
|
||||
Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForPlatform, &$lastSyncUpdate, $getSchedule, $pools) {
|
||||
$time = DateTime::now();
|
||||
$timerStart = \microtime(true);
|
||||
|
||||
$limit = 1000;
|
||||
$sum = $limit;
|
||||
$total = 0;
|
||||
$latestDocument = null;
|
||||
|
||||
Console::log("Sync tick: Running at $time");
|
||||
|
||||
while ($sum === $limit) {
|
||||
$paginationQueries = [Query::limit($limit)];
|
||||
|
||||
if ($latestDocument) {
|
||||
$paginationQueries[] = Query::cursorAfter($latestDocument);
|
||||
}
|
||||
|
||||
// Temporarly accepting both 'fra' and 'default'
|
||||
// When all migrated, only use _APP_REGION with 'default' as default value
|
||||
$regions = [System::getEnv('_APP_REGION', 'default')];
|
||||
if (!in_array('default', $regions)) {
|
||||
$regions[] = 'default';
|
||||
}
|
||||
|
||||
$results = $dbForPlatform->find('schedules', \array_merge($paginationQueries, [
|
||||
Query::equal('region', $regions),
|
||||
Query::equal('resourceType', [static::getSupportedResource()]),
|
||||
Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate),
|
||||
]));
|
||||
|
||||
$sum = count($results);
|
||||
$total = $total + $sum;
|
||||
|
||||
foreach ($results as $document) {
|
||||
$localDocument = $this->schedules[$document->getInternalId()] ?? null;
|
||||
|
||||
// Check if resource has been updated since last sync
|
||||
$org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null;
|
||||
$new = \strtotime($document['resourceUpdatedAt']);
|
||||
|
||||
if (!$document['active']) {
|
||||
Console::info("Removing: {$document['resourceType']}::{$document['resourceId']}");
|
||||
unset($this->schedules[$document->getInternalId()]);
|
||||
} elseif ($new !== $org) {
|
||||
Console::info("Updating: {$document['resourceType']}::{$document['resourceId']}");
|
||||
$this->schedules[$document->getInternalId()] = $getSchedule($document);
|
||||
}
|
||||
}
|
||||
|
||||
$latestDocument = \end($results);
|
||||
}
|
||||
|
||||
$lastSyncUpdate = $time;
|
||||
$timerEnd = \microtime(true);
|
||||
|
||||
$pools->reclaim();
|
||||
|
||||
Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds");
|
||||
});
|
||||
|
||||
Timer::tick(
|
||||
static::ENQUEUE_TIMER * 1000,
|
||||
fn () => $this->enqueueResources($pools, $dbForPlatform, $getProjectDB)
|
||||
);
|
||||
|
||||
$this->enqueueResources($pools, $dbForPlatform, $getProjectDB);
|
||||
});
|
||||
protected function recordEnqueueDelay(string $expectedExecutionSchedule): void
|
||||
{
|
||||
$now = strtotime('now');
|
||||
$scheduledAt = strtotime($expectedExecutionSchedule);
|
||||
$this->enqueueDelayTelemetry->record($now - $scheduledAt, ['resourceType' => static::getSupportedResource()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ class ScheduleExecutions extends ScheduleBase
|
||||
->setProject($schedule['project'])
|
||||
->setUserId($data['userId'] ?? '')
|
||||
->trigger();
|
||||
|
||||
$this->recordEnqueueDelay($schedule['schedule']);
|
||||
});
|
||||
|
||||
$dbForPlatform->deleteDocument(
|
||||
|
||||
@@ -95,6 +95,8 @@ class ScheduleFunctions extends ScheduleBase
|
||||
->setPath('/')
|
||||
->setProject($schedule['project'])
|
||||
->trigger();
|
||||
|
||||
$this->recordEnqueueDelay($schedule['schedule']);
|
||||
}
|
||||
|
||||
$queue->reclaim();
|
||||
|
||||
@@ -59,7 +59,7 @@ class ScheduleMessages extends ScheduleBase
|
||||
);
|
||||
|
||||
$queue->reclaim();
|
||||
|
||||
$this->recordEnqueueDelay($schedule['schedule']);
|
||||
unset($this->schedules[$schedule['$internalId']]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\Specification\Format\OpenAPI3;
|
||||
use Appwrite\Specification\Format\Swagger2;
|
||||
use Appwrite\Specification\Specification;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Specification\Format\OpenAPI3;
|
||||
use Appwrite\SDK\Specification\Format\Swagger2;
|
||||
use Appwrite\SDK\Specification\Specification;
|
||||
use Appwrite\Utopia\Request as AppwriteRequest;
|
||||
use Appwrite\Utopia\Response as AppwriteResponse;
|
||||
use Exception;
|
||||
@@ -19,7 +20,6 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MySQL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Request as UtopiaRequest;
|
||||
use Utopia\Response as UtopiaResponse;
|
||||
use Utopia\System\System;
|
||||
@@ -49,11 +49,10 @@ class Specs extends Action
|
||||
->desc('Generate Appwrite API specifications')
|
||||
->param('version', 'latest', new Text(16), 'Spec version', true)
|
||||
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
|
||||
->inject('register')
|
||||
->callback([$this, 'action']);
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $version, string $mode, Registry $register): void
|
||||
public function action(string $version, string $mode): void
|
||||
{
|
||||
$appRoutes = App::getRoutes();
|
||||
$response = $this->getResponse();
|
||||
@@ -194,7 +193,7 @@ class Specs extends Action
|
||||
}
|
||||
|
||||
foreach ($sdks as $sdk) {
|
||||
/** @var \Appwrite\SDK\Method $sdks */
|
||||
/** @var Method $sdk */
|
||||
|
||||
$hide = $sdk->isHidden();
|
||||
if ($hide === true || (\is_array($hide) && \in_array($platform, $hide))) {
|
||||
@@ -262,7 +261,6 @@ class Specs extends Action
|
||||
$services[] = [
|
||||
'name' => $service['key'] ?? '',
|
||||
'description' => $service['subtitle'] ?? '',
|
||||
'x-globalAttributes' => $service['globalAttributes'] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -274,7 +272,15 @@ class Specs extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
|
||||
$arguments = [
|
||||
new App('UTC'),
|
||||
$services,
|
||||
$routes,
|
||||
$models,
|
||||
$keys[$platform],
|
||||
$authCounts[$platform] ?? 0
|
||||
];
|
||||
|
||||
foreach (['swagger2', 'open-api3'] as $format) {
|
||||
$formatInstance = match ($format) {
|
||||
'swagger2' => new Swagger2(...$arguments),
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Certificates\Adapter as CertificatesAdapter;
|
||||
use Appwrite\Deletes\Identities;
|
||||
use Appwrite\Deletes\Targets;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Executor\Executor;
|
||||
use Throwable;
|
||||
@@ -166,7 +168,7 @@ class Deletes extends Action
|
||||
$this->deleteTopic($project, $getProjectDB, $document);
|
||||
break;
|
||||
case DELETE_TYPE_TARGET:
|
||||
$this->deleteTargetSubscribers($project, $getProjectDB, $document);
|
||||
Targets::deleteSubscribers($getProjectDB($project), $document);
|
||||
break;
|
||||
case DELETE_TYPE_EXPIRED_TARGETS:
|
||||
$this->deleteExpiredTargets($project, $getProjectDB);
|
||||
@@ -282,47 +284,6 @@ class Deletes extends Action
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
* @param Document $target
|
||||
* @throws Exception
|
||||
*/
|
||||
private function deleteTargetSubscribers(Document $project, callable $getProjectDB, Document $target): void
|
||||
{
|
||||
/** @var Database */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
// Delete subscribers and decrement topic counts
|
||||
$this->deleteByGroup(
|
||||
'subscribers',
|
||||
[
|
||||
Query::equal('targetInternalId', [$target->getInternalId()]),
|
||||
Query::orderAsc(),
|
||||
],
|
||||
$dbForProject,
|
||||
function (Document $subscriber) use ($dbForProject, $target) {
|
||||
$topicId = $subscriber->getAttribute('topicId');
|
||||
$topicInternalId = $subscriber->getAttribute('topicInternalId');
|
||||
$topic = $dbForProject->getDocument('topics', $topicId);
|
||||
if (!$topic->isEmpty() && $topic->getInternalId() === $topicInternalId) {
|
||||
$totalAttribute = match ($target->getAttribute('providerType')) {
|
||||
MESSAGE_TYPE_EMAIL => 'emailTotal',
|
||||
MESSAGE_TYPE_SMS => 'smsTotal',
|
||||
MESSAGE_TYPE_PUSH => 'pushTotal',
|
||||
default => throw new Exception('Invalid target CertificatesAdapter type'),
|
||||
};
|
||||
$dbForProject->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
min: 0
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
@@ -332,32 +293,12 @@ class Deletes extends Action
|
||||
*/
|
||||
private function deleteExpiredTargets(Document $project, callable $getProjectDB): void
|
||||
{
|
||||
$this->deleteByGroup(
|
||||
'targets',
|
||||
[
|
||||
Query::equal('expired', [true]),
|
||||
Query::orderAsc(),
|
||||
],
|
||||
$getProjectDB($project),
|
||||
function (Document $target) use ($getProjectDB, $project) {
|
||||
$this->deleteTargetSubscribers($project, $getProjectDB, $target);
|
||||
}
|
||||
);
|
||||
Targets::delete($getProjectDB($project), Query::equal('expired', [true]));
|
||||
}
|
||||
|
||||
private function deleteSessionTargets(Document $project, callable $getProjectDB, Document $session): void
|
||||
{
|
||||
$this->deleteByGroup(
|
||||
'targets',
|
||||
[
|
||||
Query::equal('sessionInternalId', [$session->getInternalId()]),
|
||||
Query::orderAsc(),
|
||||
],
|
||||
$getProjectDB($project),
|
||||
function (Document $target) use ($getProjectDB, $project) {
|
||||
$this->deleteTargetSubscribers($project, $getProjectDB, $target);
|
||||
}
|
||||
);
|
||||
Targets::delete($getProjectDB($project), Query::equal('sessionInternalId', [$session->getInternalId()]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -742,23 +683,10 @@ class Deletes extends Action
|
||||
], $dbForProject);
|
||||
|
||||
// Delete identities
|
||||
$this->deleteByGroup('identities', [
|
||||
Query::equal('userInternalId', [$userInternalId]),
|
||||
Query::orderAsc()
|
||||
], $dbForProject);
|
||||
Identities::delete($dbForProject, Query::equal('userInternalId', [$userInternalId]));
|
||||
|
||||
// Delete targets
|
||||
$this->deleteByGroup(
|
||||
'targets',
|
||||
[
|
||||
Query::equal('userInternalId', [$userInternalId]),
|
||||
Query::orderAsc()
|
||||
],
|
||||
$dbForProject,
|
||||
function (Document $target) use ($getProjectDB, $project) {
|
||||
$this->deleteTargetSubscribers($project, $getProjectDB, $target);
|
||||
}
|
||||
);
|
||||
Targets::delete($dbForProject, Query::equal('userInternalId', [$userInternalId]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,6 +38,13 @@ class Migrations extends Action
|
||||
|
||||
protected Document $project;
|
||||
|
||||
/**
|
||||
* Cached for performance.
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
protected array $sourceReport = [];
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
@@ -109,7 +116,7 @@ class Migrations extends Action
|
||||
$credentials = $migration->getAttribute('credentials');
|
||||
$migrationOptions = $migration->getAttribute('options');
|
||||
|
||||
return match ($source) {
|
||||
$migrationSource = match ($source) {
|
||||
Firebase::getName() => new Firebase(
|
||||
json_decode($credentials['serviceAccount'], true),
|
||||
),
|
||||
@@ -144,6 +151,10 @@ class Migrations extends Action
|
||||
),
|
||||
default => throw new \Exception('Invalid source type'),
|
||||
};
|
||||
|
||||
$this->sourceReport = $migrationSource->report();
|
||||
|
||||
return $migrationSource;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,8 +273,6 @@ class Migrations extends Action
|
||||
$source = $this->processSource($migration);
|
||||
$destination = $this->processDestination($migration, $tempAPIKey);
|
||||
|
||||
$source->report();
|
||||
|
||||
$transfer = new Transfer(
|
||||
$source,
|
||||
$destination
|
||||
|
||||
+10
-10
@@ -21,16 +21,14 @@ class Method
|
||||
* @param string $description
|
||||
* @param array<AuthType> $auth
|
||||
* @param array<SDKResponse> $responses
|
||||
* @param ContentType $responseType
|
||||
* @param MethodType|null $methodType
|
||||
* @param ContentType $contentType
|
||||
* @param MethodType|null $type
|
||||
* @param bool $deprecated
|
||||
* @param array|bool $hide
|
||||
* @param bool $packaging
|
||||
* @param string $requestType
|
||||
* @param array $parameters
|
||||
* @param ContentType $requestType
|
||||
* @param array<Parameter> $parameters
|
||||
* @param array $additionalParameters
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $namespace,
|
||||
@@ -44,7 +42,7 @@ class Method
|
||||
protected bool $deprecated = false,
|
||||
protected array|bool $hide = false,
|
||||
protected bool $packaging = false,
|
||||
protected string $requestType = 'application/json',
|
||||
protected ContentType $requestType = ContentType::JSON,
|
||||
protected array $parameters = [],
|
||||
protected array $additionalParameters = []
|
||||
) {
|
||||
@@ -53,7 +51,6 @@ class Method
|
||||
$this->validateDesc($description);
|
||||
|
||||
foreach ($responses as $response) {
|
||||
/** @var SDKResponse $response */
|
||||
$this->validateResponseModel($response->getModel());
|
||||
$this->validateNoContent($response);
|
||||
}
|
||||
@@ -193,11 +190,14 @@ class Method
|
||||
return $this->packaging;
|
||||
}
|
||||
|
||||
public function getRequestType(): string
|
||||
public function getRequestType(): ContentType
|
||||
{
|
||||
return $this->requestType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Parameter>
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
@@ -276,7 +276,7 @@ class Method
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRequestType(string $requestType): self
|
||||
public function setRequestType(ContentType $requestType): self
|
||||
{
|
||||
$this->requestType = $requestType;
|
||||
return $this;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\SDK;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class Parameter
|
||||
{
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $description
|
||||
* @param mixed|null $default
|
||||
* @param Validator|callable|null $validator
|
||||
* @param bool $optional
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected string $description = '',
|
||||
protected mixed $default = null,
|
||||
protected mixed $validator = null,
|
||||
protected bool $optional = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription(string $description): static
|
||||
{
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDefault(): mixed
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
public function setDefault(mixed $default): static
|
||||
{
|
||||
$this->default = $default;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValidator(): mixed
|
||||
{
|
||||
return $this->validator;
|
||||
}
|
||||
|
||||
public function setValidator(mixed $validator): static
|
||||
{
|
||||
$this->validator = $validator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOptional(): bool
|
||||
{
|
||||
return $this->optional;
|
||||
}
|
||||
|
||||
public function setOptional(bool $optional): static
|
||||
{
|
||||
$this->optional = $optional;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Specification;
|
||||
namespace Appwrite\SDK\Specification;
|
||||
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\App;
|
||||
@@ -12,12 +12,12 @@ abstract class Format
|
||||
protected App $app;
|
||||
|
||||
/**
|
||||
* @var Route[]
|
||||
* @var array<Route>
|
||||
*/
|
||||
protected array $routes;
|
||||
|
||||
/**
|
||||
* @var Model[]
|
||||
* @var array<Model>
|
||||
*/
|
||||
protected array $models;
|
||||
|
||||
@@ -487,4 +487,24 @@ abstract class Format
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
protected function getNestedModels(Model $model, array &$usedModels): void
|
||||
{
|
||||
foreach ($model->getRules() as $rule) {
|
||||
if (!in_array($model->getType(), $usedModels)) {
|
||||
continue;
|
||||
}
|
||||
$types = (array)$rule['type'];
|
||||
foreach ($types as $ruleType) {
|
||||
if (!in_array($ruleType, ['string', 'integer', 'boolean', 'json', 'float'])) {
|
||||
$usedModels[] = $ruleType;
|
||||
foreach ($this->models as $m) {
|
||||
if ($m->getType() === $ruleType) {
|
||||
$this->getNestedModels($m, $usedModels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
-64
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Specification\Format;
|
||||
namespace Appwrite\SDK\Specification\Format;
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\Specification\Format;
|
||||
use Appwrite\SDK\Response;
|
||||
use Appwrite\SDK\Specification\Format;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
@@ -22,41 +24,6 @@ class OpenAPI3 extends Format
|
||||
return 'Open API 3';
|
||||
}
|
||||
|
||||
protected function getNestedModels(Model $model, array &$usedModels): void
|
||||
{
|
||||
foreach ($model->getRules() as $rule) {
|
||||
if (!in_array($model->getType(), $usedModels)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_array($rule['type'])) {
|
||||
foreach ($rule['type'] as $ruleType) {
|
||||
if (!in_array($ruleType, ['string', 'integer', 'boolean', 'json', 'float', 'double'])) {
|
||||
$usedModels[] = $ruleType;
|
||||
|
||||
foreach ($this->models as $m) {
|
||||
if ($m->getType() === $ruleType) {
|
||||
$this->getNestedModels($m, $usedModels);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])) {
|
||||
$usedModels[] = $rule['type'];
|
||||
|
||||
foreach ($this->models as $m) {
|
||||
if ($m->getType() === $rule['type']) {
|
||||
$this->getNestedModels($m, $usedModels);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function parse(): array
|
||||
{
|
||||
/**
|
||||
@@ -133,22 +100,20 @@ class OpenAPI3 extends Format
|
||||
}
|
||||
|
||||
$additionalMethods = null;
|
||||
if (is_array($sdk)) {
|
||||
$mainSdk = array_shift($sdk);
|
||||
if (\is_array($sdk)) {
|
||||
$additionalMethods = $sdk;
|
||||
|
||||
$sdk = $mainSdk;
|
||||
$sdk = $sdk[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \Appwrite\SDK\Method $sdk
|
||||
* @var Method $sdk
|
||||
*/
|
||||
$consumes = [$sdk->getRequestType()];
|
||||
$consumes = [$sdk->getRequestType()->value];
|
||||
|
||||
$method = $sdk->getMethodName() ?? \uniqid();
|
||||
|
||||
if (!empty($method) && is_array($method)) {
|
||||
$method = array_keys($method)[0];
|
||||
if (!empty($method) && \is_array($method)) {
|
||||
$method = \array_keys($method)[0];
|
||||
}
|
||||
|
||||
$desc = $sdk->getDescriptionFilePath() ?: $sdk->getDescription();
|
||||
@@ -161,10 +126,8 @@ class OpenAPI3 extends Format
|
||||
case AuthType::SESSION:
|
||||
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||
break;
|
||||
case AuthType::KEY:
|
||||
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case AuthType::JWT:
|
||||
case AuthType::KEY:
|
||||
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case AuthType::ADMIN:
|
||||
@@ -209,39 +172,47 @@ class OpenAPI3 extends Format
|
||||
|
||||
|
||||
if (!empty($additionalMethods)) {
|
||||
$temp['x-appwrite']['additional-methods'] = [];
|
||||
$temp['x-appwrite']['methods'] = [];
|
||||
foreach ($additionalMethods as $method) {
|
||||
/** @var \Appwrite\SDK\Method $method */
|
||||
/** @var Method $method */
|
||||
$desc = $method->getDescriptionFilePath();
|
||||
$additionalMethod = [
|
||||
'name' => $method->getMethodName(),
|
||||
'parameters' => [],
|
||||
'required' => [],
|
||||
'responses' => []
|
||||
'responses' => [],
|
||||
'description' => ($desc) ? \file_get_contents($desc) : '',
|
||||
];
|
||||
|
||||
foreach ($method->getParameters() as $name => $param) {
|
||||
$additionalMethod['parameters'][] = $name;
|
||||
foreach ($method->getParameters() as $parameter) {
|
||||
$additionalMethod['parameters'][] = $parameter->getName();
|
||||
|
||||
if (!$param['optional']) {
|
||||
$additionalMethod['required'][] = $name;
|
||||
if (!$parameter->getOptional()) {
|
||||
$additionalMethod['required'][] = $parameter->getName();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($method->getResponses() as $response) {
|
||||
/** @var \Appwrite\SDK\Response $response */
|
||||
$additionalMethod['responses'][] = [
|
||||
'code' => $response->getCode(),
|
||||
'model' => '#/components/schemas/' . $response->getModel()
|
||||
];
|
||||
if (\is_array($response->getModel())) {
|
||||
$additionalMethod['responses'][] = [
|
||||
'code' => $response->getCode(),
|
||||
'model' => \array_map(fn ($m) => '#/components/schemas/' . $m, $response->getModel())
|
||||
];
|
||||
} else {
|
||||
$additionalMethod['responses'][] = [
|
||||
'code' => $response->getCode(),
|
||||
'model' => '#/components/schemas/' . $response->getModel()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$temp['x-appwrite']['additional-methods'][] = $additionalMethod;
|
||||
$temp['x-appwrite']['methods'][] = $additionalMethod;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle response models
|
||||
foreach ($sdk->getResponses() as $response) {
|
||||
/** @var \Appwrite\SDK\Response $response */
|
||||
/** @var Response $response */
|
||||
$model = $response->getModel();
|
||||
|
||||
foreach ($this->models as $value) {
|
||||
@@ -309,11 +280,11 @@ class OpenAPI3 extends Format
|
||||
}
|
||||
}
|
||||
|
||||
if ((!empty($scope))) { // && 'public' != $scope
|
||||
if ((!empty($scope))) {
|
||||
$securities = ['Project' => []];
|
||||
|
||||
foreach ($sdk->getAuth() as $security) {
|
||||
/** @var \Appwrite\SDK\AuthType $security */
|
||||
/** @var AuthType $security */
|
||||
if (array_key_exists($security->value, $this->keys)) {
|
||||
$securities[$security->value] = [];
|
||||
}
|
||||
+40
-64
@@ -1,18 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Specification\Format;
|
||||
namespace Appwrite\SDK\Specification\Format;
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\Specification\Format;
|
||||
use Appwrite\SDK\Response;
|
||||
use Appwrite\SDK\Specification\Format;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Route;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Swagger2 extends Format
|
||||
{
|
||||
@@ -21,41 +25,6 @@ class Swagger2 extends Format
|
||||
return 'Swagger 2';
|
||||
}
|
||||
|
||||
protected function getNestedModels(Model $model, array &$usedModels): void
|
||||
{
|
||||
foreach ($model->getRules() as $rule) {
|
||||
if (!in_array($model->getType(), $usedModels)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_array($rule['type'])) {
|
||||
foreach ($rule['type'] as $ruleType) {
|
||||
if (!in_array($ruleType, ['string', 'integer', 'boolean', 'json', 'float'])) {
|
||||
$usedModels[] = $ruleType;
|
||||
|
||||
foreach ($this->models as $m) {
|
||||
if ($m->getType() === $ruleType) {
|
||||
$this->getNestedModels($m, $usedModels);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])) {
|
||||
$usedModels[] = $rule['type'];
|
||||
|
||||
foreach ($this->models as $m) {
|
||||
if ($m->getType() === $rule['type']) {
|
||||
$this->getNestedModels($m, $usedModels);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function parse(): array
|
||||
{
|
||||
/*
|
||||
@@ -118,11 +87,12 @@ class Swagger2 extends Format
|
||||
$usedModels = [];
|
||||
|
||||
foreach ($this->routes as $route) {
|
||||
/** @var \Utopia\Route $route */
|
||||
/** @var Route $route */
|
||||
$url = \str_replace('/v1', '', $route->getPath());
|
||||
|
||||
$scope = $route->getLabel('scope', '');
|
||||
|
||||
/** @var \Appwrite\SDK\Method $sdk */
|
||||
/** @var Method $sdk */
|
||||
$sdk = $route->getLabel('sdk', false);
|
||||
|
||||
if (empty($sdk)) {
|
||||
@@ -130,16 +100,14 @@ class Swagger2 extends Format
|
||||
}
|
||||
|
||||
$additionalMethods = null;
|
||||
if (is_array($sdk)) {
|
||||
$mainSdk = array_shift($sdk);
|
||||
if (\is_array($sdk)) {
|
||||
$additionalMethods = $sdk;
|
||||
|
||||
$sdk = $mainSdk;
|
||||
$sdk = $sdk[0];
|
||||
}
|
||||
|
||||
$consumes = [];
|
||||
if (strtoupper($route->getMethod()) !== 'GET' && strtoupper($route->getMethod()) !== 'HEAD') {
|
||||
$consumes = [$sdk->getRequestType()];
|
||||
$consumes = [$sdk->getRequestType()->value];
|
||||
}
|
||||
|
||||
$method = $sdk->getMethodName() ?? \uniqid();
|
||||
@@ -158,10 +126,8 @@ class Swagger2 extends Format
|
||||
case AuthType::SESSION:
|
||||
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||
break;
|
||||
case AuthType::KEY:
|
||||
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case AuthType::JWT:
|
||||
case AuthType::KEY:
|
||||
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||
break;
|
||||
case AuthType::ADMIN:
|
||||
@@ -211,41 +177,49 @@ class Swagger2 extends Format
|
||||
}
|
||||
|
||||
if (!empty($additionalMethods)) {
|
||||
$temp['x-appwrite']['additional-methods'] = [];
|
||||
$temp['x-appwrite']['methods'] = [];
|
||||
foreach ($additionalMethods as $method) {
|
||||
/** @var \Appwrite\SDK\Method $method */
|
||||
/** @var Method $method */
|
||||
$desc = $method->getDescriptionFilePath();
|
||||
|
||||
$additionalMethod = [
|
||||
'name' => $method->getMethodName(),
|
||||
'parameters' => [],
|
||||
'required' => [],
|
||||
'responses' => [],
|
||||
'description' => $method->getDescription(),
|
||||
'description' => ($desc) ? \file_get_contents($desc) : '',
|
||||
];
|
||||
|
||||
foreach ($method->getParameters() as $name => $param) {
|
||||
$additionalMethod['parameters'][] = $name;
|
||||
foreach ($method->getParameters() as $parameter) {
|
||||
$additionalMethod['parameters'][] = $parameter->getName();
|
||||
|
||||
if (!$param['optional']) {
|
||||
$additionalMethod['required'][] = $name;
|
||||
if (!$parameter->getOptional()) {
|
||||
$additionalMethod['required'][] = $parameter->getName();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($method->getResponses() as $response) {
|
||||
/** @var \Appwrite\SDK\Response $response */
|
||||
$additionalMethod['responses'][] = [
|
||||
'code' => $response->getCode(),
|
||||
'model' => '#/definitions/' . $response->getModel()
|
||||
];
|
||||
/** @var Response $response */
|
||||
if (\is_array($response->getModel())) {
|
||||
$additionalMethod['responses'][] = [
|
||||
'code' => $response->getCode(),
|
||||
'model' => \array_map(fn ($m) => '#/definitions/' . $m, $response->getModel())
|
||||
];
|
||||
} else {
|
||||
$additionalMethod['responses'][] = [
|
||||
'code' => $response->getCode(),
|
||||
'model' => '#/definitions/' . $response->getModel()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$temp['x-appwrite']['additional-methods'][] = $additionalMethod;
|
||||
$temp['x-appwrite']['methods'][] = $additionalMethod;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Responses
|
||||
|
||||
foreach ($sdk->getResponses() as $response) {
|
||||
/** @var \Appwrite\SDK\Response $response */
|
||||
/** @var Response $response */
|
||||
$model = $response->getModel();
|
||||
|
||||
foreach ($this->models as $value) {
|
||||
@@ -340,7 +314,9 @@ class Swagger2 extends Format
|
||||
|
||||
foreach ($parameters as $name => $param) { // Set params
|
||||
/** @var Validator $validator */
|
||||
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
|
||||
$validator = (\is_callable($param['validator']))
|
||||
? ($param['validator'])(...$this->app->getResources($param['injections']))
|
||||
: $param['validator'];
|
||||
|
||||
$node = [
|
||||
'name' => $name,
|
||||
@@ -485,7 +461,7 @@ class Swagger2 extends Format
|
||||
$node['type'] = $validator->getType();
|
||||
break;
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
/** @var \Utopia\Validator\WhiteList $validator */
|
||||
/** @var WhiteList $validator */
|
||||
$node['type'] = $validator->getType();
|
||||
$node['x-example'] = $validator->getList()[0];
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Specification;
|
||||
namespace Appwrite\SDK\Specification;
|
||||
|
||||
class Specification
|
||||
{
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Utopia;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\Utopia\Request\Filter;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
@@ -29,35 +30,49 @@ class Request extends UtopiaRequest
|
||||
{
|
||||
$parameters = parent::getParams();
|
||||
|
||||
if ($this->hasFilters() && self::hasRoute()) {
|
||||
$methods = self::getRoute()->getLabel('sdk', null);
|
||||
if (!$this->hasFilters() || !self::hasRoute()) {
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
if (!\is_array($methods)) {
|
||||
$methods = [$methods];
|
||||
}
|
||||
$methods = self::getRoute()->getLabel('sdk', null);
|
||||
|
||||
$methods = \array_filter($methods, function ($method) {
|
||||
return !empty($method);
|
||||
});
|
||||
|
||||
$params = [];
|
||||
$endpointIdentifier = 'unknown.unknown';
|
||||
|
||||
foreach ($methods as $method) {
|
||||
/** @var \Appwrite\SDK\Method $method */
|
||||
$endpointIdentifier = $method->getNamespace() . '.' . $method->getMethodName();
|
||||
$params += $method->getParameters();
|
||||
}
|
||||
|
||||
if (!empty($params)) {
|
||||
$parameters = array_filter($parameters, function ($key) use ($params) {
|
||||
return array_key_exists($key, $params);
|
||||
}, \ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
if (empty($methods)) {
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
if (!\is_array($methods)) {
|
||||
$id = $methods->getNamespace() . '.' . $methods->getMethodName();
|
||||
foreach ($this->getFilters() as $filter) {
|
||||
$parameters = $filter->parse($parameters, $endpointIdentifier);
|
||||
$parameters = $filter->parse($parameters, $id);
|
||||
}
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
$matched = null;
|
||||
foreach ($methods as $method) {
|
||||
/** @var Method|null $method */
|
||||
if ($method === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the method that matches the parameters passed
|
||||
$methodParamNames = \array_map(fn ($param) => $param->getName(), $method->getParameters());
|
||||
$invalidParams = \array_diff(\array_keys($parameters), $methodParamNames);
|
||||
|
||||
// No params defined, or all params are valid
|
||||
if (empty($methodParamNames) || empty($invalidParams)) {
|
||||
$matched = $method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$id = $matched !== null
|
||||
? $matched->getNamespace() . '.' . $matched->getMethodName()
|
||||
: 'unknown.unknown';
|
||||
|
||||
// Apply filters
|
||||
foreach ($this->getFilters() as $filter) {
|
||||
$parameters = $filter->parse($parameters, $id);
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
|
||||
Reference in New Issue
Block a user