From e3e11c60dc7dea5f499f987285804c253202ece5 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 4 Jun 2025 10:42:19 +0300 Subject: [PATCH] delayed project deletion --- Dockerfile | 1 + app/config/collections/platform.php | 31 +++++++++- app/controllers/api/projects.php | 34 ++++++++--- bin/schedule-projects | 3 + composer.lock | 4 +- docker-compose.yml | 28 +++++++++ src/Appwrite/Platform/Services/Tasks.php | 2 + .../Platform/Tasks/ScheduleProjects.php | 59 +++++++++++++++++++ 8 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 bin/schedule-projects create mode 100644 src/Appwrite/Platform/Tasks/ScheduleProjects.php diff --git a/Dockerfile b/Dockerfile index 30b017b573..768457b132 100755 --- a/Dockerfile +++ b/Dockerfile @@ -67,6 +67,7 @@ RUN chmod +x /usr/local/bin/doctor && \ chmod +x /usr/local/bin/schedule-functions && \ chmod +x /usr/local/bin/schedule-executions && \ chmod +x /usr/local/bin/schedule-messages && \ + chmod +x /usr/local/bin/schedule-projects && \ chmod +x /usr/local/bin/sdks && \ chmod +x /usr/local/bin/specs && \ chmod +x /usr/local/bin/ssl && \ diff --git a/app/config/collections/platform.php b/app/config/collections/platform.php index 60f181df66..59f849968b 100644 --- a/app/config/collections/platform.php +++ b/app/config/collections/platform.php @@ -330,7 +330,29 @@ return [ 'default' => null, 'array' => false, 'filters' => ['datetime'], - ] + ], + [ + '$id' => ID::custom('scheduleId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('_deletedAt'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], ], 'indexes' => [ [ @@ -382,6 +404,13 @@ return [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_deleted_at'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['_deletedAt'], + 'lengths' => [], + 'orders' => [], + ] ], ], diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 5eda8e9a0e..1532bc172e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -19,6 +19,7 @@ use Appwrite\Utopia\Database\Validator\ProjectId; use Appwrite\Utopia\Database\Validator\Queries\Projects; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; +use Cron\CronExpression; use PHPMailer\PHPMailer\PHPMailer; use Utopia\App; use Utopia\Audit\Audit; @@ -1171,14 +1172,33 @@ App::delete('/v1/projects/:projectId') throw new Exception(Exception::PROJECT_NOT_FOUND); } - $queueForDeletes - ->setProject($project) - ->setType(DELETE_TYPE_DOCUMENT) - ->setDocument($project); + $interval = (int)System::getEnv('_APP_PROJECTS_DELETE_EXPIRATION','3600'); + $expiration = new \DateTime(); + $expiration->add(new \DateInterval('PT' . $interval . 'S')); + $cronPattern = sprintf('%d %d %d %d *', + (int)$expiration->format('i'), // minute + (int)$expiration->format('G'), // hour + (int)$expiration->format('j'), // day of month + (int)$expiration->format('n') // month + ); - if (!$dbForPlatform->deleteDocument('projects', $projectId)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove project from DB'); - } + $cronExpression = new CronExpression($cronPattern); + $scheduledAt = $cronExpression->getExpression(); + + $schedule = $dbForPlatform->createDocument('schedules', new Document([ + 'region' => $project->getAttribute('region'), + 'resourceType' => 'project', + 'resourceId' => $project->getId(), + 'resourceInternalId' => $project->getInternalId(), + 'resourceUpdatedAt' => DateTime::now(), + 'projectId' => $project->getId(), + 'schedule' => $scheduledAt, + 'active' => true, + ])); + + $dbForPlatform->updateDocument('projects', $project->getId(), $project + ->setAttribute('scheduleId', $schedule->getId())) + ; $response->noContent(); }); diff --git a/bin/schedule-projects b/bin/schedule-projects new file mode 100644 index 0000000000..eea1ad1c18 --- /dev/null +++ b/bin/schedule-projects @@ -0,0 +1,3 @@ +#!/bin/sh + +exec php /usr/src/code/app/cli.php schedule-projects $@ diff --git a/composer.lock b/composer.lock index d7b9f22722..d39dfef8b0 100644 --- a/composer.lock +++ b/composer.lock @@ -8234,7 +8234,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8258,5 +8258,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.2.0" } diff --git a/docker-compose.yml b/docker-compose.yml index 29a43aca91..e262803002 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -932,6 +932,34 @@ services: - _APP_DB_PASS - _APP_DATABASE_SHARED_TABLES + appwrite-task-scheduler-projects: + entrypoint: schedule-projects + <<: *x-logging + container_name: appwrite-task-scheduler-projects + image: appwrite-dev + networks: + - appwrite + volumes: + - ./app:/usr/src/code/app + - ./src:/usr/src/code/src + depends_on: + - mariadb + - redis + 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_DATABASE_SHARED_TABLES + appwrite-assistant: container_name: appwrite-assistant image: appwrite/assistant:0.7.0 diff --git a/src/Appwrite/Platform/Services/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php index 3ada193cf7..74ee2c332d 100644 --- a/src/Appwrite/Platform/Services/Tasks.php +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -10,6 +10,7 @@ use Appwrite\Platform\Tasks\QueueRetry; use Appwrite\Platform\Tasks\ScheduleExecutions; use Appwrite\Platform\Tasks\ScheduleFunctions; use Appwrite\Platform\Tasks\ScheduleMessages; +use Appwrite\Platform\Tasks\ScheduleProjects; use Appwrite\Platform\Tasks\Screenshot; use Appwrite\Platform\Tasks\SDKs; use Appwrite\Platform\Tasks\Specs; @@ -37,6 +38,7 @@ class Tasks extends Service ->addAction(ScheduleFunctions::getName(), new ScheduleFunctions()) ->addAction(ScheduleExecutions::getName(), new ScheduleExecutions()) ->addAction(ScheduleMessages::getName(), new ScheduleMessages()) + ->addAction(ScheduleProjects::getName(), new ScheduleProjects()) ->addAction(Specs::getName(), new Specs()) ->addAction(Upgrade::getName(), new Upgrade()) ->addAction(Vars::getName(), new Vars()) diff --git a/src/Appwrite/Platform/Tasks/ScheduleProjects.php b/src/Appwrite/Platform/Tasks/ScheduleProjects.php new file mode 100644 index 0000000000..e860550306 --- /dev/null +++ b/src/Appwrite/Platform/Tasks/ScheduleProjects.php @@ -0,0 +1,59 @@ +schedules as $schedule) { + if (!$schedule['active']) { + continue; + } + + $now = new \DateTime(); + $scheduledAt = new \DateTime($schedule['schedule']); + + if ($scheduledAt > $now) { + continue; + } + + \go(function () use ($schedule, $scheduledAt, $dbForPlatform) { + + (new Delete($this->publisher)) + ->setProject($schedule['project']) + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($schedule['project']) + ->trigger(); + + $dbForPlatform->deleteDocument('projects', $schedule['project']->getId()); + $dbForPlatform->deleteDocument('schedules', $schedule['$id']); + + $this->recordEnqueueDelay($scheduledAt); + unset($this->schedules[$schedule['$internalId']]); + }); + } + } +}