mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.8.x' into release-cli-13.0.0.rc3
This commit is contained in:
@@ -20,10 +20,12 @@ use Utopia\Database\Validator\Authorization;
|
||||
class TransactionState
|
||||
{
|
||||
private Database $dbForProject;
|
||||
|
||||
public function __construct(Database $dbForProject)
|
||||
private Authorization $authorization;
|
||||
/** @var Authorization $authorization */
|
||||
public function __construct(Database $dbForProject, Authorization $authorization)
|
||||
{
|
||||
$this->dbForProject = $dbForProject;
|
||||
$this->authorization = $authorization;
|
||||
}
|
||||
|
||||
|
||||
@@ -342,12 +344,12 @@ class TransactionState
|
||||
*/
|
||||
private function getTransactionState(string $transactionId): array
|
||||
{
|
||||
$transaction = Authorization::skip(fn () => $this->dbForProject->getDocument('transactions', $transactionId));
|
||||
$transaction = $this->authorization->skip(fn () => $this->dbForProject->getDocument('transactions', $transactionId));
|
||||
if ($transaction->isEmpty() || $transaction->getAttribute('status') !== 'pending') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$operations = Authorization::skip(fn () => $this->dbForProject->find('transactionLogs', [
|
||||
$operations = $this->authorization->skip(fn () => $this->dbForProject->find('transactionLogs', [
|
||||
Query::equal('transactionInternalId', [$transaction->getSequence()]),
|
||||
Query::orderAsc(),
|
||||
Query::limit(PHP_INT_MAX)
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace Appwrite\Deletes;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Targets
|
||||
@@ -42,12 +44,17 @@ class Targets
|
||||
MESSAGE_TYPE_PUSH => 'pushTotal',
|
||||
default => throw new Exception('Invalid target provider type'),
|
||||
};
|
||||
$database->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
min: 0
|
||||
);
|
||||
|
||||
try {
|
||||
$database->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
min: 0
|
||||
);
|
||||
} catch (LimitException $e) {
|
||||
Console::error("Delete subscribers decreaseDocumentAttribute (topicId={$topicId}): {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -39,6 +39,9 @@ class Event
|
||||
public const BUILDS_QUEUE_NAME = 'v1-builds';
|
||||
public const BUILDS_CLASS_NAME = 'BuildsV1';
|
||||
|
||||
public const SCREENSHOTS_QUEUE_NAME = 'v1-screenshots';
|
||||
public const SCREENSHOTS_CLASS_NAME = 'ScreenshotsV1';
|
||||
|
||||
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
||||
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Screenshot extends Event
|
||||
{
|
||||
protected string $deploymentId = '';
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_SCREENSHOTS_CLASS_NAME', Event::SCREENSHOTS_CLASS_NAME));
|
||||
}
|
||||
|
||||
public function setDeploymentId(string $deploymentId): self
|
||||
{
|
||||
$this->deploymentId = $deploymentId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
$platform = $this->platform;
|
||||
if (empty($platform)) {
|
||||
$platform = Config::getParam('platform', []);
|
||||
}
|
||||
|
||||
return [
|
||||
'project' => $this->project,
|
||||
'deploymentId' => $this->deploymentId,
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
|
||||
public function reset(): self
|
||||
{
|
||||
$this->deploymentId = '';
|
||||
parent::reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -100,8 +100,6 @@ abstract class Migration
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
$this->collections = Config::getParam('collections', []);
|
||||
|
||||
@@ -129,6 +127,7 @@ abstract class Migration
|
||||
Document $project,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
?callable $getProjectDB = null
|
||||
): self {
|
||||
$this->project = $project;
|
||||
@@ -136,6 +135,9 @@ abstract class Migration
|
||||
$this->dbForPlatform = $dbForPlatform;
|
||||
$this->getProjectDB = $getProjectDB;
|
||||
|
||||
$authorization->disable();
|
||||
$authorization->setDefaultStatus(false);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Platform;
|
||||
|
||||
use Appwrite\Platform\Modules\Account;
|
||||
use Appwrite\Platform\Modules\Avatars;
|
||||
use Appwrite\Platform\Modules\Console;
|
||||
use Appwrite\Platform\Modules\Core;
|
||||
use Appwrite\Platform\Modules\Databases;
|
||||
@@ -20,6 +21,7 @@ class Appwrite extends Platform
|
||||
{
|
||||
parent::__construct(new Core());
|
||||
$this->addModule(new Account\Module());
|
||||
$this->addModule(new Avatars\Module());
|
||||
$this->addModule(new Databases\Module());
|
||||
$this->addModule(new Projects\Module());
|
||||
$this->addModule(new Functions\Module());
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action as PlatformAction;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Throwable;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Logger\Logger;
|
||||
|
||||
class Action extends PlatformAction
|
||||
{
|
||||
protected function getAppRoot(): string
|
||||
{
|
||||
return \dirname(__DIR__, 6);
|
||||
}
|
||||
|
||||
protected function avatar(string $type, string $code, int $width, int $height, int $quality, Response $response): void
|
||||
{
|
||||
$code = \strtolower($code);
|
||||
$type = \strtolower($type);
|
||||
$set = Config::getParam('avatar-' . $type, []);
|
||||
|
||||
if (empty($set)) {
|
||||
throw new Exception(Exception::AVATAR_SET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!\array_key_exists($code, $set)) {
|
||||
throw new Exception(Exception::AVATAR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$output = 'png';
|
||||
$path = $set[$code]['path'];
|
||||
$type = 'png';
|
||||
|
||||
if (!\is_readable($path)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'File not readable in ' . $path);
|
||||
}
|
||||
|
||||
$image = new Image(\file_get_contents($path));
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
}
|
||||
|
||||
protected function getUserGitHub(string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger, Authorization $authorization): array
|
||||
{
|
||||
try {
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = null;
|
||||
foreach ($sessions as $session) {
|
||||
if ($session->getAttribute('provider', '') === 'github') {
|
||||
$gitHubSession = $session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($gitHubSession)) {
|
||||
throw new Exception(Exception::USER_SESSION_NOT_FOUND, 'GitHub session not found.');
|
||||
}
|
||||
|
||||
$provider = $gitHubSession->getAttribute('provider', '');
|
||||
$accessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
$accessTokenExpiry = $gitHubSession->getAttribute('providerAccessTokenExpiry');
|
||||
$refreshToken = $gitHubSession->getAttribute('providerRefreshToken');
|
||||
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
$oAuthProviders = Config::getParam('oAuthProviders');
|
||||
$className = $oAuthProviders[$provider]['class'];
|
||||
if (!\class_exists($className)) {
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$oauth2 = new $className($appId, $appSecret, '', [], []);
|
||||
|
||||
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
|
||||
if ($isExpired) {
|
||||
try {
|
||||
$oauth2->refreshTokens($refreshToken);
|
||||
|
||||
$accessToken = $oauth2->getAccessToken('');
|
||||
$refreshToken = $oauth2->getRefreshToken('');
|
||||
|
||||
$verificationId = $oauth2->getUserID($accessToken);
|
||||
|
||||
if (empty($verificationId)) {
|
||||
throw new \Exception("Locked tokens."); // Race codition, handeled in catch
|
||||
}
|
||||
|
||||
$gitHubSession
|
||||
->setAttribute('providerAccessToken', $accessToken)
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
} catch (Throwable $err) {
|
||||
$index = 0;
|
||||
do {
|
||||
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = new Document();
|
||||
foreach ($sessions as $session) {
|
||||
if ($session->getAttribute('provider', '') === 'github') {
|
||||
$gitHubSession = $session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$accessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
if ($accessToken !== $previousAccessToken) {
|
||||
break;
|
||||
}
|
||||
|
||||
$index++;
|
||||
\usleep(500000);
|
||||
} while ($index < 10);
|
||||
}
|
||||
}
|
||||
|
||||
$oauth2 = new $className($appId, $appSecret, '', [], []);
|
||||
$githubUser = $oauth2->getUserSlug($accessToken);
|
||||
$githubId = $oauth2->getUserID($accessToken);
|
||||
|
||||
return [
|
||||
'name' => $githubUser,
|
||||
'id' => $githubId
|
||||
];
|
||||
} catch (Exception $error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Browsers;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getBrowser';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/browsers/:code')
|
||||
->desc('Get browser icon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/browser')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getBrowser',
|
||||
description: '/docs/references/avatars/get-browser.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $code, int $width, int $height, int $quality, Response $response)
|
||||
{
|
||||
$this->avatar('browsers', $code, $width, $height, $quality, $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\Back;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCloudCardBack';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/cards/cloud-back')
|
||||
->desc('Get back Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud-back')
|
||||
->label('cache.resource', 'card-back/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['golden', 'normal', 'platinum']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
|
||||
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization)
|
||||
{
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$userId = $user->getId();
|
||||
$email = $user->getAttribute('email', '');
|
||||
|
||||
$gitHub = $this->getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $user->getSequence() % 100 === 0;
|
||||
} else {
|
||||
$userId = '63e0bcf3c3eb803ba530';
|
||||
|
||||
$isGolden = $mock === 'golden';
|
||||
$isPlatinum = $mock === 'platinum';
|
||||
}
|
||||
|
||||
$userId = 'UID ' . $userId;
|
||||
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
|
||||
$imagePath = $isGolden ? 'back-golden.png' : ($isPlatinum ? 'back-platinum.png' : 'back.png');
|
||||
|
||||
$baseImage = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $imagePath);
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/SourceCodePro-Regular.ttf');
|
||||
$text->setFillColor(new ImagickPixel($isGolden ? '#664A1E' : ($isPlatinum ? '#555555' : '#E8E9F0')));
|
||||
$text->setFontSize(28);
|
||||
$text->setFontWeight(400);
|
||||
$baseImage->annotateImage($text, 512, 596, 0, $userId);
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\Front;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCloudCard';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/cards/cloud')
|
||||
->desc('Get front Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud')
|
||||
->label('cache.resource', 'card/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
|
||||
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization)
|
||||
{
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$name = $user->getAttribute('name', 'Anonymous');
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $this->getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
$employeeNumber = $isEmployee ? $employees[$email]['spot'] : '';
|
||||
|
||||
if ($isHero) {
|
||||
$createdAt = new \DateTime($heroes[$email]['memberSince'] ?? '');
|
||||
} elseif ($isEmployee) {
|
||||
$createdAt = new \DateTime($employees[$email]['memberSince'] ?? '');
|
||||
}
|
||||
|
||||
if (!$isEmployee && !empty($githubName)) {
|
||||
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
|
||||
if (!empty($employeeGitHub)) {
|
||||
$isEmployee = true;
|
||||
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
|
||||
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$isPlatinum = $user->getSequence() % 100 === 0;
|
||||
} else {
|
||||
$name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
|
||||
$createdAt = new \DateTime('now');
|
||||
$githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrian-junior' : 'walterobrian');
|
||||
$isHero = $mock === 'hero';
|
||||
$isContributor = $mock === 'contributor';
|
||||
$isEmployee = \str_starts_with($mock, 'employee');
|
||||
$employeeNumber = match ($mock) {
|
||||
'employee' => '1',
|
||||
'employee-2digit' => '18',
|
||||
default => ''
|
||||
};
|
||||
|
||||
$isPlatinum = $mock === 'platinum';
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$isContributor = false;
|
||||
$isHero = false;
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$isContributor = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$isHero = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
$memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o'));
|
||||
|
||||
$imagePath = $isGolden ? 'front-golden.png' : ($isPlatinum ? 'front-platinum.png' : 'front.png');
|
||||
|
||||
$baseImage = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $imagePath);
|
||||
|
||||
if ($isEmployee) {
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/employee.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 35);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFADF'));
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 2 ? 54 : 48);
|
||||
$text->setFontWeight(700);
|
||||
$metricsText = $baseImage->queryFontMetrics($text, $employeeNumber);
|
||||
|
||||
$hashtag = new ImagickDraw();
|
||||
$hashtag->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$hashtag->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$hashtag->setFillColor(new ImagickPixel('#FFFADF'));
|
||||
$hashtag->setFontSize(28);
|
||||
$hashtag->setFontWeight(700);
|
||||
$metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#');
|
||||
|
||||
$startX = 898;
|
||||
$totalWidth = $metricsHashtag['textWidth'] + 12 + $metricsText['textWidth'];
|
||||
|
||||
$hashtagX = ($metricsHashtag['textWidth'] / 2);
|
||||
$textX = $hashtagX + 12 + ($metricsText['textWidth'] / 2);
|
||||
|
||||
$hashtagX -= $totalWidth / 2;
|
||||
$textX -= $totalWidth / 2;
|
||||
|
||||
$hashtagX += $startX;
|
||||
$textX += $startX;
|
||||
|
||||
$baseImage->annotateImage($hashtag, $hashtagX, 150, 0, '#');
|
||||
$baseImage->annotateImage($text, $textX, 150, 0, $employeeNumber);
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/contributor.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34);
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/hero.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34);
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $name = \iconv("utf-8", "ascii//TRANSLIT", $name);
|
||||
// $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince);
|
||||
// $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFFFF'));
|
||||
|
||||
if (\strlen($name) > 32) {
|
||||
$name = \substr($name, 0, 32);
|
||||
}
|
||||
|
||||
if (\strlen($name) <= 23) {
|
||||
$text->setFontSize(80);
|
||||
$scalingDown = false;
|
||||
} else {
|
||||
$text->setFontSize(54);
|
||||
$scalingDown = true;
|
||||
}
|
||||
$text->setFontWeight(700);
|
||||
$baseImage->annotateImage($text, 512, 477, 0, $name);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-SemiBold.ttf');
|
||||
$text->setFillColor(new ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC'));
|
||||
$text->setFontSize(27);
|
||||
$text->setFontWeight(600);
|
||||
$text->setTextKerning(1.08);
|
||||
$baseImage->annotateImage($text, 512, 541, 0, \strtoupper($memberSince));
|
||||
|
||||
if (!empty($githubName)) {
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Regular.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFFFF'));
|
||||
$text->setFontSize($scalingDown ? 28 : 32);
|
||||
$text->setFontWeight(400);
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$baseImage->annotateImage($text, 512 + 20 + 4, 373 + ($scalingDown ? 2 : 0), 0, $githubName);
|
||||
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$precisionFix = 5;
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2) - 20 - 4, 373 - ($metrics['textHeight'] - $precisionFix));
|
||||
}
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\OG;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCloudCardOG';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/cards/cloud-og')
|
||||
->desc('Get OG image From Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud-og')
|
||||
->label('cache.resource', 'card-og/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long', 'normal-long-right', 'normal-long-middle', 'normal-bg2', 'normal-bg3', 'normal-right', 'normal-middle', 'platinum-right', 'platinum-middle', 'hero-middle', 'hero-right', 'contributor-right', 'employee-right', 'contributor-middle', 'employee-middle', 'employee-2digit-middle', 'employee-2digit-right']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 1024), 'Resize image card width, Pass an integer between 0 to 1024.', true)
|
||||
->param('height', 0, new Range(0, 1024), 'Resize image card height, Pass an integer between 0 to 1024.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization)
|
||||
{
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$sequence = $user->getSequence();
|
||||
$bgVariation = $sequence % 3 === 0 ? '1' : ($sequence % 3 === 1 ? '2' : '3');
|
||||
$cardVariation = $sequence % 3 === 0 ? '1' : ($sequence % 3 === 1 ? '2' : '3');
|
||||
|
||||
$name = $user->getAttribute('name', 'Anonymous');
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $this->getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
$employeeNumber = $isEmployee ? $employees[$email]['spot'] : '';
|
||||
|
||||
if ($isHero) {
|
||||
$createdAt = new \DateTime($heroes[$email]['memberSince'] ?? '');
|
||||
} elseif ($isEmployee) {
|
||||
$createdAt = new \DateTime($employees[$email]['memberSince'] ?? '');
|
||||
}
|
||||
|
||||
if (!$isEmployee && !empty($githubName)) {
|
||||
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
|
||||
if (!empty($employeeGitHub)) {
|
||||
$isEmployee = true;
|
||||
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
|
||||
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$isPlatinum = $user->getSequence() % 100 === 0;
|
||||
} else {
|
||||
$bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1');
|
||||
$cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1');
|
||||
$name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
|
||||
$createdAt = new \DateTime('now');
|
||||
$githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrian-junior' : 'walterobrian');
|
||||
$isHero = \str_starts_with($mock, 'hero');
|
||||
$isContributor = \str_starts_with($mock, 'contributor');
|
||||
$isEmployee = \str_starts_with($mock, 'employee');
|
||||
$employeeNumber = match ($mock) {
|
||||
'employee' => '1',
|
||||
'employee-right' => '1',
|
||||
'employee-middle' => '1',
|
||||
'employee-2digit' => '18',
|
||||
'employee-2digit-right' => '18',
|
||||
'employee-2digit-middle' => '18',
|
||||
default => ''
|
||||
};
|
||||
|
||||
$isPlatinum = \str_starts_with($mock, 'platinum');
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$isContributor = false;
|
||||
$isHero = false;
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$isContributor = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$isHero = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
$memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o'));
|
||||
|
||||
$baseImage = new Imagick($this->getAppRoot() . "/public/images/cards/cloud/og-background{$bgVariation}.png");
|
||||
|
||||
$cardType = $isGolden ? '-golden' : ($isPlatinum ? '-platinum' : '');
|
||||
|
||||
$image = new Imagick($this->getAppRoot() . "/public/images/cards/cloud/og-card{$cardType}{$cardVariation}.png");
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 1008 / 2 - $image->getImageWidth() / 2, 1008 / 2 - $image->getImageHeight() / 2);
|
||||
|
||||
$imageLogo = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/og-background-logo.png');
|
||||
$imageShadow = new Imagick($this->getAppRoot() . "/public/images/cards/cloud/og-shadow{$cardType}.png");
|
||||
if ($cardVariation === '1') {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -450, 700);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -20, 710);
|
||||
} else {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -135, 710);
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$file = $cardVariation === '3' ? 'employee-skew.png' : 'employee.png';
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
$hashtag = new ImagickDraw();
|
||||
$hashtag->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$hashtag->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$hashtag->setFillColor(new ImagickPixel('#FFFADF'));
|
||||
$hashtag->setFontSize(20);
|
||||
$hashtag->setFontWeight(700);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFADF'));
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 1 ? 36 : 28);
|
||||
$text->setFontWeight(700);
|
||||
|
||||
if ($cardVariation === '3') {
|
||||
$hashtag->setFontSize(16);
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 1 ? 30 : 26);
|
||||
|
||||
$hashtag->skewY(20);
|
||||
$hashtag->skewX(20);
|
||||
$text->skewY(20);
|
||||
$text->skewX(20);
|
||||
}
|
||||
|
||||
$metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#');
|
||||
$metricsText = $baseImage->queryFontMetrics($text, $employeeNumber);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metricsHashtag['textWidth'] + 6 + $metricsText['textWidth'];
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$group->newImage($groupWidth, $metricsText['textHeight'], '#00000000');
|
||||
$group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#');
|
||||
$group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber);
|
||||
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), -22);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 660, 245);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 655, 247);
|
||||
}
|
||||
} elseif ($cardVariation === '2') {
|
||||
$group->newImage($groupWidth, $metricsText['textHeight'], '#00000000');
|
||||
$group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#');
|
||||
$group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber);
|
||||
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), 32);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 775, 465);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 767, 470);
|
||||
}
|
||||
} else {
|
||||
$group->newImage(300, 300, '#00000000');
|
||||
|
||||
$hashtag->annotation(0, $metricsText['textHeight'], '#');
|
||||
$text->annotation($metricsHashtag['textWidth'] + 2, $metricsText['textHeight'], $employeeNumber);
|
||||
|
||||
$group->drawImage($hashtag);
|
||||
$group->drawImage($text);
|
||||
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 670, 317);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 663, 322);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$file = $cardVariation === '3' ? 'contributor-skew.png' : 'contributor.png';
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
} else {
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$file = $cardVariation === '3' ? 'hero-skew.png' : 'hero.png';
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
} else {
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
}
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $name = \iconv("utf-8", "ascii//TRANSLIT", $name);
|
||||
// $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince);
|
||||
// $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName);
|
||||
|
||||
$textName = new ImagickDraw();
|
||||
$textName->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$textName->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$textName->setFillColor(new ImagickPixel('#FFFFFF'));
|
||||
|
||||
if (\strlen($name) > 32) {
|
||||
$name = \substr($name, 0, 32);
|
||||
}
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(54);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(36);
|
||||
}
|
||||
} elseif ($cardVariation === '2') {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(50);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(34);
|
||||
}
|
||||
} else {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(44);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(32);
|
||||
}
|
||||
}
|
||||
|
||||
$textName->setFontWeight(700);
|
||||
|
||||
$textMember = new ImagickDraw();
|
||||
$textMember->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$textMember->setFont($this->getAppRoot() . '/public/fonts/Inter-Medium.ttf');
|
||||
$textMember->setFillColor(new ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC'));
|
||||
$textMember->setFontWeight(500);
|
||||
$textMember->setTextKerning(1.12);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$textMember->setFontSize(21);
|
||||
|
||||
$baseImage->annotateImage($textName, 550, 600, -22, $name);
|
||||
$baseImage->annotateImage($textMember, 585, 635, -22, $memberSince);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$textMember->setFontSize(20);
|
||||
|
||||
$baseImage->annotateImage($textName, 435, 590, 31.37, $name);
|
||||
$baseImage->annotateImage($textMember, 412, 628, 31.37, $memberSince);
|
||||
} else {
|
||||
$textMember->setFontSize(16);
|
||||
|
||||
$textName->skewY(20);
|
||||
$textName->skewX(20);
|
||||
$textName->annotation(320, 700, $name);
|
||||
|
||||
$textMember->skewY(20);
|
||||
$textMember->skewX(20);
|
||||
$textMember->annotation(330, 735, $memberSince);
|
||||
|
||||
$baseImage->drawImage($textName);
|
||||
$baseImage->drawImage($textMember);
|
||||
}
|
||||
|
||||
if (!empty($githubName)) {
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Regular.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFFFF'));
|
||||
$text->setFontSize($scalingDown ? 16 : 20);
|
||||
$text->setFontWeight(400);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metrics['textWidth'] + 32 + 4;
|
||||
$group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000');
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
|
||||
$precisionFix = -1;
|
||||
|
||||
$group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0);
|
||||
$group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), -22);
|
||||
$x = 510 - $group->getImageWidth() / 2;
|
||||
$y = 530 - $group->getImageHeight() / 2;
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metrics['textWidth'] + 32 + 4;
|
||||
$group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000');
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
|
||||
$precisionFix = -1;
|
||||
|
||||
$group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0);
|
||||
$group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), 31.11);
|
||||
$x = 485 - $group->getImageWidth() / 2;
|
||||
$y = 530 - $group->getImageHeight() / 2;
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y);
|
||||
} else {
|
||||
$text->skewY(20);
|
||||
$text->skewX(20);
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
|
||||
$text->annotation(320 + 15 + 2, 640, $githubName);
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/github-skew.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2), 518 + \strlen($githubName) * 1.3);
|
||||
|
||||
$baseImage->drawImage($text);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\CreditCards;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCreditCard';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/credit-cards/:code')
|
||||
->desc('Get credit card icon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/credit-card')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getCreditCard',
|
||||
description: '/docs/references/avatars/get-credit-card.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $code, int $width, int $height, int $quality, Response $response)
|
||||
{
|
||||
$this->avatar('credit-cards', $code, $width, $height, $quality, $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Favicon;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\URL\URL as URLParse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use enshrined\svgSanitize\Sanitizer as SvgSanitizer;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Fetch\Client;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getFavicon';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/favicon')
|
||||
->desc('Get favicon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/favicon')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getFavicon',
|
||||
description: '/docs/references/avatars/get-favicon.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE
|
||||
))
|
||||
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $url, Response $response)
|
||||
{
|
||||
$width = 56;
|
||||
$height = 56;
|
||||
$quality = 80;
|
||||
$output = 'png';
|
||||
$type = 'png';
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$domain = new Domain(\parse_url($url, PHP_URL_HOST));
|
||||
|
||||
if (!$domain->isKnown()) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(true)
|
||||
->setMaxRedirects(5)
|
||||
->setUserAgent(\sprintf(
|
||||
APP_USERAGENT,
|
||||
System::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
|
||||
))
|
||||
->fetch($url);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->strictErrorChecking = false;
|
||||
@$doc->loadHTML($res->getBody());
|
||||
|
||||
$links = $doc->getElementsByTagName('link') ?? [];
|
||||
$outputHref = '';
|
||||
$outputExt = '';
|
||||
$space = 0;
|
||||
|
||||
foreach ($links as $link) { /* @var $link DOMElement */
|
||||
$href = $link->getAttribute('href');
|
||||
$rel = $link->getAttribute('rel');
|
||||
$sizes = $link->getAttribute('sizes');
|
||||
$absolute = URLParse::unparse(\array_merge(\parse_url($url), \parse_url($href)));
|
||||
|
||||
switch (\strtolower($rel)) {
|
||||
case 'icon':
|
||||
case 'shortcut icon':
|
||||
//case 'apple-touch-icon':
|
||||
$ext = \pathinfo(\parse_url($absolute, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
|
||||
switch ($ext) {
|
||||
case 'svg':
|
||||
// SVG icons are prioritized by assigning the maximum possible value.
|
||||
$space = PHP_INT_MAX;
|
||||
$outputHref = $absolute;
|
||||
$outputExt = $ext;
|
||||
break;
|
||||
case 'ico':
|
||||
case 'png':
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
$size = \explode('x', \strtolower($sizes));
|
||||
|
||||
$sizeWidth = (int) ($size[0] ?? 0);
|
||||
$sizeHeight = (int) ($size[1] ?? 0);
|
||||
|
||||
if (($sizeWidth * $sizeHeight) >= $space) {
|
||||
$space = $sizeWidth * $sizeHeight;
|
||||
$outputHref = $absolute;
|
||||
$outputExt = $ext;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($outputHref) || empty($outputExt)) {
|
||||
$default = \parse_url($url);
|
||||
|
||||
$outputHref = $default['scheme'] . '://' . $default['host'] . '/favicon.ico';
|
||||
$outputExt = 'ico';
|
||||
}
|
||||
|
||||
$domain = new Domain(\parse_url($outputHref, PHP_URL_HOST));
|
||||
|
||||
if (!$domain->isKnown()) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(true)
|
||||
->setMaxRedirects(5)
|
||||
->fetch($outputHref);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
if ($res->getStatusCode() !== 200) {
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND);
|
||||
}
|
||||
|
||||
$data = $res->getBody();
|
||||
|
||||
if ('ico' === $outputExt) { // Skip crop, Imagick isn\'t supporting icon files
|
||||
if (
|
||||
empty($data) ||
|
||||
stripos($data, '<html') === 0 ||
|
||||
stripos($data, '<!doc') === 0
|
||||
) {
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND, 'Favicon not found');
|
||||
}
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/x-icon')
|
||||
->file($data);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('svg' === $outputExt) { // Skip crop, Imagick isn\'t supporting svg files
|
||||
$sanitizer = new SvgSanitizer();
|
||||
$sanitizer->minify(true);
|
||||
$cleanSvg = $sanitizer->sanitize($data);
|
||||
if ($cleanSvg === false) {
|
||||
throw new Exception(Exception::AVATAR_SVG_SANITIZATION_FAILED);
|
||||
}
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/svg+xml')
|
||||
->file($cleanSvg);
|
||||
return;
|
||||
}
|
||||
|
||||
$image = new Image($data);
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Flags;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getFlag';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/flags/:code')
|
||||
->desc('Get country flag')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/flag')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getFlag',
|
||||
description: '/docs/references/avatars/get-flag.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $code, int $width, int $height, int $quality, Response $response)
|
||||
{
|
||||
$this->avatar('flags', $code, $width, $height, $quality, $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Image;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Fetch\Client;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getImage';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/image')
|
||||
->desc('Get image from URL')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/image')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getImage',
|
||||
description: '/docs/references/avatars/get-image.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE
|
||||
))
|
||||
->param('url', '', new URL(['http', 'https']), 'Image URL which you want to crop.')
|
||||
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000. Defaults to 400.', true)
|
||||
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000. Defaults to 400.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $url, int $width, int $height, Response $response)
|
||||
{
|
||||
$quality = 80;
|
||||
$output = 'png';
|
||||
$type = 'png';
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$domain = new Domain(\parse_url($url, PHP_URL_HOST));
|
||||
|
||||
if (!$domain->isKnown()) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(false)
|
||||
->fetch($url);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
if ($res->getStatusCode() !== 200) {
|
||||
throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$image = new Image($res->getBody());
|
||||
} catch (\Throwable $exception) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unable to parse image');
|
||||
}
|
||||
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Initials;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\HexColor;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getInitials';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/initials')
|
||||
->desc('Get user initials')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache.resource', 'avatar/initials')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getInitials',
|
||||
description: '/docs/references/avatars/get-initials.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('name', '', new Text(128), 'Full Name. When empty, current user name or email will be used. Max length: 128 chars.', true)
|
||||
->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('background', '', new HexColor(), 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $name, int $width, int $height, string $background, Response $response, Document $user)
|
||||
{
|
||||
$themes = [
|
||||
['background' => '#FD366E'], // Default (Pink)
|
||||
['background' => '#FE9567'], // Orange
|
||||
['background' => '#7C67FE'], // Purple
|
||||
['background' => '#68A3FE'], // Blue
|
||||
['background' => '#85DBD8'], // Mint
|
||||
];
|
||||
|
||||
$name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
|
||||
$words = \explode(' ', \strtoupper($name));
|
||||
// if there is no space, try to split by `_` underscore
|
||||
$words = (count($words) == 1) ? \explode('_', \strtoupper($name)) : $words;
|
||||
|
||||
$initials = '';
|
||||
$code = 0;
|
||||
|
||||
foreach ($words as $key => $w) {
|
||||
if (ctype_alnum($w[0] ?? '')) {
|
||||
$initials .= $w[0];
|
||||
$code += ord($w[0]);
|
||||
|
||||
if ($key == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rand = \substr($code, -1);
|
||||
|
||||
$rand = ($rand > \count($themes) - 1) ? $rand % \count($themes) : $rand;
|
||||
|
||||
$background = (!empty($background)) ? '#' . $background : $themes[$rand]['background'];
|
||||
|
||||
$image = new Imagick();
|
||||
$punch = new Imagick();
|
||||
$draw = new ImagickDraw();
|
||||
$fontSize = \min($width, $height) / 2;
|
||||
|
||||
$punch->newImage($width, $height, 'transparent');
|
||||
|
||||
$draw->setFont($this->getAppRoot() . '/app/assets/fonts/inter-v8-latin-regular.woff2');
|
||||
$image->setFont($this->getAppRoot() . '/app/assets/fonts/inter-v8-latin-regular.woff2');
|
||||
|
||||
$draw->setFillColor(new ImagickPixel('black'));
|
||||
$draw->setFontSize($fontSize);
|
||||
|
||||
$draw->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials);
|
||||
|
||||
$punch->drawImage($draw);
|
||||
$punch->negateImage(true, Imagick::CHANNEL_ALPHA);
|
||||
|
||||
$image->newImage($width, $height, $background);
|
||||
$image->setImageFormat("png");
|
||||
$image->compositeImage($punch, Imagick::COMPOSITE_COPYOPACITY, 0, 0);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($image->getImageBlob());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\QR;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getQR';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/qr')
|
||||
->desc('Get QR code')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getQR',
|
||||
description: '/docs/references/avatars/get-qr.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('text', '', new Text(512), 'Plain text to be converted to QR code image.')
|
||||
->param('size', 400, new Range(1, 1000), 'QR code size. Pass an integer between 1 to 1000. Defaults to 400.', true)
|
||||
->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true)
|
||||
->param('download', false, new Boolean(true), 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $text, int $size, int $margin, bool $download, Response $response)
|
||||
{
|
||||
$download = ($download === '1' || $download === 'true' || $download === 1 || $download === true);
|
||||
$options = new QROptions([
|
||||
'addQuietzone' => true,
|
||||
'quietzoneSize' => $margin,
|
||||
'outputType' => QRCode::OUTPUT_IMAGICK,
|
||||
'scale' => 15,
|
||||
]);
|
||||
|
||||
$qrcode = new QRCode($options);
|
||||
|
||||
if ($download) {
|
||||
$response->addHeader('Content-Disposition', 'attachment; filename="qr.png"');
|
||||
}
|
||||
|
||||
$image = new Image($qrcode->render($text));
|
||||
$image->crop((int) $size, (int) $size);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->send($image->output('png', 90));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Screenshots;
|
||||
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Fetch\Client;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getScreenshot';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/screenshots')
|
||||
->desc('Get webpage screenshot')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('usage.metric', METRIC_AVATARS_SCREENSHOTS_GENERATED)
|
||||
->label('abuse-limit', 60)
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'avatar/screenshot')
|
||||
->label('cache.resource', 'screenshot/{request.url}/{request.width}/{request.height}/{request.scale}/{request.theme}/{request.userAgent}/{request.fullpage}/{request.locale}/{request.timezone}/{request.latitude}/{request.longitude}/{request.accuracy}/{request.touch}/{request.permissions}/{request.sleep}/{request.quality}/{request.output}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getScreenshot',
|
||||
description: '/docs/references/avatars/get-screenshot.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to capture.', example: 'https://example.com')
|
||||
->param('headers', [], new Assoc(), 'HTTP headers to send with the browser request. Defaults to empty.', true, example: '{"Authorization":"Bearer token123","X-Custom-Header":"value"}')
|
||||
->param('viewportWidth', 1280, new Range(1, 1920), 'Browser viewport width. Pass an integer between 1 to 1920. Defaults to 1280.', true, example: '1920')
|
||||
->param('viewportHeight', 720, new Range(1, 1080), 'Browser viewport height. Pass an integer between 1 to 1080. Defaults to 720.', true, example: '1080')
|
||||
->param('scale', 1, new Range(0.1, 3, Range::TYPE_FLOAT), 'Browser scale factor. Pass a number between 0.1 to 3. Defaults to 1.', true, example: '2')
|
||||
->param('theme', 'light', new WhiteList(['light', 'dark']), 'Browser theme. Pass "light" or "dark". Defaults to "light".', true, example: 'dark')
|
||||
->param('userAgent', '', new Text(512), 'Custom user agent string. Defaults to browser default.', true, example: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15')
|
||||
->param('fullpage', false, new Boolean(true), 'Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.', true, example: 'true')
|
||||
->param('locale', '', new Text(10), 'Browser locale (e.g., "en-US", "fr-FR"). Defaults to browser default.', true, example: 'en-US')
|
||||
->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'america/new_york')
|
||||
->param('latitude', 0, new Range(-90, 90, Range::TYPE_FLOAT), 'Geolocation latitude. Pass a number between -90 to 90. Defaults to 0.', true, example: '37.7749')
|
||||
->param('longitude', 0, new Range(-180, 180, Range::TYPE_FLOAT), 'Geolocation longitude. Pass a number between -180 to 180. Defaults to 0.', true, example: '-122.4194')
|
||||
->param('accuracy', 0, new Range(0, 100000, Range::TYPE_FLOAT), 'Geolocation accuracy in meters. Pass a number between 0 to 100000. Defaults to 0.', true, example: '100')
|
||||
->param('touch', false, new Boolean(true), 'Enable touch support. Pass 0 for no touch, or 1 for touch enabled. Defaults to 0.', true, example: 'true')
|
||||
->param('permissions', [], new ArrayList(new WhiteList(['geolocation', 'camera', 'microphone', 'notifications', 'midi', 'push', 'clipboard-read', 'clipboard-write', 'payment-handler', 'usb', 'bluetooth', 'accelerometer', 'gyroscope', 'magnetometer', 'ambient-light-sensor', 'background-sync', 'persistent-storage', 'screen-wake-lock', 'web-share', 'xr-spatial-tracking'])), 'Browser permissions to grant. Pass an array of permission names like ["geolocation", "camera", "microphone"]. Defaults to empty.', true, example: '["geolocation","notifications"]')
|
||||
->param('sleep', 0, new Range(0, 10), 'Wait time in seconds before taking the screenshot. Pass an integer between 0 to 10. Defaults to 0.', true, example: '3')
|
||||
->param('width', 0, new Range(0, 2000), 'Output image width. Pass 0 to use original width, or an integer between 1 to 2000. Defaults to 0 (original width).', true, example: '800')
|
||||
->param('height', 0, new Range(0, 2000), 'Output image height. Pass 0 to use original height, or an integer between 1 to 2000. Defaults to 0 (original height).', true, example: '600')
|
||||
->param('quality', -1, new Range(-1, 100), 'Screenshot quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true, example: '85')
|
||||
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true, example: 'jpeg')
|
||||
->inject('response')
|
||||
->inject('queueForStatsUsage')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $url, array $headers, int $viewportWidth, int $viewportHeight, float $scale, string $theme, string $userAgent, bool $fullpage, string $locale, string $timezone, float $latitude, float $longitude, float $accuracy, bool $touch, array $permissions, int $sleep, int $width, int $height, int $quality, string $output, Response $response, StatsUsage $queueForStatsUsage)
|
||||
{
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$domain = new Domain(\parse_url($url, PHP_URL_HOST));
|
||||
|
||||
if (!$domain->isKnown()) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
$client->setTimeout(30 * 1000); // 30 seconds
|
||||
$client->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);
|
||||
|
||||
// Convert indexed array to empty array (should not happen due to Assoc validator)
|
||||
if (is_array($headers) && count($headers) > 0 && array_keys($headers) === range(0, count($headers) - 1)) {
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
// Create a new object to ensure proper JSON serialization
|
||||
$headersObject = new \stdClass();
|
||||
foreach ($headers as $key => $value) {
|
||||
$headersObject->$key = $value;
|
||||
}
|
||||
|
||||
// Create the config with headers as an object
|
||||
// The custom browser service accepts: url, theme, headers, sleep, viewport, userAgent, fullPage, locale, timezoneId, geolocation, hasTouch, scale
|
||||
$config = [
|
||||
'url' => $url,
|
||||
'theme' => $theme,
|
||||
'headers' => $headersObject,
|
||||
'sleep' => $sleep * 1000, // Convert seconds to milliseconds
|
||||
'waitUntil' => 'load',
|
||||
'viewport' => [
|
||||
'width' => $viewportWidth,
|
||||
'height' => $viewportHeight
|
||||
]
|
||||
];
|
||||
|
||||
// Add scale if not default
|
||||
if ($scale != 1) {
|
||||
$config['deviceScaleFactor'] = $scale;
|
||||
}
|
||||
|
||||
// Add optional parameters that were set, preserving arrays as arrays
|
||||
if (!empty($userAgent)) {
|
||||
$config['userAgent'] = $userAgent;
|
||||
}
|
||||
|
||||
if ($fullpage) {
|
||||
$config['fullPage'] = true;
|
||||
}
|
||||
|
||||
if (!empty($locale)) {
|
||||
$config['locale'] = $locale;
|
||||
}
|
||||
|
||||
if (!empty($timezone)) {
|
||||
$config['timezoneId'] = $timezone;
|
||||
}
|
||||
|
||||
// Add geolocation if any coordinates are provided
|
||||
if ($latitude != 0 || $longitude != 0) {
|
||||
$config['geolocation'] = [
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
'accuracy' => $accuracy
|
||||
];
|
||||
}
|
||||
|
||||
if ($touch) {
|
||||
$config['hasTouch'] = true;
|
||||
}
|
||||
|
||||
// Add permissions if provided (preserve as array)
|
||||
if (!empty($permissions)) {
|
||||
$config['permissions'] = $permissions; // Keep as array
|
||||
}
|
||||
|
||||
try {
|
||||
$browserEndpoint = System::getEnv('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
|
||||
|
||||
$fetchResponse = $client->fetch(
|
||||
url: $browserEndpoint . '/screenshots',
|
||||
method: 'POST',
|
||||
body: $config
|
||||
);
|
||||
|
||||
if ($fetchResponse->getStatusCode() >= 400) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot service failed: ' . $fetchResponse->getBody());
|
||||
}
|
||||
|
||||
$screenshot = $fetchResponse->getBody();
|
||||
|
||||
if (empty($screenshot)) {
|
||||
throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND, 'Screenshot not generated');
|
||||
}
|
||||
|
||||
// Determine if image processing is needed
|
||||
$needsProcessing = ($width > 0 || $height > 0) || $quality !== -1 || !empty($output);
|
||||
|
||||
if ($needsProcessing) {
|
||||
// Process image with cropping, quality adjustment, or format conversion
|
||||
$image = new Image($screenshot);
|
||||
|
||||
$image->crop($width, $height);
|
||||
|
||||
$output = $output ?: 'png'; // Default to PNG if not specified
|
||||
$resizedScreenshot = $image->output($output, $quality);
|
||||
unset($image);
|
||||
} else {
|
||||
// Return original screenshot without processing
|
||||
$resizedScreenshot = $screenshot;
|
||||
$output = 'png'; // Screenshots are typically PNG by default
|
||||
}
|
||||
|
||||
// Set content type based on output format
|
||||
$outputs = Config::getParam('storage-outputs');
|
||||
$contentType = $outputs[$output] ?? $outputs['png'];
|
||||
|
||||
$queueForStatsUsage->addMetric(METRIC_AVATARS_SCREENSHOTS_GENERATED, 1);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType($contentType)
|
||||
->file($resizedScreenshot);
|
||||
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot generation failed: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Services\Http;
|
||||
use Utopia\Platform;
|
||||
|
||||
class Module extends Platform\Module
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->addService('http', new Http());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Services;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Browsers\Get as GetBrowser;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\Back\Get as GetCloudCardBack;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\Front\Get as GetCloudCard;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\OG\Get as GetCloudCardOG;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\CreditCards\Get as GetCreditCard;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Favicon\Get as GetFavicon;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Flags\Get as GetFlag;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Image\Get as GetImage;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Initials\Get as GetInitials;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\QR\Get as GetQR;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Screenshots\Get as GetScreenshot;
|
||||
use Utopia\Platform\Service;
|
||||
|
||||
class Http extends Service
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->type = Service::TYPE_HTTP;
|
||||
|
||||
$this->addAction(GetCreditCard::getName(), new GetCreditCard());
|
||||
$this->addAction(GetBrowser::getName(), new GetBrowser());
|
||||
$this->addAction(GetFlag::getName(), new GetFlag());
|
||||
$this->addAction(GetImage::getName(), new GetImage());
|
||||
$this->addAction(GetFavicon::getName(), new GetFavicon());
|
||||
$this->addAction(GetQR::getName(), new GetQR());
|
||||
$this->addAction(GetInitials::getName(), new GetInitials());
|
||||
$this->addAction(GetScreenshot::getName(), new GetScreenshot());
|
||||
$this->addAction(GetCloudCard::getName(), new GetCloudCard());
|
||||
$this->addAction(GetCloudCardBack::getName(), new GetCloudCardBack());
|
||||
$this->addAction(GetCloudCardOG::getName(), new GetCloudCardOG());
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Swoole\Request;
|
||||
use Utopia\System\System;
|
||||
@@ -142,7 +143,7 @@ class Base extends Action
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document
|
||||
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, Authorization $authorization, string $referenceType = 'branch', string $reference = ''): Document
|
||||
{
|
||||
$deploymentId = ID::unique();
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
|
||||
@@ -239,7 +240,7 @@ class Base extends Action
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain) : ID::unique();
|
||||
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -265,7 +266,7 @@ class Base extends Action
|
||||
$domain = "commit-" . substr($commitDetails['commitHash'], 0, 16) . ".{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -302,7 +303,7 @@ class Base extends Action
|
||||
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -328,6 +329,8 @@ class Base extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform, $authorization);
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
->setResource($site)
|
||||
@@ -336,4 +339,34 @@ class Base extends Action
|
||||
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update empty manual rule for deployment.
|
||||
* In case of first deployment, deployment ID will be empty in the rules, so we need to update it here.
|
||||
*
|
||||
* @param \Utopia\Database\Document $project
|
||||
* @param \Utopia\Database\Document $resource
|
||||
* @param \Utopia\Database\Document $deployment
|
||||
* @param \Utopia\Database\Database $dbForPlatform
|
||||
* @return void
|
||||
*/
|
||||
public static function updateEmptyManualRule(Document $project, Document $resource, Document $deployment, Database $dbForPlatform, Authorization $authorization)
|
||||
{
|
||||
$resourceType = $resource->getCollection() === 'sites' ? 'site' : 'function';
|
||||
|
||||
$queries = [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::equal('deploymentResourceInternalId', [$resource->getSequence()]),
|
||||
Query::equal('deploymentResourceType', [$resourceType]),
|
||||
Query::equal('deploymentId', ['']),
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('trigger', ['manual']),
|
||||
];
|
||||
$dbForPlatform->forEach('rules', function (Document $rule) use ($deployment, $dbForPlatform, $authorization) {
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'deploymentInternalId' => $deployment->getSequence(),
|
||||
])));
|
||||
}, $queries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ class Get extends Action
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -68,7 +69,8 @@ class Get extends Action
|
||||
string $type,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
array $platform
|
||||
array $platform,
|
||||
Authorization $authorization,
|
||||
) {
|
||||
$domains = $platform['hostnames'] ?? [];
|
||||
if ($type === 'rules') {
|
||||
@@ -121,7 +123,7 @@ class Get extends Action
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
$document = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
$document = $authorization->skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$value]),
|
||||
]));
|
||||
|
||||
|
||||
+5
-5
@@ -292,7 +292,7 @@ abstract class Action extends UtopiaAction
|
||||
};
|
||||
}
|
||||
|
||||
protected function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): Document
|
||||
protected function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): Document
|
||||
{
|
||||
$key = $attribute->getAttribute('key');
|
||||
$type = $attribute->getAttribute('type', '');
|
||||
@@ -310,7 +310,7 @@ abstract class Action extends UtopiaAction
|
||||
throw new Exception($this->getSpatialTypeNotSupportedException(), params: [$type]);
|
||||
}
|
||||
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
@@ -371,7 +371,7 @@ abstract class Action extends UtopiaAction
|
||||
\in_array($attribute->getAttribute('type'), Database::SPATIAL_TYPES) &&
|
||||
$attribute->getAttribute('required')
|
||||
) {
|
||||
$hasData = !Authorization::skip(fn () => $dbForProject
|
||||
$hasData = !$authorization->skip(fn () => $dbForProject
|
||||
->findOne('database_' . $db->getSequence() . '_collection_' . $collection->getSequence()))
|
||||
->isEmpty();
|
||||
|
||||
@@ -472,9 +472,9 @@ abstract class Action extends UtopiaAction
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
protected function updateAttribute(string $databaseId, string $collectionId, string $key, Database $dbForProject, Event $queueForEvents, string $type, int $size = null, string $filter = null, string|bool|int|float|array $default = null, bool $required = null, int|float|null $min = null, int|float|null $max = null, array $elements = null, array $options = [], string $newKey = null): Document
|
||||
protected function updateAttribute(string $databaseId, string $collectionId, string $key, Database $dbForProject, Event $queueForEvents, Authorization $authorization, string $type, int $size = null, string $filter = null, string|bool|int|float|array $default = null, bool $required = null, int|float|null $min = null, int|float|null $max = null, array $elements = null, array $options = [], string $newKey = null): Document
|
||||
{
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
|
||||
+4
-2
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -69,10 +70,11 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
@@ -81,7 +83,7 @@ class Create extends Action
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -68,10 +69,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -79,6 +81,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_BOOLEAN,
|
||||
default: $default,
|
||||
required: $required,
|
||||
|
||||
+5
-2
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -70,10 +71,11 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute(
|
||||
$databaseId,
|
||||
@@ -90,7 +92,8 @@ class Create extends Action
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$queueForEvents
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
$response
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -69,10 +70,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -80,6 +82,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_DATETIME,
|
||||
default: $default,
|
||||
required: $required,
|
||||
|
||||
+3
-2
@@ -67,12 +67,13 @@ class Delete extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
+5
-2
@@ -13,6 +13,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -70,10 +71,11 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute(
|
||||
$databaseId,
|
||||
@@ -90,7 +92,8 @@ class Create extends Action
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$queueForEvents
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
$response
|
||||
|
||||
+4
-1
@@ -12,6 +12,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -69,10 +70,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -80,6 +82,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_EMAIL,
|
||||
default: $default,
|
||||
|
||||
+5
-2
@@ -13,6 +13,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -73,10 +74,11 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
if (!is_null($default) && !\in_array($default, $elements, true)) {
|
||||
throw new Exception($this->getInvalidValueException(), 'Default value not found in elements');
|
||||
@@ -98,7 +100,8 @@ class Create extends Action
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$queueForEvents
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
$response
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -71,10 +72,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -82,6 +84,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_ENUM,
|
||||
default: $default,
|
||||
|
||||
+4
-2
@@ -13,6 +13,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -74,10 +75,11 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$min ??= -PHP_FLOAT_MAX;
|
||||
$max ??= PHP_FLOAT_MAX;
|
||||
@@ -100,7 +102,7 @@ class Create extends Action
|
||||
'array' => $array,
|
||||
'format' => APP_DATABASE_ATTRIBUTE_FLOAT_RANGE,
|
||||
'formatOptions' => ['min' => $min, 'max' => $max],
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
if (!empty($formatOptions)) {
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -71,10 +72,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -82,6 +84,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_FLOAT,
|
||||
default: $default,
|
||||
required: $required,
|
||||
|
||||
+3
-2
@@ -68,12 +68,13 @@ class Get extends Action
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
+5
-2
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -70,10 +71,11 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute(
|
||||
$databaseId,
|
||||
@@ -90,7 +92,8 @@ class Create extends Action
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$queueForEvents
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
$response
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -69,10 +70,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -80,6 +82,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_IP,
|
||||
default: $default,
|
||||
|
||||
+4
-2
@@ -13,6 +13,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -74,10 +75,11 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$min ??= \PHP_INT_MIN;
|
||||
$max ??= \PHP_INT_MAX;
|
||||
@@ -102,7 +104,7 @@ class Create extends Action
|
||||
'array' => $array,
|
||||
'format' => APP_DATABASE_ATTRIBUTE_INT_RANGE,
|
||||
'formatOptions' => ['min' => $min, 'max' => $max],
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
if (!empty($formatOptions)) {
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -71,10 +72,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -82,6 +84,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_INTEGER,
|
||||
default: $default,
|
||||
required: $required,
|
||||
|
||||
+4
-2
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -69,17 +70,18 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
'type' => Database::VAR_LINESTRING,
|
||||
'required' => $required,
|
||||
'default' => $default
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -69,10 +70,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -80,6 +82,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_LINESTRING,
|
||||
default: $default,
|
||||
required: $required,
|
||||
|
||||
+4
-2
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -69,17 +70,18 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
'type' => Database::VAR_POINT,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -69,10 +70,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -80,6 +82,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_POINT,
|
||||
default: $default,
|
||||
required: $required,
|
||||
|
||||
+4
-2
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -69,17 +70,18 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
'type' => Database::VAR_POLYGON,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+4
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Spatial;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -69,10 +70,11 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -80,6 +82,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_POLYGON,
|
||||
default: $default,
|
||||
required: $required,
|
||||
|
||||
+4
-3
@@ -83,16 +83,17 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $relatedCollectionId, string $type, bool $twoWay, ?string $key, ?string $twoWayKey, string $onDelete, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $relatedCollectionId, string $type, bool $twoWay, ?string $key, ?string $twoWayKey, string $onDelete, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$key ??= $relatedCollectionId;
|
||||
$twoWayKeyWasProvided = $twoWayKey !== null;
|
||||
$twoWayKey ??= $collectionId;
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
@@ -154,7 +155,7 @@ class Create extends Action
|
||||
'twoWayKey' => $twoWayKey,
|
||||
'onDelete' => $onDelete,
|
||||
]
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
foreach ($attribute->getAttribute('options', []) as $k => $option) {
|
||||
$attribute->setAttribute($k, $option);
|
||||
|
||||
+5
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -71,6 +72,7 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -82,7 +84,8 @@ class Update extends Action
|
||||
?string $newKey,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
Event $queueForEvents
|
||||
Event $queueForEvents,
|
||||
Authorization $authorization
|
||||
): void {
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -90,6 +93,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_RELATIONSHIP,
|
||||
required: false,
|
||||
options: [
|
||||
|
||||
+6
-2
@@ -14,6 +14,7 @@ use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -77,6 +78,7 @@ class Create extends Action
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -93,7 +95,8 @@ class Create extends Action
|
||||
Database $dbForProject,
|
||||
EventDatabase $queueForDatabase,
|
||||
Event $queueForEvents,
|
||||
array $plan
|
||||
array $plan,
|
||||
Authorization $authorization
|
||||
): void {
|
||||
if (!App::isDevelopment() && $encrypt && !empty($plan) && !($plan['databasesAllowEncrypt'] ?? false)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Encrypted string ' . $this->getSDKGroup() . ' are not available on your plan. Please upgrade to create encrypted string ' . $this->getSDKGroup() . '.');
|
||||
@@ -132,7 +135,8 @@ class Create extends Action
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$queueForEvents
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
$attribute->setAttribute('encrypt', $encrypt);
|
||||
|
||||
+5
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -72,6 +73,7 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -85,7 +87,8 @@ class Update extends Action
|
||||
?string $newKey,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
Event $queueForEvents
|
||||
Event $queueForEvents,
|
||||
Authorization $authorization
|
||||
): void {
|
||||
$attribute = $this->updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
@@ -93,6 +96,7 @@ class Update extends Action
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
authorization: $authorization,
|
||||
type: Database::VAR_STRING,
|
||||
size: $size,
|
||||
default: $default,
|
||||
|
||||
+5
-2
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -70,6 +71,7 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -83,7 +85,8 @@ class Create extends Action
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
EventDatabase $queueForDatabase,
|
||||
Event $queueForEvents
|
||||
Event $queueForEvents,
|
||||
Authorization $authorization
|
||||
): void {
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
@@ -93,7 +96,7 @@ class Create extends Action
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
'format' => APP_DATABASE_ATTRIBUTE_URL,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+5
-1
@@ -11,6 +11,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -69,6 +70,7 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -81,7 +83,8 @@ class Update extends Action
|
||||
?string $newKey,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
Event $queueForEvents
|
||||
Event $queueForEvents,
|
||||
Authorization $authorization
|
||||
): void {
|
||||
$attribute = $this->updateAttribute(
|
||||
$databaseId,
|
||||
@@ -89,6 +92,7 @@ class Update extends Action
|
||||
$key,
|
||||
$dbForProject,
|
||||
$queueForEvents,
|
||||
$authorization,
|
||||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_URL,
|
||||
default: $default,
|
||||
|
||||
+3
-2
@@ -64,12 +64,13 @@ class XList extends Action
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, bool $includeTotal, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $databaseId, string $collectionId, array $queries, bool $includeTotal, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
@@ -85,12 +85,13 @@ class Create extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, array $attributes, array $indexes, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, array $attributes, array $indexes, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
|
||||
@@ -64,12 +64,13 @@ class Delete extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
+4
-3
@@ -258,9 +258,9 @@ abstract class Action extends DatabasesAction
|
||||
Document $collection,
|
||||
Document $document,
|
||||
Database $dbForProject,
|
||||
|
||||
/* options */
|
||||
array &$collectionsCache,
|
||||
Authorization $authorization,
|
||||
?int &$operations = null,
|
||||
): bool {
|
||||
|
||||
@@ -297,7 +297,7 @@ abstract class Action extends DatabasesAction
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
|
||||
if (!isset($collectionsCache[$relatedCollectionId])) {
|
||||
$relatedCollectionDoc = Authorization::skip(
|
||||
$relatedCollectionDoc = $authorization->skip(
|
||||
fn () => $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence(),
|
||||
$relatedCollectionId
|
||||
@@ -323,7 +323,8 @@ abstract class Action extends DatabasesAction
|
||||
document: $relation,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
operations: $operations
|
||||
operations: $operations,
|
||||
authorization: $authorization
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -85,20 +85,21 @@ class Decrement extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan, Authorization $authorization): void
|
||||
{
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
|
||||
}
|
||||
@@ -106,7 +107,7 @@ class Decrement extends Action
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = ($isAPIKey || $isPrivilegedUser)
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
? $authorization->skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
: $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction');
|
||||
|
||||
+7
-6
@@ -85,20 +85,21 @@ class Increment extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan): void
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, array $plan, Authorization $authorization): void
|
||||
{
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
|
||||
}
|
||||
@@ -106,7 +107,7 @@ class Increment extends Action
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = ($isAPIKey || $isPrivilegedUser)
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
? $authorization->skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
: $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction');
|
||||
|
||||
+25
-18
@@ -24,6 +24,7 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
@@ -132,9 +133,10 @@ class Create extends Action
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
|
||||
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, Authorization $authorization): void
|
||||
{
|
||||
$data = \is_string($data)
|
||||
? \json_decode($data, true)
|
||||
@@ -178,19 +180,19 @@ class Create extends Action
|
||||
$documents = [$data];
|
||||
}
|
||||
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
if ($isBulk && !$isAPIKey && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE);
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
|
||||
}
|
||||
@@ -204,7 +206,7 @@ class Create extends Action
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk create is not supported for ' . $this->getSDKNamespace() .' with relationship ' . $this->getStructureContext());
|
||||
}
|
||||
|
||||
$setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk) {
|
||||
$setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk, $dbForProject, $authorization) {
|
||||
$allowedPermissions = [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
@@ -247,8 +249,8 @@ class Create extends Action
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')');
|
||||
if (!$authorization->hasRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $authorization->getRoles()) . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,21 +261,25 @@ class Create extends Action
|
||||
|
||||
$operations = 0;
|
||||
|
||||
$checkPermissions = function (Document $collection, Document $document, string $permission) use ($isAPIKey, $isPrivilegedUser, &$checkPermissions, $dbForProject, $database, &$operations) {
|
||||
$checkPermissions = function (Document $collection, Document $document, string $permission) use ($isAPIKey, $isPrivilegedUser, &$checkPermissions, $dbForProject, $database, &$operations, $authorization) {
|
||||
$operations++;
|
||||
|
||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||
$validator = new Authorization($permission);
|
||||
|
||||
$valid = $validator->isValid($collection->getPermissionsByType($permission));
|
||||
if (($permission === Database::PERMISSION_UPDATE && !$documentSecurity) || !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
$validCollection = $authorization->isValid(
|
||||
new Input($permission, $collection->getPermissionsByType($permission))
|
||||
);
|
||||
if (($permission === Database::PERMISSION_UPDATE && !$documentSecurity) || !$validCollection) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if ($permission === Database::PERMISSION_UPDATE) {
|
||||
$valid = $valid || $validator->isValid($document->getUpdate());
|
||||
$validDocument = $authorization->isValid(
|
||||
new Input($permission, $document->getUpdate())
|
||||
);
|
||||
$valid = $validCollection || $validDocument;
|
||||
if ($documentSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +304,7 @@ class Create extends Action
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
$relatedCollection = Authorization::skip(
|
||||
$relatedCollection = $authorization->skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
|
||||
);
|
||||
|
||||
@@ -314,7 +320,7 @@ class Create extends Action
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$current = Authorization::skip(
|
||||
$current = $authorization->skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId())
|
||||
);
|
||||
|
||||
@@ -369,7 +375,7 @@ class Create extends Action
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = ($isAPIKey || $isPrivilegedUser)
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
? $authorization->skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
: $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND, params: [$transactionId]);
|
||||
@@ -468,6 +474,7 @@ class Create extends Action
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
authorization: $authorization
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+10
-7
@@ -83,6 +83,7 @@ class Delete extends Action
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('transactionState')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -97,18 +98,19 @@ class Delete extends Action
|
||||
Event $queueForEvents,
|
||||
StatsUsage $queueForStatsUsage,
|
||||
TransactionState $transactionState,
|
||||
array $plan
|
||||
array $plan,
|
||||
Authorization $authorization
|
||||
): void {
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
|
||||
@@ -121,7 +123,7 @@ class Delete extends Action
|
||||
// Use transaction-aware document retrieval to see changes from same transaction
|
||||
$document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId);
|
||||
} else {
|
||||
$document = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId));
|
||||
$document = $authorization->skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId));
|
||||
}
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
@@ -131,7 +133,7 @@ class Delete extends Action
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = ($isAPIKey || $isPrivilegedUser)
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
? $authorization->skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
: $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND, params: [$transactionId]);
|
||||
@@ -205,6 +207,7 @@ class Delete extends Action
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
authorization: $authorization
|
||||
);
|
||||
|
||||
$queueForStatsUsage
|
||||
|
||||
+7
-5
@@ -70,20 +70,21 @@ class Get extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('transactionState')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionState $transactionState): void
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionState $transactionState, Authorization $authorization): void
|
||||
{
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
|
||||
@@ -125,6 +126,7 @@ class Get extends Action
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
authorization: $authorization,
|
||||
operations: $operations
|
||||
);
|
||||
|
||||
|
||||
+3
-2
@@ -72,13 +72,14 @@ class XList extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->inject('audit')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit): void
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization, Audit $audit): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
+14
-12
@@ -87,10 +87,11 @@ class Update extends Action
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('transactionState')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionState $transactionState, array $plan): void
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionState $transactionState, array $plan, Authorization $authorization): void
|
||||
{
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
@@ -98,16 +99,16 @@ class Update extends Action
|
||||
throw new Exception($this->getMissingPayloadException());
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
|
||||
@@ -125,7 +126,7 @@ class Update extends Action
|
||||
// Use transaction-aware document retrieval to see changes from same transaction
|
||||
$document = $transactionState->getDocument($collectionTableId, $documentId, $transactionId);
|
||||
} else {
|
||||
$document = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId));
|
||||
$document = $authorization->skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId));
|
||||
}
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
@@ -140,7 +141,7 @@ class Update extends Action
|
||||
]);
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
@@ -153,7 +154,7 @@ class Update extends Action
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
if (!$authorization->hasRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
@@ -171,7 +172,7 @@ class Update extends Action
|
||||
|
||||
$operations = 0;
|
||||
|
||||
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) {
|
||||
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations, $authorization) {
|
||||
$operations++;
|
||||
|
||||
$relationships = \array_filter(
|
||||
@@ -195,7 +196,7 @@ class Update extends Action
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
$relatedCollection = Authorization::skip(
|
||||
$relatedCollection = $authorization->skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
|
||||
);
|
||||
|
||||
@@ -212,7 +213,7 @@ class Update extends Action
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
||||
$oldDocument = $authorization->skip(fn () => $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
||||
$relation->getId()
|
||||
));
|
||||
@@ -249,7 +250,7 @@ class Update extends Action
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = ($isAPIKey || $isPrivilegedUser)
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
? $authorization->skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
: $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND, params: [$transactionId]);
|
||||
@@ -340,6 +341,7 @@ class Update extends Action
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
authorization: $authorization,
|
||||
);
|
||||
|
||||
$response->dynamic($document, $this->getResponseModel());
|
||||
|
||||
+14
-12
@@ -91,10 +91,11 @@ class Upsert extends Action
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('transactionState')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionState $transactionState, array $plan): void
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?string $transactionId, ?\DateTime $requestTimestamp, UtopiaResponse $response, Document $user, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage, TransactionState $transactionState, array $plan, Authorization $authorization): void
|
||||
{
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
@@ -106,15 +107,15 @@ class Upsert extends Action
|
||||
throw new Exception($this->getMissingPayloadException());
|
||||
}
|
||||
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
|
||||
}
|
||||
@@ -139,7 +140,7 @@ class Upsert extends Action
|
||||
// Use transaction-aware document retrieval to see changes from same transaction
|
||||
$oldDocument = $transactionState->getDocument($collectionTableId, $documentId, $transactionId);
|
||||
} else {
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId));
|
||||
$oldDocument = $authorization->skip(fn () => $dbForProject->getDocument($collectionTableId, $documentId));
|
||||
}
|
||||
if ($oldDocument->isEmpty()) {
|
||||
if (!empty($user->getId())) {
|
||||
@@ -155,7 +156,7 @@ class Upsert extends Action
|
||||
}
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
@@ -168,7 +169,7 @@ class Upsert extends Action
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
if (!$authorization->hasRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
@@ -181,7 +182,7 @@ class Upsert extends Action
|
||||
$newDocument = new Document($data);
|
||||
$operations = 0;
|
||||
|
||||
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) {
|
||||
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations, $authorization) {
|
||||
$operations++;
|
||||
|
||||
$relationships = \array_filter(
|
||||
@@ -205,7 +206,7 @@ class Upsert extends Action
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
$relatedCollection = Authorization::skip(
|
||||
$relatedCollection = $authorization->skip(
|
||||
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
|
||||
);
|
||||
|
||||
@@ -222,7 +223,7 @@ class Upsert extends Action
|
||||
if ($relation instanceof Document) {
|
||||
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
|
||||
|
||||
$oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
|
||||
$oldDocument = $authorization->skip(fn () => $dbForProject->getDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
|
||||
$relation->getId()
|
||||
));
|
||||
@@ -259,7 +260,7 @@ class Upsert extends Action
|
||||
// Handle transaction staging
|
||||
if ($transactionId !== null) {
|
||||
$transaction = ($isAPIKey || $isPrivilegedUser)
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
? $authorization->skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
: $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND, params: [$transactionId]);
|
||||
@@ -361,6 +362,7 @@ class Upsert extends Action
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
authorization: $authorization
|
||||
);
|
||||
|
||||
$relationships = \array_map(
|
||||
|
||||
+9
-7
@@ -74,20 +74,21 @@ class XList extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('transactionState')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, bool $includeTotal, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionState $transactionState): void
|
||||
public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, bool $includeTotal, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionState $transactionState, Authorization $authorization): void
|
||||
{
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
|
||||
}
|
||||
@@ -115,7 +116,7 @@ class XList extends Action
|
||||
|
||||
$documentId = $cursor->getValue();
|
||||
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
$type = ucfirst($this->getContext());
|
||||
@@ -161,7 +162,8 @@ class XList extends Action
|
||||
document: $document,
|
||||
dbForProject: $dbForProject,
|
||||
collectionsCache: $collectionsCache,
|
||||
operations: $operations,
|
||||
authorization: $authorization,
|
||||
operations: $operations
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,12 +57,13 @@ class Get extends Action
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
|
||||
+3
-2
@@ -79,12 +79,13 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, array $lengths, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, array $lengths, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
|
||||
+3
-2
@@ -70,12 +70,13 @@ class Delete extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
|
||||
@@ -59,12 +59,13 @@ class Get extends Action
|
||||
->param('key', null, new Key(), 'Index Key.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
|
||||
+4
-3
@@ -66,13 +66,14 @@ class XList extends Action
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, bool $includeTotal, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $databaseId, string $collectionId, array $queries, bool $includeTotal, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
/** @var Document $database */
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
@@ -112,7 +113,7 @@ class XList extends Action
|
||||
}
|
||||
|
||||
$indexId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->find('indexes', [
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->find('indexes', [
|
||||
Query::equal('collectionInternalId', [$collection->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$database->getSequence()]),
|
||||
Query::equal('key', [$indexId]),
|
||||
|
||||
@@ -71,13 +71,14 @@ class XList extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->inject('audit')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit): void
|
||||
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization, Audit $audit): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
@@ -112,9 +113,9 @@ class XList extends Action
|
||||
$detector = new Detector($log['userAgent']);
|
||||
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
|
||||
|
||||
$os = $detector->getOS();
|
||||
$client = $detector->getClient();
|
||||
$device = $detector->getDevice();
|
||||
$os = $detector->getOS() ?: [];
|
||||
$client = $detector->getClient() ?: [];
|
||||
$device = $detector->getDevice() ?: [];
|
||||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
@@ -122,20 +123,20 @@ class XList extends Action
|
||||
'userEmail' => $log['data']['userEmail'] ?? null,
|
||||
'userName' => $log['data']['userName'] ?? null,
|
||||
'mode' => $log['data']['mode'] ?? null,
|
||||
'ip' => $log['ip'],
|
||||
'time' => $log['time'],
|
||||
'osCode' => $os['osCode'],
|
||||
'osName' => $os['osName'],
|
||||
'osVersion' => $os['osVersion'],
|
||||
'clientType' => $client['clientType'],
|
||||
'clientCode' => $client['clientCode'],
|
||||
'clientName' => $client['clientName'],
|
||||
'clientVersion' => $client['clientVersion'],
|
||||
'clientEngine' => $client['clientEngine'],
|
||||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel']
|
||||
'ip' => $log['ip'] ?? null,
|
||||
'time' => $log['time'] ?? null,
|
||||
'osCode' => $os['osCode'] ?? null,
|
||||
'osName' => $os['osName'] ?? null,
|
||||
'osVersion' => $os['osVersion'] ?? null,
|
||||
'clientType' => $client['clientType'] ?? null,
|
||||
'clientCode' => $client['clientCode'] ?? null,
|
||||
'clientName' => $client['clientName'] ?? null,
|
||||
'clientVersion' => $client['clientVersion'] ?? null,
|
||||
'clientEngine' => $client['clientEngine'] ?? null,
|
||||
'clientEngineVersion' => $client['clientEngineVersion'] ?? null,
|
||||
'deviceName' => $device['deviceName'] ?? null,
|
||||
'deviceBrand' => $device['deviceBrand'] ?? null,
|
||||
'deviceModel' => $device['deviceModel'] ?? null
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
|
||||
@@ -71,12 +71,13 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
}
|
||||
|
||||
@@ -63,10 +63,11 @@ class Get extends Action
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $range, string $collectionId, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $databaseId, string $range, string $collectionId, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
$collectionDocument = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
|
||||
@@ -83,7 +84,7 @@ class Get extends Action
|
||||
str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getSequence(), $collectionDocument->getSequence()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS),
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
|
||||
@@ -67,12 +67,13 @@ class XList extends Action
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, array $queries, string $search, bool $includeTotal, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $databaseId, array $queries, string $search, bool $includeTotal, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$databaseId]);
|
||||
|
||||
@@ -55,10 +55,11 @@ class Create extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(int $ttl, UtopiaResponse $response, Database $dbForProject, Document $user): void
|
||||
public function action(int $ttl, UtopiaResponse $response, Database $dbForProject, Document $user, Authorization $authorization): void
|
||||
{
|
||||
$permissions = [];
|
||||
if (!empty($user->getId())) {
|
||||
@@ -73,7 +74,7 @@ class Create extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$transaction = Authorization::skip(fn () => $dbForProject->createDocument('transactions', new Document([
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->createDocument('transactions', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => $permissions,
|
||||
'status' => 'pending',
|
||||
|
||||
+21
-13
@@ -18,6 +18,7 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
@@ -63,21 +64,22 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('transactionState')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $transactionId, array $operations, UtopiaResponse $response, Database $dbForProject, TransactionState $transactionState, array $plan): void
|
||||
public function action(string $transactionId, array $operations, UtopiaResponse $response, Database $dbForProject, TransactionState $transactionState, array $plan, Authorization $authorization): void
|
||||
{
|
||||
if (empty($operations)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Operations array cannot be empty');
|
||||
}
|
||||
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
// API keys and admins can read any transaction, regular users need permissions
|
||||
$transaction = ($isAPIKey || $isPrivilegedUser)
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
? $authorization->skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
: $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND, params: [$transactionId]);
|
||||
@@ -113,13 +115,13 @@ class Create extends Action
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$database = $databases[$operation['databaseId']] ??= Authorization::skip(fn () => $dbForProject->getDocument('databases', $operation['databaseId']));
|
||||
$database = $databases[$operation['databaseId']] ??= $authorization->skip(fn () => $dbForProject->getDocument('databases', $operation['databaseId']));
|
||||
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND, params: [$operation['databaseId']]);
|
||||
}
|
||||
|
||||
$collection = $collections[$operation[$this->getGroupId()]] ??=
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $operation[$this->getGroupId()]));
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $operation[$this->getGroupId()]));
|
||||
|
||||
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND, params: [$operation[$this->getGroupId()]]);
|
||||
@@ -165,14 +167,20 @@ class Create extends Action
|
||||
// For individual operations, enforce permissions unless using API key/admin
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||
$validator = new Authorization($permissionType);
|
||||
$collectionValid = $validator->isValid($collection->getPermissionsByType($permissionType));
|
||||
|
||||
$collectionValid = $authorization->isValid(
|
||||
new Input($permissionType, $collection->getPermissionsByType($permissionType))
|
||||
);
|
||||
$documentValid = false;
|
||||
if ($document !== null && !$document->isEmpty() && $documentSecurity) {
|
||||
if ($permissionType === Database::PERMISSION_UPDATE) {
|
||||
$documentValid = $validator->isValid($document->getUpdate());
|
||||
$documentValid = $authorization->isValid(
|
||||
new Input(Database::PERMISSION_UPDATE, $document->getUpdate())
|
||||
);
|
||||
} elseif ($permissionType === Database::PERMISSION_DELETE) {
|
||||
$documentValid = $validator->isValid($document->getDelete());
|
||||
$documentValid = $authorization->isValid(
|
||||
new Input(Database::PERMISSION_DELETE, $document->getDelete())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +197,7 @@ class Create extends Action
|
||||
// Users can only set permissions for roles they have
|
||||
if (isset($operation['data']['$permissions'])) {
|
||||
$permissions = $operation['data']['$permissions'];
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = Permission::parse($permission);
|
||||
@@ -201,7 +209,7 @@ class Create extends Action
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
if (!$authorization->hasRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
@@ -230,7 +238,7 @@ class Create extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$transaction = Authorization::skip(fn () => $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) {
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) {
|
||||
$dbForProject->createDocuments('transactionLogs', $staged);
|
||||
return $dbForProject->increaseDocumentAttribute(
|
||||
'transactions',
|
||||
|
||||
@@ -76,6 +76,7 @@ class Update extends Action
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -102,7 +103,7 @@ class Update extends Action
|
||||
* @throws Structure
|
||||
* @throws \Utopia\Exception
|
||||
*/
|
||||
public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void
|
||||
public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, Authorization $authorization): void
|
||||
{
|
||||
if (!$commit && !$rollback) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true');
|
||||
@@ -111,11 +112,11 @@ class Update extends Action
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Cannot commit and rollback at the same time');
|
||||
}
|
||||
|
||||
$isAPIKey = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
$transaction = ($isAPIKey || $isPrivilegedUser)
|
||||
? Authorization::skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
? $authorization->skip(fn () => $dbForProject->getDocument('transactions', $transactionId))
|
||||
: $dbForProject->getDocument('transactions', $transactionId);
|
||||
if ($transaction->isEmpty()) {
|
||||
throw new Exception(Exception::TRANSACTION_NOT_FOUND, params: [$transactionId]);
|
||||
@@ -138,12 +139,12 @@ class Update extends Action
|
||||
$currentDocumentId = null;
|
||||
|
||||
try {
|
||||
$dbForProject->withTransaction(function () use ($dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $queueForEvents, $queueForStatsUsage, $queueForRealtime, $queueForFunctions, $queueForWebhooks) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
$dbForProject->withTransaction(function () use ($dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $queueForEvents, $queueForStatsUsage, $queueForRealtime, $queueForFunctions, $queueForWebhooks, $authorization) {
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'committing',
|
||||
])));
|
||||
|
||||
$operations = Authorization::skip(fn () => $dbForProject->find('transactionLogs', [
|
||||
$operations = $authorization->skip(fn () => $dbForProject->find('transactionLogs', [
|
||||
Query::equal('transactionInternalId', [$transaction->getSequence()]),
|
||||
Query::orderAsc(),
|
||||
Query::limit(PHP_INT_MAX),
|
||||
@@ -167,7 +168,7 @@ class Update extends Action
|
||||
}
|
||||
|
||||
if (!isset($collections[$collectionId])) {
|
||||
$collections[$collectionId] = Authorization::skip(
|
||||
$collections[$collectionId] = $authorization->skip(
|
||||
fn () => $dbForProject->getCollection($collectionId)
|
||||
);
|
||||
}
|
||||
@@ -232,7 +233,7 @@ class Update extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$transaction = Authorization::skip(fn () => $dbForProject->updateDocument(
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->updateDocument(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
new Document(['status' => 'committed'])
|
||||
@@ -243,33 +244,33 @@ class Update extends Action
|
||||
->setDocument($transaction);
|
||||
});
|
||||
} catch (NotFoundException $e) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'failed',
|
||||
])));
|
||||
|
||||
throw new Exception(Exception::DOCUMENT_NOT_FOUND, previous: $e, params: [$currentDocumentId ?? 'unknown']);
|
||||
} catch (DuplicateException | ConflictException $e) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'failed',
|
||||
])));
|
||||
throw new Exception(Exception::TRANSACTION_CONFLICT, previous: $e);
|
||||
} catch (StructureException $e) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'failed',
|
||||
])));
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
} catch (LimitException $e) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'failed',
|
||||
])));
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, $e->getMessage());
|
||||
} catch (TransactionException $e) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'failed',
|
||||
])));
|
||||
throw new Exception(Exception::TRANSACTION_FAILED, $e->getMessage());
|
||||
} catch (QueryException $e) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'failed',
|
||||
])));
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
@@ -297,11 +298,11 @@ class Update extends Action
|
||||
$data = $data->getArrayCopy();
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->findOne('databases', [
|
||||
$database = $authorization->skip(fn () => $dbForProject->findOne('databases', [
|
||||
Query::equal('$sequence', [$databaseInternalId])
|
||||
]));
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->findOne('database_' . $databaseInternalId, [
|
||||
$collection = $authorization->skip(fn () => $dbForProject->findOne('database_' . $databaseInternalId, [
|
||||
Query::equal('$sequence', [$collectionInternalId])
|
||||
]));
|
||||
|
||||
@@ -393,7 +394,7 @@ class Update extends Action
|
||||
}
|
||||
|
||||
if ($rollback) {
|
||||
$transaction = Authorization::skip(fn () => $dbForProject->updateDocument(
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->updateDocument(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
new Document(['status' => 'failed'])
|
||||
|
||||
@@ -59,10 +59,11 @@ class Get extends Action
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $range, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $databaseId, string $range, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
|
||||
@@ -81,7 +82,7 @@ class Get extends Action
|
||||
str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES)
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
|
||||
@@ -56,10 +56,11 @@ class XList extends Action
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $range, UtopiaResponse $response, Database $dbForProject): void
|
||||
public function action(string $range, UtopiaResponse $response, Database $dbForProject, Authorization $authorization): void
|
||||
{
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
@@ -74,7 +75,7 @@ class XList extends Action
|
||||
METRIC_DATABASES_OPERATIONS_WRITES,
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
|
||||
+1
@@ -60,6 +60,7 @@ class Create extends BooleanCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -61,6 +61,7 @@ class Update extends BooleanUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -62,6 +62,7 @@ class Create extends DatetimeCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -63,6 +63,7 @@ class Update extends DatetimeUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ class Delete extends AttributesDelete
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class Create extends EmailCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class Update extends EmailUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ class Create extends EnumCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ class Update extends EnumUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class Create extends FloatCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ class Update extends FloatUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class Get extends AttributesGet
|
||||
->param('key', '', new Key(), 'Column Key.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class Create extends IPCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class Update extends IPUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -63,6 +63,7 @@ class Create extends IntegerCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -64,6 +64,7 @@ class Update extends IntegerUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class Create extends LineCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class Update extends LineUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class Create extends PointCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class Update extends PointUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -61,6 +61,7 @@ class Create extends PolygonCreate
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -63,6 +63,7 @@ class Update extends PolygonUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user