mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.7.x' into fix-missing-filter
This commit is contained in:
@@ -196,9 +196,9 @@ abstract class OAuth2
|
||||
|
||||
if (!empty($payload)) {
|
||||
\curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
$headers[] = 'Content-length: ' . \strlen($payload);
|
||||
}
|
||||
|
||||
$headers[] = 'Content-length: ' . \strlen($payload);
|
||||
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
// Send the request & save response to $response
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
// Reference Material
|
||||
// https://www.figma.com/developers/api#oauth2
|
||||
// https://www.figma.com/developers/api#authentication
|
||||
|
||||
class Figma extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
'current_user:read'
|
||||
];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'figma';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
return 'https://www.figma.com/oauth?' . \http_build_query([
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
'state' => \json_encode($this->state)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$headers = [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret)
|
||||
];
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
'https://api.figma.com/v1/oauth/token',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'redirect_uri' => $this->callback,
|
||||
'code' => $code,
|
||||
'grant_type' => 'authorization_code'
|
||||
])
|
||||
), true);
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$headers = [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Authorization: Basic ' . \base64_encode($this->appID . ':' . $this->appSecret)
|
||||
];
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
'https://api.figma.com/v1/oauth/refresh',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'refresh_token' => $refreshToken
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['id'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['email'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth email is verified
|
||||
*
|
||||
* Figma requires email verification during signup,
|
||||
* so if we have an email, it's verified
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$email = $this->getUserEmail($accessToken);
|
||||
|
||||
return !empty($email);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['handle'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$headers = ['Authorization: Bearer ' . $accessToken];
|
||||
$user = $this->request(
|
||||
'GET',
|
||||
'https://api.figma.com/v1/me',
|
||||
$headers
|
||||
);
|
||||
$this->user = \json_decode($user, true);
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ class Audit extends Event
|
||||
protected string $ip = '';
|
||||
protected string $hostname = '';
|
||||
|
||||
protected bool $critical = false;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
@@ -24,23 +24,12 @@ class Event
|
||||
public const FUNCTIONS_QUEUE_NAME = 'v1-functions';
|
||||
public const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
|
||||
|
||||
/** remove */
|
||||
public const USAGE_QUEUE_NAME = 'v1-usage';
|
||||
public const USAGE_CLASS_NAME = 'UsageV1';
|
||||
|
||||
public const USAGE_DUMP_QUEUE_NAME = 'v1-usage-dump';
|
||||
public const USAGE_DUMP_CLASS_NAME = 'UsageDumpV1';
|
||||
/** /remove */
|
||||
|
||||
public const STATS_RESOURCES_QUEUE_NAME = 'v1-stats-resources';
|
||||
public const STATS_RESOURCES_CLASS_NAME = 'StatsResourcesV1';
|
||||
|
||||
public const STATS_USAGE_QUEUE_NAME = 'v1-stats-usage';
|
||||
public const STATS_USAGE_CLASS_NAME = 'StatsUsageV1';
|
||||
|
||||
public const STATS_USAGE_DUMP_QUEUE_NAME = 'v1-stats-usage-dump';
|
||||
public const STATS_USAGE_DUMP_CLASS_NAME = 'StatsUsageDumpV1';
|
||||
|
||||
public const WEBHOOK_QUEUE_NAME = 'v1-webhooks';
|
||||
public const WEBHOOK_CLASS_NAME = 'WebhooksV1';
|
||||
|
||||
@@ -68,6 +57,9 @@ class Event
|
||||
protected ?string $userId = null;
|
||||
protected bool $paused = false;
|
||||
|
||||
/** @var bool Non-critical events will not throw an exception when enqueuing of the event fails. */
|
||||
protected bool $critical = true;
|
||||
|
||||
/**
|
||||
* @param Publisher $publisher
|
||||
* @return void
|
||||
@@ -351,6 +343,7 @@ class Event
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
|
||||
if ($this->paused) {
|
||||
return false;
|
||||
}
|
||||
@@ -360,7 +353,15 @@ class Event
|
||||
|
||||
// Merge the base payload with any trimmed values
|
||||
$payload = array_merge($this->preparePayload(), $this->trimPayload());
|
||||
return $this->publisher->enqueue($queue, $payload);
|
||||
|
||||
try {
|
||||
return $this->publisher->enqueue($queue, $payload);
|
||||
} catch (\Throwable $th) {
|
||||
if ($this->critical) {
|
||||
throw $th;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,15 +2,22 @@
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Appwrite\Messaging\Adapter;
|
||||
use Appwrite\Messaging\Adapter\Realtime as RealtimeAdapter;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception;
|
||||
|
||||
class Realtime extends Event
|
||||
{
|
||||
protected array $subscribers = [];
|
||||
|
||||
private Adapter $realtime;
|
||||
|
||||
protected bool $critical = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->realtime = new Adapter\Realtime();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +64,7 @@ class Realtime extends Event
|
||||
* Execute Event.
|
||||
*
|
||||
* @return string|bool
|
||||
* @throws InvalidArgumentException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
@@ -87,7 +94,7 @@ class Realtime extends Event
|
||||
: [$target['projectId'] ?? $this->getProject()->getId()];
|
||||
|
||||
foreach ($projectIds as $projectId) {
|
||||
RealtimeAdapter::send(
|
||||
$this->realtime->send(
|
||||
projectId: $projectId,
|
||||
payload: $this->getRealtimePayload(),
|
||||
events: $allEvents,
|
||||
|
||||
@@ -6,6 +6,8 @@ use Utopia\Queue\Publisher;
|
||||
|
||||
class StatsResources extends Event
|
||||
{
|
||||
protected bool $critical = false;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
@@ -11,6 +11,8 @@ class StatsUsage extends Event
|
||||
protected array $reduce = [];
|
||||
protected array $disabled = [];
|
||||
|
||||
protected bool $critical = false;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Queue\Publisher;
|
||||
|
||||
class StatsUsageDump extends Event
|
||||
{
|
||||
protected array $stats;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::STATS_USAGE_DUMP_QUEUE_NAME)
|
||||
->setClass(Event::STATS_USAGE_DUMP_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Stats.
|
||||
*
|
||||
* @param array $stats
|
||||
* @return self
|
||||
*/
|
||||
public function setStats(array $stats): self
|
||||
{
|
||||
$this->stats = $stats;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the payload for the usage dump event.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
return [
|
||||
'stats' => $this->stats,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,7 @@ class Exception extends \Exception
|
||||
public const DATABASE_NOT_FOUND = 'database_not_found';
|
||||
public const DATABASE_ALREADY_EXISTS = 'database_already_exists';
|
||||
public const DATABASE_TIMEOUT = 'database_timeout';
|
||||
public const DATABASE_QUERY_ORDER_NULL = 'database_query_order_null';
|
||||
|
||||
/** Collections */
|
||||
public const COLLECTION_NOT_FOUND = 'collection_not_found';
|
||||
|
||||
@@ -6,5 +6,5 @@ abstract class Adapter
|
||||
{
|
||||
abstract public function subscribe(string $projectId, mixed $identifier, array $roles, array $channels): void;
|
||||
abstract public function unsubscribe(mixed $identifier): void;
|
||||
abstract public static function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options): void;
|
||||
abstract public function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options): void;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Pools\Pool;
|
||||
|
||||
class Realtime extends Adapter
|
||||
{
|
||||
@@ -35,6 +36,14 @@ class Realtime extends Adapter
|
||||
*/
|
||||
public array $subscriptions = [];
|
||||
|
||||
private Pool $pubsubPool;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $register;
|
||||
$this->pubsubPool = $register->get('pools')->get('pubsub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a subscription.
|
||||
*
|
||||
@@ -129,7 +138,7 @@ class Realtime extends Adapter
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public static function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options = []): void
|
||||
public function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options = []): void
|
||||
{
|
||||
if (empty($channels) || empty($roles) || empty($projectId)) {
|
||||
return;
|
||||
@@ -138,26 +147,20 @@ class Realtime extends Adapter
|
||||
$permissionsChanged = array_key_exists('permissionsChanged', $options) && $options['permissionsChanged'];
|
||||
$userId = array_key_exists('userId', $options) ? $options['userId'] : null;
|
||||
|
||||
global $register;
|
||||
$pubsub = $register->get('pools')->get('pubsub')->pop();
|
||||
try {
|
||||
/** @var \Appwrite\PubSub\Adapter $redis */
|
||||
$redis = $pubsub->getResource();
|
||||
$redis->publish('realtime', json_encode([
|
||||
'project' => $projectId,
|
||||
'roles' => $roles,
|
||||
'permissionsChanged' => $permissionsChanged,
|
||||
'userId' => $userId,
|
||||
'data' => [
|
||||
'events' => $events,
|
||||
'channels' => $channels,
|
||||
'timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
'payload' => $payload
|
||||
]
|
||||
]));
|
||||
} finally {
|
||||
$pubsub->reclaim();
|
||||
}
|
||||
$message = [
|
||||
'project' => $projectId,
|
||||
'roles' => $roles,
|
||||
'permissionsChanged' => $permissionsChanged,
|
||||
'userId' => $userId,
|
||||
'data' => [
|
||||
'events' => $events,
|
||||
'channels' => $channels,
|
||||
'timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
'payload' => $payload
|
||||
]
|
||||
];
|
||||
|
||||
$this->pubsubPool->use(fn (\Appwrite\PubSub\Adapter $pubsub) => $pubsub->publish('realtime', json_encode($message)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,6 +82,14 @@ class V21 extends Migration
|
||||
Console::warning("'type' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
case 'migrations':
|
||||
// Create destination attribute
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'destination');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'destination' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
case 'schedules':
|
||||
// Create data attribute
|
||||
try {
|
||||
@@ -91,7 +99,14 @@ class V21 extends Migration
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'databases':
|
||||
// Create originalId attribute
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'originalId');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'originalId' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
case 'functions':
|
||||
// Create scopes attribute
|
||||
try {
|
||||
|
||||
@@ -36,6 +36,7 @@ class Get extends Action
|
||||
->label('scope', 'rules.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'console',
|
||||
group: null,
|
||||
name: 'getResource',
|
||||
description: <<<EOT
|
||||
Check if a resource ID is available.
|
||||
|
||||
@@ -52,6 +52,7 @@ class Create extends Action
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'createDeployment',
|
||||
description: <<<EOT
|
||||
Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.
|
||||
|
||||
@@ -41,6 +41,7 @@ class Delete extends Action
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'deleteDeployment',
|
||||
description: <<<EOT
|
||||
Delete a code deployment by its unique ID.
|
||||
|
||||
@@ -33,11 +33,12 @@ class Get extends Action
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
->httpAlias('/v1/functions/:functionId/deployments/:deploymentId/build/download', ['type' => 'output'])
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Download deployment')
|
||||
->desc('Get deployment download')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'getDeploymentDownload',
|
||||
description: <<<EOT
|
||||
Get a function deployment content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.
|
||||
|
||||
@@ -41,6 +41,7 @@ class Create extends Action
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'createDuplicateDeployment',
|
||||
description: <<<EOT
|
||||
Create a new build for an existing function deployment. This endpoint allows you to rebuild a deployment with the updated function configuration, including its entrypoint and build commands if they have been modified. The build process will be queued and executed asynchronously. The original deployment's code will be preserved and used for the new build.
|
||||
|
||||
@@ -32,6 +32,7 @@ class Get extends Action
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'getDeployment',
|
||||
description: <<<EOT
|
||||
Get a function deployment by its unique ID.
|
||||
|
||||
@@ -39,6 +39,7 @@ class Update extends Action
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'updateDeploymentStatus',
|
||||
description: <<<EOT
|
||||
Cancel an ongoing function deployment build. If the build is already in progress, it will be stopped and marked as canceled. If the build hasn't started yet, it will be marked as canceled without executing. You cannot cancel builds that have already completed (status 'ready') or failed. The response includes the final build status and details.
|
||||
|
||||
@@ -46,6 +46,7 @@ class Create extends Base
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'createTemplateDeployment',
|
||||
description: <<<EOT
|
||||
Create a deployment based on a template.
|
||||
|
||||
@@ -44,6 +44,7 @@ class Create extends Base
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'createVcsDeployment',
|
||||
description: <<<EOT
|
||||
Create a deployment when a function is connected to VCS.
|
||||
|
||||
@@ -10,6 +10,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Deployments;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
@@ -38,6 +39,7 @@ class XList extends Action
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'deployments',
|
||||
name: 'listDeployments',
|
||||
description: <<<EOT
|
||||
Get a list of all the function's code deployments. You can use the query params to filter your results.
|
||||
@@ -112,8 +114,12 @@ class XList extends Action
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$results = $dbForProject->find('deployments', $queries);
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
try {
|
||||
$results = $dbForProject->find('deployments', $queries);
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'deployments' => $results,
|
||||
|
||||
@@ -62,6 +62,7 @@ class Create extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'executions',
|
||||
name: 'createExecution',
|
||||
description: <<<EOT
|
||||
Trigger a function execution. The returned object will return you the current execution status. You can ping the `Get Execution` endpoint to get updates on the current execution status. Once this endpoint is called, your function execution process will start asynchronously.
|
||||
@@ -451,15 +452,6 @@ class Create extends Base
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) {
|
||||
$execution->setAttribute('logs', '');
|
||||
$execution->setAttribute('errors', '');
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
foreach (($executionResponse['headers'] ?? []) as $key => $value) {
|
||||
$headers[] = ['name' => $key, 'value' => $value];
|
||||
|
||||
@@ -42,6 +42,7 @@ class Delete extends Base
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'executions',
|
||||
name: 'deleteExecution',
|
||||
description: <<<EOT
|
||||
Delete a function execution by its unique ID.
|
||||
|
||||
@@ -35,6 +35,7 @@ class Get extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'executions',
|
||||
name: 'getExecution',
|
||||
description: <<<EOT
|
||||
Get a function execution log by its unique ID.
|
||||
@@ -79,14 +80,6 @@ class Get extends Base
|
||||
throw new Exception(Exception::EXECUTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
if (!$isPrivilegedUser && !$isAppUser) {
|
||||
$execution->setAttribute('logs', '');
|
||||
$execution->setAttribute('errors', '');
|
||||
}
|
||||
|
||||
$response->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Executions;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
@@ -40,6 +41,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'executions',
|
||||
name: 'listExecutions',
|
||||
description: <<<EOT
|
||||
Get a list of all the current user function execution logs. You can use the query params to filter your results.
|
||||
@@ -111,18 +113,11 @@ class XList extends Base
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$results = $dbForProject->find('executions', $queries);
|
||||
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
if (!$isPrivilegedUser && !$isAppUser) {
|
||||
$results = array_map(function ($execution) {
|
||||
$execution->setAttribute('logs', '');
|
||||
$execution->setAttribute('errors', '');
|
||||
return $execution;
|
||||
}, $results);
|
||||
try {
|
||||
$results = $dbForProject->find('executions', $queries);
|
||||
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
|
||||
@@ -56,6 +56,7 @@ class Create extends Base
|
||||
->label('audits.resource', 'function/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'functions',
|
||||
name: 'create',
|
||||
description: <<<EOT
|
||||
Create a new function. You can pass a list of [permissions](https://appwrite.io/docs/permissions) to allow different project users or team with access to execute the function using the client API.
|
||||
|
||||
@@ -41,6 +41,7 @@ class Delete extends Base
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'functions',
|
||||
name: 'delete',
|
||||
description: <<<EOT
|
||||
Delete a function by its unique ID.
|
||||
|
||||
@@ -42,6 +42,7 @@ class Update extends Base
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'functions',
|
||||
name: 'updateFunctionDeployment',
|
||||
description: <<<EOT
|
||||
Update the function active deployment. Use this endpoint to switch the code deployment that should be used when visitor opens your function.
|
||||
|
||||
@@ -33,6 +33,7 @@ class Get extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'functions',
|
||||
name: 'get',
|
||||
description: <<<EOT
|
||||
Get a function by its unique ID.
|
||||
|
||||
@@ -60,6 +60,7 @@ class Update extends Base
|
||||
->label('audits.resource', 'function/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'functions',
|
||||
name: 'update',
|
||||
description: <<<EOT
|
||||
Update function by its unique ID.
|
||||
|
||||
@@ -11,6 +11,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Functions;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
@@ -38,6 +39,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'functions',
|
||||
name: 'list',
|
||||
description: <<<EOT
|
||||
Get a list of all the project's functions. You can use the query params to filter your results.
|
||||
@@ -100,9 +102,16 @@ class XList extends Base
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
try {
|
||||
$functions = $dbForProject->find('functions', $queries);
|
||||
$total = $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'functions' => $dbForProject->find('functions', $queries),
|
||||
'total' => $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT),
|
||||
'functions' => $functions,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_FUNCTION_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'runtimes',
|
||||
name: 'listRuntimes',
|
||||
description: <<<EOT
|
||||
Get a list of all runtimes that are currently active on your instance.
|
||||
|
||||
@@ -33,6 +33,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'runtimes',
|
||||
name: 'listSpecifications',
|
||||
description: <<<EOT
|
||||
List allowed function specifications for this instance.
|
||||
|
||||
@@ -34,6 +34,7 @@ class Get extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'templates',
|
||||
name: 'getTemplate',
|
||||
description: <<<EOT
|
||||
Get a function template using ID. You can use template details in [createFunction](/docs/references/cloud/server-nodejs/functions#create) method.
|
||||
|
||||
@@ -35,6 +35,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'templates',
|
||||
name: 'listTemplates',
|
||||
description: <<<EOT
|
||||
List available function templates. You can use template details in [createFunction](/docs/references/cloud/server-nodejs/functions#create) method.
|
||||
|
||||
@@ -38,6 +38,7 @@ class Get extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: null,
|
||||
name: 'getUsage',
|
||||
description: <<<EOT
|
||||
Get usage metrics and statistics for a for a specific function. View statistics including total deployments, builds, executions, storage usage, and compute time. The response includes both current totals and historical data for each metric. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, defaults to 30 days.
|
||||
|
||||
@@ -36,6 +36,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: null,
|
||||
name: 'listUsage',
|
||||
description: <<<EOT
|
||||
Get usage metrics and statistics for all functions in the project. View statistics including total deployments, builds, logs, storage usage, and compute time. The response includes both current totals and historical data for each metric. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, defaults to 30 days.
|
||||
|
||||
@@ -44,6 +44,7 @@ class Create extends Base
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'variables',
|
||||
name: 'createVariable',
|
||||
description: <<<EOT
|
||||
Create a new function environment variable. These variables can be accessed in the function at runtime as environment variables.
|
||||
|
||||
@@ -38,6 +38,7 @@ class Delete extends Base
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'variables',
|
||||
name: 'deleteVariable',
|
||||
description: <<<EOT
|
||||
Delete a variable by its unique ID.
|
||||
|
||||
@@ -35,6 +35,7 @@ class Get extends Base
|
||||
'sdk',
|
||||
new Method(
|
||||
namespace: 'functions',
|
||||
group: 'variables',
|
||||
name: 'getVariable',
|
||||
description: <<<EOT
|
||||
Get a variable by its unique ID.
|
||||
|
||||
@@ -40,6 +40,7 @@ class Update extends Base
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
group: 'variables',
|
||||
name: 'updateVariable',
|
||||
description: <<<EOT
|
||||
Update variable by its unique ID.
|
||||
|
||||
@@ -36,6 +36,7 @@ class XList extends Base
|
||||
'sdk',
|
||||
new Method(
|
||||
namespace: 'functions',
|
||||
group: 'variables',
|
||||
name: 'listVariables',
|
||||
description: <<<EOT
|
||||
Get a list of all variables of a specific function.
|
||||
|
||||
@@ -37,6 +37,7 @@ class Create extends Action
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
name: 'createDevKey',
|
||||
description: <<<EOT
|
||||
Create a new project dev key. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development. Strictly meant for development purposes only.
|
||||
|
||||
@@ -31,6 +31,7 @@ class Delete extends Action
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
name: 'deleteDevKey',
|
||||
description: <<<EOT
|
||||
Delete a project\'s dev key by its unique ID. Once deleted, the key will no longer allow bypassing of rate limits and better logging of errors.
|
||||
|
||||
@@ -31,6 +31,7 @@ class Get extends Action
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
name: 'getDevKey',
|
||||
description: <<<EOT
|
||||
Get a project\'s dev key by its unique ID. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.
|
||||
|
||||
@@ -32,6 +32,7 @@ class Update extends Action
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
name: 'updateDevKey',
|
||||
description: <<<EOT
|
||||
Update a project\'s dev key by its unique ID. Use this endpoint to update a project\'s dev key name or expiration time.'
|
||||
|
||||
@@ -36,6 +36,7 @@ class XList extends Action
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
name: 'listDevKeys',
|
||||
description: <<<EOT
|
||||
List all the project\'s dev keys. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.'
|
||||
|
||||
@@ -44,6 +44,7 @@ class Create extends Action
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
group: null,
|
||||
name: 'createAPIRule',
|
||||
description: <<<EOT
|
||||
Create a new proxy rule for serving Appwrite's API on custom domain.
|
||||
@@ -70,24 +71,39 @@ class Create extends Action
|
||||
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
|
||||
{
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$deniedDomains = [
|
||||
$mainDomain,
|
||||
$sitesDomain,
|
||||
$functionsDomain,
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL,
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
}
|
||||
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
if (!empty($functionsDomain)) {
|
||||
$deniedDomains[] = $functionsDomain;
|
||||
}
|
||||
|
||||
$denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', '');
|
||||
$denyListDomains = \array_map('trim', explode(',', $denyListDomains));
|
||||
foreach ($denyListDomains as $denyListDomain) {
|
||||
if (empty($denyListDomain)) {
|
||||
continue;
|
||||
}
|
||||
$deniedDomains[] = $denyListDomain;
|
||||
}
|
||||
|
||||
if (\in_array($domain, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -46,6 +46,7 @@ class Create extends Action
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
group: null,
|
||||
name: 'createFunctionRule',
|
||||
description: <<<EOT
|
||||
Create a new proxy rule for executing Appwrite Function on custom domain.
|
||||
@@ -75,20 +76,39 @@ class Create extends Action
|
||||
|
||||
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
{
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$deniedDomains = [
|
||||
$mainDomain,
|
||||
$sitesDomain,
|
||||
$functionsDomain,
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL,
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
}
|
||||
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
if (!empty($functionsDomain)) {
|
||||
$deniedDomains[] = $functionsDomain;
|
||||
}
|
||||
|
||||
$denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', '');
|
||||
$denyListDomains = \array_map('trim', explode(',', $denyListDomains));
|
||||
foreach ($denyListDomains as $denyListDomain) {
|
||||
if (empty($denyListDomain)) {
|
||||
continue;
|
||||
}
|
||||
$deniedDomains[] = $denyListDomain;
|
||||
}
|
||||
|
||||
if (\in_array($domain, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -46,6 +46,7 @@ class Create extends Action
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
group: null,
|
||||
name: 'createRedirectRule',
|
||||
description: <<<EOT
|
||||
Create a new proxy rule for to redirect from custom domain to another domain.
|
||||
@@ -74,20 +75,39 @@ class Create extends Action
|
||||
|
||||
public function action(string $domain, string $url, int $statusCode, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
|
||||
{
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$deniedDomains = [
|
||||
$mainDomain,
|
||||
$sitesDomain,
|
||||
$functionsDomain,
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL,
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
}
|
||||
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
if (!empty($functionsDomain)) {
|
||||
$deniedDomains[] = $functionsDomain;
|
||||
}
|
||||
|
||||
$denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', '');
|
||||
$denyListDomains = \array_map('trim', explode(',', $denyListDomains));
|
||||
foreach ($denyListDomains as $denyListDomain) {
|
||||
if (empty($denyListDomain)) {
|
||||
continue;
|
||||
}
|
||||
$deniedDomains[] = $denyListDomain;
|
||||
}
|
||||
|
||||
if (\in_array($domain, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -46,6 +46,7 @@ class Create extends Action
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
group: null,
|
||||
name: 'createSiteRule',
|
||||
description: <<<EOT
|
||||
Create a new proxy rule for serving Appwrite Site on custom domain.
|
||||
@@ -75,20 +76,39 @@ class Create extends Action
|
||||
|
||||
public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
{
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$deniedDomains = [
|
||||
$mainDomain,
|
||||
$sitesDomain,
|
||||
$functionsDomain,
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL,
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
}
|
||||
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
if (!empty($functionsDomain)) {
|
||||
$deniedDomains[] = $functionsDomain;
|
||||
}
|
||||
|
||||
$denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', '');
|
||||
$denyListDomains = \array_map('trim', explode(',', $denyListDomains));
|
||||
foreach ($denyListDomains as $denyListDomain) {
|
||||
if (empty($denyListDomain)) {
|
||||
continue;
|
||||
}
|
||||
$deniedDomains[] = $denyListDomain;
|
||||
}
|
||||
|
||||
if (\in_array($domain, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -53,6 +53,7 @@ class Create extends Action
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'createDeployment',
|
||||
description: <<<EOT
|
||||
Create a new site code deployment. Use this endpoint to upload a new version of your site code. To activate your newly uploaded code, you'll need to update the function's deployment to use your new deployment ID.
|
||||
|
||||
@@ -41,6 +41,7 @@ class Delete extends Action
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'deleteDeployment',
|
||||
description: <<<EOT
|
||||
Delete a site deployment by its unique ID.
|
||||
|
||||
@@ -31,12 +31,13 @@ class Get extends Action
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/download')
|
||||
->desc('Download deployment')
|
||||
->desc('Get deployment download')
|
||||
->groups(['api', 'sites'])
|
||||
->label('scope', 'sites.read')
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'getDeploymentDownload',
|
||||
description: <<<EOT
|
||||
Get a site deployment content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.
|
||||
|
||||
@@ -41,6 +41,7 @@ class Create extends Action
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'createDuplicateDeployment',
|
||||
description: <<<EOT
|
||||
Create a new build for an existing site deployment. This endpoint allows you to rebuild a deployment with the updated site configuration, including its commands and output directory if they have been modified. The build process will be queued and executed asynchronously. The original deployment's code will be preserved and used for the new build.
|
||||
|
||||
@@ -32,6 +32,7 @@ class Get extends Action
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'getDeployment',
|
||||
description: <<<EOT
|
||||
Get a site deployment by its unique ID.
|
||||
|
||||
@@ -37,6 +37,7 @@ class Update extends Action
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'updateDeploymentStatus',
|
||||
description: <<<EOT
|
||||
Cancel an ongoing site deployment build. If the build is already in progress, it will be stopped and marked as canceled. If the build hasn't started yet, it will be marked as canceled without executing. You cannot cancel builds that have already completed (status 'ready') or failed. The response includes the final build status and details.
|
||||
|
||||
@@ -48,6 +48,7 @@ class Create extends Base
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'createTemplateDeployment',
|
||||
description: <<<EOT
|
||||
Create a deployment based on a template.
|
||||
|
||||
@@ -44,6 +44,7 @@ class Create extends Base
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'createVcsDeployment',
|
||||
description: <<<EOT
|
||||
Create a deployment when a site is connected to VCS.
|
||||
|
||||
@@ -10,6 +10,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Deployments;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
@@ -38,6 +39,7 @@ class XList extends Action
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'deployments',
|
||||
name: 'listDeployments',
|
||||
description: <<<EOT
|
||||
Get a list of all the site's code deployments. You can use the query params to filter your results.
|
||||
@@ -107,8 +109,12 @@ class XList extends Action
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$results = $dbForProject->find('deployments', $queries);
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
try {
|
||||
$results = $dbForProject->find('deployments', $queries);
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'deployments' => $results,
|
||||
|
||||
@@ -32,6 +32,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'frameworks',
|
||||
name: 'listFrameworks',
|
||||
description: <<<EOT
|
||||
Get a list of all frameworks that are currently available on the server instance.
|
||||
|
||||
@@ -37,6 +37,7 @@ class Delete extends Base
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'logs',
|
||||
name: 'deleteLog',
|
||||
description: <<<EOT
|
||||
Delete a site log by its unique ID.
|
||||
|
||||
@@ -33,6 +33,7 @@ class Get extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'logs',
|
||||
name: 'getLog',
|
||||
description: <<<EOT
|
||||
Get a site request log by its unique ID.
|
||||
|
||||
@@ -12,6 +12,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Logs;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
@@ -39,6 +40,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'logs',
|
||||
name: 'listLogs',
|
||||
description: <<<EOT
|
||||
Get a list of all site logs. You can use the query params to filter your results.
|
||||
@@ -103,8 +105,12 @@ class XList extends Base
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$results = $dbForProject->find('executions', $queries);
|
||||
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
|
||||
try {
|
||||
$results = $dbForProject->find('executions', $queries);
|
||||
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'executions' => $results,
|
||||
|
||||
@@ -49,6 +49,7 @@ class Create extends Base
|
||||
->label('audits.resource', 'site/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'sites',
|
||||
name: 'create',
|
||||
description: <<<EOT
|
||||
Create a new site.
|
||||
|
||||
@@ -39,6 +39,7 @@ class Delete extends Base
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'sites',
|
||||
name: 'delete',
|
||||
description: <<<EOT
|
||||
Delete a site by its unique ID.
|
||||
|
||||
@@ -39,6 +39,7 @@ class Update extends Base
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'sites',
|
||||
name: 'updateSiteDeployment',
|
||||
description: <<<EOT
|
||||
Update the site active deployment. Use this endpoint to switch the code deployment that should be used when visitor opens your site.
|
||||
|
||||
@@ -33,6 +33,7 @@ class Get extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'sites',
|
||||
name: 'get',
|
||||
description: <<<EOT
|
||||
Get a site by its unique ID.
|
||||
|
||||
@@ -53,6 +53,7 @@ class Update extends Base
|
||||
->label('audits.resource', 'site/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'sites',
|
||||
name: 'update',
|
||||
description: <<<EOT
|
||||
Update site by its unique ID.
|
||||
|
||||
@@ -11,6 +11,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Sites;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
@@ -38,6 +39,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'sites',
|
||||
name: 'list',
|
||||
description: <<<EOT
|
||||
Get a list of all the project's sites. You can use the query params to filter your results.
|
||||
@@ -96,9 +98,16 @@ class XList extends Base
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
try {
|
||||
$sites = $dbForProject->find('sites', $queries);
|
||||
$total = $dbForProject->count('sites', $filterQueries, APP_LIMIT_COUNT);
|
||||
} catch (OrderException $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'sites' => $dbForProject->find('sites', $queries),
|
||||
'total' => $dbForProject->count('sites', $filterQueries, APP_LIMIT_COUNT),
|
||||
'sites' => $sites,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_SITE_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'frameworks',
|
||||
name: 'listSpecifications',
|
||||
description: <<<EOT
|
||||
List allowed site specifications for this instance.
|
||||
|
||||
@@ -34,6 +34,7 @@ class Get extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'templates',
|
||||
name: 'getTemplate',
|
||||
description: <<<EOT
|
||||
Get a site template using ID. You can use template details in [createSite](/docs/references/cloud/server-nodejs/sites#create) method.
|
||||
|
||||
@@ -35,6 +35,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'templates',
|
||||
name: 'listTemplates',
|
||||
description: <<<EOT
|
||||
List available site templates. You can use template details in [createSite](/docs/references/cloud/server-nodejs/sites#create) method.
|
||||
|
||||
@@ -38,6 +38,7 @@ class Get extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: null,
|
||||
name: 'getUsage',
|
||||
description: <<<EOT
|
||||
Get usage metrics and statistics for a for a specific site. View statistics including total deployments, builds, executions, storage usage, and compute time. The response includes both current totals and historical data for each metric. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, defaults to 30 days.
|
||||
|
||||
@@ -36,6 +36,7 @@ class XList extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: null,
|
||||
name: 'listUsage',
|
||||
description: <<<EOT
|
||||
Get usage metrics and statistics for all sites in the project. View statistics including total deployments, builds, logs, storage usage, and compute time. The response includes both current totals and historical data for each metric. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, defaults to 30 days.
|
||||
|
||||
@@ -42,6 +42,7 @@ class Create extends Base
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'variables',
|
||||
name: 'createVariable',
|
||||
description: <<<EOT
|
||||
Create a new site variable. These variables can be accessed during build and runtime (server-side rendering) as environment variables.
|
||||
|
||||
@@ -36,6 +36,7 @@ class Delete extends Base
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'variables',
|
||||
name: 'deleteVariable',
|
||||
description: <<<EOT
|
||||
Delete a variable by its unique ID.
|
||||
|
||||
@@ -35,6 +35,7 @@ class Get extends Base
|
||||
'sdk',
|
||||
new Method(
|
||||
namespace: 'sites',
|
||||
group: 'variables',
|
||||
name: 'getVariable',
|
||||
description: <<<EOT
|
||||
Get a variable by its unique ID.
|
||||
|
||||
@@ -38,6 +38,7 @@ class Update extends Base
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
group: 'variables',
|
||||
name: 'updateVariable',
|
||||
description: <<<EOT
|
||||
Update variable by its unique ID.
|
||||
|
||||
@@ -36,6 +36,7 @@ class XList extends Base
|
||||
'sdk',
|
||||
new Method(
|
||||
namespace: 'sites',
|
||||
group: 'variables',
|
||||
name: 'listVariables',
|
||||
description: <<<EOT
|
||||
Get a list of all variables of a specific site.
|
||||
|
||||
@@ -46,6 +46,7 @@ class Create extends Action
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tokens',
|
||||
group: 'files',
|
||||
name: 'createFileToken',
|
||||
description: <<<EOT
|
||||
Create a new token. A token is linked to a file or a bucket and manages permissions for those file(s). Token can be passed as a header or request get parameter.
|
||||
|
||||
@@ -36,6 +36,7 @@ class XList extends Action
|
||||
->label('usage.metric', 'tokens.requests.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tokens',
|
||||
group: 'files',
|
||||
name: 'list',
|
||||
description: <<<EOT
|
||||
List all the tokens created for a specific file or bucket. You can use the query params to filter your results.
|
||||
|
||||
@@ -40,6 +40,7 @@ class Delete extends Action
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tokens',
|
||||
group: 'tokens',
|
||||
name: 'delete',
|
||||
description: <<<EOT
|
||||
Delete a token by its unique ID.
|
||||
|
||||
@@ -33,6 +33,7 @@ class Get extends Action
|
||||
->label('usage.params', ['tokenId:{request.tokenId}'])
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tokens',
|
||||
group: 'tokens',
|
||||
name: 'get',
|
||||
description: <<<EOT
|
||||
Get a token by its unique ID.
|
||||
|
||||
@@ -36,6 +36,7 @@ class Get extends Action
|
||||
->label('usage.params', ['tokenId:{request.tokenId}'])
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tokens',
|
||||
group: 'tokens',
|
||||
name: 'getJWT',
|
||||
description: <<<EOT
|
||||
Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.
|
||||
|
||||
@@ -47,6 +47,7 @@ class Update extends Action
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'tokens',
|
||||
group: 'tokens',
|
||||
name: 'update',
|
||||
description: <<<EOT
|
||||
Update a token by its unique ID. Use this endpoint to update a token's expiry date or permissions.
|
||||
|
||||
@@ -12,7 +12,6 @@ use Appwrite\Platform\Workers\Messaging;
|
||||
use Appwrite\Platform\Workers\Migrations;
|
||||
use Appwrite\Platform\Workers\StatsResources;
|
||||
use Appwrite\Platform\Workers\StatsUsage;
|
||||
use Appwrite\Platform\Workers\StatsUsageDump;
|
||||
use Appwrite\Platform\Workers\Webhooks;
|
||||
use Utopia\Platform\Service;
|
||||
|
||||
@@ -30,7 +29,6 @@ class Workers extends Service
|
||||
->addAction(Mails::getName(), new Mails())
|
||||
->addAction(Messaging::getName(), new Messaging())
|
||||
->addAction(Webhooks::getName(), new Webhooks())
|
||||
->addAction(StatsUsageDump::getName(), new StatsUsageDump())
|
||||
->addAction(StatsUsage::getName(), new StatsUsage())
|
||||
->addAction(Migrations::getName(), new Migrations())
|
||||
->addAction(StatsResources::getName(), new StatsResources())
|
||||
|
||||
@@ -47,15 +47,20 @@ class Maintenance extends Action
|
||||
|
||||
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
|
||||
|
||||
$dbForPlatform->foreach('projects', function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_MAINTENANCE)
|
||||
->setProject($project)
|
||||
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
|
||||
->trigger();
|
||||
}, [
|
||||
Query::limit(100),
|
||||
]);
|
||||
$dbForPlatform->foreach(
|
||||
'projects',
|
||||
function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_MAINTENANCE)
|
||||
->setProject($project)
|
||||
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
|
||||
->trigger();
|
||||
},
|
||||
[
|
||||
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
|
||||
Query::limit(100),
|
||||
]
|
||||
);
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_MAINTENANCE)
|
||||
|
||||
@@ -224,8 +224,12 @@ class Screenshot extends Action
|
||||
$idLight = '';
|
||||
$idDark = '';
|
||||
|
||||
if ($templateId === 'starter-for-react-native') {
|
||||
Console::warning("React Native template takes long to build, increasing waiting time ...");
|
||||
$slowTemplates = [
|
||||
'starter-for-react-native',
|
||||
'playground-for-react-native'
|
||||
];
|
||||
if (\in_array($templateId, $slowTemplates)) {
|
||||
Console::warning("Build for this template is slow, increasing waiting time ...");
|
||||
$attempts = 180; // 15 min
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,7 @@ class Specs extends Action
|
||||
->setParam('name', APP_NAME)
|
||||
->setParam('description', 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)')
|
||||
->setParam('endpoint', 'https://cloud.appwrite.io/v1')
|
||||
->setParam('endpoint.docs', 'https://<REGION>.cloud.appwrite.io/v1')
|
||||
->setParam('version', APP_VERSION_STABLE)
|
||||
->setParam('terms', $endpoint . '/policy/terms')
|
||||
->setParam('support.email', $email)
|
||||
|
||||
@@ -67,7 +67,8 @@ class StatsResources extends Action
|
||||
* For each project that were accessed in last 24 hours
|
||||
*/
|
||||
$this->foreachDocument($this->dbForPlatform, 'projects', [
|
||||
Query::greaterThanEqual('accessedAt', DateTime::format($last24Hours))
|
||||
Query::greaterThanEqual('accessedAt', DateTime::format($last24Hours)),
|
||||
Query::equal('region', [System::getEnv('_APP_REGION', 'default')])
|
||||
], function ($project) use ($queue) {
|
||||
$queue
|
||||
->setProject($project)
|
||||
|
||||
@@ -570,22 +570,19 @@ class Databases extends Action
|
||||
$start = \microtime(true);
|
||||
|
||||
try {
|
||||
$documents = $database->deleteDocuments($collectionId, $queries);
|
||||
$count = $database->deleteDocuments(
|
||||
$collectionId,
|
||||
$queries,
|
||||
Database::DELETE_BATCH_SIZE,
|
||||
$callback
|
||||
);
|
||||
} catch (\Throwable $th) {
|
||||
$tenant = $database->getSharedTables() ? 'Tenant:'.$database->getTenant() : '';
|
||||
Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collectionId} {$tenant} :{$th->getMessage()}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (\is_callable($callback)) {
|
||||
foreach ($documents as $document) {
|
||||
$callback($document);
|
||||
}
|
||||
}
|
||||
|
||||
$end = \microtime(true);
|
||||
$count = \count($documents);
|
||||
|
||||
Console::info("Deleted {$count} documents by group in " . ($end - $start) . " seconds");
|
||||
}
|
||||
|
||||
|
||||
@@ -511,21 +511,22 @@ class Deletes extends Action
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Database $dbForPlatform
|
||||
* @param Document $document
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws DatabaseException
|
||||
* @throws Conflict
|
||||
* @throws Restricted
|
||||
* @throws Structure
|
||||
* @throws Exception
|
||||
*/
|
||||
private function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void
|
||||
* @param Database $dbForPlatform
|
||||
* @param Document $document
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws DatabaseException
|
||||
* @throws Conflict
|
||||
* @throws Restricted
|
||||
* @throws Structure
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void
|
||||
{
|
||||
|
||||
$projects = $dbForPlatform->find('projects', [
|
||||
Query::equal('teamInternalId', [$document->getInternalId()])
|
||||
Query::equal('teamInternalId', [$document->getInternalId()]),
|
||||
Query::equal('region', [System::getEnv('_APP_REGION', 'default')])
|
||||
]);
|
||||
|
||||
foreach ($projects as $project) {
|
||||
@@ -1205,24 +1206,20 @@ class Deletes extends Action
|
||||
/**
|
||||
* deleteDocuments uses a cursor, we need to add a unique order by field or use default
|
||||
*/
|
||||
|
||||
try {
|
||||
$documents = $database->deleteDocuments($collection, $queries);
|
||||
$count = $database->deleteDocuments(
|
||||
$collection,
|
||||
$queries,
|
||||
Database::DELETE_BATCH_SIZE,
|
||||
$callback
|
||||
);
|
||||
} catch (Throwable $th) {
|
||||
$tenant = $database->getSharedTables() ? 'Tenant:'.$database->getTenant() : '';
|
||||
Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collection} {$tenant} :{$th->getMessage()}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (\is_callable($callback)) {
|
||||
foreach ($documents as $document) {
|
||||
$callback($document);
|
||||
}
|
||||
}
|
||||
|
||||
$end = \microtime(true);
|
||||
$count = \count($documents);
|
||||
|
||||
Console::info("Deleted {$count} documents by group in " . ($end - $start) . " seconds");
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ class StatsResources extends Action
|
||||
}
|
||||
|
||||
if (empty($project->getAttribute('database'))) {
|
||||
var_dump($payload);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,22 @@
|
||||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Event\StatsUsageDump;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
|
||||
class StatsUsage extends Action
|
||||
{
|
||||
/**
|
||||
* In memory per project metrics calculation
|
||||
*/
|
||||
private array $stats = [];
|
||||
private int $lastTriggeredTime = 0;
|
||||
private int $keys = 0;
|
||||
@@ -20,6 +25,77 @@ class StatsUsage extends Action
|
||||
private const BATCH_SIZE_DEVELOPMENT = 1;
|
||||
private const BATCH_SIZE_PRODUCTION = 10_000;
|
||||
|
||||
/**
|
||||
* Stats for batch write separated per project
|
||||
* @var array
|
||||
*/
|
||||
private array $projects = [];
|
||||
|
||||
/**
|
||||
* Array of stat documents to batch write to logsDB
|
||||
* @var array
|
||||
*/
|
||||
private array $statDocuments = [];
|
||||
|
||||
protected Registry $register;
|
||||
|
||||
/**
|
||||
* Metrics to skip writing to logsDB
|
||||
* As these metrics are calculated separately
|
||||
* by logs DB
|
||||
* @var array
|
||||
*/
|
||||
protected array $skipBaseMetrics = [
|
||||
METRIC_DATABASES => true,
|
||||
METRIC_BUCKETS => true,
|
||||
METRIC_USERS => true,
|
||||
METRIC_FUNCTIONS => true,
|
||||
METRIC_TEAMS => true,
|
||||
METRIC_MESSAGES => true,
|
||||
METRIC_MAU => true,
|
||||
METRIC_WEBHOOKS => true,
|
||||
METRIC_PLATFORMS => true,
|
||||
METRIC_PROVIDERS => true,
|
||||
METRIC_TOPICS => true,
|
||||
METRIC_KEYS => true,
|
||||
METRIC_FILES => true,
|
||||
METRIC_FILES_STORAGE => true,
|
||||
METRIC_DEPLOYMENTS_STORAGE => true,
|
||||
METRIC_BUILDS_STORAGE => true,
|
||||
METRIC_DEPLOYMENTS => true,
|
||||
METRIC_BUILDS => true,
|
||||
METRIC_COLLECTIONS => true,
|
||||
METRIC_DOCUMENTS => true,
|
||||
METRIC_DATABASES_STORAGE => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Skip metrics associated with parent IDs
|
||||
* these need to be checked individually with `str_ends_with`
|
||||
*/
|
||||
protected array $skipParentIdMetrics = [
|
||||
'.files',
|
||||
'.files.storage',
|
||||
'.collections',
|
||||
'.documents',
|
||||
'.deployments',
|
||||
'.deployments.storage',
|
||||
'.builds',
|
||||
'.builds.storage',
|
||||
'.databases.storage'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var callable(): Database
|
||||
*/
|
||||
protected mixed $getLogsDB;
|
||||
|
||||
protected array $periods = [
|
||||
'1h' => 'Y-m-d H:00',
|
||||
'1d' => 'Y-m-d 00:00',
|
||||
'inf' => '0000-00-00 00:00'
|
||||
];
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'stats-usage';
|
||||
@@ -41,7 +117,8 @@ class StatsUsage extends Action
|
||||
->desc('Stats usage worker')
|
||||
->inject('message')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForStatsUsageDump')
|
||||
->inject('getLogsDB')
|
||||
->inject('register')
|
||||
->callback([$this, 'action']);
|
||||
|
||||
$this->lastTriggeredTime = time();
|
||||
@@ -49,14 +126,17 @@ class StatsUsage extends Action
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param callable $getProjectDB
|
||||
* @param StatsUsageDump $queueForStatsUsageDump
|
||||
* @param callable(): Database $getProjectDB
|
||||
* @param callable(): Database $getLogsDB
|
||||
* @param Registry $register
|
||||
* @return void
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function action(Message $message, callable $getProjectDB, StatsUsageDump $queueForStatsUsageDump): void
|
||||
public function action(Message $message, callable $getProjectDB, callable $getLogsDB, Registry $register): void
|
||||
{
|
||||
$this->getLogsDB = $getLogsDB;
|
||||
$this->register = $register;
|
||||
$payload = $message->getPayload() ?? [];
|
||||
if (empty($payload)) {
|
||||
throw new Exception('Missing payload');
|
||||
@@ -98,9 +178,7 @@ class StatsUsage extends Action
|
||||
) {
|
||||
Console::warning('[' . DateTime::now() . '] Aggregated ' . $this->keys . ' keys');
|
||||
|
||||
$queueForStatsUsageDump
|
||||
->setStats($this->stats)
|
||||
->trigger();
|
||||
$this->commitToDB($getProjectDB);
|
||||
|
||||
$this->stats = [];
|
||||
$this->keys = 0;
|
||||
@@ -114,7 +192,7 @@ class StatsUsage extends Action
|
||||
* @param Document $project
|
||||
* @param Document $document
|
||||
* @param array $metrics
|
||||
* @param callable $getProjectDB
|
||||
* @param callable(): Database $getProjectDB
|
||||
* @return void
|
||||
*/
|
||||
private function reduce(Document $project, Document $document, array &$metrics, callable $getProjectDB): void
|
||||
@@ -278,8 +356,128 @@ class StatsUsage extends Action
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
console::error("[reducer] " . " {DateTime::now()} " . " {$project->getInternalId()} " . " {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit stats to DB
|
||||
* @param callable(): Database $getProjectDB
|
||||
* @return void
|
||||
*/
|
||||
public function commitToDb(callable $getProjectDB): void
|
||||
{
|
||||
foreach ($this->stats as $stats) {
|
||||
$project = $stats['project'] ?? new Document([]);
|
||||
$numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0;
|
||||
$receivedAt = $stats['receivedAt'] ?? null;
|
||||
if ($numberOfKeys === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
|
||||
|
||||
try {
|
||||
foreach ($stats['keys'] ?? [] as $key => $value) {
|
||||
if ($value == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->periods as $period => $format) {
|
||||
$time = null;
|
||||
|
||||
if ($period !== 'inf') {
|
||||
$time = !empty($receivedAt) ? (new \DateTime($receivedAt))->format($format) : date($format, time());
|
||||
}
|
||||
$id = \md5("{$time}_{$period}_{$key}");
|
||||
|
||||
$document = new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $key,
|
||||
'value' => $value,
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]);
|
||||
|
||||
|
||||
$this->projects[$project->getInternalId()]['project'] = new Document([
|
||||
'$id' => $project->getId(),
|
||||
'$internalId' => $project->getInternalId(),
|
||||
'database' => $project->getAttribute('database'),
|
||||
]);
|
||||
$this->projects[$project->getInternalId()]['stats'][] = $document;
|
||||
|
||||
$this->prepareForLogsDB($project, $document);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->projects as $internalId => $projectStats) {
|
||||
if (empty($internalId)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$dbForProject = $getProjectDB($projectStats['project']);
|
||||
Console::log('Processing batch with ' . count($projectStats['stats']) . ' stats');
|
||||
$dbForProject->createOrUpdateDocumentsWithIncrease('stats', 'value', $projectStats['stats']);
|
||||
Console::success('Batch successfully written to DB');
|
||||
|
||||
unset($this->projects[$internalId]);
|
||||
} catch (Throwable $e) {
|
||||
Console::error('Error processing stats: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->writeToLogsDB();
|
||||
|
||||
}
|
||||
|
||||
protected function prepareForLogsDB(Document $project, Document $stat)
|
||||
{
|
||||
if (System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', 'disabled') === 'disabled') {
|
||||
return;
|
||||
}
|
||||
if (array_key_exists($stat->getAttribute('metric'), $this->skipBaseMetrics)) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->skipParentIdMetrics as $skipMetric) {
|
||||
if (str_ends_with($stat->getAttribute('metric'), $skipMetric)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$documentClone = clone $stat;
|
||||
$documentClone->setAttribute('$tenant', (int) $project->getInternalId());
|
||||
$this->statDocuments[] = $documentClone;
|
||||
}
|
||||
|
||||
protected function writeToLogsDB(): void
|
||||
{
|
||||
if (System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', 'disabled') === 'disabled') {
|
||||
Console::log('Dual Writing is disabled. Skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
$dbForLogs = call_user_func($this->getLogsDB);
|
||||
$dbForLogs
|
||||
->setTenant(null)
|
||||
->setTenantPerDocument(true);
|
||||
|
||||
try {
|
||||
Console::log('Processing batch with ' . count($this->statDocuments) . ' stats');
|
||||
$dbForLogs->createOrUpdateDocumentsWithIncrease(
|
||||
'stats',
|
||||
'value',
|
||||
$this->statDocuments
|
||||
);
|
||||
Console::success('Usage logs pushed to Logs DB');
|
||||
} catch (Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
$this->register->get('pools')->get('logs')->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -12,6 +11,9 @@ use Utopia\Queue\Message;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
|
||||
/**
|
||||
* TODO remove later
|
||||
*/
|
||||
class StatsUsageDump extends Action
|
||||
{
|
||||
public const METRIC_COLLECTION_LEVEL_STORAGE = 4;
|
||||
@@ -135,11 +137,6 @@ class StatsUsageDump extends Action
|
||||
}
|
||||
|
||||
if (str_contains($key, METRIC_DATABASES_STORAGE)) {
|
||||
try {
|
||||
$this->handleDatabaseStorage($key, $dbForProject, $project, $receivedAt);
|
||||
} catch (\Exception $e) {
|
||||
console::error('[' . DateTime::now() . '] failed to calculate database storage for key [' . $key . '] ' . $e->getMessage());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -160,7 +157,7 @@ class StatsUsageDump extends Action
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]);
|
||||
|
||||
$documentClone = new Document($document->getArrayCopy());
|
||||
$documentClone = clone $document;
|
||||
|
||||
$dbForProject->createOrUpdateDocumentsWithIncrease(
|
||||
'stats',
|
||||
@@ -177,157 +174,6 @@ class StatsUsageDump extends Action
|
||||
}
|
||||
}
|
||||
|
||||
private function handleDatabaseStorage(string $key, Database $dbForProject, Document $project, string $receivedAt): void
|
||||
{
|
||||
$data = explode('.', $key);
|
||||
$start = microtime(true);
|
||||
|
||||
$updateMetric = function (Database $dbForProject, Document $project, int $value, string $key, string $period, string|null $time) use ($receivedAt) {
|
||||
$id = \md5("{$time}_{$period}_{$key}");
|
||||
|
||||
$document = new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
'time' => $time,
|
||||
'metric' => $key,
|
||||
'value' => $value,
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]);
|
||||
$documentClone = new Document($document->getArrayCopy());
|
||||
$dbForProject->createOrUpdateDocumentsWithIncrease(
|
||||
'stats',
|
||||
'value',
|
||||
[$document]
|
||||
);
|
||||
$this->writeToLogsDB($project, $documentClone);
|
||||
};
|
||||
|
||||
foreach ($this->periods as $period => $format) {
|
||||
$time = null;
|
||||
|
||||
if ($period !== 'inf') {
|
||||
$time = !empty($receivedAt) ? (new \DateTime($receivedAt))->format($format) : date($format, time());
|
||||
}
|
||||
$id = \md5("{$time}_{$period}_{$key}");
|
||||
|
||||
$value = 0;
|
||||
$previousValue = 0;
|
||||
try {
|
||||
$previousValue = ($dbForProject->getDocument('stats', $id))->getAttribute('value', 0);
|
||||
} catch (\Exception $e) {
|
||||
// No previous value
|
||||
}
|
||||
|
||||
switch (count($data)) {
|
||||
// Collection Level
|
||||
case self::METRIC_COLLECTION_LEVEL_STORAGE:
|
||||
Console::log('[' . DateTime::now() . '] Collection Level Storage Calculation [' . $key . ']');
|
||||
$databaseInternalId = $data[0];
|
||||
$collectionInternalId = $data[1];
|
||||
|
||||
try {
|
||||
$value = $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collectionInternalId);
|
||||
} catch (\Exception $e) {
|
||||
// Collection not found
|
||||
if ($e->getMessage() !== 'Collection not found') {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare with previous value
|
||||
$diff = $value - $previousValue;
|
||||
|
||||
if ($diff === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Update Collection
|
||||
$updateMetric($dbForProject, $project, $diff, $key, $period, $time);
|
||||
|
||||
// Update Database
|
||||
$databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
|
||||
$updateMetric($dbForProject, $project, $diff, $databaseKey, $period, $time);
|
||||
|
||||
// Update Project
|
||||
$projectKey = METRIC_DATABASES_STORAGE;
|
||||
$updateMetric($dbForProject, $project, $diff, $projectKey, $period, $time);
|
||||
break;
|
||||
// Database Level
|
||||
case self::METRIC_DATABASE_LEVEL_STORAGE:
|
||||
Console::log('[' . DateTime::now() . '] Database Level Storage Calculation [' . $key . ']');
|
||||
$databaseInternalId = $data[0];
|
||||
|
||||
$collections = [];
|
||||
try {
|
||||
$collections = $dbForProject->find('database_' . $databaseInternalId);
|
||||
} catch (\Exception $e) {
|
||||
// Database not found
|
||||
if ($e->getMessage() !== 'Collection not found') {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($collections as $collection) {
|
||||
try {
|
||||
$value += $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
|
||||
} catch (\Exception $e) {
|
||||
// Collection not found
|
||||
if ($e->getMessage() !== 'Collection not found') {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$diff = $value - $previousValue;
|
||||
|
||||
if ($diff === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Update Database
|
||||
$databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
|
||||
$updateMetric($dbForProject, $project, $diff, $databaseKey, $period, $time);
|
||||
|
||||
// Update Project
|
||||
$projectKey = METRIC_DATABASES_STORAGE;
|
||||
$updateMetric($dbForProject, $project, $diff, $projectKey, $period, $time);
|
||||
break;
|
||||
// Project Level
|
||||
case self::METRIC_PROJECT_LEVEL_STORAGE:
|
||||
Console::log('[' . DateTime::now() . '] Project Level Storage Calculation [' . $key . ']');
|
||||
// Get all project databases
|
||||
$databases = $dbForProject->find('database');
|
||||
|
||||
// Recalculate all databases
|
||||
foreach ($databases as $database) {
|
||||
$collections = $dbForProject->find('database_' . $database->getInternalId());
|
||||
|
||||
foreach ($collections as $collection) {
|
||||
try {
|
||||
$value += $dbForProject->getSizeOfCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||
} catch (\Exception $e) {
|
||||
// Collection not found
|
||||
if ($e->getMessage() !== 'Collection not found') {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$diff = $value - $previousValue;
|
||||
|
||||
// Update Project
|
||||
$projectKey = METRIC_DATABASES_STORAGE;
|
||||
$updateMetric($dbForProject, $project, $diff, $projectKey, $period, $time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$end = microtime(true);
|
||||
|
||||
console::log('[' . DateTime::now() . '] DB Storage Calculation [' . $key . '] took ' . (($end - $start) * 1000) . ' milliseconds');
|
||||
}
|
||||
|
||||
protected function writeToLogsDB(Document $project, Document $document): void
|
||||
{
|
||||
if (System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', 'disabled') === 'disabled') {
|
||||
|
||||
@@ -54,6 +54,8 @@ class Webhooks extends Action
|
||||
$this->errors = [];
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ class Method
|
||||
* Initialise a new SDK method
|
||||
*
|
||||
* @param string $namespace
|
||||
* @param ?string $group
|
||||
* @param string $name
|
||||
* @param string $description
|
||||
* @param array<AuthType> $auth
|
||||
@@ -33,6 +34,7 @@ class Method
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $namespace,
|
||||
protected ?string $group,
|
||||
protected string $name,
|
||||
protected string $description,
|
||||
protected array $auth,
|
||||
@@ -128,6 +130,11 @@ class Method
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function getGroup(): ?string
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
|
||||
public function getMethodName(): string
|
||||
{
|
||||
return $this->name;
|
||||
|
||||
@@ -84,6 +84,9 @@ class OpenAPI3 extends Format
|
||||
[
|
||||
'url' => $this->getParam('endpoint', ''),
|
||||
],
|
||||
[
|
||||
'url' => $this->getParam('endpoint.docs', ''),
|
||||
],
|
||||
],
|
||||
'paths' => [],
|
||||
'tags' => $this->services,
|
||||
@@ -188,6 +191,7 @@ class OpenAPI3 extends Format
|
||||
'responses' => [],
|
||||
'x-appwrite' => [ // Appwrite related metadata
|
||||
'method' => $method,
|
||||
'group' => $sdk->getGroup(),
|
||||
'weight' => $route->getOrder(),
|
||||
'cookies' => $route->getLabel('sdk.cookies', false),
|
||||
'type' => $sdk->getType()->value ?? '',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user