From dea3e74b6adb0f983a00d08e5a026d7dad50477f Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 28 Nov 2023 10:19:55 +0000 Subject: [PATCH 01/58] Implement Job based hamster --- Dockerfile | 3 +- app/cli.php | 4 + app/init.php | 4 + app/worker.php | 4 + bin/worker-hamster | 3 + docker-compose.yml | 31 ++ src/Appwrite/Event/Event.php | 3 + src/Appwrite/Event/Hamster.php | 153 +++++++ src/Appwrite/Platform/Services/Workers.php | 2 + src/Appwrite/Platform/Tasks/Hamster.php | 373 +++-------------- src/Appwrite/Platform/Workers/Hamster.php | 454 +++++++++++++++++++++ 11 files changed, 708 insertions(+), 326 deletions(-) create mode 100644 bin/worker-hamster create mode 100644 src/Appwrite/Event/Hamster.php create mode 100644 src/Appwrite/Platform/Workers/Hamster.php diff --git a/Dockerfile b/Dockerfile index 059c499bd..599e4ea70 100755 --- a/Dockerfile +++ b/Dockerfile @@ -105,7 +105,8 @@ RUN chmod +x /usr/local/bin/hamster && \ chmod +x /usr/local/bin/delete-orphaned-projects && \ chmod +x /usr/local/bin/clear-card-cache && \ chmod +x /usr/local/bin/calc-users-stats && \ - chmod +x /usr/local/bin/calc-tier-stats + chmod +x /usr/local/bin/calc-tier-stats && \ + chmod +x /usr/local/bin/worker-hamster # Letsencrypt Permissions RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/ diff --git a/app/cli.php b/app/cli.php index 643a615c4..003f3a1f7 100644 --- a/app/cli.php +++ b/app/cli.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/controllers/general.php'; use Appwrite\Event\Delete; use Appwrite\Event\Certificate; use Appwrite\Event\Func; +use Appwrite\Event\Hamster; use Appwrite\Platform\Appwrite; use Utopia\CLI\CLI; use Utopia\Database\Validator\Authorization; @@ -154,6 +155,9 @@ CLI::setResource('queue', function (Group $pools) { CLI::setResource('queueForFunctions', function (Connection $queue) { return new Func($queue); }, ['queue']); +CLI::setResource('queueForHamster', function (Connection $queue) { + return new Hamster($queue); +}, ['queue']); CLI::setResource('queueForDeletes', function (Connection $queue) { return new Delete($queue); }, ['queue']); diff --git a/app/init.php b/app/init.php index 2c0219eec..c30eb77e8 100644 --- a/app/init.php +++ b/app/init.php @@ -72,6 +72,7 @@ use Ahc\Jwt\JWTException; use Appwrite\Event\Build; use Appwrite\Event\Certificate; use Appwrite\Event\Func; +use Appwrite\Event\Hamster; use MaxMind\Db\Reader; use PHPMailer\PHPMailer\PHPMailer; use Swoole\Database\PDOProxy; @@ -916,6 +917,9 @@ App::setResource('queueForCertificates', function (Connection $queue) { App::setResource('queueForMigrations', function (Connection $queue) { return new Migration($queue); }, ['queue']); +App::setResource('queueForHamster', function (Connection $queue) { + return new Hamster($queue); +}, ['queue']); App::setResource('usage', function ($register) { return new Stats($register->get('statsd')); }, ['register']); diff --git a/app/worker.php b/app/worker.php index 32a8b9804..4f7355311 100644 --- a/app/worker.php +++ b/app/worker.php @@ -9,6 +9,7 @@ use Appwrite\Event\Certificate; use Appwrite\Event\Database as EventDatabase; use Appwrite\Event\Delete; use Appwrite\Event\Func; +use Appwrite\Event\Hamster; use Appwrite\Event\Mail; use Appwrite\Event\Messaging; use Appwrite\Event\Migration; @@ -155,6 +156,9 @@ Server::setResource('queueForCertificates', function (Connection $queue) { Server::setResource('queueForMigrations', function (Connection $queue) { return new Migration($queue); }, ['queue']); +Server::setResource('queueForHamster', function (Connection $queue) { + return new Hamster($queue); +}, ['queue']); Server::setResource('logger', function (Registry $register) { return $register->get('logger'); }, ['register']); diff --git a/bin/worker-hamster b/bin/worker-hamster new file mode 100644 index 000000000..b388dd13c --- /dev/null +++ b/bin/worker-hamster @@ -0,0 +1,3 @@ +#!/bin/sh + +php /usr/src/code/app/worker.php hamster $@ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 42091e5e4..da9b0e51e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -717,6 +717,37 @@ services: environment: - _APP_ASSISTANT_OPENAI_API_KEY + appwrite-worker-hamster: + entrypoint: worker-hamster + <<: *x-logging + container_name: appwrite-worker-hamster + image: appwrite-dev + networks: + - appwrite + volumes: + - ./app:/usr/src/code/app + - ./src:/usr/src/code/src + depends_on: + - redis + - mariadb + environment: + - _APP_ENV + - _APP_WORKER_PER_CORE + - _APP_OPENSSL_KEY_V1 + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_REDIS_USER + - _APP_REDIS_PASS + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + - _APP_USAGE_STATS + - _APP_LOGGING_CONFIG + - _APP_LOGGING_PROVIDER + - _APP_MIXPANEL_TOKEN + openruntimes-executor: container_name: openruntimes-executor hostname: appwrite-executor diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 46b430d12..fc12c5b5b 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -42,6 +42,9 @@ class Event public const MIGRATIONS_QUEUE_NAME = 'v1-migrations'; public const MIGRATIONS_CLASS_NAME = 'MigrationsV1'; + public const HAMSTER_QUEUE_NAME = 'v1-hamster'; + public const HAMSTER_CLASS_NAME = 'HamsterV1'; + protected string $queue = ''; protected string $class = ''; protected string $event = ''; diff --git a/src/Appwrite/Event/Hamster.php b/src/Appwrite/Event/Hamster.php new file mode 100644 index 000000000..9ae730367 --- /dev/null +++ b/src/Appwrite/Event/Hamster.php @@ -0,0 +1,153 @@ +setQueue(Event::HAMSTER_QUEUE_NAME) + ->setClass(Event::HAMSTER_CLASS_NAME); + } + + /** + * Sets the type for the hamster event. + * + * @param string $type + * @return self + */ + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + /** + * Returns the set type for the hamster event. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Sets the project for the hamster event. + * + * @param Document $project + */ + public function setProject(Document $project): self + { + $this->project = $project; + + return $this; + } + + /** + * Returns the set project for the hamster event. + * + * @return Document + */ + public function getProject(): Document + { + return $this->project; + } + + /** + * Sets the organization for the hamster event. + * + * @param Document $organization + */ + public function setOrganization(Document $organization): self + { + $this->organization = $organization; + + return $this; + } + + /** + * Returns the set organization for the hamster event. + * + * @return string + */ + public function getOrganization(): Document + { + return $this->organization; + } + + /** + * Sets the user for the hamster event. + * + * @param Document $user + */ + public function setUser(Document $user): self + { + $this->user = $user; + + return $this; + } + + /** + * Returns the set user for the hamster event. + * + * @return Document + */ + public function getUser(): Document + { + return $this->user; + } + + /** + * Executes the function event and sends it to the functions worker. + * + * @return string|bool + * @throws \InvalidArgumentException + */ + public function trigger(): string|bool + { + if ($this->paused) { + return false; + } + + $client = new Client($this->queue, $this->connection); + + $events = $this->getEvent() ? Event::generateEvents($this->getEvent(), $this->getParams()) : null; + + return $client->enqueue([ + 'type' => $this->type, + 'project' => $this->project, + 'organization' => $this->organization, + 'user' => $this->user, + 'events' => $events, + ]); + } + + /** + * Generate a function event from a base event + * + * @param Event $event + * + * @return self + * + */ + public function from(Event $event): self + { + $this->event = $event->getEvent(); + $this->params = $event->getParams(); + return $this; + } +} diff --git a/src/Appwrite/Platform/Services/Workers.php b/src/Appwrite/Platform/Services/Workers.php index 07fc25434..c5a051476 100644 --- a/src/Appwrite/Platform/Services/Workers.php +++ b/src/Appwrite/Platform/Services/Workers.php @@ -12,6 +12,7 @@ use Appwrite\Platform\Workers\Databases; use Appwrite\Platform\Workers\Functions; use Appwrite\Platform\Workers\Builds; use Appwrite\Platform\Workers\Deletes; +use Appwrite\Platform\Workers\Hamster; use Appwrite\Platform\Workers\Migrations; class Workers extends Service @@ -30,6 +31,7 @@ class Workers extends Service ->addAction(Builds::getName(), new Builds()) ->addAction(Deletes::getName(), new Deletes()) ->addAction(Migrations::getName(), new Migrations()) + ->addAction(Hamster::getName(), new Hamster()) ; } diff --git a/src/Appwrite/Platform/Tasks/Hamster.php b/src/Appwrite/Platform/Tasks/Hamster.php index 1d5d3b0b2..fea4c88ad 100644 --- a/src/Appwrite/Platform/Tasks/Hamster.php +++ b/src/Appwrite/Platform/Tasks/Hamster.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Tasks; +use Appwrite\Event\Hamster as EventHamster; use Appwrite\Network\Validator\Origin; use Exception; use Utopia\App; @@ -19,20 +20,6 @@ use Utopia\Pools\Group; class Hamster extends Action { - private array $metrics = [ - 'usage_files' => 'files.$all.count.total', - 'usage_buckets' => 'buckets.$all.count.total', - 'usage_databases' => 'databases.$all.count.total', - 'usage_documents' => 'documents.$all.count.total', - 'usage_collections' => 'collections.$all.count.total', - 'usage_storage' => 'project.$all.storage.size', - 'usage_requests' => 'project.$all.network.requests', - 'usage_bandwidth' => 'project.$all.network.bandwidth', - 'usage_users' => 'users.$all.count.total', - 'usage_sessions' => 'sessions.email.requests.create', - 'usage_executions' => 'executions.$all.compute.total', - ]; - protected string $directory = '/usr/local'; protected string $path; @@ -60,233 +47,20 @@ class Hamster extends Action }); } - private function getStatsPerProject(Group $pools, Cache $cache, Database $dbForConsole) + private function getStatsPerProject(Group $pools, Database $dbForConsole) { - $this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($pools, $cache) { - /** - * Skip user projects with id 'console' - */ - if ($project->getId() === 'console') { - Console::info("Skipping project console"); - return; - } + $this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($pools) { + $queue = $pools->get('queue')->pop(); + $connection = $queue->getResource(); - Console::log("Getting stats for {$project->getId()}"); + $hamsterTask = new EventHamster($connection); - try { - $db = $project->getAttribute('database'); - $adapter = $pools - ->get($db) - ->pop() - ->getResource(); + $hamsterTask + ->setType('project') + ->setProject($project) + ->trigger(); - $dbForProject = new Database($adapter, $cache); - $dbForProject->setDefaultDatabase('appwrite'); - $dbForProject->setNamespace('_' . $project->getInternalId()); - - $statsPerProject = []; - - $statsPerProject['time'] = microtime(true); - - /** Get Project ID */ - $statsPerProject['project_id'] = $project->getId(); - - /** Get project created time */ - $statsPerProject['project_created'] = $project->getAttribute('$createdAt'); - - /** Get Project Name */ - $statsPerProject['project_name'] = $project->getAttribute('name'); - - /** Total Project Variables */ - $statsPerProject['custom_variables'] = $dbForProject->count('variables', [], APP_LIMIT_COUNT); - - /** Total Migrations */ - $statsPerProject['custom_migrations'] = $dbForProject->count('migrations', [], APP_LIMIT_COUNT); - - /** Get Custom SMTP */ - $smtp = $project->getAttribute('smtp', null); - if ($smtp) { - $statsPerProject['custom_smtp_status'] = $smtp['enabled'] === true ? 'enabled' : 'disabled'; - - /** Get Custom Templates Count */ - $templates = array_keys($project->getAttribute('templates', [])); - $statsPerProject['custom_email_templates'] = array_filter($templates, function ($template) { - return str_contains($template, 'email'); - }); - $statsPerProject['custom_sms_templates'] = array_filter($templates, function ($template) { - return str_contains($template, 'sms'); - }); - } - - /** Get total relationship attributes */ - $statsPerProject['custom_relationship_attributes'] = $dbForProject->count('attributes', [ - Query::equal('type', ['relationship']) - ], APP_LIMIT_COUNT); - - /** Get Total Functions */ - $statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT); - - foreach (\array_keys(Config::getParam('runtimes')) as $runtime) { - $statsPerProject['custom_functions_' . $runtime] = $dbForProject->count('functions', [ - Query::equal('runtime', [$runtime]), - ], APP_LIMIT_COUNT); - } - - /** Get Total Deployments */ - $statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT); - $statsPerProject['custom_deployments_manual'] = $dbForProject->count('deployments', [ - Query::equal('type', ['manual']) - ], APP_LIMIT_COUNT); - $statsPerProject['custom_deployments_git'] = $dbForProject->count('deployments', [ - Query::equal('type', ['vcs']) - ], APP_LIMIT_COUNT); - - /** Get VCS repos connected */ - $statsPerProject['custom_vcs_repositories'] = $dbForConsole->count('repositories', [ - Query::equal('projectInternalId', [$project->getInternalId()]) - ], APP_LIMIT_COUNT); - - /** Get Total Teams */ - $statsPerProject['custom_teams'] = $dbForProject->count('teams', [], APP_LIMIT_COUNT); - - /** Get Total Members */ - $teamInternalId = $project->getAttribute('teamInternalId', null); - if ($teamInternalId) { - $statsPerProject['custom_organization_members'] = $dbForConsole->count('memberships', [ - Query::equal('teamInternalId', [$teamInternalId]) - ], APP_LIMIT_COUNT); - } else { - $statsPerProject['custom_organization_members'] = 0; - } - - /** Get Email and Name of the project owner */ - if ($teamInternalId) { - $membership = $dbForConsole->findOne('memberships', [ - Query::equal('teamInternalId', [$teamInternalId]), - ]); - - if (!$membership || $membership->isEmpty()) { - throw new Exception('Membership not found. Skipping project : ' . $project->getId()); - } - - $userId = $membership->getAttribute('userId', null); - if ($userId) { - $user = $dbForConsole->getDocument('users', $userId); - $statsPerProject['email'] = $user->getAttribute('email', null); - $statsPerProject['name'] = $user->getAttribute('name', null); - } - } - - /** Get Domains */ - $statsPerProject['custom_domains'] = $dbForConsole->count('rules', [ - Query::equal('projectInternalId', [$project->getInternalId()]), - Query::limit(APP_LIMIT_COUNT) - ]); - - /** Get Platforms */ - $platforms = $dbForConsole->find('platforms', [ - Query::equal('projectInternalId', [$project->getInternalId()]), - Query::limit(APP_LIMIT_COUNT) - ]); - - $statsPerProject['custom_platforms_web'] = sizeof(array_filter($platforms, function ($platform) { - return $platform['type'] === 'web'; - })); - - $statsPerProject['custom_platforms_android'] = sizeof(array_filter($platforms, function ($platform) { - return $platform['type'] === 'android'; - })); - - $statsPerProject['custom_platforms_apple'] = sizeof(array_filter($platforms, function ($platform) { - return str_contains($platform['type'], 'apple'); - })); - - $statsPerProject['custom_platforms_flutter'] = sizeof(array_filter($platforms, function ($platform) { - return str_contains($platform['type'], 'flutter'); - })); - - $flutterPlatforms = [Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_FLUTTER_LINUX]; - - foreach ($flutterPlatforms as $flutterPlatform) { - $statsPerProject['custom_platforms_' . $flutterPlatform] = sizeof(array_filter($platforms, function ($platform) use ($flutterPlatform) { - return $platform['type'] === $flutterPlatform; - })); - } - - $statsPerProject['custom_platforms_api_keys'] = $dbForConsole->count('keys', [ - Query::equal('projectInternalId', [$project->getInternalId()]), - Query::limit(APP_LIMIT_COUNT) - ]); - - /** Get Usage $statsPerProject */ - $periods = [ - 'infinity' => [ - 'period' => '1d', - 'limit' => 90, - ], - '24h' => [ - 'period' => '1h', - 'limit' => 24, - ], - ]; - - Authorization::skip(function () use ($dbForProject, $periods, &$statsPerProject) { - foreach ($this->metrics as $key => $metric) { - foreach ($periods as $periodKey => $periodValue) { - $limit = $periodValue['limit']; - $period = $periodValue['period']; - - $requestDocs = $dbForProject->find('stats', [ - Query::equal('period', [$period]), - Query::equal('metric', [$metric]), - Query::limit($limit), - Query::orderDesc('time'), - ]); - - $statsPerProject[$key . '_' . $periodKey] = []; - foreach ($requestDocs as $requestDoc) { - $statsPerProject[$key . '_' . $periodKey][] = [ - 'value' => $requestDoc->getAttribute('value'), - 'date' => $requestDoc->getAttribute('time'), - ]; - } - - $statsPerProject[$key . '_' . $periodKey] = array_reverse($statsPerProject[$key . '_' . $periodKey]); - // Calculate aggregate of each metric - $statsPerProject[$key . '_' . $periodKey] = array_sum(array_column($statsPerProject[$key . '_' . $periodKey], 'value')); - } - } - }); - - if (isset($statsPerProject['email'])) { - /** Send data to mixpanel */ - $res = $this->mixpanel->createProfile($statsPerProject['email'], '', [ - 'name' => $statsPerProject['name'], - 'email' => $statsPerProject['email'] - ]); - - if (!$res) { - Console::error('Failed to create user profile for project: ' . $project->getId()); - } - } - - $event = new Event(); - $event - ->setName('Project Daily Usage') - ->setProps($statsPerProject); - $res = $this->mixpanel->createEvent($event); - - if (!$res) { - Console::error('Failed to create event for project: ' . $project->getId()); - } - } catch (Exception $e) { - Console::error('Failed to send stats for project: ' . $project->getId()); - Console::error($e->getMessage()); - } finally { - $pools - ->get($db) - ->reclaim(); - } + $queue->reclaim(); }); } @@ -305,6 +79,8 @@ class Hamster extends Action $next->setTimezone(new \DateTimeZone(date_default_timezone_get())); $delay = $next->getTimestamp() - $now->getTimestamp(); + $delay = 5; + /** * If time passed for the target day. */ @@ -323,17 +99,17 @@ class Hamster extends Action /* Initialise new Utopia app */ $app = new App('UTC'); - Console::info('Getting stats for all projects'); - $this->getStatsPerProject($pools, $cache, $dbForConsole); - Console::success('Completed getting stats for all projects'); + Console::info('Queuing stats for all projects'); + $this->getStatsPerProject($pools, $dbForConsole); + Console::success('Completed queuing stats for all projects'); - Console::info('Getting stats for all organizations'); - $this->getStatsPerOrganization($dbForConsole); - Console::success('Completed getting stats for all organizations'); + Console::info('Queuing stats for all organizations'); + $this->getStatsPerOrganization($pools, $dbForConsole); + Console::success('Completed queuing stats for all organizations'); - Console::info('Getting stats for all users'); - $this->getStatsPerUser($dbForConsole); - Console::success('Completed getting stats for all users'); + Console::info('Queuing stats for all users'); + $this->getStatsPerUser($pools, $dbForConsole); + Console::success('Completed queuing stats for all users'); $pools ->get('console') @@ -378,96 +154,43 @@ class Hamster extends Action Console::log("Processed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); } - protected function getStatsPerOrganization(Database $dbForConsole) + protected function getStatsPerOrganization(Group $pools, Database $dbForConsole) { - $this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $document) { + $this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $organization) use ($pools) { try { - $statsPerOrganization = []; - - /** Organization name */ - $statsPerOrganization['name'] = $document->getAttribute('name'); - - /** Get Email and of the organization owner */ - $membership = $dbForConsole->findOne('memberships', [ - Query::equal('teamInternalId', [$document->getInternalId()]), - ]); - - if (!$membership || $membership->isEmpty()) { - throw new Exception('Membership not found. Skipping organization : ' . $document->getId()); - } - - $userId = $membership->getAttribute('userId', null); - if ($userId) { - $user = $dbForConsole->getDocument('users', $userId); - $statsPerOrganization['email'] = $user->getAttribute('email', null); - } - - /** Organization Creation Date */ - $statsPerOrganization['created'] = $document->getAttribute('$createdAt'); - - /** Number of team members */ - $statsPerOrganization['members'] = $document->getAttribute('total'); - - /** Number of projects in this organization */ - $statsPerOrganization['projects'] = $dbForConsole->count('projects', [ - Query::equal('teamId', [$document->getId()]), - Query::limit(APP_LIMIT_COUNT) - ]); - - if (!isset($statsPerOrganization['email'])) { - throw new Exception('Email not found. Skipping organization : ' . $document->getId()); - } - - $event = new Event(); - $event - ->setName('Organization Daily Usage') - ->setProps($statsPerOrganization); - $res = $this->mixpanel->createEvent($event); - if (!$res) { - throw new Exception('Failed to create event for organization : ' . $document->getId()); - } + $queue = $pools->get('queue')->pop(); + $connection = $queue->getResource(); + + $hamsterTask = new EventHamster($connection); + + $hamsterTask + ->setType('organization') + ->setOrganization($organization) + ->trigger(); + + $queue->reclaim(); } catch (Exception $e) { Console::error($e->getMessage()); } }); } - protected function getStatsPerUser(Database $dbForConsole) + protected function getStatsPerUser(Group $pools, Database $dbForConsole) { - $this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $document) { + $this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $user) use ($pools) { try { - $statsPerUser = []; - - /** Organization name */ - $statsPerUser['name'] = $document->getAttribute('name'); - - /** Organization ID (needs to be stored as an email since mixpanel uses the email attribute as a distinctID) */ - $statsPerUser['email'] = $document->getAttribute('email'); - - /** Organization Creation Date */ - $statsPerUser['created'] = $document->getAttribute('$createdAt'); - - /** Number of teams this user is a part of */ - $statsPerUser['memberships'] = $dbForConsole->count('memberships', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_COUNT) - ]); - - if (!isset($statsPerUser['email'])) { - throw new Exception('User has no email: ' . $document->getId()); - } - - /** Send data to mixpanel */ - $event = new Event(); - $event - ->setName('User Daily Usage') - ->setProps($statsPerUser); - $res = $this->mixpanel->createEvent($event); - - if (!$res) { - throw new Exception('Failed to create user profile for user: ' . $document->getId()); - } + $queue = $pools->get('queue')->pop(); + $connection = $queue->getResource(); + + $hamsterTask = new EventHamster($connection); + + $hamsterTask + ->setType('user') + ->setUser($user) + ->trigger(); + + $queue->reclaim(); } catch (Exception $e) { Console::error($e->getMessage()); } diff --git a/src/Appwrite/Platform/Workers/Hamster.php b/src/Appwrite/Platform/Workers/Hamster.php new file mode 100644 index 000000000..d1c03744c --- /dev/null +++ b/src/Appwrite/Platform/Workers/Hamster.php @@ -0,0 +1,454 @@ + 'files.$all.count.total', + 'usage_buckets' => 'buckets.$all.count.total', + 'usage_databases' => 'databases.$all.count.total', + 'usage_documents' => 'documents.$all.count.total', + 'usage_collections' => 'collections.$all.count.total', + 'usage_storage' => 'project.$all.storage.size', + 'usage_requests' => 'project.$all.network.requests', + 'usage_bandwidth' => 'project.$all.network.bandwidth', + 'usage_users' => 'users.$all.count.total', + 'usage_sessions' => 'sessions.email.requests.create', + 'usage_executions' => 'executions.$all.compute.total', + ]; + + protected string $directory = '/usr/local'; + + protected string $path; + + protected string $date; + + protected Mixpanel $mixpanel; + + public static function getName(): string + { + return 'hamster'; + } + + /** + * @throws \Exception + */ + public function __construct() + { + $this->mixpanel = new Mixpanel(App::getEnv('_APP_MIXPANEL_TOKEN', '')); + + $this + ->desc('Hamster worker') + ->inject('message') + ->inject('pools') + ->inject('cache') + ->inject('dbForConsole') + ->inject('queueForHamster') + ->inject('queueForEvents') + ->inject('usage') + ->inject('log') + ->callback(fn (Message $message, Group $pools, Cache $cache, Database $dbForConsole, EventHamster $queueForHamster, Event $queueForEvents, Stats $usage, Log $log) => $this->action($message, $pools, $cache, $dbForConsole, $queueForHamster, $queueForEvents, $usage, $log)); + } + + /** + * @param Message $message + * @param Group $pools + * @param Cache $cache + * @param Database $dbForConsole + * @param EventHamster $queueForHamster + * @param Event $queueForEvents + * @param Stats $usage + * @param Log $log + * @return void + * @throws Authorization + * @throws Structure + * @throws \Utopia\Database\Exception + * @throws Conflict + */ + public function action(Message $message, Group $pools, Cache $cache, Database $dbForConsole, EventHamster $queueForHamster, Event $queueForEvents, Stats $usage, Log $log): void + { + $payload = $message->getPayload() ?? []; + + if (empty($payload)) { + throw new \Exception('Missing payload'); + } + + + $payload = $message->getPayload() ?? []; + + if (empty($payload)) { + throw new \Exception('Missing payload'); + } + + $type = $payload['type'] ?? ''; + + switch ($type) { + case 'project': + $this->getStatsForProject(new Document($payload['project']), $pools, $cache, $dbForConsole, $log); + break; + case 'organization': + $this->getStatsForOrganization(new Document($payload['organization']), $pools, $cache, $dbForConsole, $log); + break; + case 'user': + $this->getStatsPerUser(new Document($payload['user']), $dbForConsole); + break; + } + } + + /** + * @param Document $project + * @param Group $pools + * @param Cache $cache + * @param Database $dbForConsole + * @param Log $log + * @throws \Utopia\Database\Exception + */ + private function getStatsForProject(Document $project, Group $pools, Cache $cache, Database $dbForConsole, Log $log): void + { + /** + * Skip user projects with id 'console' + */ + if ($project->getId() === 'console') { + Console::info("Skipping project console"); + return; + } + + Console::log("Getting stats for {$project->getId()}"); + + + try { + $db = $project->getAttribute('database'); + $adapter = $pools + ->get($db) + ->pop() + ->getResource(); + + $dbForProject = new Database($adapter, $cache); + $dbForProject->setDefaultDatabase('appwrite'); + $dbForProject->setNamespace('_' . $project->getInternalId()); + + $statsPerProject = []; + + $statsPerProject['time'] = microtime(true); + + /** Get Project ID */ + $statsPerProject['project_id'] = $project->getId(); + + /** Get project created time */ + $statsPerProject['project_created'] = $project->getAttribute('$createdAt'); + + /** Get Project Name */ + $statsPerProject['project_name'] = $project->getAttribute('name'); + + /** Total Project Variables */ + $statsPerProject['custom_variables'] = $dbForProject->count('variables', [], APP_LIMIT_COUNT); + + /** Total Migrations */ + $statsPerProject['custom_migrations'] = $dbForProject->count('migrations', [], APP_LIMIT_COUNT); + + /** Get Custom SMTP */ + $smtp = $project->getAttribute('smtp', null); + if ($smtp) { + $statsPerProject['custom_smtp_status'] = $smtp['enabled'] === true ? 'enabled' : 'disabled'; + + /** Get Custom Templates Count */ + $templates = array_keys($project->getAttribute('templates', [])); + $statsPerProject['custom_email_templates'] = array_filter($templates, function ($template) { + return str_contains($template, 'email'); + }); + $statsPerProject['custom_sms_templates'] = array_filter($templates, function ($template) { + return str_contains($template, 'sms'); + }); + } + + /** Get total relationship attributes */ + $statsPerProject['custom_relationship_attributes'] = $dbForProject->count('attributes', [ + Query::equal('type', ['relationship']) + ], APP_LIMIT_COUNT); + + /** Get Total Functions */ + $statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT); + + foreach (\array_keys(Config::getParam('runtimes')) as $runtime) { + $statsPerProject['custom_functions_' . $runtime] = $dbForProject->count('functions', [ + Query::equal('runtime', [$runtime]), + ], APP_LIMIT_COUNT); + } + + /** Get Total Deployments */ + $statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT); + $statsPerProject['custom_deployments_manual'] = $dbForProject->count('deployments', [ + Query::equal('type', ['manual']) + ], APP_LIMIT_COUNT); + $statsPerProject['custom_deployments_git'] = $dbForProject->count('deployments', [ + Query::equal('type', ['vcs']) + ], APP_LIMIT_COUNT); + + /** Get VCS repos connected */ + $statsPerProject['custom_vcs_repositories'] = $dbForConsole->count('repositories', [ + Query::equal('projectInternalId', [$project->getInternalId()]) + ], APP_LIMIT_COUNT); + + /** Get Total Teams */ + $statsPerProject['custom_teams'] = $dbForProject->count('teams', [], APP_LIMIT_COUNT); + + /** Get Total Members */ + $teamInternalId = $project->getAttribute('teamInternalId', null); + if ($teamInternalId) { + $statsPerProject['custom_organization_members'] = $dbForConsole->count('memberships', [ + Query::equal('teamInternalId', [$teamInternalId]) + ], APP_LIMIT_COUNT); + } else { + $statsPerProject['custom_organization_members'] = 0; + } + + /** Get Email and Name of the project owner */ + if ($teamInternalId) { + $membership = $dbForConsole->findOne('memberships', [ + Query::equal('teamInternalId', [$teamInternalId]), + ]); + + if (!$membership || $membership->isEmpty()) { + throw new \Exception('Membership not found. Skipping project : ' . $project->getId()); + } + + $userId = $membership->getAttribute('userId', null); + if ($userId) { + $user = $dbForConsole->getDocument('users', $userId); + $statsPerProject['email'] = $user->getAttribute('email', null); + $statsPerProject['name'] = $user->getAttribute('name', null); + } + } + + /** Get Domains */ + $statsPerProject['custom_domains'] = $dbForConsole->count('rules', [ + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::limit(APP_LIMIT_COUNT) + ]); + + /** Get Platforms */ + $platforms = $dbForConsole->find('platforms', [ + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::limit(APP_LIMIT_COUNT) + ]); + + $statsPerProject['custom_platforms_web'] = sizeof(array_filter($platforms, function ($platform) { + return $platform['type'] === 'web'; + })); + + $statsPerProject['custom_platforms_android'] = sizeof(array_filter($platforms, function ($platform) { + return $platform['type'] === 'android'; + })); + + $statsPerProject['custom_platforms_apple'] = sizeof(array_filter($platforms, function ($platform) { + return str_contains($platform['type'], 'apple'); + })); + + $statsPerProject['custom_platforms_flutter'] = sizeof(array_filter($platforms, function ($platform) { + return str_contains($platform['type'], 'flutter'); + })); + + $flutterPlatforms = [Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_FLUTTER_LINUX]; + + foreach ($flutterPlatforms as $flutterPlatform) { + $statsPerProject['custom_platforms_' . $flutterPlatform] = sizeof(array_filter($platforms, function ($platform) use ($flutterPlatform) { + return $platform['type'] === $flutterPlatform; + })); + } + + $statsPerProject['custom_platforms_api_keys'] = $dbForConsole->count('keys', [ + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::limit(APP_LIMIT_COUNT) + ]); + + /** Get Usage $statsPerProject */ + $periods = [ + 'infinity' => [ + 'period' => '1d', + 'limit' => 90, + ], + '24h' => [ + 'period' => '1h', + 'limit' => 24, + ], + ]; + + Authorization::skip(function () use ($dbForProject, $periods, &$statsPerProject) { + foreach ($this->metrics as $key => $metric) { + foreach ($periods as $periodKey => $periodValue) { + $limit = $periodValue['limit']; + $period = $periodValue['period']; + + $requestDocs = $dbForProject->find('stats', [ + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); + + $statsPerProject[$key . '_' . $periodKey] = []; + foreach ($requestDocs as $requestDoc) { + $statsPerProject[$key . '_' . $periodKey][] = [ + 'value' => $requestDoc->getAttribute('value'), + 'date' => $requestDoc->getAttribute('time'), + ]; + } + + $statsPerProject[$key . '_' . $periodKey] = array_reverse($statsPerProject[$key . '_' . $periodKey]); + // Calculate aggregate of each metric + $statsPerProject[$key . '_' . $periodKey] = array_sum(array_column($statsPerProject[$key . '_' . $periodKey], 'value')); + } + } + }); + + if (isset($statsPerProject['email'])) { + /** Send data to mixpanel */ + $res = $this->mixpanel->createProfile($statsPerProject['email'], '', [ + 'name' => $statsPerProject['name'], + 'email' => $statsPerProject['email'] + ]); + + if (!$res) { + Console::error('Failed to create user profile for project: ' . $project->getId()); + } + } + + $event = new AnalyticsEvent(); + $event + ->setName('Project Daily Usage') + ->setProps($statsPerProject); + $res = $this->mixpanel->createEvent($event); + + if (!$res) { + Console::error('Failed to create event for project: ' . $project->getId()); + } + } catch (\Exception $e) { + Console::error('Failed to send stats for project: ' . $project->getId()); + Console::error($e->getMessage()); + } finally { + $pools + ->get($db) + ->reclaim(); + } + } + + /** + * @param Document $organization + * @param Group $pools + * @param Cache $cache + * @param Database $dbForConsole + * @param Log $log + * @throws \Utopia\Database\Exception + */ + private function getStatsForOrganization(Document $organization, Group $pools, Cache $cache, Database $dbForConsole, Log $log): void + { + try { + $statsPerOrganization = []; + + /** Organization name */ + $statsPerOrganization['name'] = $organization->getAttribute('name'); + + /** Get Email and of the organization owner */ + $membership = $dbForConsole->findOne('memberships', [ + Query::equal('teamInternalId', [$organization->getInternalId()]), + ]); + + if (!$membership || $membership->isEmpty()) { + throw new \Exception('Membership not found. Skipping organization : ' . $organization->getId()); + } + + $userId = $membership->getAttribute('userId', null); + if ($userId) { + $user = $dbForConsole->getDocument('users', $userId); + $statsPerOrganization['email'] = $user->getAttribute('email', null); + } + + + /** Organization Creation Date */ + $statsPerOrganization['created'] = $organization->getAttribute('$createdAt'); + + /** Number of team members */ + $statsPerOrganization['members'] = $organization->getAttribute('total'); + + /** Number of projects in this organization */ + $statsPerOrganization['projects'] = $dbForConsole->count('projects', [ + Query::equal('teamId', [$organization->getId()]), + Query::limit(APP_LIMIT_COUNT) + ]); + + if (!isset($statsPerOrganization['email'])) { + throw new \Exception('Email not found. Skipping organization : ' . $organization->getId()); + } + + $event = new AnalyticsEvent(); + $event + ->setName('Organization Daily Usage') + ->setProps($statsPerOrganization); + $res = $this->mixpanel->createEvent($event); + if (!$res) { + throw new \Exception('Failed to create event for organization : ' . $organization->getId()); + } + } catch (\Exception $e) { + Console::error($e->getMessage()); + } + } + + protected function getStatsPerUser(Document $user, Database $dbForConsole) + { + try { + $statsPerUser = []; + + /** Organization name */ + $statsPerUser['name'] = $user->getAttribute('name'); + + /** Organization ID (needs to be stored as an email since mixpanel uses the email attribute as a distinctID) */ + $statsPerUser['email'] = $user->getAttribute('email'); + + /** Organization Creation Date */ + $statsPerUser['created'] = $user->getAttribute('$createdAt'); + + /** Number of teams this user is a part of */ + $statsPerUser['memberships'] = $dbForConsole->count('memberships', [ + Query::equal('userInternalId', [$user->getInternalId()]), + Query::limit(APP_LIMIT_COUNT) + ]); + + if (!isset($statsPerUser['email'])) { + throw new \Exception('User has no email: ' . $user->getId()); + } + + /** Send data to mixpanel */ + $event = new AnalyticsEvent(); + $event + ->setName('User Daily Usage') + ->setProps($statsPerUser); + $res = $this->mixpanel->createEvent($event); + + if (!$res) { + throw new \Exception('Failed to create user profile for user: ' . $user->getId()); + } + } catch (\Exception $e) { + Console::error($e->getMessage()); + } + } +} From 8bec64b2a2063f6384a578715ddb980226000532 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 30 Nov 2023 11:05:15 +0000 Subject: [PATCH 02/58] Update Hamster.php --- src/Appwrite/Platform/Tasks/Hamster.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/Hamster.php b/src/Appwrite/Platform/Tasks/Hamster.php index fea4c88ad..1df16fe9f 100644 --- a/src/Appwrite/Platform/Tasks/Hamster.php +++ b/src/Appwrite/Platform/Tasks/Hamster.php @@ -78,9 +78,6 @@ class Hamster extends Action $next = new \DateTime($now->format("Y-m-d $jobInitTime")); $next->setTimezone(new \DateTimeZone(date_default_timezone_get())); $delay = $next->getTimestamp() - $now->getTimestamp(); - - $delay = 5; - /** * If time passed for the target day. */ From d34050a5dff79bc37d38dd5c66776e66dda8b8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 16:22:26 +0000 Subject: [PATCH 03/58] chore: address review comments --- src/Appwrite/Event/Hamster.php | 16 ++-- src/Appwrite/Platform/Tasks/Hamster.php | 115 ++++++++---------------- 2 files changed, 48 insertions(+), 83 deletions(-) diff --git a/src/Appwrite/Event/Hamster.php b/src/Appwrite/Event/Hamster.php index 9ae730367..54cf0012f 100644 --- a/src/Appwrite/Event/Hamster.php +++ b/src/Appwrite/Event/Hamster.php @@ -13,6 +13,10 @@ class Hamster extends Event protected ?Document $organization = null; protected ?Document $user = null; + const TYPE_PROJECT = 'project'; + const TYPE_ORGANISATION = 'organisation'; + const TYPE_USER = 'user'; + public function __construct(protected Connection $connection) { parent::__construct($connection); @@ -47,7 +51,7 @@ class Hamster extends Event /** * Sets the project for the hamster event. - * + * * @param Document $project */ public function setProject(Document $project): self @@ -59,7 +63,7 @@ class Hamster extends Event /** * Returns the set project for the hamster event. - * + * * @return Document */ public function getProject(): Document @@ -69,7 +73,7 @@ class Hamster extends Event /** * Sets the organization for the hamster event. - * + * * @param Document $organization */ public function setOrganization(Document $organization): self @@ -81,7 +85,7 @@ class Hamster extends Event /** * Returns the set organization for the hamster event. - * + * * @return string */ public function getOrganization(): Document @@ -91,7 +95,7 @@ class Hamster extends Event /** * Sets the user for the hamster event. - * + * * @param Document $user */ public function setUser(Document $user): self @@ -103,7 +107,7 @@ class Hamster extends Event /** * Returns the set user for the hamster event. - * + * * @return Document */ public function getUser(): Document diff --git a/src/Appwrite/Platform/Tasks/Hamster.php b/src/Appwrite/Platform/Tasks/Hamster.php index fea4c88ad..7a105ba45 100644 --- a/src/Appwrite/Platform/Tasks/Hamster.php +++ b/src/Appwrite/Platform/Tasks/Hamster.php @@ -3,31 +3,16 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Hamster as EventHamster; -use Appwrite\Network\Validator\Origin; use Exception; use Utopia\App; use Utopia\Platform\Action; -use Utopia\Cache\Cache; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; -use Utopia\Analytics\Adapter\Mixpanel; -use Utopia\Analytics\Event; -use Utopia\Config\Config; use Utopia\Database\Document; -use Utopia\Pools\Group; class Hamster extends Action { - protected string $directory = '/usr/local'; - - protected string $path; - - protected string $date; - - protected Mixpanel $mixpanel; - public static function getName(): string { return 'hamster'; @@ -35,48 +20,30 @@ class Hamster extends Action public function __construct() { - $this->mixpanel = new Mixpanel(App::getEnv('_APP_MIXPANEL_TOKEN', '')); - $this ->desc('Get stats for projects') - ->inject('pools') - ->inject('cache') + ->inject('queueForHamster') ->inject('dbForConsole') - ->callback(function (Group $pools, Cache $cache, Database $dbForConsole) { - $this->action($pools, $cache, $dbForConsole); + ->callback(function (EventHamster $queueForHamster, Database $dbForConsole) { + $this->action($queueForHamster, $dbForConsole); }); } - private function getStatsPerProject(Group $pools, Database $dbForConsole) + public function action(EventHamster $queueForHamster, Database $dbForConsole): void { - $this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($pools) { - $queue = $pools->get('queue')->pop(); - $connection = $queue->getResource(); - - $hamsterTask = new EventHamster($connection); - - $hamsterTask - ->setType('project') - ->setProject($project) - ->trigger(); - - $queue->reclaim(); - }); - } - - public function action(Group $pools, Cache $cache, Database $dbForConsole): void - { - Console::title('Cloud Hamster V1'); Console::success(APP_NAME . ' cloud hamster process has started'); $sleep = (int) App::getEnv('_APP_HAMSTER_INTERVAL', '30'); // 30 seconds (by default) $jobInitTime = App::getEnv('_APP_HAMSTER_TIME', '22:00'); // (hour:minutes) + $now = new \DateTime(); $now->setTimezone(new \DateTimeZone(date_default_timezone_get())); + $next = new \DateTime($now->format("Y-m-d $jobInitTime")); $next->setTimezone(new \DateTimeZone(date_default_timezone_get())); + $delay = $next->getTimestamp() - $now->getTimestamp(); $delay = 5; @@ -91,29 +58,24 @@ class Hamster extends Action Console::log('[' . $now->format("Y-m-d H:i:s.v") . '] Delaying for ' . $delay . ' setting loop to [' . $next->format("Y-m-d H:i:s.v") . ']'); - Console::loop(function () use ($pools, $cache, $dbForConsole, $sleep) { + Console::loop(function () use ($queueForHamster, $dbForConsole, $sleep) { $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Getting Cloud Usage Stats every {$sleep} seconds"); + Console::info("[{$now}] Queuing Cloud Usage Stats every {$sleep} seconds"); $loopStart = microtime(true); - /* Initialise new Utopia app */ - $app = new App('UTC'); - Console::info('Queuing stats for all projects'); - $this->getStatsPerProject($pools, $dbForConsole); + $this->getStatsPerProject($queueForHamster, $dbForConsole); Console::success('Completed queuing stats for all projects'); Console::info('Queuing stats for all organizations'); - $this->getStatsPerOrganization($pools, $dbForConsole); + $this->getStatsPerOrganization($queueForHamster, $dbForConsole); Console::success('Completed queuing stats for all organizations'); Console::info('Queuing stats for all users'); - $this->getStatsPerUser($pools, $dbForConsole); + $this->getStatsPerUser($queueForHamster, $dbForConsole); Console::success('Completed queuing stats for all users'); - $pools - ->get('console') - ->reclaim(); + $queue->reclaim(); $loopTook = microtime(true) - $loopStart; $now = date('d-m-Y H:i:s', time()); @@ -121,7 +83,7 @@ class Hamster extends Action }, $sleep, $delay); } - protected function calculateByGroup(string $collection, Database $dbForConsole, callable $callback) + protected function calculateByGroup(string $collection, Database $database, callable $callback) { $count = 0; $chunk = 0; @@ -134,7 +96,7 @@ class Hamster extends Action while ($sum === $limit) { $chunk++; - $results = $dbForConsole->find($collection, \array_merge([ + $results = $database->find($collection, \array_merge([ Query::limit($limit), Query::offset($count) ])); @@ -144,7 +106,7 @@ class Hamster extends Action Console::log('Processing chunk #' . $chunk . '. Found ' . $sum . ' documents'); foreach ($results as $document) { - call_user_func($callback, $dbForConsole, $document); + call_user_func($callback, $database, $document); $count++; } } @@ -154,43 +116,42 @@ class Hamster extends Action Console::log("Processed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); } - protected function getStatsPerOrganization(Group $pools, Database $dbForConsole) + protected function getStatsPerOrganization(EventHamster $hamster, Database $dbForConsole) { - - $this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $organization) use ($pools) { + $this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $organization) use ($hamster) { try { - $queue = $pools->get('queue')->pop(); - $connection = $queue->getResource(); - - $hamsterTask = new EventHamster($connection); - - $hamsterTask - ->setType('organization') + $hamster + ->setType(EventHamster::TYPE_ORGANISATION) ->setOrganization($organization) ->trigger(); - - $queue->reclaim(); } catch (Exception $e) { Console::error($e->getMessage()); } }); } - protected function getStatsPerUser(Group $pools, Database $dbForConsole) + private function getStatsPerProject(EventHamster $hamster, Database $dbForConsole) { - $this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $user) use ($pools) { + $this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($hamster) { try { - $queue = $pools->get('queue')->pop(); - $connection = $queue->getResource(); - - $hamsterTask = new EventHamster($connection); - - $hamsterTask - ->setType('user') + $hamster + ->setType(EventHamster::TYPE_PROJECT) + ->setProject($project) + ->trigger(); + } catch (Exception $e) { + Console::error($e->getMessage()); + } + }); + } + + protected function getStatsPerUser(EventHamster $hamster, Database $dbForConsole) + { + $this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $user) use ($hamster) { + try { + $hamster + ->setType(EventHamster::TYPE_USER) ->setUser($user) ->trigger(); - - $queue->reclaim(); } catch (Exception $e) { Console::error($e->getMessage()); } From 4b7676158e540bc1711fc45981fc2fbc54d4466e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 16:24:11 +0000 Subject: [PATCH 04/58] chore: address review comments --- src/Appwrite/Platform/Tasks/Hamster.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/Hamster.php b/src/Appwrite/Platform/Tasks/Hamster.php index 157f3db39..9ecab942a 100644 --- a/src/Appwrite/Platform/Tasks/Hamster.php +++ b/src/Appwrite/Platform/Tasks/Hamster.php @@ -72,8 +72,6 @@ class Hamster extends Action $this->getStatsPerUser($queueForHamster, $dbForConsole); Console::success('Completed queuing stats for all users'); - $queue->reclaim(); - $loopTook = microtime(true) - $loopStart; $now = date('d-m-Y H:i:s', time()); Console::info("[{$now}] Cloud Stats took {$loopTook} seconds"); From f3544485e54c44c63f228f53f6354016b374e364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 17:41:23 +0000 Subject: [PATCH 05/58] chore: address review comments --- app/init.php | 3 -- src/Appwrite/Platform/Tasks/Hamster.php | 21 ++++---- src/Appwrite/Platform/Workers/Hamster.php | 62 ++++++++--------------- 3 files changed, 33 insertions(+), 53 deletions(-) diff --git a/app/init.php b/app/init.php index c30eb77e8..2beeb2845 100644 --- a/app/init.php +++ b/app/init.php @@ -917,9 +917,6 @@ App::setResource('queueForCertificates', function (Connection $queue) { App::setResource('queueForMigrations', function (Connection $queue) { return new Migration($queue); }, ['queue']); -App::setResource('queueForHamster', function (Connection $queue) { - return new Hamster($queue); -}, ['queue']); App::setResource('usage', function ($register) { return new Stats($register->get('statsd')); }, ['register']); diff --git a/src/Appwrite/Platform/Tasks/Hamster.php b/src/Appwrite/Platform/Tasks/Hamster.php index 9ecab942a..1dca095e9 100644 --- a/src/Appwrite/Platform/Tasks/Hamster.php +++ b/src/Appwrite/Platform/Tasks/Hamster.php @@ -61,15 +61,15 @@ class Hamster extends Action $loopStart = microtime(true); Console::info('Queuing stats for all projects'); - $this->getStatsPerProject($queueForHamster, $dbForConsole); + $this->getStatsPerProject($queueForHamster, $dbForConsole, $loopStart); Console::success('Completed queuing stats for all projects'); Console::info('Queuing stats for all organizations'); - $this->getStatsPerOrganization($queueForHamster, $dbForConsole); + $this->getStatsPerOrganization($queueForHamster, $dbForConsole, $loopStart); Console::success('Completed queuing stats for all organizations'); Console::info('Queuing stats for all users'); - $this->getStatsPerUser($queueForHamster, $dbForConsole); + $this->getStatsPerUser($queueForHamster, $dbForConsole, $loopStart); Console::success('Completed queuing stats for all users'); $loopTook = microtime(true) - $loopStart; @@ -111,10 +111,11 @@ class Hamster extends Action Console::log("Processed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); } - protected function getStatsPerOrganization(EventHamster $hamster, Database $dbForConsole) + protected function getStatsPerOrganization(EventHamster $hamster, Database $dbForConsole, float $loopStart) { - $this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $organization) use ($hamster) { + $this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $organization) use ($hamster, $loopStart) { try { + $organization->setAttribute('$time', $loopStart); $hamster ->setType(EventHamster::TYPE_ORGANISATION) ->setOrganization($organization) @@ -125,10 +126,11 @@ class Hamster extends Action }); } - private function getStatsPerProject(EventHamster $hamster, Database $dbForConsole) + private function getStatsPerProject(EventHamster $hamster, Database $dbForConsole, float $loopStart) { - $this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($hamster) { + $this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($hamster, $loopStart) { try { + $project->setAttribute('$time', $loopStart); $hamster ->setType(EventHamster::TYPE_PROJECT) ->setProject($project) @@ -139,10 +141,11 @@ class Hamster extends Action }); } - protected function getStatsPerUser(EventHamster $hamster, Database $dbForConsole) + protected function getStatsPerUser(EventHamster $hamster, Database $dbForConsole, float $loopStart) { - $this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $user) use ($hamster) { + $this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $user) use ($hamster, $loopStart) { try { + $user->setAttribute('$time', $loopStart); $hamster ->setType(EventHamster::TYPE_USER) ->setUser($user) diff --git a/src/Appwrite/Platform/Workers/Hamster.php b/src/Appwrite/Platform/Workers/Hamster.php index d1c03744c..2931741c8 100644 --- a/src/Appwrite/Platform/Workers/Hamster.php +++ b/src/Appwrite/Platform/Workers/Hamster.php @@ -2,8 +2,6 @@ namespace Appwrite\Platform\Workers; -use Appwrite\Usage\Stats; -use Appwrite\Event\Event; use Appwrite\Event\Hamster as EventHamster; use Appwrite\Network\Validator\Origin; use Utopia\Analytics\Adapter\Mixpanel; @@ -37,12 +35,6 @@ class Hamster extends Action 'usage_executions' => 'executions.$all.compute.total', ]; - protected string $directory = '/usr/local'; - - protected string $path; - - protected string $date; - protected Mixpanel $mixpanel; public static function getName(): string @@ -55,19 +47,13 @@ class Hamster extends Action */ public function __construct() { - $this->mixpanel = new Mixpanel(App::getEnv('_APP_MIXPANEL_TOKEN', '')); - $this ->desc('Hamster worker') ->inject('message') ->inject('pools') ->inject('cache') ->inject('dbForConsole') - ->inject('queueForHamster') - ->inject('queueForEvents') - ->inject('usage') - ->inject('log') - ->callback(fn (Message $message, Group $pools, Cache $cache, Database $dbForConsole, EventHamster $queueForHamster, Event $queueForEvents, Stats $usage, Log $log) => $this->action($message, $pools, $cache, $dbForConsole, $queueForHamster, $queueForEvents, $usage, $log)); + ->callback(fn (Message $message, Group $pools, Cache $cache, Database $dbForConsole) => $this->action($message, $pools, $cache, $dbForConsole)); } /** @@ -75,24 +61,17 @@ class Hamster extends Action * @param Group $pools * @param Cache $cache * @param Database $dbForConsole - * @param EventHamster $queueForHamster - * @param Event $queueForEvents - * @param Stats $usage - * @param Log $log + * * @return void - * @throws Authorization - * @throws Structure * @throws \Utopia\Database\Exception - * @throws Conflict */ - public function action(Message $message, Group $pools, Cache $cache, Database $dbForConsole, EventHamster $queueForHamster, Event $queueForEvents, Stats $usage, Log $log): void + public function action(Message $message, Group $pools, Cache $cache, Database $dbForConsole): void { - $payload = $message->getPayload() ?? []; - - if (empty($payload)) { - throw new \Exception('Missing payload'); + $token = App::getEnv('_APP_MIXPANEL_TOKEN', ''); + if (empty($token)) { + throw new \Exception('Missing MixPanel Token'); } - + $this->mixpanel = new Mixpanel($token); $payload = $message->getPayload() ?? []; @@ -103,13 +82,13 @@ class Hamster extends Action $type = $payload['type'] ?? ''; switch ($type) { - case 'project': - $this->getStatsForProject(new Document($payload['project']), $pools, $cache, $dbForConsole, $log); + case EventHamster::TYPE_PROJECT: + $this->getStatsForProject(new Document($payload['project']), $pools, $cache, $dbForConsole); break; - case 'organization': - $this->getStatsForOrganization(new Document($payload['organization']), $pools, $cache, $dbForConsole, $log); + case EventHamster::TYPE_ORGANISATION: + $this->getStatsForOrganization(new Document($payload['organization']), $dbForConsole); break; - case 'user': + case EventHamster::TYPE_USER: $this->getStatsPerUser(new Document($payload['user']), $dbForConsole); break; } @@ -120,10 +99,9 @@ class Hamster extends Action * @param Group $pools * @param Cache $cache * @param Database $dbForConsole - * @param Log $log * @throws \Utopia\Database\Exception */ - private function getStatsForProject(Document $project, Group $pools, Cache $cache, Database $dbForConsole, Log $log): void + private function getStatsForProject(Document $project, Group $pools, Cache $cache, Database $dbForConsole): void { /** * Skip user projects with id 'console' @@ -149,7 +127,7 @@ class Hamster extends Action $statsPerProject = []; - $statsPerProject['time'] = microtime(true); + $statsPerProject['time'] = $project->getAttribute('$time'); /** Get Project ID */ $statsPerProject['project_id'] = $project->getId(); @@ -354,20 +332,20 @@ class Hamster extends Action /** * @param Document $organization - * @param Group $pools - * @param Cache $cache * @param Database $dbForConsole - * @param Log $log * @throws \Utopia\Database\Exception */ - private function getStatsForOrganization(Document $organization, Group $pools, Cache $cache, Database $dbForConsole, Log $log): void + private function getStatsForOrganization(Document $organization, Database $dbForConsole): void { try { $statsPerOrganization = []; + $statsPerOrganization['time'] = $organization->getAttribute('$time'); + /** Organization name */ $statsPerOrganization['name'] = $organization->getAttribute('name'); + /** Get Email and of the organization owner */ $membership = $dbForConsole->findOne('memberships', [ Query::equal('teamInternalId', [$organization->getInternalId()]), @@ -383,7 +361,6 @@ class Hamster extends Action $statsPerOrganization['email'] = $user->getAttribute('email', null); } - /** Organization Creation Date */ $statsPerOrganization['created'] = $organization->getAttribute('$createdAt'); @@ -418,6 +395,8 @@ class Hamster extends Action try { $statsPerUser = []; + $statsPerUser['time'] = $user->getAttribute('$time'); + /** Organization name */ $statsPerUser['name'] = $user->getAttribute('name'); @@ -442,6 +421,7 @@ class Hamster extends Action $event ->setName('User Daily Usage') ->setProps($statsPerUser); + $res = $this->mixpanel->createEvent($event); if (!$res) { From 799fe2acca6871821256f8f340e596674def159f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 17:43:07 +0000 Subject: [PATCH 06/58] chore: address review comments --- app/init.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/init.php b/app/init.php index 2beeb2845..2c0219eec 100644 --- a/app/init.php +++ b/app/init.php @@ -72,7 +72,6 @@ use Ahc\Jwt\JWTException; use Appwrite\Event\Build; use Appwrite\Event\Certificate; use Appwrite\Event\Func; -use Appwrite\Event\Hamster; use MaxMind\Db\Reader; use PHPMailer\PHPMailer\PHPMailer; use Swoole\Database\PDOProxy; From 0382b2250aec40974e810e3365903f126b6e38ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 19:22:35 +0000 Subject: [PATCH 07/58] chore: address review comments --- src/Appwrite/Platform/Workers/Hamster.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Hamster.php b/src/Appwrite/Platform/Workers/Hamster.php index 2931741c8..c8ab1952c 100644 --- a/src/Appwrite/Platform/Workers/Hamster.php +++ b/src/Appwrite/Platform/Workers/Hamster.php @@ -61,7 +61,7 @@ class Hamster extends Action * @param Group $pools * @param Cache $cache * @param Database $dbForConsole - * + * * @return void * @throws \Utopia\Database\Exception */ From d4ff6961734e16b4159f5a8cadf8a9dfdd1f0bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 20:35:18 +0000 Subject: [PATCH 08/58] chore: fix linter issues --- src/Appwrite/Event/Hamster.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Event/Hamster.php b/src/Appwrite/Event/Hamster.php index 54cf0012f..5d79fce56 100644 --- a/src/Appwrite/Event/Hamster.php +++ b/src/Appwrite/Event/Hamster.php @@ -13,9 +13,9 @@ class Hamster extends Event protected ?Document $organization = null; protected ?Document $user = null; - const TYPE_PROJECT = 'project'; - const TYPE_ORGANISATION = 'organisation'; - const TYPE_USER = 'user'; + public const TYPE_PROJECT = 'project'; + public const TYPE_ORGANISATION = 'organisation'; + public const TYPE_USER = 'user'; public function __construct(protected Connection $connection) { From e382a9d58bccec9359aaa947d847f9cc830bcef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 20:37:59 +0000 Subject: [PATCH 09/58] feat: update console --- .gitmodules | 2 +- app/console | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index dad7c9ed3..39b60d5a0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 3.2.9 + branch = 3.2.10 diff --git a/app/console b/app/console index 212b74299..ab9ef73fe 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 212b7429926d097a31ed71d2410e39c600c56f3b +Subproject commit ab9ef73fe0e74bdbb195331bb868af3ede7d1aa3 From 49bb3444bf1d7194871e0285e59427c430f6a253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 21:11:31 +0000 Subject: [PATCH 10/58] chore: add logs --- docker-compose.yml | 34 ++++++++++++++++++++--- src/Appwrite/Platform/Workers/Hamster.php | 7 +++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index da9b0e51e..97cf7e513 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -722,6 +722,34 @@ services: <<: *x-logging container_name: appwrite-worker-hamster image: appwrite-dev + networks: + - appwrite + volumes: + - ./app:/usr/src/code/app + - ./src:/usr/src/code/src + depends_on: + - redis + - mariadb + environment: + - _APP_ENV + - _APP_WORKER_PER_CORE + - _APP_OPENSSL_KEY_V1 + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_REDIS_USER + - _APP_REDIS_PASS + - _APP_MIXPANEL_TOKEN + + appwrite-hamster-scheduler: + entrypoint: hamster + <<: *x-logging + container_name: appwrite-hamster-scheduler + image: appwrite-dev networks: - appwrite volumes: @@ -743,10 +771,8 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_USAGE_STATS - - _APP_LOGGING_CONFIG - - _APP_LOGGING_PROVIDER - - _APP_MIXPANEL_TOKEN + - _APP_HAMSTER_TIME + - _APP_HAMSTER_INTERVAL openruntimes-executor: container_name: openruntimes-executor diff --git a/src/Appwrite/Platform/Workers/Hamster.php b/src/Appwrite/Platform/Workers/Hamster.php index c8ab1952c..e911bb6c7 100644 --- a/src/Appwrite/Platform/Workers/Hamster.php +++ b/src/Appwrite/Platform/Workers/Hamster.php @@ -111,8 +111,7 @@ class Hamster extends Action return; } - Console::log("Getting stats for {$project->getId()}"); - + Console::log("Getting stats for Project {$project->getId()}"); try { $db = $project->getAttribute('database'); @@ -337,6 +336,8 @@ class Hamster extends Action */ private function getStatsForOrganization(Document $organization, Database $dbForConsole): void { + Console::log("Getting stats for Organization {$organization->getId()}"); + try { $statsPerOrganization = []; @@ -392,6 +393,8 @@ class Hamster extends Action protected function getStatsPerUser(Document $user, Database $dbForConsole) { + Console::log("Getting stats for User {$user->getId()}"); + try { $statsPerUser = []; From 11a9583c72ee092de97cc778dd83a2e940619bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 21:16:17 +0000 Subject: [PATCH 11/58] feat: update cache buster --- app/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/init.php b/app/init.php index 4cbf08cf2..72bf75d41 100644 --- a/app/init.php +++ b/app/init.php @@ -109,7 +109,7 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours -const APP_CACHE_BUSTER = 328; +const APP_CACHE_BUSTER = 329; const APP_VERSION_STABLE = '1.4.13'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; From 8218e95f17b3c4449d45177928d0efa5b652a0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Thu, 30 Nov 2023 21:26:07 +0000 Subject: [PATCH 12/58] chore: revert console --- app/console | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/console b/app/console index 49d039ed0..ab9ef73fe 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 49d039ed07628155e7f56e2c997fcef90ecde267 +Subproject commit ab9ef73fe0e74bdbb195331bb868af3ede7d1aa3 From eb87b951c692f0897aa3744512d94cfe4ad51180 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 4 Dec 2023 10:23:29 +0100 Subject: [PATCH 13/58] chore: bump console version --- .gitmodules | 2 +- app/console | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 39b60d5a0..1b780fe9c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 3.2.10 + branch = 3.2.11 diff --git a/app/console b/app/console index ab9ef73fe..50d4e4790 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit ab9ef73fe0e74bdbb195331bb868af3ede7d1aa3 +Subproject commit 50d4e4790c7bdf4bb9578b34cf7efcc052917531 From d79411813a28b6a4329071af94be86ff64a736ab Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 4 Dec 2023 10:45:18 +0100 Subject: [PATCH 14/58] bump: version --- .gitmodules | 2 +- app/console | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 39b60d5a0..1b780fe9c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 3.2.10 + branch = 3.2.11 diff --git a/app/console b/app/console index ab9ef73fe..50d4e4790 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit ab9ef73fe0e74bdbb195331bb868af3ede7d1aa3 +Subproject commit 50d4e4790c7bdf4bb9578b34cf7efcc052917531 From f127ca651f49a06eb758a849e79ac0ffb648bbb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Tue, 5 Dec 2023 00:54:52 +0000 Subject: [PATCH 15/58] chore: update console version --- .gitmodules | 2 +- app/console | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 1b780fe9c..a44e37790 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 3.2.11 + branch = 3.2.13 diff --git a/app/console b/app/console index 50d4e4790..cd96fcad2 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 50d4e4790c7bdf4bb9578b34cf7efcc052917531 +Subproject commit cd96fcad23a54a90006c9383e85c232ab89eacb0 From 38a3c087720a533e7343d7e17dde85912d61789d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Tue, 5 Dec 2023 09:16:35 +0000 Subject: [PATCH 16/58] chore: update docker base image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2f85f2cc4..76d434330 100755 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT RUN npm ci RUN npm run build -FROM appwrite/base:0.4.3 as final +FROM appwrite/base:0.4.5 as final LABEL maintainer="team@appwrite.io" From 1595d41490dedb307b01ff2d64b78e18f15e2e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Tue, 5 Dec 2023 14:13:24 +0000 Subject: [PATCH 17/58] fix: update qr code test images due to Imagemagic extension upgrade in the base image --- app/controllers/api/avatars.php | 2 +- tests/resources/qr/qr-default.png | Bin 14593 -> 29121 bytes tests/resources/qr/qr-size-200-margin-10.png | Bin 3750 -> 5512 bytes tests/resources/qr/qr-size-200.png | Bin 6075 -> 10906 bytes 4 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index e0d967eb0..540d85dd2 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -485,7 +485,7 @@ App::get('/v1/avatars/qr') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->setContentType('image/png') - ->send($image->output('png', 9)); + ->send($image->output('png', 5)); }); App::get('/v1/avatars/initials') diff --git a/tests/resources/qr/qr-default.png b/tests/resources/qr/qr-default.png index a7da496d9fd62ecb6b0afc8d1f2bfb154a660cbf..d6bbba4d12458ffecd85d1221b6717b710213f88 100644 GIT binary patch delta 28982 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 z⁢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
    P!Nwrpd-qT`N? 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 diff --git a/tests/resources/qr/qr-size-200.png b/tests/resources/qr/qr-size-200.png index 202c0cc1970196532f0fde06446de707ad7ab718..94f08d81700f4d845fd699e1f343c5f7452ee60d 100644 GIT binary patch delta 10634 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 z zlg}&NnKeILPdvbmO6l0RTz+@Q|jrcpeW2~M!+xwafuHyhZg~p3l;$9C(}?=YIy==>YjgRVT9Y zjh3Ps6M5M0AVmp7cz4UMZrfb?F#eG9UeWPtSv%SW!p*;-+bQ05hUE>rG8A%P)}>68 zRn119Bd>`O)+Y}PCastsjE1}jaBx1eGce$V>{(Qpn^Q*&?^$okn*{^7f8uaEODCzHjbRr}2* zXAayl*7B%oCOCq!;y<5;e1Bz2Z7pt~TTSWA6a?N|`n|^S8hEY$`=5_N0WjzRfLW;% z+7NK-0)ap@u2R~rX2!1O{HEW`|0^JNC_4u;8#^kAtvKa>yP=Yldqq5M#26LW~c z|0V1l&8#iF{vYwbV8BUe`#(VczY(t1_U10G#`aGCf0JNNEc~A#{~zPxXyNMdqZjY- F{{XOSDT4q2 delta 5717 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 CHP^cU From fa7f9be6f7966e243d1cfc4e89d122b4730296ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Tue, 5 Dec 2023 14:25:19 +0000 Subject: [PATCH 18/58] fix: update image sizes in graphQL test --- tests/e2e/Services/GraphQL/AvatarsTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/e2e/Services/GraphQL/AvatarsTest.php b/tests/e2e/Services/GraphQL/AvatarsTest.php index b95e3b251..e496cf7ac 100644 --- a/tests/e2e/Services/GraphQL/AvatarsTest.php +++ b/tests/e2e/Services/GraphQL/AvatarsTest.php @@ -29,7 +29,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(18767, \strlen($creditCardIcon['body'])); + $this->assertEquals(18546, \strlen($creditCardIcon['body'])); return $creditCardIcon['body']; } @@ -50,7 +50,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(11100, \strlen($browserIcon['body'])); + $this->assertEquals(13328, \strlen($browserIcon['body'])); return $browserIcon['body']; } @@ -71,7 +71,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(7460, \strlen($countryFlag['body'])); + $this->assertEquals(8830, \strlen($countryFlag['body'])); return $countryFlag['body']; } @@ -92,7 +92,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(36036, \strlen($image['body'])); + $this->assertEquals(52601, \strlen($image['body'])); return $image['body']; } @@ -134,7 +134,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(14771, \strlen($qrCode['body'])); + $this->assertEquals(29444, \strlen($qrCode['body'])); return $qrCode['body']; } From 298d6ea917809fea6b18327e5ed194617834bff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Tue, 5 Dec 2023 14:41:05 +0000 Subject: [PATCH 19/58] fix: revert qr code quality --- app/controllers/api/avatars.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 540d85dd2..e0d967eb0 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -485,7 +485,7 @@ App::get('/v1/avatars/qr') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->setContentType('image/png') - ->send($image->output('png', 5)); + ->send($image->output('png', 9)); }); App::get('/v1/avatars/initials') From 14e2488f984cd66f5085f644e93a78680858113c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 5 Dec 2023 18:20:47 +0100 Subject: [PATCH 20/58] chore: bump version --- .gitmodules | 2 +- app/console | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index a44e37790..1d47a44db 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 3.2.13 + branch = 3.2.14 diff --git a/app/console b/app/console index cd96fcad2..11244b1ad 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit cd96fcad23a54a90006c9383e85c232ab89eacb0 +Subproject commit 11244b1ad32648ef080f914ff5936a8380b5e199 From ea3b9ca0ec6a86c59a5ac396d9ba36ec7a11e1fc Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 7 Dec 2023 17:37:15 +0100 Subject: [PATCH 21/58] Revert "Update appwrite base image" --- Dockerfile | 2 +- tests/e2e/Services/GraphQL/AvatarsTest.php | 10 +++++----- tests/resources/qr/qr-default.png | Bin 29121 -> 14593 bytes tests/resources/qr/qr-size-200-margin-10.png | Bin 5512 -> 3750 bytes tests/resources/qr/qr-size-200.png | Bin 10906 -> 6075 bytes 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 76d434330..2f85f2cc4 100755 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT RUN npm ci RUN npm run build -FROM appwrite/base:0.4.5 as final +FROM appwrite/base:0.4.3 as final LABEL maintainer="team@appwrite.io" diff --git a/tests/e2e/Services/GraphQL/AvatarsTest.php b/tests/e2e/Services/GraphQL/AvatarsTest.php index e496cf7ac..b95e3b251 100644 --- a/tests/e2e/Services/GraphQL/AvatarsTest.php +++ b/tests/e2e/Services/GraphQL/AvatarsTest.php @@ -29,7 +29,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(18546, \strlen($creditCardIcon['body'])); + $this->assertEquals(18767, \strlen($creditCardIcon['body'])); return $creditCardIcon['body']; } @@ -50,7 +50,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(13328, \strlen($browserIcon['body'])); + $this->assertEquals(11100, \strlen($browserIcon['body'])); return $browserIcon['body']; } @@ -71,7 +71,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(8830, \strlen($countryFlag['body'])); + $this->assertEquals(7460, \strlen($countryFlag['body'])); return $countryFlag['body']; } @@ -92,7 +92,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(52601, \strlen($image['body'])); + $this->assertEquals(36036, \strlen($image['body'])); return $image['body']; } @@ -134,7 +134,7 @@ class AvatarsTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $graphQLPayload); - $this->assertEquals(29444, \strlen($qrCode['body'])); + $this->assertEquals(14771, \strlen($qrCode['body'])); return $qrCode['body']; } diff --git a/tests/resources/qr/qr-default.png b/tests/resources/qr/qr-default.png index d6bbba4d12458ffecd85d1221b6717b710213f88..a7da496d9fd62ecb6b0afc8d1f2bfb154a660cbf 100644 GIT binary patch literal 14593 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 z⁢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

      P!Nwrpd-qT`N? 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 delta 5149 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 CHP^cU delta 10634 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 z zlg}&NnKeILPdvbmO6l0RTz+@Q|jrcpeW2~M!+xwafuHyhZg~p3l;$9C(}?=YIy==>YjgRVT9Y zjh3Ps6M5M0AVmp7cz4UMZrfb?F#eG9UeWPtSv%SW!p*;-+bQ05hUE>rG8A%P)}>68 zRn119Bd>`O)+Y}PCastsjE1}jaBx1eGce$V>{(Qpn^Q*&?^$okn*{^7f8uaEODCzHjbRr}2* zXAayl*7B%oCOCq!;y<5;e1Bz2Z7pt~TTSWA6a?N|`n|^S8hEY$`=5_N0WjzRfLW;% z+7NK-0)ap@u2R~rX2!1O{HEW`|0^JNC_4u;8#^kAtvKa>yP=Yldqq5M#26LW~c z|0V1l&8#iF{vYwbV8BUe`#(VczY(t1_U10G#`aGCf0JNNEc~A#{~zPxXyNMdqZjY- F{{XOSDT4q2 From c1af9f4201ee99f1908c594295160730d0b3a036 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 8 Dec 2023 18:09:06 +0100 Subject: [PATCH 22/58] feat: upgrade console to 3.2.15 --- .gitmodules | 2 +- app/console | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 1d47a44db..cc08f93fc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 3.2.14 + branch = 3.2.15 diff --git a/app/console b/app/console index 11244b1ad..94e4c1a73 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 11244b1ad32648ef080f914ff5936a8380b5e199 +Subproject commit 94e4c1a73024b0e974fbe6077674281f6e973c9d From f5c2c8d8f7a46fc9285323fac54aa24741ce1e87 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 8 Dec 2023 20:23:04 +0100 Subject: [PATCH 23/58] chore: reset old cookie --- app/controllers/api/account.php | 3 + app/controllers/general.php | 9 ++ composer.json | 2 +- composer.lock | 193 ++++++++++++++++++-------------- docker-compose.yml | 1 + 5 files changed, 123 insertions(+), 85 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c210b19f4..e7f6edec6 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -266,6 +266,9 @@ App::post('/v1/account/sessions/email') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + /** TODO: @christyjacob remove it after 1 month + * Temporarily expire the old cookie to stop the client from sending it */ + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; diff --git a/app/controllers/general.php b/app/controllers/general.php index e443b96fc..2dea5ba3c 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -370,6 +370,15 @@ App::init() ) ); + /** TODO: @christyjacob remove it after 1 month + * Temporarily expire the old cookie to stop the client from sending it */ + Config::getParam( + 'cookieDomainReset', + $isLocalHost || $isIpAddress + ? null + : ($isConsoleProject ? '.' . $request->getHostname() : null) + ); + /* * Response format */ diff --git a/composer.json b/composer.json index cc4ba8f24..413160a7f 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "utopia-php/database": "0.45.*", "utopia-php/domains": "0.3.*", "utopia-php/dsn": "0.1.*", - "utopia-php/framework": "0.31.0", + "utopia-php/framework": "0.31.1", "utopia-php/image": "0.5.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.3.*", diff --git a/composer.lock b/composer.lock index eb2d8e956..351839666 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eff47c9354bb55140097067b71425f91", + "content-hash": "7041499af2e7b23795d8ef82c9d7a072", "packages": [ { "name": "adhocore/jwt", @@ -156,11 +156,11 @@ }, { "name": "appwrite/php-runtimes", - "version": "0.13.1", + "version": "0.13.2", "source": { "type": "git", "url": "https://github.com/appwrite/runtimes.git", - "reference": "b584d19cdcd82737d0ee5c34d23de791f5ed3610" + "reference": "214a37c2c66e0f2bc9c30fdfde66955d9fd084a1" }, "require": { "php": ">=8.0", @@ -195,7 +195,7 @@ "php", "runtimes" ], - "time": "2023-10-16T15:39:53+00:00" + "time": "2023-11-22T15:36:00+00:00" }, { "name": "chillerlan/php-qrcode", @@ -402,16 +402,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.8.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", - "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { @@ -426,11 +426,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -508,7 +508,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, "funding": [ { @@ -524,28 +524,28 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:20:53+00:00" + "time": "2023-12-03T20:35:24+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "type": "library", "extra": { @@ -591,7 +591,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.1" + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, "funding": [ { @@ -607,20 +607,20 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:11:55+00:00" + "time": "2023-12-03T20:19:20+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.1", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", - "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { @@ -634,9 +634,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -707,7 +707,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.1" + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { @@ -723,7 +723,7 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:13:57+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { "name": "influxdb/influxdb-php", @@ -1465,7 +1465,7 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -1512,7 +1512,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -2069,16 +2069,16 @@ }, { "name": "utopia-php/framework", - "version": "0.31.0", + "version": "0.31.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0" + "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/207f77378965fca9a9bc3783ea379d3549f86bc0", - "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", + "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", "shasum": "" }, "require": { @@ -2108,9 +2108,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.31.0" + "source": "https://github.com/utopia-php/framework/tree/0.31.1" }, - "time": "2023-08-30T16:10:04+00:00" + "time": "2023-12-08T18:47:29+00:00" }, { "name": "utopia-php/image", @@ -2217,16 +2217,16 @@ }, { "name": "utopia-php/logger", - "version": "0.3.1", + "version": "0.3.2", "source": { "type": "git", "url": "https://github.com/utopia-php/logger.git", - "reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc" + "reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/logger/zipball/de623f1ec1c672c795d113dd25c5bf212f7ef4fc", - "reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc", + "url": "https://api.github.com/repos/utopia-php/logger/zipball/ba763c10688fe2ed715ad2bed3f13d18dfec6253", + "reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253", "shasum": "" }, "require": { @@ -2264,9 +2264,9 @@ ], "support": { "issues": "https://github.com/utopia-php/logger/issues", - "source": "https://github.com/utopia-php/logger/tree/0.3.1" + "source": "https://github.com/utopia-php/logger/tree/0.3.2" }, - "time": "2023-02-10T15:52:50+00:00" + "time": "2023-11-22T14:45:43+00:00" }, { "name": "utopia-php/messaging", @@ -3822,29 +3822,29 @@ }, { "name": "phpspec/prophecy", - "version": "v1.17.0", + "version": "v1.18.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "15873c65b207b07765dbc3c95d20fdf4a320cbe2" + "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/15873c65b207b07765dbc3c95d20fdf4a320cbe2", - "reference": "15873c65b207b07765dbc3c95d20fdf4a320cbe2", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d4f454f7e1193933f04e6500de3e79191648ed0c", + "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", - "php": "^7.2 || 8.0.* || 8.1.* || 8.2.*", + "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0" }, "require-dev": { "phpspec/phpspec": "^6.0 || ^7.0", "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^8.0 || ^9.0" + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" }, "type": "library", "extra": { @@ -3877,6 +3877,7 @@ "keywords": [ "Double", "Dummy", + "dev", "fake", "mock", "spy", @@ -3884,22 +3885,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.17.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.18.0" }, - "time": "2023-02-02T15:41:36+00:00" + "time": "2023-12-07T16:22:33+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.2", + "version": "1.24.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "bcad8d995980440892759db0c32acae7c8e79442" + "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", - "reference": "bcad8d995980440892759db0c32acae7c8e79442", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496", + "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496", "shasum": "" }, "require": { @@ -3931,9 +3932,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4" }, - "time": "2023-09-26T12:28:12+00:00" + "time": "2023-11-26T18:29:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5373,16 +5374,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.8.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7", "shasum": "" }, "require": { @@ -5392,7 +5393,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/phpcs", @@ -5411,22 +5412,45 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T12:32:31+00:00" }, { "name": "swoole/ide-helper", @@ -5676,16 +5700,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -5714,7 +5738,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -5722,30 +5746,31 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "twig/twig", - "version": "v3.7.1", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3" + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" }, "type": "library", "autoload": { @@ -5781,7 +5806,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.1" + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" }, "funding": [ { @@ -5793,7 +5818,7 @@ "type": "tidelift" } ], - "time": "2023-08-28T11:09:02+00:00" + "time": "2023-11-21T18:54:41+00:00" } ], "aliases": [], diff --git a/docker-compose.yml b/docker-compose.yml index 97cf7e513..9b3a0ee19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,6 +84,7 @@ services: - ./public:/usr/src/code/public - ./src:/usr/src/code/src - ./dev:/usr/src/code/dev + # - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework depends_on: - mariadb - redis From eb61d5c25c727e6e7229c672b7a5146ab3cbabc7 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 8 Dec 2023 20:24:07 +0100 Subject: [PATCH 24/58] chore: reset old cookie --- app/controllers/api/account.php | 4 ++++ app/controllers/api/teams.php | 1 + 2 files changed, 5 insertions(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e7f6edec6..8b5d2f9ad 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -789,6 +789,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->addHeader('Pragma', 'no-cache') ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->redirect($state['success']) ; }); @@ -1199,6 +1200,7 @@ App::put('/v1/account/sessions/magic-url') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -1449,6 +1451,7 @@ App::put('/v1/account/sessions/phone') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1586,6 +1589,7 @@ App::post('/v1/account/sessions/anonymous') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 2ee351f46..3b5689738 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -982,6 +982,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; $response->dynamic( From 1e9ee66231146379a9957a7474727f946b390cd5 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 8 Dec 2023 20:30:04 +0100 Subject: [PATCH 25/58] chore: reset old cookie --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9b3a0ee19..97cf7e513 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,7 +84,6 @@ services: - ./public:/usr/src/code/public - ./src:/usr/src/code/src - ./dev:/usr/src/code/dev - # - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework depends_on: - mariadb - redis From 5d121c1887d5781f18ab43a89975c5ccec2d186b Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 8 Dec 2023 20:36:22 +0100 Subject: [PATCH 26/58] fix: reset the legacy cookie --- app/controllers/api/account.php | 9 +++++++++ app/controllers/api/teams.php | 2 ++ 2 files changed, 11 insertions(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8b5d2f9ad..71715bbeb 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -269,6 +269,7 @@ App::post('/v1/account/sessions/email') /** TODO: @christyjacob remove it after 1 month * Temporarily expire the old cookie to stop the client from sending it */ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -790,6 +791,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) + ->setStatusCode(Response::STATUS_CODE_CREATED) ->redirect($state['success']) ; }); @@ -1201,6 +1204,8 @@ App::put('/v1/account/sessions/magic-url') ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) + ->setStatusCode(Response::STATUS_CODE_CREATED) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -1452,6 +1457,8 @@ App::put('/v1/account/sessions/phone') ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) + ->setStatusCode(Response::STATUS_CODE_CREATED) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1590,6 +1597,8 @@ App::post('/v1/account/sessions/anonymous') ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) + ->setStatusCode(Response::STATUS_CODE_CREATED) ->setStatusCode(Response::STATUS_CODE_CREATED) ; diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 3b5689738..5a1427095 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -983,6 +983,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) + ->setStatusCode(Response::STATUS_CODE_CREATED) ; $response->dynamic( From 9dca3c4300fb246b5a0a9fe920a2b449e9a8c9b1 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 8 Dec 2023 20:38:55 +0100 Subject: [PATCH 27/58] fix: linter --- app/controllers/api/account.php | 2 +- app/controllers/general.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 71715bbeb..b3f23c124 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -266,7 +266,7 @@ App::post('/v1/account/sessions/email') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - /** TODO: @christyjacob remove it after 1 month + /** TODO: @christyjacob remove it after 1 month * Temporarily expire the old cookie to stop the client from sending it */ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) diff --git a/app/controllers/general.php b/app/controllers/general.php index 2dea5ba3c..9ed50bbab 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -370,7 +370,7 @@ App::init() ) ); - /** TODO: @christyjacob remove it after 1 month + /** TODO: @christyjacob remove it after 1 month * Temporarily expire the old cookie to stop the client from sending it */ Config::getParam( 'cookieDomainReset', From 8b5783d3ffba1b3c370459eed47926bb8d654c94 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 8 Dec 2023 21:02:07 +0100 Subject: [PATCH 28/58] fix: update setParam --- app/console | 2 +- app/controllers/general.php | 2 +- tests/e2e/Scopes/Scope.php | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/console b/app/console index 94e4c1a73..11244b1ad 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 94e4c1a73024b0e974fbe6077674281f6e973c9d +Subproject commit 11244b1ad32648ef080f914ff5936a8380b5e199 diff --git a/app/controllers/general.php b/app/controllers/general.php index 9ed50bbab..7d9f92c56 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -372,7 +372,7 @@ App::init() /** TODO: @christyjacob remove it after 1 month * Temporarily expire the old cookie to stop the client from sending it */ - Config::getParam( + Config::setParam( 'cookieDomainReset', $isLocalHost || $isIpAddress ? null diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index 14eb83897..2fa0c1df6 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -98,6 +98,7 @@ abstract class Scope extends TestCase 'password' => $password, ]); + var_dump($session['headers']['set-cookie']); $session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_console']; self::$root = [ From 731e7b86a7321350475805b7681002e45efb1e3d Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 00:17:13 +0100 Subject: [PATCH 29/58] fix: failing tests --- tests/e2e/Client.php | 12 ++++++++++-- tests/e2e/Scopes/Scope.php | 5 ++--- tests/e2e/Services/Account/AccountBase.php | 10 +++++----- .../e2e/Services/Account/AccountCustomClientTest.php | 12 ++++++------ .../Services/Projects/ProjectsConsoleClientTest.php | 2 +- .../Services/Realtime/RealtimeCustomClientTest.php | 2 +- tests/e2e/Services/Teams/TeamsBaseClient.php | 2 +- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 21e4ccc95..a571f8743 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -168,6 +168,7 @@ class Client $headers = array_merge($this->headers, $headers); $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : '')); $responseHeaders = []; + $cookies = []; $query = match ($headers['content-type']) { 'application/json' => json_encode($params), @@ -189,7 +190,7 @@ class Client curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 15); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders, &$cookies) { $len = strlen($header); $header = explode(':', $header, 2); @@ -197,8 +198,14 @@ class Client return $len; } + if (strtolower(trim($header[0])) == 'set-cookie') { + $parsed = $this->parseCookie((string)trim($header[1])); + $name = array_key_first($parsed); + $cookies[$name] = $parsed[$name]; + } + $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); - + return $len; }); @@ -241,6 +248,7 @@ class Client return [ 'headers' => $responseHeaders, + 'cookies' => $cookies, 'body' => $responseBody ]; } diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index 2fa0c1df6..8d0f44118 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -98,8 +98,7 @@ abstract class Scope extends TestCase 'password' => $password, ]); - var_dump($session['headers']['set-cookie']); - $session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_console']; + $session = $session['cookies']['a_session_console']; self::$root = [ '$id' => ID::custom($root['body']['$id']), @@ -151,7 +150,7 @@ abstract class Scope extends TestCase 'password' => $password, ]); - $token = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $token = $session['cookies']['a_session_' . $this->getProject()['$id']]; self::$user[$this->getProject()['$id']] = [ '$id' => ID::custom($user['body']['$id']), diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 953b89ced..e6f5feaa8 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -126,7 +126,7 @@ trait AccountBase $this->assertNotFalse(\DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $response['body']['expire'])); $sessionId = $response['body']['$id']; - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; // apiKey is only available in custom client test $apiKey = $this->getProject()['apiKey']; @@ -993,7 +993,7 @@ trait AccountBase ]); $sessionNewId = $response['body']['$id']; - $sessionNew = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $sessionNew = $response['cookies']['a_session_' . $this->getProject()['$id']]; $this->assertEquals($response['headers']['status-code'], 201); @@ -1059,7 +1059,7 @@ trait AccountBase 'password' => $password, ]); - $sessionNew = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $sessionNew = $response['cookies']['a_session_' . $this->getProject()['$id']]; $this->assertEquals($response['headers']['status-code'], 201); @@ -1141,7 +1141,7 @@ trait AccountBase 'password' => $password, ]); - $data['session'] = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $data['session'] = $response['cookies']['a_session_' . $this->getProject()['$id']]; return $data; } @@ -1417,7 +1417,7 @@ trait AccountBase $this->assertNotEmpty($response['body']['userId']); $sessionId = $response['body']['$id']; - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ 'origin' => 'http://localhost', diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 8e2117032..56280b4b4 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -126,7 +126,7 @@ class AccountCustomClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 201); $sessionId = $response['body']['$id']; - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ 'origin' => 'http://localhost', @@ -206,7 +206,7 @@ class AccountCustomClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 201); - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ 'origin' => 'http://localhost', @@ -288,7 +288,7 @@ class AccountCustomClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 201); $sessionId = $response['body']['$id']; - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ 'origin' => 'http://localhost', @@ -368,7 +368,7 @@ class AccountCustomClientTest extends Scope $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; \usleep(1000 * 30); // wait for 30ms to let the shutdown update accessedAt @@ -571,7 +571,7 @@ class AccountCustomClientTest extends Scope 'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure', ]); - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('success', $response['body']['result']); @@ -849,7 +849,7 @@ class AccountCustomClientTest extends Scope $this->assertNotEmpty($response['body']['$id']); $this->assertNotEmpty($response['body']['userId']); - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ 'origin' => 'http://localhost', diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index f90a04f29..8d88f0def 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -931,7 +931,7 @@ class ProjectsConsoleClientTest extends Scope 'password' => $originalPassword, ]); - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $id]; + $session = $response['cookies']['a_session_' . $id]; /** * Test for SUCCESS diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 56796280a..e8baa2044 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -468,7 +468,7 @@ class RealtimeCustomClientTest extends Scope 'password' => 'new-password', ]); - $sessionNew = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $projectId]; + $sessionNew = $response['cookies']['a_session_' . $projectId]; $sessionNewId = $response['body']['$id']; return array("session" => $sessionNew, "sessionId" => $sessionNewId); diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 7b6b43955..2c2ff02c4 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -403,7 +403,7 @@ trait TeamsBaseClient $this->assertCount(2, $response['body']['roles']); $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['joined'])); $this->assertEquals(true, $response['body']['confirm']); - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; $data['session'] = $session; $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ From aa7d1a87f4ac011fb80493ae78c28a95a20e9971 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 00:27:35 +0100 Subject: [PATCH 30/58] fix: failing tests --- tests/e2e/Services/Databases/DatabasesBase.php | 4 ++-- .../Services/Databases/DatabasesPermissionsScope.php | 2 +- tests/e2e/Services/GraphQL/AccountTest.php | 2 +- tests/e2e/Services/GraphQL/AuthTest.php | 8 ++------ tests/e2e/Services/Storage/StoragePermissionsScope.php | 2 +- .../e2e/Services/Webhooks/WebhooksCustomClientTest.php | 10 +++++----- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index f86b81777..638c39412 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -2791,7 +2791,7 @@ trait DatabasesBase 'email' => $email, 'password' => $password, ]); - $session2 = $this->client->parseCookie((string)$session2['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session2 = $session2['cookies']['a_session_' . $this->getProject()['$id']]; $document3GetWithDocumentRead = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $document3['body']['$id'], [ 'origin' => 'http://localhost', @@ -2979,7 +2979,7 @@ trait DatabasesBase 'email' => $email, 'password' => $password, ]); - $session2 = $this->client->parseCookie((string)$session2['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session2 = $session2['a_session_' . $this->getProject()['$id']]; $document3GetWithDocumentRead = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $document3['body']['$id'], [ 'origin' => 'http://localhost', diff --git a/tests/e2e/Services/Databases/DatabasesPermissionsScope.php b/tests/e2e/Services/Databases/DatabasesPermissionsScope.php index 181231adc..a147ff56b 100644 --- a/tests/e2e/Services/Databases/DatabasesPermissionsScope.php +++ b/tests/e2e/Services/Databases/DatabasesPermissionsScope.php @@ -32,7 +32,7 @@ trait DatabasesPermissionsScope 'password' => $password, ]); - $session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $session['a_session_' . $this->getProject()['$id']]; $user = [ '$id' => $user['body']['$id'], diff --git a/tests/e2e/Services/GraphQL/AccountTest.php b/tests/e2e/Services/GraphQL/AccountTest.php index 7fd70b501..275bbf9ad 100644 --- a/tests/e2e/Services/GraphQL/AccountTest.php +++ b/tests/e2e/Services/GraphQL/AccountTest.php @@ -63,7 +63,7 @@ class AccountTest extends Scope $this->assertIsArray($session['body']['data']); $this->assertIsArray($session['body']['data']['accountCreateEmailSession']); - $cookie = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $cookie = $session['a_session_' . $this->getProject()['$id']]; $this->assertNotEmpty($cookie); } diff --git a/tests/e2e/Services/GraphQL/AuthTest.php b/tests/e2e/Services/GraphQL/AuthTest.php index c331fb843..ef809e5f1 100644 --- a/tests/e2e/Services/GraphQL/AuthTest.php +++ b/tests/e2e/Services/GraphQL/AuthTest.php @@ -73,9 +73,7 @@ class AuthTest extends Scope 'x-appwrite-project' => $projectId, ], $graphQLPayload); - $this->token1 = $this->client->parseCookie( - (string)$session1['headers']['set-cookie'] - )['a_session_' . $projectId]; + $this->token1 = $session1['a_session_' . $projectId]; // Create session 2 $graphQLPayload['variables']['email'] = $email2; @@ -85,9 +83,7 @@ class AuthTest extends Scope 'x-appwrite-project' => $projectId, ], $graphQLPayload); - $this->token2 = $this->client->parseCookie( - (string)$session2['headers']['set-cookie'] - )['a_session_' . $projectId]; + $this->token2 = $session1['a_session_' . $projectId]; // Create database $query = $this->getQuery(self::$CREATE_DATABASE); diff --git a/tests/e2e/Services/Storage/StoragePermissionsScope.php b/tests/e2e/Services/Storage/StoragePermissionsScope.php index 09b35fad3..34f75ac19 100644 --- a/tests/e2e/Services/Storage/StoragePermissionsScope.php +++ b/tests/e2e/Services/Storage/StoragePermissionsScope.php @@ -32,7 +32,7 @@ trait StoragePermissionsScope 'password' => $password, ]); - $session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $session['a_session_' . $this->getProject()['$id']]; $user = [ diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index 21c270d19..bf6e05c73 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -107,7 +107,7 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); $id = $account['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['a_session_' . $this->getProject()['$id']]; $account = $this->client->call(Client::METHOD_PATCH, '/account/status', array_merge([ 'origin' => 'http://localhost', @@ -170,7 +170,7 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['a_session_' . $this->getProject()['$id']]; $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; @@ -248,7 +248,7 @@ class WebhooksCustomClientTest extends Scope ]); $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['a_session_' . $this->getProject()['$id']]; $this->assertEquals($accountSession['headers']['status-code'], 201); @@ -334,7 +334,7 @@ class WebhooksCustomClientTest extends Scope ]); $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['a_session_' . $this->getProject()['$id']]; $this->assertEquals($accountSession['headers']['status-code'], 201); @@ -407,7 +407,7 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['a_session_' . $this->getProject()['$id']]; return array_merge($data, [ 'sessionId' => $sessionId, From 72a2a043cd54de724b60d47d0456375843f02c6c Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 00:28:10 +0100 Subject: [PATCH 31/58] fix: linter --- tests/e2e/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index a571f8743..7083095da 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -203,9 +203,9 @@ class Client $name = array_key_first($parsed); $cookies[$name] = $parsed[$name]; } - + $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); - + return $len; }); From 75e1779921fae5bfba24da82bb4f53d17ed174e3 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 00:29:14 +0100 Subject: [PATCH 32/58] fix: failing tests --- tests/e2e/Services/Projects/ProjectsConsoleClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 8d88f0def..71b664969 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1313,7 +1313,7 @@ class ProjectsConsoleClientTest extends Scope 'password' => $password, ]); $this->assertEquals(201, $session['headers']['status-code']); - $session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $id]; + $session = $session['cookie']['a_session_' . $id]; $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ 'origin' => 'http://localhost', From aeeb7e1d55c3865260f036fc3e8846dcf9197a8e Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 00:36:01 +0100 Subject: [PATCH 33/58] fix: failing tests --- tests/e2e/Services/GraphQL/AccountTest.php | 2 +- tests/e2e/Services/GraphQL/AuthTest.php | 4 ++-- tests/e2e/Services/Projects/ProjectsConsoleClientTest.php | 2 +- tests/e2e/Services/Storage/StoragePermissionsScope.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/e2e/Services/GraphQL/AccountTest.php b/tests/e2e/Services/GraphQL/AccountTest.php index 275bbf9ad..3985988d7 100644 --- a/tests/e2e/Services/GraphQL/AccountTest.php +++ b/tests/e2e/Services/GraphQL/AccountTest.php @@ -63,7 +63,7 @@ class AccountTest extends Scope $this->assertIsArray($session['body']['data']); $this->assertIsArray($session['body']['data']['accountCreateEmailSession']); - $cookie = $session['a_session_' . $this->getProject()['$id']]; + $cookie = $session['cookies']['a_session_' . $this->getProject()['$id']]; $this->assertNotEmpty($cookie); } diff --git a/tests/e2e/Services/GraphQL/AuthTest.php b/tests/e2e/Services/GraphQL/AuthTest.php index ef809e5f1..b8689c144 100644 --- a/tests/e2e/Services/GraphQL/AuthTest.php +++ b/tests/e2e/Services/GraphQL/AuthTest.php @@ -73,7 +73,7 @@ class AuthTest extends Scope 'x-appwrite-project' => $projectId, ], $graphQLPayload); - $this->token1 = $session1['a_session_' . $projectId]; + $this->token1 = $session1['cookies']['a_session_' . $projectId]; // Create session 2 $graphQLPayload['variables']['email'] = $email2; @@ -83,7 +83,7 @@ class AuthTest extends Scope 'x-appwrite-project' => $projectId, ], $graphQLPayload); - $this->token2 = $session1['a_session_' . $projectId]; + $this->token2 = $session2['cookies']['a_session_' . $projectId]; // Create database $query = $this->getQuery(self::$CREATE_DATABASE); diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 71b664969..b3fcd828c 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1313,7 +1313,7 @@ class ProjectsConsoleClientTest extends Scope 'password' => $password, ]); $this->assertEquals(201, $session['headers']['status-code']); - $session = $session['cookie']['a_session_' . $id]; + $session = $session['cookies']['a_session_' . $id]; $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ 'origin' => 'http://localhost', diff --git a/tests/e2e/Services/Storage/StoragePermissionsScope.php b/tests/e2e/Services/Storage/StoragePermissionsScope.php index 34f75ac19..546931601 100644 --- a/tests/e2e/Services/Storage/StoragePermissionsScope.php +++ b/tests/e2e/Services/Storage/StoragePermissionsScope.php @@ -32,7 +32,7 @@ trait StoragePermissionsScope 'password' => $password, ]); - $session = $session['a_session_' . $this->getProject()['$id']]; + $session = $session['cookies']['a_session_' . $this->getProject()['$id']]; $user = [ From 28d00668bdc83c6ecb264dc386024a62272f5e28 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 00:48:34 +0100 Subject: [PATCH 34/58] fix: failing tests --- tests/e2e/Services/Databases/DatabasesBase.php | 2 +- .../e2e/Services/Databases/DatabasesPermissionsScope.php | 2 +- tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 638c39412..ece495686 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -2979,7 +2979,7 @@ trait DatabasesBase 'email' => $email, 'password' => $password, ]); - $session2 = $session2['a_session_' . $this->getProject()['$id']]; + $session2 = $session2['cookies']['a_session_' . $this->getProject()['$id']]; $document3GetWithDocumentRead = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $document3['body']['$id'], [ 'origin' => 'http://localhost', diff --git a/tests/e2e/Services/Databases/DatabasesPermissionsScope.php b/tests/e2e/Services/Databases/DatabasesPermissionsScope.php index a147ff56b..336e47db0 100644 --- a/tests/e2e/Services/Databases/DatabasesPermissionsScope.php +++ b/tests/e2e/Services/Databases/DatabasesPermissionsScope.php @@ -32,7 +32,7 @@ trait DatabasesPermissionsScope 'password' => $password, ]); - $session = $session['a_session_' . $this->getProject()['$id']]; + $session = $session['cookies']['a_session_' . $this->getProject()['$id']]; $user = [ '$id' => $user['body']['$id'], diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index bf6e05c73..fb5f4e97a 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -107,7 +107,7 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); $id = $account['body']['$id']; - $session = $accountSession['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['cookies']['a_session_' . $this->getProject()['$id']]; $account = $this->client->call(Client::METHOD_PATCH, '/account/status', array_merge([ 'origin' => 'http://localhost', @@ -170,7 +170,7 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); $sessionId = $accountSession['body']['$id']; - $session = $accountSession['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['cookies']['a_session_' . $this->getProject()['$id']]; $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; @@ -248,7 +248,7 @@ class WebhooksCustomClientTest extends Scope ]); $sessionId = $accountSession['body']['$id']; - $session = $accountSession['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['cookies']['a_session_' . $this->getProject()['$id']]; $this->assertEquals($accountSession['headers']['status-code'], 201); @@ -334,7 +334,7 @@ class WebhooksCustomClientTest extends Scope ]); $sessionId = $accountSession['body']['$id']; - $session = $accountSession['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['cookies']['a_session_' . $this->getProject()['$id']]; $this->assertEquals($accountSession['headers']['status-code'], 201); From c21a0636422c953a6ccedc5be598cc80ec9c845f Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 01:02:13 +0100 Subject: [PATCH 35/58] fix: failing tests --- app/controllers/api/teams.php | 2 +- tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 5a1427095..fbb1ebcfc 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -984,7 +984,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) - ->setStatusCode(Response::STATUS_CODE_CREATED) + ->setStatusCode(Response::STATUS_CODE_OK) ; $response->dynamic( diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index fb5f4e97a..4ad0a96e5 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -407,7 +407,7 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); $sessionId = $accountSession['body']['$id']; - $session = $accountSession['a_session_' . $this->getProject()['$id']]; + $session = $accountSession['cookies']['a_session_' . $this->getProject()['$id']]; return array_merge($data, [ 'sessionId' => $sessionId, From 951a749b504a4e11b590a3fabac16a7d86bb02a8 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 01:19:36 +0100 Subject: [PATCH 36/58] fix: revert files --- app/console | 2 +- app/controllers/api/account.php | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/console b/app/console index 11244b1ad..94e4c1a73 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 11244b1ad32648ef080f914ff5936a8380b5e199 +Subproject commit 94e4c1a73024b0e974fbe6077674281f6e973c9d diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index b3f23c124..76e6a81e9 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -792,7 +792,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) - ->setStatusCode(Response::STATUS_CODE_CREATED) ->redirect($state['success']) ; }); @@ -1205,7 +1204,6 @@ App::put('/v1/account/sessions/magic-url') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) - ->setStatusCode(Response::STATUS_CODE_CREATED) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -1459,7 +1457,6 @@ App::put('/v1/account/sessions/phone') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ->setStatusCode(Response::STATUS_CODE_CREATED) - ->setStatusCode(Response::STATUS_CODE_CREATED) ; $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -1599,7 +1596,6 @@ App::post('/v1/account/sessions/anonymous') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ->setStatusCode(Response::STATUS_CODE_CREATED) - ->setStatusCode(Response::STATUS_CODE_CREATED) ; $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); From 4f1d8ed6f401a4b513d20fc21ebd9954c14abd86 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 9 Dec 2023 01:36:57 +0100 Subject: [PATCH 37/58] fix: revert files --- app/controllers/api/teams.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index fbb1ebcfc..f6942bf87 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -984,7 +984,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) - ->setStatusCode(Response::STATUS_CODE_OK) ; $response->dynamic( From f334168f9082a10284ba8f9156e2ee9080c19e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Wed, 13 Dec 2023 08:45:47 +0000 Subject: [PATCH 38/58] fix: remove expired cookie --- app/controllers/api/account.php | 12 ------------ app/controllers/api/teams.php | 2 -- app/controllers/general.php | 9 --------- 3 files changed, 23 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 76e6a81e9..c210b19f4 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -266,10 +266,6 @@ App::post('/v1/account/sessions/email') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - /** TODO: @christyjacob remove it after 1 month - * Temporarily expire the old cookie to stop the client from sending it */ - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -790,8 +786,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->addHeader('Pragma', 'no-cache') ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ->redirect($state['success']) ; }); @@ -1202,8 +1196,6 @@ App::put('/v1/account/sessions/magic-url') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -1454,8 +1446,6 @@ App::put('/v1/account/sessions/phone') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1593,8 +1583,6 @@ App::post('/v1/account/sessions/anonymous') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ->setStatusCode(Response::STATUS_CODE_CREATED) ; diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index f6942bf87..2ee351f46 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -982,8 +982,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $response ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp() - 3600, '/', Config::getParam('cookieDomainReset'), ('https' == $protocol), true, null) ; $response->dynamic( diff --git a/app/controllers/general.php b/app/controllers/general.php index 7d9f92c56..e443b96fc 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -370,15 +370,6 @@ App::init() ) ); - /** TODO: @christyjacob remove it after 1 month - * Temporarily expire the old cookie to stop the client from sending it */ - Config::setParam( - 'cookieDomainReset', - $isLocalHost || $isIpAddress - ? null - : ($isConsoleProject ? '.' . $request->getHostname() : null) - ); - /* * Response format */ From e56b30e6a6c9d7d5416383e7329ccc4738a645eb Mon Sep 17 00:00:00 2001 From: Serhat Aksakal <49079271+fanksin@users.noreply.github.com> Date: Wed, 13 Dec 2023 12:10:44 +0300 Subject: [PATCH 39/58] Update tr.json --- app/config/locale/translations/tr.json | 56 ++++++++++++++------------ 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/app/config/locale/translations/tr.json b/app/config/locale/translations/tr.json index 6a94aeaca..e82317de0 100644 --- a/app/config/locale/translations/tr.json +++ b/app/config/locale/translations/tr.json @@ -3,30 +3,36 @@ "settings.locale": "tr", "settings.direction": "ltr", "emails.sender": "%s Takımı", - "emails.verification.subject": "", - "emails.verification.hello": "", - "emails.verification.body": "", - "emails.verification.footer": "", - "emails.verification.thanks": "", - "emails.verification.signature": "", - "emails.magicSession.subject": "", - "emails.magicSession.hello": "", - "emails.magicSession.body": "", - "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", - "emails.magicSession.signature": "", - "emails.recovery.subject": "", - "emails.recovery.hello": "", - "emails.recovery.body": "", - "emails.recovery.footer": "", - "emails.recovery.thanks": "", - "emails.recovery.signature": "", - "emails.invitation.subject": "", - "emails.invitation.hello": "", - "emails.invitation.body": "", - "emails.invitation.footer": "", - "emails.invitation.thanks": "", - "emails.invitation.signature": "", + "emails.verification.subject": "Hesabını Doğrula", + "emails.verification.hello": "Merhaba {{user}}", + "emails.verification.body": "Eposta adresini doğrulamak için bu bağlantıyı kullanın.", + "emails.verification.footer": "Eğer bu eposta adresini doğrulamak isteyen siz değilseniz devam etmeyin.", + "emails.verification.thanks": "Teşekkürler", + "emails.verification.signature": "{{project}} takımı", + "emails.magicSession.subject": "Giriş", + "emails.magicSession.hello": "Merhaba,", + "emails.magicSession.body": "Giriş yapmak için tıklayın.", + "emails.magicSession.footer": "Eğer bu eposta adresini kullanarak giriş yapmak istemediyseniz devam etmeyin.", + "emails.magicSession.thanks": "Teşekkürler", + "emails.magicSession.signature": "{{project}} takımı", + "emails.recovery.subject": "Şifremi Sıfırla", + "emails.recovery.hello": "Merhaba {{user}}", + "emails.recovery.body": "{{project}} şifrenizi sıfırlamak için bu bağlantıyı kullanın.", + "emails.recovery.footer": "Eğer şifre sıfırlama talebinde bulunmadıysanız devam etmeyin.", + "emails.recovery.thanks": "Teşekkürler", + "emails.recovery.signature": "{{project}} takımı", + "emails.invitation.subject": "%s üzerinde %s Takımına Davet", + "emails.invitation.hello": "Merhaba", + "emails.invitation.body": "Bu epostayı aldınız, çünkü {{owner}} sizi {{project}} üzerinde {{team}} takımının üyesi olmaya davet etti.", + "emails.invitation.footer": "Eğer ilgilenmiyorsanız devam etmeyin.", + "emails.invitation.thanks": "Teşekkürler", + "emails.invitation.signature": "{{project}} takımı", + "emails.certificate.subject": "%s için sertifika hatası", + "emails.certificate.hello": "Merhaba", + "emails.certificate.body": "Alan adınız '{{domain}}' için sertifika oluşturulamadı. Deneme sayısı {{attempt}} ve hata sebebi: {{error}}", + "emails.certificate.footer": "Geçmiş sertifikanız ilk denemeden sonra 30 gün daha geçerli kalacaktır. Bu konuyu araştırmanızı öneriyoruz, aksi taktirde alan adınız SSL sertifikasız kalacaktır.", + "emails.certificate.thanks": "Teşekkürler", + "emails.certificate.signature": "{{project}} takımı", "locale.country.unknown": "Bilinmeyen", "countries.af": "Afganistan", "countries.ao": "Angola", @@ -229,4 +235,4 @@ "continents.na": "Kuzey Amerika", "continents.oc": "Okyanusya", "continents.sa": "Güney Amerika" -} \ No newline at end of file +} From 92a307c164b00f1b888b3ea9bd2f52b98bbbc7be Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Wed, 13 Dec 2023 18:45:05 +0000 Subject: [PATCH 40/58] Fix user identity attaching to wrong user Suppose a user has 2 accounts on Appwrite: 1. joe@example.com 2. joe@gmail.com Prior to this PR, if joe@example.com created a Google OAuth2 session using his joe@gmail.com email, a new joe@gmail.com identity would be created linked to joe@example.com. This is especially problematic because if the user tried to create a Google OAuth2 session using joe@gmail.com, Appwrite would lookup the user via email and find the joe@gmail.com user, but then find an identity from joe@example.com. This mismatching user ID would then cause an error. This PR prevents an identity from being created if the email from the OAuth2 provider matches another user's email. --- app/controllers/api/account.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c210b19f4..54967fb50 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -550,11 +550,19 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') if (!$user->isEmpty()) { $userId = $user->getId(); - $identitiesWithMatchingEmail = $dbForProject->find('identities', [ + $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), Query::notEqual('userId', $userId), ]); - if (!empty($identitiesWithMatchingEmail)) { + if (!empty($identityWithMatchingEmail)) { + throw new Exception(Exception::USER_ALREADY_EXISTS); + } + + $userWithMatchingEmail = $dbForProject->find('users', [ + Query::equal('email', [$email]), + Query::notEqual('$id', $userId), + ]); + if (!empty($userWithMatchingEmail)) { throw new Exception(Exception::USER_ALREADY_EXISTS); } } From 6b6946e39bfd4a653ed2bd9dec57b3de301995d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=B7=E5=8D=8E=20=E5=88=98?= Date: Fri, 15 Dec 2023 20:40:04 +0000 Subject: [PATCH 41/58] chore: update console --- .gitmodules | 2 +- app/console | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index cc08f93fc..0c2321bcf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 3.2.15 + branch = 3.2.16 diff --git a/app/console b/app/console index 94e4c1a73..0a007a3b1 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 94e4c1a73024b0e974fbe6077674281f6e973c9d +Subproject commit 0a007a3b1b6eafc39dc19b7129f41643102f9676 From 6d17f8e87779b51e6eacd39297c2066c6af6bfee Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 15 Dec 2023 21:37:26 +0000 Subject: [PATCH 42/58] Fix user last activity not updating Add back a section of code that was mistakenly removed. --- app/controllers/shared/api.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index b37d76a81..1ea2fdc65 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -553,6 +553,22 @@ App::shutdown() ->setParam('project.{scope}.network.outbound', $response->getSize()) ->submit(); } + + /** + * Update user last activity + */ + if (!$user->isEmpty()) { + $accessedAt = $user->getAttribute('accessedAt', ''); + if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCCESS)) > $accessedAt) { + $user->setAttribute('accessedAt', DateTime::now()); + + if (APP_MODE_ADMIN !== $mode) { + $dbForProject->updateDocument('users', $user->getId(), $user); + } else { + $dbForConsole->updateDocument('users', $user->getId(), $user); + } + } + } }); App::init() From e41194ee3c6163c72ee7c3d24464e11088a720db Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Sat, 16 Dec 2023 17:42:29 +0000 Subject: [PATCH 43/58] Fix import to match class name The class is SMS rather than Sms. --- src/Appwrite/Platform/Workers/Messaging.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 76b86e4f0..876ca50d0 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -6,7 +6,7 @@ use Exception; use Utopia\App; use Utopia\CLI\Console; use Utopia\DSN\DSN; -use Utopia\Messaging\Messages\Sms; +use Utopia\Messaging\Messages\SMS; use Utopia\Messaging\Adapters\SMS\Mock; use Utopia\Messaging\Adapters\SMS\Msg91; use Utopia\Messaging\Adapters\SMS\Telesign; From 26ac88c32e3548c6b14add906e75ed512f4a8662 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 25 Dec 2023 05:54:59 +0000 Subject: [PATCH 44/58] validate create permission while updating chunk uploaded file --- app/controllers/api/storage.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 1fae48dae..d390f9ef1 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -621,8 +621,13 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('openSSLIV', $openSSLIV) ->setAttribute('metadata', $metadata) ->setAttribute('chunksUploaded', $chunksUploaded); - - $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); + + // Validate create permission + $validator = new Authorization(Database::PERMISSION_CREATE); + if (!$validator->isValid($bucket->getCreate())) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } + $file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } } catch (AuthorizationException) { throw new Exception(Exception::USER_UNAUTHORIZED); @@ -659,7 +664,12 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('chunksUploaded', $chunksUploaded) ->setAttribute('metadata', $metadata); - $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); + // Validate create permission + $validator = new Authorization(Database::PERMISSION_CREATE); + if (!$validator->isValid($bucket->getCreate())) { + throw new Exception(Exception::USER_UNAUTHORIZED); + } + $file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } } catch (AuthorizationException) { throw new Exception(Exception::USER_UNAUTHORIZED); From b6b1b396b384c4d4b6cc520586c0468753a7d14e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 25 Dec 2023 06:00:43 +0000 Subject: [PATCH 45/58] update chunk upload test --- tests/e2e/Services/Storage/StorageBase.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 546374076..be01d9536 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -74,10 +74,7 @@ trait StorageBase 'name' => 'Test Bucket 2', 'fileSecurity' => true, 'permissions' => [ - Permission::read(Role::any()), Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), ], ]); $this->assertEquals(201, $bucket2['headers']['status-code']); From a6b4ade39b72bb595bd5c7c03063bf6d475437d8 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 25 Dec 2023 06:06:18 +0000 Subject: [PATCH 46/58] update large file upload test to not include update permission --- tests/e2e/Services/Storage/StorageBase.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index be01d9536..eb6a52db6 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -107,9 +107,7 @@ trait StorageBase 'fileId' => $fileId, 'file' => $curlFile, 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), + Permission::read(Role::any()) ], ]); $counter++; From cbd3e85b389400740973a9e8948b457062adcb9e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 25 Dec 2023 06:07:28 +0000 Subject: [PATCH 47/58] fix formatting --- app/controllers/api/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index d390f9ef1..687971c2d 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -621,7 +621,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('openSSLIV', $openSSLIV) ->setAttribute('metadata', $metadata) ->setAttribute('chunksUploaded', $chunksUploaded); - + // Validate create permission $validator = new Authorization(Database::PERMISSION_CREATE); if (!$validator->isValid($bucket->getCreate())) { From e8ff828039bd2e60c003d9a828b6ea5196a76052 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 28 Dec 2023 01:31:25 +0000 Subject: [PATCH 48/58] fix for file extension not supported - the error occured with jfif extension, which is essentially a jpeg file --- app/controllers/api/storage.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 1fae48dae..860075273 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -915,6 +915,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') } if (empty($output)) { + // when file extension is provided but it's not one of our + // supported outputs we fallback to `jpg` + if(!empty($type) && !array_key_exists($type, $outputs)) { + $type = 'jpg'; + } + // when file extension is not provided and the mime type is not one of our supported outputs // we fallback to `jpg` output format $output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type; From 94a18ede78a89db8caf959d1aab723c6256e4b2f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 28 Dec 2023 01:35:10 +0000 Subject: [PATCH 49/58] fix formatting --- app/controllers/api/storage.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 860075273..5ef810fec 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -915,12 +915,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') } if (empty($output)) { - // when file extension is provided but it's not one of our + // when file extension is provided but it's not one of our // supported outputs we fallback to `jpg` - if(!empty($type) && !array_key_exists($type, $outputs)) { + if (!empty($type) && !array_key_exists($type, $outputs)) { $type = 'jpg'; } - + // when file extension is not provided and the mime type is not one of our supported outputs // we fallback to `jpg` output format $output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type; From 83a009dcba7955f9a2b317534b44b52ded571ddc Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 28 Dec 2023 02:22:05 +0000 Subject: [PATCH 50/58] update tests for preview --- tests/e2e/Services/Storage/StorageBase.php | 28 ++++++++++++++++++++- tests/resources/disk-a/preview-test.jfif | Bin 0 -> 131958 bytes 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/resources/disk-a/preview-test.jfif diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 546374076..36d2ee236 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -27,7 +27,7 @@ trait StorageBase 'name' => 'Test Bucket', 'fileSecurity' => true, 'maximumFileSize' => 2000000, //2MB - 'allowedFileExtensions' => ["jpg", "png"], + 'allowedFileExtensions' => ["jpg", "png", 'jfif'], 'permissions' => [ Permission::read(Role::any()), Permission::create(Role::any()), @@ -462,6 +462,32 @@ trait StorageBase $this->assertEquals('image/png', $file2['headers']['content-type']); $this->assertNotEmpty($file2['body']); + // upload JXL file for preview + $fileJfif = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'fileId' => ID::unique(), + 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/disk-a/preview-test.jfif'), 'image/jxl', 'preview-test.jfif'), + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $this->assertEquals(201, $fileJfif['headers']['status-code']); + $this->assertNotEmpty($fileJfif['body']['$id']); + + // TEST preview JXL + $preview = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileJfif['body']['$id'] . '/preview', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $preview['headers']['status-code']); + $this->assertEquals('image/jpeg', $preview['headers']['content-type']); + $this->assertNotEmpty($preview['body']); + //new image preview features $file3 = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $data['fileId'] . '/preview', array_merge([ 'content-type' => 'application/json', diff --git a/tests/resources/disk-a/preview-test.jfif b/tests/resources/disk-a/preview-test.jfif new file mode 100644 index 0000000000000000000000000000000000000000..e50021f95d5254cdfd68dfaa91a8b851de46740b GIT binary patch literal 131958 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

      Ru*<0cFRO#9N7PBF= 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 Date: Thu, 28 Dec 2023 11:26:45 +0000 Subject: [PATCH 51/58] improve order --- app/controllers/api/storage.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 5ef810fec..533e5c73a 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -872,14 +872,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') throw new Exception(Exception::USER_UNAUTHORIZED); } - if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support - $output = 'jpg'; - } - - $inputs = Config::getParam('storage-inputs'); - $outputs = Config::getParam('storage-outputs'); - $fileLogos = Config::getParam('storage-logos'); - if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { @@ -890,6 +882,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); } + if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support + $output = 'jpg'; + } + + $inputs = Config::getParam('storage-inputs'); + $outputs = Config::getParam('storage-outputs'); + $fileLogos = Config::getParam('storage-logos'); + $path = $file->getAttribute('path'); $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); $algorithm = $file->getAttribute('algorithm', 'none'); @@ -926,7 +926,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type; } - $source = $deviceFiles->read($path); if (!empty($cipher)) { // Decrypt From a28be2bf48f91291af3b5099d239e81052075d50 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 28 Dec 2023 11:32:04 +0000 Subject: [PATCH 52/58] return when response is sent to prevent further execution --- app/controllers/api/storage.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 533e5c73a..99aa16226 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1258,10 +1258,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') $response->send(substr($source, $start, ($end - $start + 1))); } $response->send($source); + return; } if (!empty($rangeHeader)) { $response->send($deviceFiles->read($path, $start, ($end - $start + 1))); + return; } $size = $deviceFiles->getFileSize($path); From 9cb5eb0180f94bba220c65b7b60f1e4815db7723 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 28 Dec 2023 11:42:40 +0000 Subject: [PATCH 53/58] use constants for compression type --- app/controllers/api/storage.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 99aa16226..5a03edaf4 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -892,7 +892,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $path = $file->getAttribute('path'); $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); - $algorithm = $file->getAttribute('algorithm', 'none'); + $algorithm = $file->getAttribute('algorithm', COMPRESSION_TYPE_NONE); $cipher = $file->getAttribute('openSSLCipher'); $mime = $file->getAttribute('mimeType'); if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) App::getEnv('_APP_STORAGE_PREVIEW_LIMIT', 20000000)) { @@ -903,7 +903,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $path = $fileLogos['default_image']; } - $algorithm = 'none'; + $algorithm = COMPRESSION_TYPE_NONE; $cipher = null; $background = (empty($background)) ? 'eceff1' : $background; $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); @@ -940,11 +940,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') } switch ($algorithm) { - case 'zstd': + case COMPRESSION_TYPE_ZSTD: $compressor = new Zstd(); $source = $compressor->decompress($source); break; - case 'gzip': + case COMPRESSION_TYPE_GZIP: $compressor = new GZIP(); $source = $compressor->decompress($source); break; @@ -1085,15 +1085,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ); } - switch ($file->getAttribute('algorithm', 'none')) { - case 'zstd': + switch ($file->getAttribute('algorithm', COMPRESSION_TYPE_NONE)) { + case COMPRESSION_TYPE_ZSTD: if (empty($source)) { $source = $deviceFiles->read($path); } $compressor = new Zstd(); $source = $compressor->decompress($source); break; - case 'gzip': + case COMPRESSION_TYPE_GZIP: if (empty($source)) { $source = $deviceFiles->read($path); } @@ -1236,15 +1236,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ); } - switch ($file->getAttribute('algorithm', 'none')) { - case 'zstd': + switch ($file->getAttribute('algorithm', COMPRESSION_TYPE_NONE)) { + case COMPRESSION_TYPE_ZSTD: if (empty($source)) { $source = $deviceFiles->read($path); } $compressor = new Zstd(); $source = $compressor->decompress($source); break; - case 'gzip': + case COMPRESSION_TYPE_GZIP: if (empty($source)) { $source = $deviceFiles->read($path); } From 1c236959ba776e2877b42fb3ddfa8f3ac118a1f2 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 29 Dec 2023 01:49:56 +0000 Subject: [PATCH 54/58] fix algorithm attribute on file if size is above read buffer size --- app/controllers/api/storage.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 5a03edaf4..aaa1df9d3 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -551,6 +551,11 @@ App::post('/v1/storage/buckets/:bucketId/files') break; } $data = $compressor->compress($data); + } else { + // reset the algorithm to none as we do not compress the file + // if file size exceedes the APP_STORAGE_READ_BUFFER + // regardless the bucket compression algoorithm + $algorithm = COMPRESSION_TYPE_NONE; } if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) { From c99976560d13a56401b808650e38d6d817005cf7 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 31 Dec 2023 11:37:22 +0000 Subject: [PATCH 55/58] update utopia-storage --- composer.lock | 118 +++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/composer.lock b/composer.lock index 351839666..17f0325de 100644 --- a/composer.lock +++ b/composer.lock @@ -1906,16 +1906,16 @@ }, { "name": "utopia-php/database", - "version": "0.45.2", + "version": "0.45.3", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "dc789f2c1fd8b5ee07ff883e11c9ad7970824788" + "reference": "33b4e9a4a6c29f6bb7e108e134b283d585955789" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/dc789f2c1fd8b5ee07ff883e11c9ad7970824788", - "reference": "dc789f2c1fd8b5ee07ff883e11c9ad7970824788", + "url": "https://api.github.com/repos/utopia-php/database/zipball/33b4e9a4a6c29f6bb7e108e134b283d585955789", + "reference": "33b4e9a4a6c29f6bb7e108e134b283d585955789", "shasum": "" }, "require": { @@ -1956,9 +1956,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.45.2" + "source": "https://github.com/utopia-php/database/tree/0.45.3" }, - "time": "2023-11-15T03:38:47+00:00" + "time": "2023-12-28T11:12:26+00:00" }, { "name": "utopia-php/domains", @@ -2476,16 +2476,16 @@ }, { "name": "utopia-php/platform", - "version": "0.5.0", + "version": "0.5.1", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "229a7b1fa1f39afd1532f7a515326a6afc222a26" + "reference": "3eceef0b6593fe0f7d2efd36d40402a395a4c285" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/platform/zipball/229a7b1fa1f39afd1532f7a515326a6afc222a26", - "reference": "229a7b1fa1f39afd1532f7a515326a6afc222a26", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/3eceef0b6593fe0f7d2efd36d40402a395a4c285", + "reference": "3eceef0b6593fe0f7d2efd36d40402a395a4c285", "shasum": "" }, "require": { @@ -2493,7 +2493,7 @@ "ext-redis": "*", "php": ">=8.0", "utopia-php/cli": "0.15.*", - "utopia-php/framework": "0.31.*" + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2519,9 +2519,9 @@ ], "support": { "issues": "https://github.com/utopia-php/platform/issues", - "source": "https://github.com/utopia-php/platform/tree/0.5.0" + "source": "https://github.com/utopia-php/platform/tree/0.5.1" }, - "time": "2023-10-16T20:28:49+00:00" + "time": "2023-12-26T16:14:41+00:00" }, { "name": "utopia-php/pools", @@ -2742,16 +2742,16 @@ }, { "name": "utopia-php/storage", - "version": "0.18.1", + "version": "0.18.2", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "983e6dee137012f9f57f126d3c79aab54e4e8824" + "reference": "130e7c4f305c2e1d5aa86226aaee25c9d5a41073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/983e6dee137012f9f57f126d3c79aab54e4e8824", - "reference": "983e6dee137012f9f57f126d3c79aab54e4e8824", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/130e7c4f305c2e1d5aa86226aaee25c9d5a41073", + "reference": "130e7c4f305c2e1d5aa86226aaee25c9d5a41073", "shasum": "" }, "require": { @@ -2791,9 +2791,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.18.1" + "source": "https://github.com/utopia-php/storage/tree/0.18.2" }, - "time": "2023-10-24T14:44:19+00:00" + "time": "2023-12-31T11:33:28+00:00" }, { "name": "utopia-php/swoole", @@ -2904,23 +2904,23 @@ }, { "name": "utopia-php/vcs", - "version": "0.6.2", + "version": "0.6.4", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "f135291b87cb45335fc6608722e7f89894bc33ee" + "reference": "b2595a50a4897a8c88319240810055b7a96efd6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/f135291b87cb45335fc6608722e7f89894bc33ee", - "reference": "f135291b87cb45335fc6608722e7f89894bc33ee", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/b2595a50a4897a8c88319240810055b7a96efd6d", + "reference": "b2595a50a4897a8c88319240810055b7a96efd6d", "shasum": "" }, "require": { "adhocore/jwt": "^1.1", "php": ">=8.0", "utopia-php/cache": "^0.8.0", - "utopia-php/framework": "0.31.*" + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2947,9 +2947,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.6.2" + "source": "https://github.com/utopia-php/vcs/tree/0.6.4" }, - "time": "2023-11-08T15:36:03+00:00" + "time": "2023-12-26T15:38:19+00:00" }, { "name": "utopia-php/websocket", @@ -3487,16 +3487,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -3537,9 +3537,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "phar-io/manifest", @@ -3891,16 +3891,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.4", + "version": "1.24.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496" + "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc", + "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc", "shasum": "" }, "require": { @@ -3932,29 +3932,29 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.5" }, - "time": "2023-11-26T18:29:22+00:00" + "time": "2023-12-16T09:33:33+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -4004,7 +4004,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" }, "funding": [ { @@ -4012,7 +4012,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4651,20 +4651,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -4696,7 +4696,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -4704,7 +4704,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -4978,20 +4978,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -5023,7 +5023,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -5031,7 +5031,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", From d070989f73c50b3515d1e5317673c22e69c7c570 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 31 Dec 2023 11:46:27 +0000 Subject: [PATCH 56/58] update utopia storage --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 17f0325de..acd65d61a 100644 --- a/composer.lock +++ b/composer.lock @@ -2742,16 +2742,16 @@ }, { "name": "utopia-php/storage", - "version": "0.18.2", + "version": "0.18.3", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "130e7c4f305c2e1d5aa86226aaee25c9d5a41073" + "reference": "faa0279519ac14f3501e8b138e0865ad9d12bff6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/130e7c4f305c2e1d5aa86226aaee25c9d5a41073", - "reference": "130e7c4f305c2e1d5aa86226aaee25c9d5a41073", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/faa0279519ac14f3501e8b138e0865ad9d12bff6", + "reference": "faa0279519ac14f3501e8b138e0865ad9d12bff6", "shasum": "" }, "require": { @@ -2791,9 +2791,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.18.2" + "source": "https://github.com/utopia-php/storage/tree/0.18.3" }, - "time": "2023-12-31T11:33:28+00:00" + "time": "2023-12-31T11:45:12+00:00" }, { "name": "utopia-php/swoole", From a2ad8700845b1b1b9c3e0fa4d0949798054a98a4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 31 Dec 2023 11:46:45 +0000 Subject: [PATCH 57/58] use compression constant from utopia/storage --- app/controllers/api/storage.php | 35 ++++++++++--------- app/init.php | 4 --- src/Appwrite/Utopia/Response/Model/Bucket.php | 3 +- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index aaa1df9d3..91efb21f3 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -44,6 +44,7 @@ use Utopia\Validator\Text; use Utopia\Validator\WhiteList; use Utopia\DSN\DSN; use Utopia\Swoole\Request; +use Utopia\Storage\Compression\Compression; App::post('/v1/storage/buckets') ->desc('Create bucket') @@ -67,7 +68,7 @@ App::post('/v1/storage/buckets') ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) ->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) - ->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) + ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) ->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true) ->inject('response') @@ -241,7 +242,7 @@ App::put('/v1/storage/buckets/:bucketId') ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) ->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) - ->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) + ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) ->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true) ->inject('response') @@ -538,14 +539,14 @@ App::post('/v1/storage/buckets/:bucketId/files') $fileHash = $deviceFiles->getFileHash($path); // Get file hash before compression and encryption $data = ''; // Compression - $algorithm = $bucket->getAttribute('compression', COMPRESSION_TYPE_NONE); - if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != COMPRESSION_TYPE_NONE) { + $algorithm = $bucket->getAttribute('compression', Compression::NONE); + if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != Compression::NONE) { $data = $deviceFiles->read($path); switch ($algorithm) { - case COMPRESSION_TYPE_ZSTD: + case Compression::ZSTD: $compressor = new Zstd(); break; - case COMPRESSION_TYPE_GZIP: + case Compression::GZIP: default: $compressor = new GZIP(); break; @@ -555,7 +556,7 @@ App::post('/v1/storage/buckets/:bucketId/files') // reset the algorithm to none as we do not compress the file // if file size exceedes the APP_STORAGE_READ_BUFFER // regardless the bucket compression algoorithm - $algorithm = COMPRESSION_TYPE_NONE; + $algorithm = Compression::NONE; } if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) { @@ -897,7 +898,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $path = $file->getAttribute('path'); $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); - $algorithm = $file->getAttribute('algorithm', COMPRESSION_TYPE_NONE); + $algorithm = $file->getAttribute('algorithm', Compression::NONE); $cipher = $file->getAttribute('openSSLCipher'); $mime = $file->getAttribute('mimeType'); if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) App::getEnv('_APP_STORAGE_PREVIEW_LIMIT', 20000000)) { @@ -908,7 +909,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $path = $fileLogos['default_image']; } - $algorithm = COMPRESSION_TYPE_NONE; + $algorithm = Compression::NONE; $cipher = null; $background = (empty($background)) ? 'eceff1' : $background; $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); @@ -945,11 +946,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') } switch ($algorithm) { - case COMPRESSION_TYPE_ZSTD: + case Compression::ZSTD: $compressor = new Zstd(); $source = $compressor->decompress($source); break; - case COMPRESSION_TYPE_GZIP: + case Compression::GZIP: $compressor = new GZIP(); $source = $compressor->decompress($source); break; @@ -1090,15 +1091,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ); } - switch ($file->getAttribute('algorithm', COMPRESSION_TYPE_NONE)) { - case COMPRESSION_TYPE_ZSTD: + switch ($file->getAttribute('algorithm', Compression::NONE)) { + case Compression::ZSTD: if (empty($source)) { $source = $deviceFiles->read($path); } $compressor = new Zstd(); $source = $compressor->decompress($source); break; - case COMPRESSION_TYPE_GZIP: + case Compression::GZIP: if (empty($source)) { $source = $deviceFiles->read($path); } @@ -1241,15 +1242,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ); } - switch ($file->getAttribute('algorithm', COMPRESSION_TYPE_NONE)) { - case COMPRESSION_TYPE_ZSTD: + switch ($file->getAttribute('algorithm', Compression::NONE)) { + case Compression::ZSTD: if (empty($source)) { $source = $deviceFiles->read($path); } $compressor = new Zstd(); $source = $compressor->decompress($source); break; - case COMPRESSION_TYPE_GZIP: + case Compression::GZIP: if (empty($source)) { $source = $deviceFiles->read($path); } diff --git a/app/init.php b/app/init.php index 72bf75d41..924122ac2 100644 --- a/app/init.php +++ b/app/init.php @@ -173,10 +173,6 @@ const DELETE_TYPE_SESSIONS = 'sessions'; const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp'; const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource'; const DELETE_TYPE_SCHEDULES = 'schedules'; -// Compression type -const COMPRESSION_TYPE_NONE = 'none'; -const COMPRESSION_TYPE_GZIP = 'gzip'; -const COMPRESSION_TYPE_ZSTD = 'zstd'; // Mail Types const MAIL_TYPE_VERIFICATION = 'verification'; const MAIL_TYPE_MAGIC_SESSION = 'magicSession'; diff --git a/src/Appwrite/Utopia/Response/Model/Bucket.php b/src/Appwrite/Utopia/Response/Model/Bucket.php index 3c5715efc..f5261c026 100644 --- a/src/Appwrite/Utopia/Response/Model/Bucket.php +++ b/src/Appwrite/Utopia/Response/Model/Bucket.php @@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; +use Utopia\Storage\Compression\Compression; class Bucket extends Model { @@ -68,7 +69,7 @@ class Bucket extends Model ]) ->addRule('compression', [ 'type' => self::TYPE_STRING, - 'description' => 'Compression algorithm choosen for compression. Will be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd).', + 'description' => 'Compression algorithm choosen for compression. Will be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd).', 'default' => '', 'example' => 'gzip', 'array' => false From 879320e23ec198cd9049856301fbfb5342b9af40 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 2 Jan 2024 11:53:48 +0545 Subject: [PATCH 58/58] update comment regarding validation --- app/controllers/api/storage.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index e668ec255..011f83f4b 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -628,7 +628,12 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('metadata', $metadata) ->setAttribute('chunksUploaded', $chunksUploaded); - // Validate create permission + /** + * Validate create permission and skip authorization in updateDocument + * Without this, the file creation will fail when user doesn't have update permission + * However as with chunk upload even if we are updating, we are essentially creating a file + * adding it's new chunk so we validate create permission instead of update + */ $validator = new Authorization(Database::PERMISSION_CREATE); if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED); @@ -670,7 +675,12 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('chunksUploaded', $chunksUploaded) ->setAttribute('metadata', $metadata); - // Validate create permission + /** + * Validate create permission and skip authorization in updateDocument + * Without this, the file creation will fail when user doesn't have update permission + * However as with chunk upload even if we are updating, we are essentially creating a file + * adding it's new chunk so we validate create permission instead of update + */ $validator = new Authorization(Database::PERMISSION_CREATE); if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED);