From 1da476e5acf1e52342d2371914abb923ac73f389 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 9 Jan 2024 11:13:58 +0200 Subject: [PATCH 01/19] remove cloud related scripts --- Dockerfile | 14 +- bin/calc-tier-stats | 3 - bin/calc-users-stats | 3 - bin/clear-card-cache | 3 - bin/delete-orphaned-projects | 3 - bin/get-migration-stats | 3 - bin/hamster | 3 - bin/patch-delete-project-collections | 3 - ...patch-delete-schedule-updated-at-attribute | 3 - bin/patch-recreate-repositories-documents | 3 - bin/volume-sync | 3 - bin/worker-hamster | 3 - docker-compose.yml | 57 --- src/Appwrite/Event/Event.php | 3 - src/Appwrite/Event/Hamster.php | 157 ------- src/Appwrite/Platform/Services/Tasks.php | 11 - src/Appwrite/Platform/Tasks/CalcTierStats.php | 359 -------------- .../Platform/Tasks/DeleteOrphanedProjects.php | 161 ------- .../Platform/Tasks/GetMigrationStats.php | 187 -------- src/Appwrite/Platform/Tasks/Hamster.php | 158 ------- .../PatchRecreateRepositoriesDocuments.php | 169 ------- src/Appwrite/Platform/Tasks/VolumeSync.php | 59 --- src/Appwrite/Platform/Workers/Hamster.php | 437 ------------------ 23 files changed, 1 insertion(+), 1804 deletions(-) delete mode 100644 bin/calc-tier-stats delete mode 100644 bin/calc-users-stats delete mode 100644 bin/clear-card-cache delete mode 100644 bin/delete-orphaned-projects delete mode 100644 bin/get-migration-stats delete mode 100644 bin/hamster delete mode 100644 bin/patch-delete-project-collections delete mode 100644 bin/patch-delete-schedule-updated-at-attribute delete mode 100644 bin/patch-recreate-repositories-documents delete mode 100644 bin/volume-sync delete mode 100644 bin/worker-hamster delete mode 100644 src/Appwrite/Event/Hamster.php delete mode 100644 src/Appwrite/Platform/Tasks/CalcTierStats.php delete mode 100644 src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php delete mode 100644 src/Appwrite/Platform/Tasks/GetMigrationStats.php delete mode 100644 src/Appwrite/Platform/Tasks/Hamster.php delete mode 100644 src/Appwrite/Platform/Tasks/PatchRecreateRepositoriesDocuments.php delete mode 100644 src/Appwrite/Platform/Tasks/VolumeSync.php delete mode 100644 src/Appwrite/Platform/Workers/Hamster.php diff --git a/Dockerfile b/Dockerfile index 2f85f2cc43..4fba47cdbf 100755 --- a/Dockerfile +++ b/Dockerfile @@ -94,20 +94,8 @@ RUN chmod +x /usr/local/bin/doctor && \ chmod +x /usr/local/bin/worker-mails && \ chmod +x /usr/local/bin/worker-messaging && \ chmod +x /usr/local/bin/worker-webhooks && \ - chmod +x /usr/local/bin/worker-migrations && \ - chmod +x /usr/local/bin/worker-hamster + chmod +x /usr/local/bin/worker-migrations -# Cloud Executabless -RUN chmod +x /usr/local/bin/hamster && \ - chmod +x /usr/local/bin/volume-sync && \ - chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \ - chmod +x /usr/local/bin/patch-recreate-repositories-documents && \ - chmod +x /usr/local/bin/patch-delete-project-collections && \ - 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/get-migration-stats # Letsencrypt Permissions RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/ diff --git a/bin/calc-tier-stats b/bin/calc-tier-stats deleted file mode 100644 index c7fb71e6fd..0000000000 --- a/bin/calc-tier-stats +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php calc-tier-stats $@ \ No newline at end of file diff --git a/bin/calc-users-stats b/bin/calc-users-stats deleted file mode 100644 index 60472ed7a2..0000000000 --- a/bin/calc-users-stats +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php calc-users-stats $@ \ No newline at end of file diff --git a/bin/clear-card-cache b/bin/clear-card-cache deleted file mode 100644 index b39bc13651..0000000000 --- a/bin/clear-card-cache +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php clear-card-cache $@ \ No newline at end of file diff --git a/bin/delete-orphaned-projects b/bin/delete-orphaned-projects deleted file mode 100644 index 16b9e3184d..0000000000 --- a/bin/delete-orphaned-projects +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php delete-orphaned-projects $@ \ No newline at end of file diff --git a/bin/get-migration-stats b/bin/get-migration-stats deleted file mode 100644 index efc1934e15..0000000000 --- a/bin/get-migration-stats +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php get-migration-stats $@ \ No newline at end of file diff --git a/bin/hamster b/bin/hamster deleted file mode 100644 index dcc7ed308d..0000000000 --- a/bin/hamster +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php hamster $@ \ No newline at end of file diff --git a/bin/patch-delete-project-collections b/bin/patch-delete-project-collections deleted file mode 100644 index 8bf0736cf3..0000000000 --- a/bin/patch-delete-project-collections +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php patch-delete-project-collections $@ \ No newline at end of file diff --git a/bin/patch-delete-schedule-updated-at-attribute b/bin/patch-delete-schedule-updated-at-attribute deleted file mode 100644 index 3e28289cbe..0000000000 --- a/bin/patch-delete-schedule-updated-at-attribute +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php patch-delete-schedule-updated-at-attribute $@ \ No newline at end of file diff --git a/bin/patch-recreate-repositories-documents b/bin/patch-recreate-repositories-documents deleted file mode 100644 index 8c6c4157f4..0000000000 --- a/bin/patch-recreate-repositories-documents +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php patch-recreate-repositories-documents $@ \ No newline at end of file diff --git a/bin/volume-sync b/bin/volume-sync deleted file mode 100644 index 5190750a24..0000000000 --- a/bin/volume-sync +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php volume-sync $@ \ No newline at end of file diff --git a/bin/worker-hamster b/bin/worker-hamster deleted file mode 100644 index b388dd13c9..0000000000 --- a/bin/worker-hamster +++ /dev/null @@ -1,3 +0,0 @@ -#!/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 97cf7e5136..42091e5e46 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -717,63 +717,6 @@ 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_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: - - ./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_HAMSTER_TIME - - _APP_HAMSTER_INTERVAL - openruntimes-executor: container_name: openruntimes-executor hostname: appwrite-executor diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index fc12c5b5b3..46b430d122 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -42,9 +42,6 @@ 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 deleted file mode 100644 index 5d79fce568..0000000000 --- a/src/Appwrite/Event/Hamster.php +++ /dev/null @@ -1,157 +0,0 @@ -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/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php index dc6ddc1a5b..0da42e3545 100644 --- a/src/Appwrite/Platform/Services/Tasks.php +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -15,12 +15,7 @@ use Appwrite\Platform\Tasks\Hamster; use Appwrite\Platform\Tasks\Usage; use Appwrite\Platform\Tasks\Vars; use Appwrite\Platform\Tasks\Version; -use Appwrite\Platform\Tasks\VolumeSync; -use Appwrite\Platform\Tasks\CalcTierStats; use Appwrite\Platform\Tasks\Upgrade; -use Appwrite\Platform\Tasks\DeleteOrphanedProjects; -use Appwrite\Platform\Tasks\GetMigrationStats; -use Appwrite\Platform\Tasks\PatchRecreateRepositoriesDocuments; class Tasks extends Service { @@ -40,13 +35,7 @@ class Tasks extends Service ->addAction(Schedule::getName(), new Schedule()) ->addAction(Migrate::getName(), new Migrate()) ->addAction(SDKs::getName(), new SDKs()) - ->addAction(VolumeSync::getName(), new VolumeSync()) ->addAction(Specs::getName(), new Specs()) - ->addAction(CalcTierStats::getName(), new CalcTierStats()) - ->addAction(DeleteOrphanedProjects::getName(), new DeleteOrphanedProjects()) - ->addAction(PatchRecreateRepositoriesDocuments::getName(), new PatchRecreateRepositoriesDocuments()) - ->addAction(GetMigrationStats::getName(), new GetMigrationStats()) - ; } } diff --git a/src/Appwrite/Platform/Tasks/CalcTierStats.php b/src/Appwrite/Platform/Tasks/CalcTierStats.php deleted file mode 100644 index e6559a05d7..0000000000 --- a/src/Appwrite/Platform/Tasks/CalcTierStats.php +++ /dev/null @@ -1,359 +0,0 @@ - 'Requests', - 'project.$all.network.bandwidth' => 'Bandwidth', - - ]; - - public static function getName(): string - { - return 'calc-tier-stats'; - } - - public function __construct() - { - - $this - ->desc('Get stats for projects') - ->inject('pools') - ->inject('cache') - ->inject('dbForConsole') - ->inject('register') - ->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) { - $this->action($pools, $cache, $dbForConsole, $register); - }); - } - - /** - * @throws \Utopia\Exception - * @throws CannotInsertRecord - */ - public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void - { - //docker compose exec -t appwrite calc-tier-stats - - Console::title('Cloud free tier stats calculation V1'); - Console::success(APP_NAME . ' cloud free tier stats calculation has started'); - - /* Initialise new Utopia app */ - $app = new App('UTC'); - $console = $app->getResource('console'); - - /** CSV stuff */ - $this->date = date('Y-m-d'); - $this->path = "{$this->directory}/tier_stats_{$this->date}.csv"; - $csv = Writer::createFromPath($this->path, 'w'); - $csv->insertOne($this->columns); - - /** Database connections */ - $totalProjects = $dbForConsole->count('projects'); - Console::success("Found a total of: {$totalProjects} projects"); - - $projects = [$console]; - $count = 0; - $limit = 100; - $sum = 100; - $offset = 0; - while (!empty($projects)) { - foreach ($projects as $project) { - - /** - * Skip user projects with id 'console' - */ - if ($project->getId() === 'console') { - continue; - } - - Console::info("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()); - - /** Get Project ID */ - $stats['Project ID'] = $project->getId(); - - $stats['Organization ID'] = $project->getAttribute('teamId', null); - - /** Get Total Members */ - $teamInternalId = $project->getAttribute('teamInternalId', null); - if ($teamInternalId) { - $stats['Organization Members'] = $dbForConsole->count('memberships', [ - Query::equal('teamInternalId', [$teamInternalId]) - ]); - } else { - $stats['Organization Members'] = 0; - } - - /** Get Total internal Teams */ - try { - $stats['Teams'] = $dbForProject->count('teams', []); - } catch (\Throwable) { - $stats['Teams'] = 0; - } - - /** Get Total users */ - try { - $stats['Users'] = $dbForProject->count('users', []); - } catch (\Throwable) { - $stats['Users'] = 0; - } - - /** Get Usage stats */ - $range = '30d'; - $periods = [ - '30d' => [ - 'period' => '1d', - 'limit' => 30, - ] - ]; - - $tmp = []; - $metrics = $this->usageStats; - Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$tmp) { - foreach ($metrics as $metric => $name) { - $limit = $periods[$range]['limit']; - $period = $periods[$range]['period']; - - $requestDocs = $dbForProject->find('stats', [ - Query::equal('period', [$period]), - Query::equal('metric', [$metric]), - Query::limit($limit), - Query::orderDesc('time'), - ]); - - $tmp[$metric] = []; - foreach ($requestDocs as $requestDoc) { - if (empty($requestDoc)) { - continue; - } - - $tmp[$metric][] = [ - 'value' => $requestDoc->getAttribute('value'), - 'date' => $requestDoc->getAttribute('time'), - ]; - } - - $tmp[$metric] = array_reverse($tmp[$metric]); - $tmp[$metric] = array_sum(array_column($tmp[$metric], 'value')); - } - }); - - foreach ($tmp as $key => $value) { - $stats[$metrics[$key]] = $value; - } - - try { - /** Get Domains */ - $stats['Domains'] = $dbForConsole->count('rules', [ - Query::equal('projectInternalId', [$project->getInternalId()]), - ]); - } catch (\Throwable) { - $stats['Domains'] = 0; - } - - try { - /** Get Api keys */ - $stats['Api keys'] = $dbForConsole->count('keys', [ - Query::equal('projectInternalId', [$project->getInternalId()]), - ]); - } catch (\Throwable) { - $stats['Api keys'] = 0; - } - - try { - /** Get Webhooks */ - $stats['Webhooks'] = $dbForConsole->count('webhooks', [ - Query::equal('projectInternalId', [$project->getInternalId()]), - ]); - } catch (\Throwable) { - $stats['Webhooks'] = 0; - } - - try { - /** Get Platforms */ - $stats['Platforms'] = $dbForConsole->count('platforms', [ - Query::equal('projectInternalId', [$project->getInternalId()]), - ]); - } catch (\Throwable) { - $stats['Platforms'] = 0; - } - - /** Get Files & Buckets */ - $filesCount = 0; - $filesSum = 0; - $maxFileSize = 0; - $counter = 0; - try { - $buckets = $dbForProject->find('buckets', []); - foreach ($buckets as $bucket) { - $file = $dbForProject->findOne('bucket_' . $bucket->getInternalId(), [Query::orderDesc('sizeOriginal'),]); - if (empty($file)) { - continue; - } - $filesSum += $dbForProject->sum('bucket_' . $bucket->getInternalId(), 'sizeOriginal', []); - $filesCount += $dbForProject->count('bucket_' . $bucket->getInternalId(), []); - if ($file->getAttribute('sizeOriginal') > $maxFileSize) { - $maxFileSize = $file->getAttribute('sizeOriginal'); - } - $counter++; - } - } catch (\Throwable) { - ; - } - $stats['Buckets'] = $counter; - $stats['Files'] = $filesCount; - $stats['Storage (bytes)'] = $filesSum; - $stats['Max File Size (bytes)'] = $maxFileSize; - - - try { - /** Get Total Functions */ - $stats['Databases'] = $dbForProject->count('databases', []); - } catch (\Throwable) { - $stats['Databases'] = 0; - } - - /** Get Total Functions */ - try { - $stats['Functions'] = $dbForProject->count('functions', []); - } catch (\Throwable) { - $stats['Functions'] = 0; - } - - /** Get Total Deployments */ - try { - $stats['Deployments'] = $dbForProject->count('deployments', []); - } catch (\Throwable) { - $stats['Deployments'] = 0; - } - - /** Get Total Executions */ - try { - $stats['Executions'] = $dbForProject->count('executions', []); - } catch (\Throwable) { - $stats['Executions'] = 0; - } - - /** Get Total Migrations */ - try { - $stats['Migrations'] = $dbForProject->count('migrations', []); - } catch (\Throwable) { - $stats['Migrations'] = 0; - } - - $csv->insertOne(array_values($stats)); - } catch (\Throwable $th) { - Console::error('Failed on project ("' . $project->getId() . '") version with error on File: ' . $th->getFile() . ' line no: ' . $th->getline() . ' with message: ' . $th->getMessage()); - } finally { - $pools - ->get($db) - ->reclaim(); - } - } - - $sum = \count($projects); - - $projects = $dbForConsole->find('projects', [ - Query::limit($limit), - Query::offset($offset), - ]); - - $offset = $offset + $limit; - $count = $count + $sum; - } - - Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...'); - - $pools - ->get('console') - ->reclaim(); - - /** @var PHPMailer $mail */ - $mail = $register->get('smtp'); - - $mail->clearAddresses(); - $mail->clearAllRecipients(); - $mail->clearReplyTos(); - $mail->clearAttachments(); - $mail->clearBCCs(); - $mail->clearCCs(); - - try { - /** Addresses */ - $mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster'); - $recipients = explode(',', App::getEnv('_APP_USERS_STATS_RECIPIENTS', '')); - - foreach ($recipients as $recipient) { - $mail->addAddress($recipient); - } - - /** Attachments */ - $mail->addAttachment($this->path); - - /** Content */ - $mail->Subject = "Cloud Report for {$this->date}"; - $mail->Body = "Please find the daily cloud report atttached"; - $mail->send(); - Console::success('Email has been sent!'); - } catch (Exception $e) { - Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}"); - } - } -} diff --git a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php b/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php deleted file mode 100644 index 757b29c1b6..0000000000 --- a/src/Appwrite/Platform/Tasks/DeleteOrphanedProjects.php +++ /dev/null @@ -1,161 +0,0 @@ -desc('Delete orphaned projects') - ->param('commit', false, new Boolean(true), 'Commit project deletion', true) - ->inject('pools') - ->inject('cache') - ->inject('dbForConsole') - ->inject('register') - ->callback(function (bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register) { - $this->action($commit, $pools, $cache, $dbForConsole, $register); - }); - } - - - public function action(bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void - { - - Console::title('Delete orphaned projects V1'); - Console::success(APP_NAME . ' Delete orphaned projects started'); - - /** @var array $collections */ - $collectionsConfig = Config::getParam('collections', [])['projects'] ?? []; - - $collectionsConfig = array_merge([ - 'audit' => [ - '$id' => ID::custom('audit'), - '$collection' => Database::METADATA - ], - 'abuse' => [ - '$id' => ID::custom('abuse'), - '$collection' => Database::METADATA - ] - ], $collectionsConfig); - - /* Initialise new Utopia app */ - $app = new App('UTC'); - $console = $app->getResource('console'); - $projects = [$console]; - - /** Database connections */ - $totalProjects = $dbForConsole->count('projects'); - Console::success("Found a total of: {$totalProjects} projects"); - - $orphans = 1; - $cnt = 0; - $count = 0; - $limit = 30; - $sum = 30; - $offset = 0; - while (!empty($projects)) { - foreach ($projects as $project) { - - /** - * Skip user projects with id 'console' - */ - if ($project->getId() === 'console') { - continue; - } - - try { - $db = $project->getAttribute('database'); - $adapter = $pools - ->get($db) - ->pop() - ->getResource(); - - $dbForProject = new Database($adapter, $cache); - $dbForProject->setDefaultDatabase('appwrite'); - $dbForProject->setNamespace('_' . $project->getInternalId()); - - $collectionsCreated = 0; - $cnt++; - if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) { - $collectionsCreated = $dbForProject->count(Database::METADATA); - } - - $msg = '(' . $cnt . ') found (' . $collectionsCreated . ') collections on project (' . $project->getInternalId() . ') , database (' . $project['database'] . ')'; - - if ($collectionsCreated >= count($collectionsConfig)) { - Console::log($msg . ' ignoring....'); - continue; - } - - Console::log($msg); - - if ($collectionsCreated > 0) { - $collections = $dbForProject->find(Database::METADATA, []); - foreach ($collections as $collection) { - if ($commit) { - $dbForProject->deleteCollection($collection->getId()); - $dbForConsole->deleteCachedCollection($collection->getId()); - } - Console::info('--Deleting collection (' . $collection->getId() . ') project no (' . $project->getInternalId() . ')'); - } - } - - if ($commit) { - $dbForConsole->deleteDocument('projects', $project->getId()); - $dbForConsole->deleteCachedDocument('projects', $project->getId()); - - if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) { - try { - $dbForProject->deleteCollection(Database::METADATA); - $dbForProject->deleteCachedCollection(Database::METADATA); - } catch (\Throwable $th) { - Console::warning('Metadata collection does not exist'); - } - } - } - - Console::info('--Deleting project no (' . $project->getInternalId() . ')'); - - $orphans++; - } catch (\Throwable $th) { - Console::error('Error: ' . $th->getMessage() . ' ' . $th->getTraceAsString()); - } finally { - $pools - ->get($db) - ->reclaim(); - } - } - - $sum = \count($projects); - - $projects = $dbForConsole->find('projects', [ - Query::limit($limit), - Query::offset($offset), - ]); - - $offset = $offset + $limit; - $count = $count + $sum; - } - - Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans - 1 . ' orphans'); - } -} diff --git a/src/Appwrite/Platform/Tasks/GetMigrationStats.php b/src/Appwrite/Platform/Tasks/GetMigrationStats.php deleted file mode 100644 index b76e0428d7..0000000000 --- a/src/Appwrite/Platform/Tasks/GetMigrationStats.php +++ /dev/null @@ -1,187 +0,0 @@ -desc('Get stats for projects') - ->inject('pools') - ->inject('cache') - ->inject('dbForConsole') - ->inject('register') - ->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) { - $this->action($pools, $cache, $dbForConsole, $register); - }); - } - - /** - * @throws \Utopia\Exception - * @throws CannotInsertRecord - */ - public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void - { - //docker compose exec -t appwrite get-migration-stats - - Console::title('Migration stats calculation V1'); - Console::success(APP_NAME . ' Migration stats calculation has started'); - - /* Initialise new Utopia app */ - $app = new App('UTC'); - $console = $app->getResource('console'); - - /** CSV stuff */ - $this->date = date('Y-m-d'); - $this->path = "{$this->directory}/migration_stats_{$this->date}.csv"; - $csv = Writer::createFromPath($this->path, 'w'); - $csv->insertOne($this->columns); - - /** Database connections */ - $totalProjects = $dbForConsole->count('projects'); - Console::success("Found a total of: {$totalProjects} projects"); - - $projects = [$console]; - $count = 0; - $limit = 100; - $sum = 100; - $offset = 0; - while (!empty($projects)) { - foreach ($projects as $project) { - - /** - * Skip user projects with id 'console' - */ - if ($project->getId() === 'console') { - continue; - } - - Console::info("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()); - - /** Get Project ID */ - $stats['Project ID'] = $project->getId(); - - /** Get Migration details */ - $migrations = $dbForProject->find('migrations', [ - Query::limit(500) - ]); - - $migrations = array_map(function ($migration) use ($project) { - return [ - $project->getId(), - $migration->getAttribute('$id'), - $migration->getAttribute('$createdAt'), - $migration->getAttribute('status'), - $migration->getAttribute('stage'), - $migration->getAttribute('source'), - ]; - }, $migrations); - - if (!empty($migrations)) { - $csv->insertAll($migrations); - } - } catch (\Throwable $th) { - Console::error('Failed on project ("' . $project->getId() . '") with error on File: ' . $th->getFile() . ' line no: ' . $th->getline() . ' with message: ' . $th->getMessage()); - } finally { - $pools - ->get($db) - ->reclaim(); - } - } - - $sum = \count($projects); - - $projects = $dbForConsole->find('projects', [ - Query::limit($limit), - Query::offset($offset), - ]); - - $offset = $offset + $limit; - $count = $count + $sum; - } - - Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...'); - - $pools - ->get('console') - ->reclaim(); - - /** @var PHPMailer $mail */ - $mail = $register->get('smtp'); - - $mail->clearAddresses(); - $mail->clearAllRecipients(); - $mail->clearReplyTos(); - $mail->clearAttachments(); - $mail->clearBCCs(); - $mail->clearCCs(); - - try { - /** Addresses */ - $mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster'); - $recipients = explode(',', App::getEnv('_APP_USERS_STATS_RECIPIENTS', '')); - - foreach ($recipients as $recipient) { - $mail->addAddress($recipient); - } - - /** Attachments */ - $mail->addAttachment($this->path); - - /** Content */ - $mail->Subject = "Migration Report for {$this->date}"; - $mail->Body = "Please find the migration report atttached"; - $mail->send(); - Console::success('Email has been sent!'); - } catch (Exception $e) { - Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}"); - } - } -} diff --git a/src/Appwrite/Platform/Tasks/Hamster.php b/src/Appwrite/Platform/Tasks/Hamster.php deleted file mode 100644 index 1dca095e93..0000000000 --- a/src/Appwrite/Platform/Tasks/Hamster.php +++ /dev/null @@ -1,158 +0,0 @@ -desc('Get stats for projects') - ->inject('queueForHamster') - ->inject('dbForConsole') - ->callback(function (EventHamster $queueForHamster, Database $dbForConsole) { - $this->action($queueForHamster, $dbForConsole); - }); - } - - public function action(EventHamster $queueForHamster, 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(); - /** - * If time passed for the target day. - */ - if ($delay <= 0) { - $next->add(\DateInterval::createFromDateString('1 days')); - $delay = $next->getTimestamp() - $now->getTimestamp(); - } - - 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 ($queueForHamster, $dbForConsole, $sleep) { - $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Queuing Cloud Usage Stats every {$sleep} seconds"); - $loopStart = microtime(true); - - Console::info('Queuing stats for all projects'); - $this->getStatsPerProject($queueForHamster, $dbForConsole, $loopStart); - Console::success('Completed queuing stats for all projects'); - - Console::info('Queuing stats for all organizations'); - $this->getStatsPerOrganization($queueForHamster, $dbForConsole, $loopStart); - Console::success('Completed queuing stats for all organizations'); - - Console::info('Queuing stats for all users'); - $this->getStatsPerUser($queueForHamster, $dbForConsole, $loopStart); - Console::success('Completed queuing stats for all users'); - - $loopTook = microtime(true) - $loopStart; - $now = date('d-m-Y H:i:s', time()); - Console::info("[{$now}] Cloud Stats took {$loopTook} seconds"); - }, $sleep, $delay); - } - - protected function calculateByGroup(string $collection, Database $database, callable $callback) - { - $count = 0; - $chunk = 0; - $limit = 50; - $results = []; - $sum = $limit; - - $executionStart = \microtime(true); - - while ($sum === $limit) { - $chunk++; - - $results = $database->find($collection, \array_merge([ - Query::limit($limit), - Query::offset($count) - ])); - - $sum = count($results); - - Console::log('Processing chunk #' . $chunk . '. Found ' . $sum . ' documents'); - - foreach ($results as $document) { - call_user_func($callback, $database, $document); - $count++; - } - } - - $executionEnd = \microtime(true); - - Console::log("Processed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); - } - - protected function getStatsPerOrganization(EventHamster $hamster, Database $dbForConsole, float $loopStart) - { - $this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $organization) use ($hamster, $loopStart) { - try { - $organization->setAttribute('$time', $loopStart); - $hamster - ->setType(EventHamster::TYPE_ORGANISATION) - ->setOrganization($organization) - ->trigger(); - } catch (Exception $e) { - Console::error($e->getMessage()); - } - }); - } - - private function getStatsPerProject(EventHamster $hamster, Database $dbForConsole, float $loopStart) - { - $this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($hamster, $loopStart) { - try { - $project->setAttribute('$time', $loopStart); - $hamster - ->setType(EventHamster::TYPE_PROJECT) - ->setProject($project) - ->trigger(); - } catch (Exception $e) { - Console::error($e->getMessage()); - } - }); - } - - protected function getStatsPerUser(EventHamster $hamster, Database $dbForConsole, float $loopStart) - { - $this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $user) use ($hamster, $loopStart) { - try { - $user->setAttribute('$time', $loopStart); - $hamster - ->setType(EventHamster::TYPE_USER) - ->setUser($user) - ->trigger(); - } catch (Exception $e) { - Console::error($e->getMessage()); - } - }); - } -} diff --git a/src/Appwrite/Platform/Tasks/PatchRecreateRepositoriesDocuments.php b/src/Appwrite/Platform/Tasks/PatchRecreateRepositoriesDocuments.php deleted file mode 100644 index 93e6c527bb..0000000000 --- a/src/Appwrite/Platform/Tasks/PatchRecreateRepositoriesDocuments.php +++ /dev/null @@ -1,169 +0,0 @@ -desc('Recreate missing repositories in consoleDB from projectDBs. They can be missing if you used Appwrite 1.4.10 or 1.4.11, and deleted a function.') - ->param('after', '', new Text(36), 'After cursor', true) - ->param('projectId', '', new Text(36), 'Select project to validate', true) - ->inject('dbForConsole') - ->inject('getProjectDB') - ->callback(fn ($after, $projectId, $dbForConsole, $getProjectDB) => $this->action($after, $projectId, $dbForConsole, $getProjectDB)); - } - - public function action($after, $projectId, Database $dbForConsole, callable $getProjectDB): void - { - Console::info("Starting the patch"); - - $startTime = microtime(true); - - if (!empty($projectId)) { - try { - $project = $dbForConsole->getDocument('projects', $projectId); - $dbForProject = call_user_func($getProjectDB, $project); - $this->recreateRepositories($dbForConsole, $dbForProject, $project); - } catch (\Throwable $th) { - Console::error("Unexpected error occured with Project ID {$projectId}"); - Console::error('[Error] Type: ' . get_class($th)); - Console::error('[Error] Message: ' . $th->getMessage()); - Console::error('[Error] File: ' . $th->getFile()); - Console::error('[Error] Line: ' . $th->getLine()); - } - } else { - $queries = []; - if (!empty($after)) { - Console::info("Iterating remaining projects after project with ID {$after}"); - $project = $dbForConsole->getDocument('projects', $after); - $queries = [Query::cursorAfter($project)]; - } else { - Console::info("Iterating all projects"); - } - $this->foreachDocument($dbForConsole, 'projects', $queries, function (Document $project) use ($getProjectDB, $dbForConsole) { - $projectId = $project->getId(); - - try { - $dbForProject = call_user_func($getProjectDB, $project); - $this->recreateRepositories($dbForConsole, $dbForProject, $project); - } catch (\Throwable $th) { - Console::error("Unexpected error occured with Project ID {$projectId}"); - Console::error('[Error] Type: ' . get_class($th)); - Console::error('[Error] Message: ' . $th->getMessage()); - Console::error('[Error] File: ' . $th->getFile()); - Console::error('[Error] Line: ' . $th->getLine()); - } - }); - } - - $endTime = microtime(true); - $timeTaken = $endTime - $startTime; - - $hours = (int)($timeTaken / 3600); - $timeTaken -= $hours * 3600; - $minutes = (int)($timeTaken / 60); - $timeTaken -= $minutes * 60; - $seconds = (int)$timeTaken; - $milliseconds = ($timeTaken - $seconds) * 1000; - Console::info("Recreate patch completed in $hours h, $minutes m, $seconds s, $milliseconds mis ( total $timeTaken milliseconds)"); - } - - protected function foreachDocument(Database $database, string $collection, array $queries = [], callable $callback = null): void - { - $limit = 1000; - $results = []; - $sum = $limit; - $latestDocument = null; - - while ($sum === $limit) { - $newQueries = $queries; - - if ($latestDocument != null) { - array_unshift($newQueries, Query::cursorAfter($latestDocument)); - } - $newQueries[] = Query::limit($limit); - $results = $database->find($collection, $newQueries); - - if (empty($results)) { - return; - } - - $sum = count($results); - - foreach ($results as $document) { - if (is_callable($callback)) { - $callback($document); - } - } - $latestDocument = $results[array_key_last($results)]; - } - } - - public function recreateRepositories(Database $dbForConsole, Database $dbForProject, Document $project): void - { - $projectId = $project->getId(); - Console::log("Running patch for project {$projectId}"); - - $this->foreachDocument($dbForProject, 'functions', [], function (Document $function) use ($dbForProject, $dbForConsole, $project) { - $isConnected = !empty($function->getAttribute('providerRepositoryId', '')); - - if ($isConnected) { - $repository = $dbForConsole->getDocument('repositories', $function->getAttribute('repositoryId', '')); - - if ($repository->isEmpty()) { - $projectId = $project->getId(); - $functionId = $function->getId(); - Console::success("Recreating repositories document for project ID {$projectId}, function ID {$functionId}"); - - $repository = $dbForConsole->createDocument('repositories', new Document([ - '$id' => ID::unique(), - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'installationId' => $function->getAttribute('installationId', ''), - 'installationInternalId' => $function->getAttribute('installationInternalId', ''), - 'projectId' => $project->getId(), - 'projectInternalId' => $project->getInternalId(), - 'providerRepositoryId' => $function->getAttribute('providerRepositoryId', ''), - 'resourceId' => $function->getId(), - 'resourceInternalId' => $function->getInternalId(), - 'resourceType' => 'function', - 'providerPullRequestIds' => [] - ])); - - $function = $dbForProject->updateDocument('functions', $function->getId(), $function - ->setAttribute('repositoryId', $repository->getId()) - ->setAttribute('repositoryInternalId', $repository->getInternalId())); - - $this->foreachDocument($dbForProject, 'deployments', [ - Query::equal('resourceInternalId', [$function->getInternalId()]), - Query::equal('resourceType', ['functions']) - ], function (Document $deployment) use ($dbForProject, $repository) { - $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment - ->setAttribute('repositoryId', $repository->getId()) - ->setAttribute('repositoryInternalId', $repository->getInternalId())); - }); - } - } - }); - } -} diff --git a/src/Appwrite/Platform/Tasks/VolumeSync.php b/src/Appwrite/Platform/Tasks/VolumeSync.php deleted file mode 100644 index 6197b20fbd..0000000000 --- a/src/Appwrite/Platform/Tasks/VolumeSync.php +++ /dev/null @@ -1,59 +0,0 @@ -desc('Runs rsync to sync certificates between the storage mount and traefik.') - ->param('source', null, new Text(255), 'Source path to sync from.', false) - ->param('destination', null, new Text(255), 'Destination path to sync to.', false) - ->param('interval', null, new Integer(true), 'Interval to run rsync', false) - ->callback(fn ($source, $destination, $interval) => $this->action($source, $destination, $interval)); - } - - public function action(string $source, string $destination, int $interval) - { - - Console::title('RSync V1'); - Console::success(APP_NAME . ' rsync process v1 has started'); - - if (!file_exists($source)) { - Console::error('Source directory does not exist. Exiting ... '); - Console::exit(0); - } - - Console::loop(function () use ($interval, $source, $destination) { - $time = DateTime::now(); - - Console::info("[{$time}] Executing rsync every {$interval} seconds"); - Console::info("Syncing between $source and $destination"); - - if (!file_exists($source)) { - Console::error('Source directory does not exist. Skipping ... '); - return; - } - - $stdin = ""; - $stdout = ""; - $stderr = ""; - - Console::execute("rsync -av $source $destination", $stdin, $stdout, $stderr); - Console::success($stdout); - Console::error($stderr); - }, $interval); - } -} diff --git a/src/Appwrite/Platform/Workers/Hamster.php b/src/Appwrite/Platform/Workers/Hamster.php deleted file mode 100644 index e911bb6c7a..0000000000 --- a/src/Appwrite/Platform/Workers/Hamster.php +++ /dev/null @@ -1,437 +0,0 @@ - '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 Mixpanel $mixpanel; - - public static function getName(): string - { - return 'hamster'; - } - - /** - * @throws \Exception - */ - public function __construct() - { - $this - ->desc('Hamster worker') - ->inject('message') - ->inject('pools') - ->inject('cache') - ->inject('dbForConsole') - ->callback(fn (Message $message, Group $pools, Cache $cache, Database $dbForConsole) => $this->action($message, $pools, $cache, $dbForConsole)); - } - - /** - * @param Message $message - * @param Group $pools - * @param Cache $cache - * @param Database $dbForConsole - * - * @return void - * @throws \Utopia\Database\Exception - */ - public function action(Message $message, Group $pools, Cache $cache, Database $dbForConsole): void - { - $token = App::getEnv('_APP_MIXPANEL_TOKEN', ''); - if (empty($token)) { - throw new \Exception('Missing MixPanel Token'); - } - $this->mixpanel = new Mixpanel($token); - - $payload = $message->getPayload() ?? []; - - if (empty($payload)) { - throw new \Exception('Missing payload'); - } - - $type = $payload['type'] ?? ''; - - switch ($type) { - case EventHamster::TYPE_PROJECT: - $this->getStatsForProject(new Document($payload['project']), $pools, $cache, $dbForConsole); - break; - case EventHamster::TYPE_ORGANISATION: - $this->getStatsForOrganization(new Document($payload['organization']), $dbForConsole); - break; - case EventHamster::TYPE_USER: - $this->getStatsPerUser(new Document($payload['user']), $dbForConsole); - break; - } - } - - /** - * @param Document $project - * @param Group $pools - * @param Cache $cache - * @param Database $dbForConsole - * @throws \Utopia\Database\Exception - */ - private function getStatsForProject(Document $project, Group $pools, Cache $cache, Database $dbForConsole): void - { - /** - * Skip user projects with id 'console' - */ - if ($project->getId() === 'console') { - Console::info("Skipping project console"); - return; - } - - Console::log("Getting stats for Project {$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'] = $project->getAttribute('$time'); - - /** 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 Database $dbForConsole - * @throws \Utopia\Database\Exception - */ - private function getStatsForOrganization(Document $organization, Database $dbForConsole): void - { - Console::log("Getting stats for Organization {$organization->getId()}"); - - 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()]), - ]); - - 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) - { - Console::log("Getting stats for User {$user->getId()}"); - - try { - $statsPerUser = []; - - $statsPerUser['time'] = $user->getAttribute('$time'); - - /** 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 eb70990bf01b77bf6586d131710b80620bfe3d47 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 9 Jan 2024 15:22:02 +0200 Subject: [PATCH 02/19] remove cloud related scripts --- src/Appwrite/Platform/Services/Tasks.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Platform/Services/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php index 0da42e3545..6b8a111611 100644 --- a/src/Appwrite/Platform/Services/Tasks.php +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -11,7 +11,6 @@ use Appwrite\Platform\Tasks\Schedule; use Appwrite\Platform\Tasks\SDKs; use Appwrite\Platform\Tasks\Specs; use Appwrite\Platform\Tasks\SSL; -use Appwrite\Platform\Tasks\Hamster; use Appwrite\Platform\Tasks\Usage; use Appwrite\Platform\Tasks\Vars; use Appwrite\Platform\Tasks\Version; From 85950868f25308436eab7da3eb891b3ebed661c1 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 9 Jan 2024 15:30:43 +0200 Subject: [PATCH 03/19] remove cloud related scripts --- src/Appwrite/Platform/Services/Tasks.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Platform/Services/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php index 6b8a111611..808fb07a81 100644 --- a/src/Appwrite/Platform/Services/Tasks.php +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -26,7 +26,6 @@ class Tasks extends Service ->addAction(Usage::getName(), new Usage()) ->addAction(Vars::getName(), new Vars()) ->addAction(SSL::getName(), new SSL()) - ->addAction(Hamster::getName(), new Hamster()) ->addAction(Doctor::getName(), new Doctor()) ->addAction(Install::getName(), new Install()) ->addAction(Upgrade::getName(), new Upgrade()) From 4b23440c2ddb2c9a8527223f7611d68837df934e Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 9 Jan 2024 15:44:56 +0200 Subject: [PATCH 04/19] remove cloud related scripts --- src/Appwrite/Platform/Services/Workers.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Appwrite/Platform/Services/Workers.php b/src/Appwrite/Platform/Services/Workers.php index c5a0514760..40366f2313 100644 --- a/src/Appwrite/Platform/Services/Workers.php +++ b/src/Appwrite/Platform/Services/Workers.php @@ -12,7 +12,6 @@ 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 @@ -31,8 +30,6 @@ class Workers extends Service ->addAction(Builds::getName(), new Builds()) ->addAction(Deletes::getName(), new Deletes()) ->addAction(Migrations::getName(), new Migrations()) - ->addAction(Hamster::getName(), new Hamster()) - ; } } From 6ad63b6f7bc58cf504c1d004db8b8afbfd8ce564 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 17 Jan 2024 10:44:16 +0200 Subject: [PATCH 05/19] addressing comments --- app/cli.php | 3 --- app/worker.php | 3 --- 2 files changed, 6 deletions(-) diff --git a/app/cli.php b/app/cli.php index 003f3a1f79..6e16ab1ddb 100644 --- a/app/cli.php +++ b/app/cli.php @@ -155,9 +155,6 @@ 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/worker.php b/app/worker.php index 4f7355311e..a7396b1169 100644 --- a/app/worker.php +++ b/app/worker.php @@ -156,9 +156,6 @@ 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']); From ab80999d1e9def7e3bc1483990b50e411a97ac34 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 31 Jan 2024 12:13:02 +0200 Subject: [PATCH 06/19] cleanup --- bin/create-inf-metric | 3 - src/Appwrite/Platform/Tasks/CalcTierStats.php | 0 .../Platform/Tasks/CreateInfMetric.php | 413 ------------------ 3 files changed, 416 deletions(-) delete mode 100644 bin/create-inf-metric delete mode 100644 src/Appwrite/Platform/Tasks/CalcTierStats.php delete mode 100644 src/Appwrite/Platform/Tasks/CreateInfMetric.php diff --git a/bin/create-inf-metric b/bin/create-inf-metric deleted file mode 100644 index ea7b2b4da0..0000000000 --- a/bin/create-inf-metric +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -php /usr/src/code/app/cli.php create-inf-metric $@ \ No newline at end of file diff --git a/src/Appwrite/Platform/Tasks/CalcTierStats.php b/src/Appwrite/Platform/Tasks/CalcTierStats.php deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/Appwrite/Platform/Tasks/CreateInfMetric.php b/src/Appwrite/Platform/Tasks/CreateInfMetric.php deleted file mode 100644 index 49b852ff6f..0000000000 --- a/src/Appwrite/Platform/Tasks/CreateInfMetric.php +++ /dev/null @@ -1,413 +0,0 @@ -desc('Create infinity stats metric') - ->param('after', '', new Text(36), 'After cursor', true) - ->param('projectId', '', new Text(36), 'Select project to validate', true) - ->inject('getProjectDB') - ->inject('dbForConsole') - ->callback(function (string $after, string $projectId, callable $getProjectDB, Database $dbForConsole) { - $this->action($after, $projectId, $getProjectDB, $dbForConsole); - }); - } - - - /** - * @throws Exception - * @throws Exception\Timeout - * @throws Exception\Query - */ - public function action(string $after, string $projectId, callable $getProjectDB, Database $dbForConsole): void - { - - Console::title('Create infinity metric V1'); - Console::success(APP_NAME . ' Create infinity metric started'); - - if (!empty($projectId)) { - try { - $project = $dbForConsole->getDocument('projects', $projectId); - $dbForProject = call_user_func($getProjectDB, $project); - $this->getUsageData($dbForProject, $project); - } catch (\Throwable $th) { - Console::error("Unexpected error occured with Project ID {$projectId}"); - Console::error('[Error] Type: ' . get_class($th)); - Console::error('[Error] Message: ' . $th->getMessage()); - Console::error('[Error] File: ' . $th->getFile()); - Console::error('[Error] Line: ' . $th->getLine()); - } - } else { - $queries = []; - if (!empty($after)) { - Console::info("Iterating remaining projects after project with ID {$after}"); - $project = $dbForConsole->getDocument('projects', $after); - $queries = [Query::cursorAfter($project)]; - } else { - Console::info("Iterating all projects"); - } - $this->foreachDocument($dbForConsole, 'projects', $queries, function (Document $project) use ($getProjectDB) { - $projectId = $project->getId(); - - try { - $dbForProject = call_user_func($getProjectDB, $project); - $this->getUsageData($dbForProject, $project); - } catch (\Throwable $th) { - Console::error("Unexpected error occured with Project ID {$projectId}"); - Console::error('[Error] Type: ' . get_class($th)); - Console::error('[Error] Message: ' . $th->getMessage()); - Console::error('[Error] File: ' . $th->getFile()); - Console::error('[Error] Line: ' . $th->getLine()); - } - }); - } - } - - /** - * @param Database $database - * @param string $collection - * @param array $queries - * @param callable|null $callback - * @return void - * @throws Exception - * @throws Exception\Query - * @throws Exception\Timeout - */ - private function foreachDocument(Database $database, string $collection, array $queries = [], callable $callback = null): void - { - $limit = 1000; - $results = []; - $sum = $limit; - $latestDocument = null; - - while ($sum === $limit) { - $newQueries = $queries; - - if ($latestDocument != null) { - array_unshift($newQueries, Query::cursorAfter($latestDocument)); - } - $newQueries[] = Query::limit($limit); - $results = $database->find($collection, $newQueries); - - if (empty($results)) { - return; - } - - $sum = count($results); - - foreach ($results as $document) { - if (is_callable($callback)) { - $callback($document); - } - } - $latestDocument = $results[array_key_last($results)]; - } - } - - - /** - * @param Database $dbForProject - * @param Document $project - * @return void - */ - private function getUsageData(Database $dbForProject, Document $project): void - { - try { - $this->network($dbForProject); - $this->sessions($dbForProject); - $this->users($dbForProject); - $this->teams($dbForProject); - $this->databases($dbForProject); - $this->functions($dbForProject); - $this->storage($dbForProject); - } catch (\Throwable $th) { - var_dump($th->getMessage()); - } - - Console::log('Finished project ' . $project->getId() . ' ' . $project->getInternalId()); - } - - - /** - * @param Database $dbForProject - * @param string $metric - * @param int|float $value - * @return void - * @throws Exception - * @throws Exception\Authorization - * @throws Exception\Conflict - * @throws Exception\Restricted - * @throws Exception\Structure - */ - private function createInfMetric(database $dbForProject, string $metric, int|float $value): void - { - - try { - $id = \md5("_inf_{$metric}"); - $dbForProject->deleteDocument('stats_v2', $id); - $dbForProject->createDocument('stats_v2', new Document([ - '$id' => $id, - 'metric' => $metric, - 'period' => 'inf', - 'value' => (int)$value, - 'time' => null, - 'region' => 'default', - ])); - } catch (Duplicate $th) { - console::log("Error while creating inf metric: duplicate id {$metric} {$id}"); - } - } - - /** - * @param Database $dbForProject - * @param string $metric - * @return int|float - * @throws Exception - */ - protected function getFromMetric(database $dbForProject, string $metric): int|float - { - - return $dbForProject->sum('stats_v2', 'value', [ - Query::equal('metric', [ - $metric, - ]), - Query::equal('period', ['1d']), - ]); - } - - /** - * @param Database $dbForProject - * @throws Exception - * @throws Exception\Authorization - * @throws Exception\Conflict - * @throws Exception\Restricted - * @throws Exception\Structure - */ - private function network(database $dbForProject) - { - $this->createInfMetric($dbForProject, 'network.inbound', $this->getFromMetric($dbForProject, 'network.inbound')); - $this->createInfMetric($dbForProject, 'network.outbound', $this->getFromMetric($dbForProject, 'network.outbound')); - $this->createInfMetric($dbForProject, 'network.requests', $this->getFromMetric($dbForProject, 'network.requests')); - } - - - /** - * @throws Exception\Authorization - * @throws Exception\Restricted - * @throws Exception\Conflict - * @throws Exception\Timeout - * @throws Exception\Structure - * @throws Exception - * @throws Exception\Query - */ - private function storage(database $dbForProject) - { - $bucketsCount = 0; - $filesCount = 0; - $filesStorageSum = 0; - - $buckets = $dbForProject->find('buckets'); - foreach ($buckets as $bucket) { - $files = $dbForProject->count('bucket_' . $bucket->getInternalId()); - $this->createInfMetric($dbForProject, $bucket->getInternalId() . '.files', $files); - - $filesStorage = $dbForProject->sum('bucket_' . $bucket->getInternalId(), 'sizeOriginal'); - $this->createInfMetric($dbForProject, $bucket->getInternalId() . '.files.storage', $filesStorage); - - $bucketsCount++; - $filesCount += $files; - $filesStorageSum += $filesStorage; - } - - $this->createInfMetric($dbForProject, 'buckets', $bucketsCount); - $this->createInfMetric($dbForProject, 'files', $filesCount); - $this->createInfMetric($dbForProject, 'files.storage', $filesStorageSum); - } - - - /** - * @throws Exception\Authorization - * @throws Exception\Timeout - * @throws Exception\Restricted - * @throws Exception\Structure - * @throws Exception\Conflict - * @throws Exception - * @throws Exception\Query - */ - private function functions(Database $dbForProject) - { - $functionsCount = 0; - $deploymentsCount = 0; - $buildsCount = 0; - $buildsStorageSum = 0; - $buildsComputeSum = 0; - $executionsCount = 0; - $executionsComputeSum = 0; - $deploymentsStorageSum = 0; - - //functions - $functions = $dbForProject->find('functions'); - foreach ($functions as $function) { - //deployments - $deployments = $dbForProject->find('deployments', [ - Query::equal('resourceType', ['functions']), - Query::equal('resourceInternalId', [$function->getInternalId()]), - ]); - - $deploymentCount = 0; - $deploymentStorageSum = 0; - foreach ($deployments as $deployment) { - //builds - $builds = $dbForProject->count('builds', [ - Query::equal('deploymentInternalId', [$deployment->getInternalId()]), - ]); - - $buildsCompute = $dbForProject->sum('builds', 'duration', [ - Query::equal('deploymentInternalId', [$deployment->getInternalId()]), - ]); - - $buildsStorage = $dbForProject->sum('builds', 'size', [ - Query::equal('deploymentInternalId', [$deployment->getInternalId()]), - ]); - - $this->createInfMetric($dbForProject, $function->getInternalId() . '.builds', $builds); - $this->createInfMetric($dbForProject, $function->getInternalId() . '.builds.storage', $buildsCompute * 1000); - $this->createInfMetric($dbForProject, $function->getInternalId() . '.builds.compute', $buildsStorage); - - $buildsCount += $builds; - $buildsComputeSum += $buildsCompute; - $buildsStorageSum += $buildsStorage; - - - $deploymentCount++; - $deploymentsCount++; - $deploymentsStorageSum += $deployment['size']; - $deploymentStorageSum += $deployment['size']; - } - $this->createInfMetric($dbForProject, 'functions.' . $function->getInternalId() . '.deployments', $deploymentCount); - $this->createInfMetric($dbForProject, 'functions.' . $function->getInternalId() . '.deployments.storage', $deploymentStorageSum); - - //executions - $executions = $dbForProject->count('executions', [ - Query::equal('functionInternalId', [$function->getInternalId()]), - ]); - - $executionsCompute = $dbForProject->sum('executions', 'duration', [ - Query::equal('functionInternalId', [$function->getInternalId()]), - ]); - - $this->createInfMetric($dbForProject, $function->getInternalId() . '.executions', $executions); - $this->createInfMetric($dbForProject, $function->getInternalId() . '.executions.compute', $executionsCompute * 1000); - $executionsCount += $executions; - $executionsComputeSum += $executionsCompute; - - $functionsCount++; - } - - $this->createInfMetric($dbForProject, 'functions', $functionsCount); - $this->createInfMetric($dbForProject, 'deployments', $deploymentsCount); - $this->createInfMetric($dbForProject, 'deployments.storage', $deploymentsStorageSum); - $this->createInfMetric($dbForProject, 'builds', $buildsCount); - $this->createInfMetric($dbForProject, 'builds.compute', $buildsComputeSum * 1000); - $this->createInfMetric($dbForProject, 'builds.storage', $buildsStorageSum); - $this->createInfMetric($dbForProject, 'executions', $executionsCount); - $this->createInfMetric($dbForProject, 'executions.compute', $executionsComputeSum * 1000); - } - - /** - * @throws Exception\Authorization - * @throws Exception\Timeout - * @throws Exception\Structure - * @throws Exception\Restricted - * @throws Exception\Conflict - * @throws Exception - * @throws Exception\Query - */ - private function databases(Database $dbForProject) - { - $databasesCount = 0; - $collectionsCount = 0; - $documentsCount = 0; - $databases = $dbForProject->find('databases'); - foreach ($databases as $database) { - $collectionCount = 0; - $collections = $dbForProject->find('database_' . $database->getInternalId()); - foreach ($collections as $collection) { - $documents = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); - $this->createInfMetric($dbForProject, $database->getInternalId() . '.' . $collection->getInternalId() . '.documents', $documents); - $documentsCount += $documents; - $collectionCount++; - $collectionsCount++; - } - $this->createInfMetric($dbForProject, $database->getInternalId() . '.collections', $collectionCount); - $this->createInfMetric($dbForProject, $database->getInternalId() . '.documents', $documentsCount); - $databasesCount++; - } - $this->createInfMetric($dbForProject, 'collections', $collectionsCount); - $this->createInfMetric($dbForProject, 'databases', $databasesCount); - $this->createInfMetric($dbForProject, 'documents', $documentsCount); - } - - - /** - * @throws Exception\Authorization - * @throws Exception\Structure - * @throws Exception\Restricted - * @throws Exception\Conflict - * @throws Exception - */ - private function users(Database $dbForProject) - { - $users = $dbForProject->count('users'); - $this->createInfMetric($dbForProject, 'users', $users); - } - - /** - * @throws Exception\Authorization - * @throws Exception\Structure - * @throws Exception\Restricted - * @throws Exception\Conflict - * @throws Exception - */ - private function sessions(Database $dbForProject) - { - $users = $dbForProject->count('sessions'); - $this->createInfMetric($dbForProject, 'sessions', $users); - } - - /** - * @throws Exception\Authorization - * @throws Exception\Structure - * @throws Exception\Restricted - * @throws Exception\Conflict - * @throws Exception - */ - private function teams(Database $dbForProject) - { - $teams = $dbForProject->count('teams'); - $this->createInfMetric($dbForProject, 'teams', $teams); - } -} From a0e82ccfab8a6748892734f8d6dfaa44f13dbae1 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 13 Feb 2024 10:35:33 +0200 Subject: [PATCH 07/19] sync with main --- Dockerfile | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index e1d0d32ed6..a264ac8795 100755 --- a/Dockerfile +++ b/Dockerfile @@ -101,20 +101,6 @@ RUN chmod +x /usr/local/bin/doctor && \ chmod +x /usr/local/bin/worker-usage && \ chmod +x /usr/local/bin/worker-usage-dump - -# Cloud Executabless -RUN chmod +x /usr/local/bin/hamster && \ - chmod +x /usr/local/bin/volume-sync && \ - chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \ - chmod +x /usr/local/bin/patch-recreate-repositories-documents && \ - chmod +x /usr/local/bin/patch-delete-project-collections && \ - 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/get-migration-stats && \ - chmod +x /usr/local/bin/create-inf-metric - # Letsencrypt Permissions RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/ From 96317f0acb660c483b37666ae4e029ad47fdd48b Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 13 Feb 2024 10:46:24 +0200 Subject: [PATCH 08/19] sync with main --- app/controllers/api/health.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index a85f9da321..e10a0d8b81 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -755,12 +755,12 @@ App::get('/v1/health/queue/failed/:name') Event::MAILS_QUEUE_NAME, Event::FUNCTIONS_QUEUE_NAME, Event::USAGE_QUEUE_NAME, + Event::USAGE_DUMP_QUEUE_NAME, Event::WEBHOOK_CLASS_NAME, Event::CERTIFICATES_QUEUE_NAME, Event::BUILDS_QUEUE_NAME, Event::MESSAGING_QUEUE_NAME, - Event::MIGRATIONS_QUEUE_NAME, - Event::HAMSTER_CLASS_NAME + Event::MIGRATIONS_QUEUE_NAME ]), 'The name of the queue') ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) ->label('sdk.description', '/docs/references/health/get-failed-queue-jobs.md') From 4e2120952fc135c465c6c2ce0bdd4f50be59815d Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:27:36 +0530 Subject: [PATCH 09/19] Fix vcs silent mode --- app/controllers/api/vcs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 1b0c993e11..0d87c101ad 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -95,7 +95,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $latestCommentId = ''; - if (!empty($providerPullRequestId)) { + if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) { $latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerPullRequestId', [$providerPullRequestId]), From fc498fd80fff82e9103986923fdbc2c763c0ed83 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 3 Mar 2024 08:19:36 +0545 Subject: [PATCH 10/19] update cover image for SDKs --- src/Appwrite/Platform/Tasks/SDKs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 256b36d885..068da7c68e 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -74,7 +74,7 @@ class SDKs extends Action $spec = file_get_contents(__DIR__ . '/../../../../app/config/specs/swagger2-' . $version . '-' . $language['family'] . '.json'); - $cover = 'https://appwrite.io/images/github.png'; + $cover = 'https://github.com/appwrite/appwrite/raw/main/public/images/github.png'; $result = \realpath(__DIR__ . '/../../../../app') . '/sdks/' . $key . '-' . $language['key']; $resultExamples = \realpath(__DIR__ . '/../../../..') . '/docs/examples/' . $version . '/' . $key . '-' . $language['key']; $target = \realpath(__DIR__ . '/../../../../app') . '/sdks/git/' . $language['key'] . '/'; From ed62a0ca4ab43d8eeeb93a4e4c3dcbc45a1d7f74 Mon Sep 17 00:00:00 2001 From: Dylan <105213810+DylanG-64@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:31:58 +0100 Subject: [PATCH 11/19] Updated header --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62b084d320..88b4173f1d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> Great news! Appwrite Cloud is now in public beta! Sign up at [cloud.appwrite.io](https://cloud.appwrite.io) for a hassle-free, hosted experience. Join us in the Cloud today! ☁️🎉 +> Our Appwrite Init event has concluded. You can check out all the new and upcoming features [on our Init website](https://appwrite.io/init) 🚀

From c849ff0b0f642c9e444e3e69f958485f4c63c992 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 18 Mar 2024 08:52:47 +0000 Subject: [PATCH 12/19] use internal ids for query --- app/controllers/api/account.php | 4 ++-- app/controllers/api/users.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 345486b657..3df4d47355 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -555,7 +555,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), - Query::notEqual('userId', $userId), + Query::notEqual('userInternalId', $user->getInternalId()), ]); if (!empty($identityWithMatchingEmail)) { throw new Exception(Exception::USER_ALREADY_EXISTS); @@ -1985,7 +1985,7 @@ App::patch('/v1/account/email') // Makes sure this email is not already used in another identity $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), - Query::notEqual('userId', $user->getId()), + Query::notEqual('userInternalId', $user->getInternalId()), ]); if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 5ce2263f47..6a0d6f3d7a 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -941,7 +941,7 @@ App::patch('/v1/users/:userId/email') // Makes sure this email is not already used in another identity $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), - Query::notEqual('userId', $user->getId()), + Query::notEqual('userInternalId', $user->getInternalId()), ]); if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); From 21d4b8feef5b28faf11f042187ddfb1f3b5d9d4d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 18 Mar 2024 09:01:54 +0000 Subject: [PATCH 13/19] check user internal Id on membership --- app/controllers/api/teams.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 978429e51c..03f158c131 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -916,7 +916,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $user->setAttributes($dbForProject->getDocument('users', $userId)->getArrayCopy()); // Get user } - if ($membership->getAttribute('userId') !== $user->getId()) { + if ($membership->getAttribute('userInternalId') !== $user->getInternalId()) { throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')'); } From a306cb87d88c16789cca1fab9c6eece2520d4b2f Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 18 Mar 2024 12:07:14 +0000 Subject: [PATCH 14/19] chore: update avatars API --- app/controllers/api/avatars.php | 61 ++++++++++++++--------------- composer.json | 1 + composer.lock | 69 +++++++++++++++++++++++++-------- 3 files changed, 84 insertions(+), 47 deletions(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index e0d967eb00..b9df4b6345 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -23,6 +23,7 @@ use Utopia\Validator\URL; use Utopia\Validator\WhiteList; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; +use Utopia\Fetch\Client; $avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) { @@ -283,14 +284,17 @@ App::get('/v1/avatars/image') throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); } - $fetch = @\file_get_contents($url); + $client = new Client(); + $res = $client + ->setAllowRedirects(false) + ->fetch($url); - if (!$fetch) { + if ($res->getStatusCode() !== 200) { throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND); } try { - $image = new Image($fetch); + $image = new Image($res->getBody()); } catch (\Exception $exception) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unable to parse image'); } @@ -339,31 +343,23 @@ App::get('/v1/avatars/favicon') throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); } - $curl = \curl_init(); + $client = new Client(); + $res = $client + ->setAllowRedirects(false) + ->setUserAgent(\sprintf( + APP_USERAGENT, + App::getEnv('_APP_VERSION', 'UNKNOWN'), + App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) + )) + ->fetch($url); - \curl_setopt_array($curl, [ - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_MAXREDIRS => 3, - CURLOPT_URL => $url, - CURLOPT_USERAGENT => \sprintf( - APP_USERAGENT, - App::getEnv('_APP_VERSION', 'UNKNOWN'), - App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) - ), - ]); - - $html = \curl_exec($curl); - - \curl_close($curl); - - if (!$html) { + if ($res->getStatusCode() !== 200) { throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); } $doc = new DOMDocument(); $doc->strictErrorChecking = false; - @$doc->loadHTML($html); + @$doc->loadHTML($res->getBody()); $links = $doc->getElementsByTagName('link'); $outputHref = ''; @@ -418,9 +414,18 @@ App::get('/v1/avatars/favicon') throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); } - if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files - $data = @\file_get_contents($outputHref, false); + $client = new Client(); + $res = $client + ->setAllowRedirects(false) + ->fetch($outputHref); + if ($res->getStatusCode() !== 200) { + throw new Exception(Exception::AVATAR_ICON_NOT_FOUND); + } + + $data = $res->getBody(); + + if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files if (empty($data) || (\mb_substr($data, 0, 5) === 'file($data); } - $fetch = @\file_get_contents($outputHref, false); - - if (!$fetch) { - throw new Exception(Exception::AVATAR_ICON_NOT_FOUND); - } - - $image = new Image($fetch); + $image = new Image($data); $image->crop((int) $width, (int) $height); $output = (empty($output)) ? $type : $output; $data = $image->output($output, $quality); diff --git a/composer.json b/composer.json index faf30a117d..eaa6cfa206 100644 --- a/composer.json +++ b/composer.json @@ -54,6 +54,7 @@ "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.1.*", "utopia-php/framework": "0.33.*", + "utopia-php/fetch": "0.2.*", "utopia-php/image": "0.6.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.3.*", diff --git a/composer.lock b/composer.lock index c7fd4959b5..606338c675 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": "9492cb777590ff776c2a73620783d74b", + "content-hash": "95d4c9ccd4b2f958ca247d83b04d1391", "packages": [ { "name": "adhocore/jwt", @@ -1352,6 +1352,45 @@ }, "time": "2022-10-26T10:06:20+00:00" }, + { + "name": "utopia-php/fetch", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/fetch.git", + "reference": "1423c0ee3eef944d816ca6e31706895b585aea82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/fetch/zipball/1423c0ee3eef944d816ca6e31706895b585aea82", + "reference": "1423c0ee3eef944d816ca6e31706895b585aea82", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "^1.5.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Fetch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple library that provides an interface for making HTTP Requests.", + "support": { + "issues": "https://github.com/utopia-php/fetch/issues", + "source": "https://github.com/utopia-php/fetch/tree/0.2.1" + }, + "time": "2024-03-18T11:50:59+00:00" + }, { "name": "utopia-php/framework", "version": "0.33.3", @@ -2585,16 +2624,16 @@ }, { "name": "matthiasmullie/minify", - "version": "1.3.72", + "version": "1.3.73", "source": { "type": "git", "url": "https://github.com/matthiasmullie/minify.git", - "reference": "531fdeef1911ffe27a53f8a19c297648c78f757e" + "reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/531fdeef1911ffe27a53f8a19c297648c78f757e", - "reference": "531fdeef1911ffe27a53f8a19c297648c78f757e", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/cb7a9297b4ab070909cefade30ee95054d4ae87a", + "reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a", "shasum": "" }, "require": { @@ -2644,7 +2683,7 @@ ], "support": { "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.72" + "source": "https://github.com/matthiasmullie/minify/tree/1.3.73" }, "funding": [ { @@ -2652,7 +2691,7 @@ "type": "github" } ], - "time": "2024-03-13T12:02:00+00:00" + "time": "2024-03-15T10:27:10+00:00" }, { "name": "matthiasmullie/path-converter", @@ -4500,16 +4539,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -4521,7 +4560,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -4542,8 +4581,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -4551,8 +4589,7 @@ "type": "github" } ], - "abandoned": true, - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", From 5e6c94d30720714c819b506de5a289e7584d8b29 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 18 Mar 2024 12:41:51 +0000 Subject: [PATCH 15/19] chore: update checks --- app/controllers/api/avatars.php | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index b9df4b6345..734cfb2dc5 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -285,10 +285,14 @@ App::get('/v1/avatars/image') } $client = new Client(); - $res = $client + try { + $res = $client ->setAllowRedirects(false) ->fetch($url); - + } catch (\Throwable) { + throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); + } + if ($res->getStatusCode() !== 200) { throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND); } @@ -344,7 +348,8 @@ App::get('/v1/avatars/favicon') } $client = new Client(); - $res = $client + try { + $res = $client ->setAllowRedirects(false) ->setUserAgent(\sprintf( APP_USERAGENT, @@ -352,7 +357,10 @@ App::get('/v1/avatars/favicon') App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) )) ->fetch($url); - + } catch (\Throwable) { + throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); + } + if ($res->getStatusCode() !== 200) { throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); } @@ -415,9 +423,13 @@ App::get('/v1/avatars/favicon') } $client = new Client(); - $res = $client - ->setAllowRedirects(false) - ->fetch($outputHref); + try { + $res = $client + ->setAllowRedirects(false) + ->fetch($outputHref); + } catch (\Throwable) { + throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); + } if ($res->getStatusCode() !== 200) { throw new Exception(Exception::AVATAR_ICON_NOT_FOUND); From eeba452fb536032b4dc467cd08e4cc7be338b5dd Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 18 Mar 2024 12:43:42 +0000 Subject: [PATCH 16/19] chore: linter --- app/controllers/api/avatars.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 734cfb2dc5..d0f8072f2c 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -292,7 +292,7 @@ App::get('/v1/avatars/image') } catch (\Throwable) { throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); } - + if ($res->getStatusCode() !== 200) { throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND); } @@ -360,7 +360,7 @@ App::get('/v1/avatars/favicon') } catch (\Throwable) { throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); } - + if ($res->getStatusCode() !== 200) { throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); } @@ -429,7 +429,7 @@ App::get('/v1/avatars/favicon') ->fetch($outputHref); } catch (\Throwable) { throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); - } + } if ($res->getStatusCode() !== 200) { throw new Exception(Exception::AVATAR_ICON_NOT_FOUND); From fcfff2ab048f03eac060f86a542f568595598acb Mon Sep 17 00:00:00 2001 From: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:46:42 +0000 Subject: [PATCH 17/19] chore: remove dead code in error template --- app/views/general/error.phtml | 39 ----------------------------------- 1 file changed, 39 deletions(-) diff --git a/app/views/general/error.phtml b/app/views/general/error.phtml index 450dcf8973..6c75ee1e5f 100644 --- a/app/views/general/error.phtml +++ b/app/views/general/error.phtml @@ -111,45 +111,6 @@ $title = $this->getParam('title', '') -