diff --git a/README.md b/README.md index a30a8f5..680ab26 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,35 @@ There are no immediate plans to implement these features. The primary goal of th This project requires minimal configuration. The main configuration is done in the `wrangler.toml` file, where you specify your D1 database binding. +### Other Environment Variables + +You can configure the following environment variables in `wrangler.toml` under the `[vars]` section, or set them via Cloudflare Dashboard: + +* **`TRASH_AUTO_DELETE_DAYS`** (Optional, Default: `30`) + + Number of days to keep soft-deleted items before automatically purging them. When a cipher is deleted, it's marked with a `deleted_at` timestamp (soft delete). After the specified number of days, the item will be permanently removed from the database. + + * Set to `0` or a negative value to disable automatic purging + * Defaults to `30` days if not specified + * Example: `TRASH_AUTO_DELETE_DAYS = "7"` to keep deleted items for 7 days + +* **`IMPORT_BATCH_SIZE`** (Optional, Default: `30`) + + Number of records to process in each batch when importing data. This helps manage memory usage and processing time for large imports. + + * Set to `0` to disable batching (all records imported in a single batch) + * Defaults to `30` records per batch if not specified + * Example: `IMPORT_BATCH_SIZE = "50"` to process 50 records per batch + +### Scheduled Tasks (Cron) + +The worker includes a scheduled task that runs automatically to clean up soft-deleted items. By default, this task runs daily at 03:00 UTC. + +* **Automatic Cleanup:** The scheduled task automatically purges ciphers that have been soft-deleted for longer than the `TRASH_AUTO_DELETE_DAYS` period +* **Schedule:** Configured in `wrangler.toml` under `[triggers]` section with cron expression `"0 3 * * *"` (daily at 03:00 UTC) + +You can modify the cron schedule in `wrangler.toml` if you want to run the cleanup task at a different time or frequency. See [Cloudflare Cron Triggers documentation](https://developers.cloudflare.com/workers/configuration/cron-triggers/) for cron expression syntax. + ## Contributing Contributions are welcome! If you find a bug, have a feature request, or want to improve the code, please open an issue or submit a pull request. diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index d49bd56..5644148 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -5,3 +5,4 @@ pub mod identity; pub mod sync; pub mod folders; pub mod import; +pub mod purge; diff --git a/src/handlers/purge.rs b/src/handlers/purge.rs new file mode 100644 index 0000000..3465c50 --- /dev/null +++ b/src/handlers/purge.rs @@ -0,0 +1,84 @@ +//! Purge handler for cleaning up soft-deleted ciphers +//! +//! This module handles the automatic cleanup of ciphers that have been +//! soft-deleted (marked with deleted_at) for longer than the configured +//! retention period. + +use chrono::{Duration, Utc}; +use worker::{query, D1Database, Env}; + +/// Default number of days to keep soft-deleted items before purging +const DEFAULT_PURGE_DAYS: i64 = 30; + +/// Get the purge threshold days from environment variable or use default +fn get_purge_days(env: &Env) -> i64 { + env.var("TRASH_AUTO_DELETE_DAYS") + .ok() + .and_then(|v| v.to_string().parse::().ok()) + .unwrap_or(DEFAULT_PURGE_DAYS) +} + +/// Purge soft-deleted ciphers that are older than the configured threshold. +/// +/// This function: +/// 1. Calculates the cutoff timestamp based on TRASH_AUTO_DELETE_DAYS env var (default: 30 days) +/// 2. Deletes all ciphers where deleted_at is not null and older than the cutoff +/// 3. If TRASH_AUTO_DELETE_DAYS is set to 0 or negative, skips purging (disabled) +/// +/// Returns the number of purged records on success. +pub async fn purge_deleted_ciphers(env: &Env) -> Result { + let purge_days = get_purge_days(env); + + // If purge_days is 0 or negative, auto-purge is disabled + if purge_days <= 0 { + log::info!("Auto-purge is disabled (TRASH_AUTO_DELETE_DAYS <= 0)"); + return Ok(0); + } + + let db: D1Database = env.d1("vault1")?; + + // Calculate the cutoff timestamp + let now = Utc::now(); + let cutoff = now - Duration::days(purge_days); + let cutoff_str = cutoff.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(); + + log::info!( + "Purging soft-deleted ciphers older than {} days (before {})", + purge_days, + cutoff_str + ); + + // First, count the records to be deleted (for logging purposes) + let count_result = query!( + &db, + "SELECT COUNT(*) as count FROM ciphers WHERE deleted_at IS NOT NULL AND deleted_at < ?1", + cutoff_str + )? + .first::(None) + .await?; + + let count = count_result.map(|r| r.count).unwrap_or(0); + + if count > 0 { + // Delete the records + query!( + &db, + "DELETE FROM ciphers WHERE deleted_at IS NOT NULL AND deleted_at < ?1", + cutoff_str + )? + .run() + .await?; + + log::info!("Successfully purged {} soft-deleted cipher(s)", count); + } else { + log::info!("No soft-deleted ciphers to purge"); + } + + Ok(count) +} + +/// Helper struct for count query result +#[derive(serde::Deserialize)] +struct CountResult { + count: u32, +} diff --git a/src/lib.rs b/src/lib.rs index 3b6cbda..806752f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,3 +30,26 @@ pub async fn main( Ok(app.call(req).await?) } + +/// Scheduled event handler for cron-triggered tasks. +/// +/// This handler is triggered by Cloudflare's cron triggers configured in wrangler.toml. +/// It performs automatic cleanup of soft-deleted ciphers that have exceeded the +/// retention period (default: 30 days, configurable via TRASH_AUTO_DELETE_DAYS env var). +#[event(scheduled)] +pub async fn scheduled(_event: ScheduledEvent, env: Env, _ctx: ScheduleContext) { + // Set up logging + console_error_panic_hook::set_once(); + let _ = console_log::init_with_level(log::Level::Debug); + + log::info!("Scheduled task triggered: purging soft-deleted ciphers"); + + match handlers::purge::purge_deleted_ciphers(&env).await { + Ok(count) => { + log::info!("Scheduled purge completed: {} cipher(s) removed", count); + } + Err(e) => { + log::error!("Scheduled purge failed: {:?}", e); + } + } +} diff --git a/wrangler.toml b/wrangler.toml index 83a08a9..e7ac6d3 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -1,6 +1,7 @@ name = "warden-worker" main = "build/index.js" compatibility_date = "2025-09-19" +keep_vars = true [build] command = "cargo install -q worker-build && worker-build --release" @@ -8,7 +9,16 @@ command = "cargo install -q worker-build && worker-build --release" [vars] # Optional: Set the batch size for imports. Defaults to 30 if not set. # Set to 0 means no batching (all records imported in a single batch). -# IMPORT_BATCH_SIZE = "30" +# IMPORT_BATCH_SIZE = "30" + +# Number of days to keep soft-deleted items before auto-purging. +# Defaults to 30 days if not set. Set to 0 to disable auto-purge. +# TRASH_AUTO_DELETE_DAYS = "30" + +# Cron triggers for scheduled tasks +# Runs daily at 03:00 UTC to purge soft-deleted ciphers +[triggers] +crons = ["0 3 * * *"] [[d1_databases]] binding = "vault1" @@ -17,6 +27,11 @@ database_id = "${D1_DATABASE_ID}" [env.dev] name = "warden-worker-dev" +keep_vars = true + +# Dev environment also needs cron triggers +[env.dev.triggers] +crons = ["0 3 * * *"] [[env.dev.d1_databases]] binding = "vault1"