From b25857f3afe5b3dc0df2916572022b8cc0c6e5d8 Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 1 Mar 2026 18:18:49 +0100 Subject: [PATCH] First working rescheduling --- Cargo.lock | 26 ++++++++++++++++++++++---- Cargo.toml | 1 + src/collect.rs | 13 +++++++------ src/main.rs | 1 + src/scheduler.rs | 48 +++++++++++++++++++++++++++++++++++++++++------- src/task.rs | 6 +++--- src/util.rs | 10 ++++++++++ 7 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 4952124..391b5a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,6 +217,7 @@ dependencies = [ "anyhow", "env_logger", "forgejo-api", + "jiff", "log", "serde", "serde_json", @@ -713,28 +714,45 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" +checksum = "819b44bc7c87d9117eb522f14d46e918add69ff12713c475946b0a29363ed1c2" dependencies = [ "jiff-static", + "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde_core", + "windows-sys 0.61.2", ] [[package]] name = "jiff-static" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" +checksum = "470252db18ecc35fd766c0891b1e3ec6cbbcd62507e85276c01bf75d8e94d4a1" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "js-sys" version = "0.3.87" diff --git a/Cargo.toml b/Cargo.toml index 163f00c..ac1f8a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" tokio = { version = "1.45.0", features = ["full"] } time = "0.3.41" +jiff = "0.2.22" diff --git a/src/collect.rs b/src/collect.rs index 5e4a62e..6d73e81 100644 --- a/src/collect.rs +++ b/src/collect.rs @@ -1,4 +1,5 @@ use crate::task; +use crate::util; use anyhow::Context as _; use forgejo_api::structs::Issue; @@ -33,12 +34,12 @@ fn task_from_issue(issue: &Issue) -> anyhow::Result { fn task_state_from_issue(issue: &Issue) -> anyhow::Result { match issue.get_state()? { forgejo_api::structs::StateType::Open => Ok(task::State::Open { - due: issue.due_date, + due: issue.due_date.map(util::time_to_jiff), }), forgejo_api::structs::StateType::Closed => Ok(task::State::Completed { - date: issue + date: util::time_to_jiff(issue .closed_at - .context("Closed issue without a closed_at date")?, + .context("Closed issue without a closed_at date")?), }), } } @@ -124,19 +125,19 @@ async fn list_all_issues(ctx: &crate::Context) -> anyhow::Result> { } trait IssueExt { - fn get_number(&self) -> anyhow::Result; + fn get_number(&self) -> anyhow::Result; fn get_title(&self) -> anyhow::Result; fn get_state(&self) -> anyhow::Result; fn get_label_names(&self) -> anyhow::Result>; } impl IssueExt for Issue { - fn get_number(&self) -> anyhow::Result { + fn get_number(&self) -> anyhow::Result { Ok(self .number .context("Missing issue number")? .try_into() - .context("Failed converting issue number to u64")?) + .context("Failed converting issue number to u32")?) } fn get_title(&self) -> anyhow::Result { diff --git a/src/main.rs b/src/main.rs index d0c3b8c..f7de45c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod collect; mod ci_meta; mod context; mod scheduler; +mod util; use context::Context; diff --git a/src/scheduler.rs b/src/scheduler.rs index bff5520..d4111ec 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -1,4 +1,7 @@ use crate::task; +use crate::util; + +use anyhow::Context as _; pub async fn reschedule_recurring_tasks( ctx: &crate::Context, @@ -17,10 +20,10 @@ pub async fn reschedule_recurring_tasks( continue; }; - let due_date = next_due_date(*completed_date, recurring.interval); - + let due_date = next_due_date(completed_date, recurring.interval); log::info!("Rescheduling {task} for {due_date}..."); - reopen_issue_with_due_date(ctx, task, due_date).await?; + + reopen_issue_with_due_date(ctx, task, &due_date).await?; rescheduled += 1; } @@ -31,10 +34,41 @@ pub async fn reschedule_recurring_tasks( Ok(()) } -fn next_due_date(completed_date: time::OffsetDateTime, interval: task::RecurringInterval) -> time::OffsetDateTime { - todo!() +fn next_due_date(completed_date: &jiff::Zoned, interval: task::RecurringInterval) -> jiff::Zoned { + let span = match interval { + task::RecurringInterval::Months(m) => jiff::Span::new().months(m), + task::RecurringInterval::Weeks(w) => jiff::Span::new().weeks(w), + task::RecurringInterval::Days(d) => jiff::Span::new().days(d), + }; + + completed_date + span } -async fn reopen_issue_with_due_date(ctx: &crate::Context, task: &task::Task, due_date: time::OffsetDateTime) -> anyhow::Result<()> { - todo!() +async fn reopen_issue_with_due_date( + ctx: &crate::Context, + task: &task::Task, + due_date: &jiff::Zoned, +) -> anyhow::Result<()> { + ctx.forgejo + .issue_edit_issue( + &ctx.owner, + &ctx.repo, + task.issue_number.into(), + forgejo_api::structs::EditIssueOption { + due_date: Some(util::jiff_to_time(due_date)), + state: Some("open".to_owned()), + assignee: None, + assignees: None, + body: None, + milestone: None, + r#ref: None, + title: None, + unset_due_date: None, + updated_at: None, + }, + ) + .await + .context("Failed reopening recurring issue")?; + + Ok(()) } diff --git a/src/task.rs b/src/task.rs index 97de9ff..a4dab73 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,7 +1,7 @@ #[derive(Debug, Clone)] pub struct Task { /// Issue Number for referencing the task - pub issue_number: u64, + pub issue_number: u32, /// Human-readable summary of the task pub title: String, @@ -18,10 +18,10 @@ pub enum State { /// The task is open and pending completion. /// /// An optional due date may be present. - Open { due: Option }, + Open { due: Option }, /// The task has been completed at the specified time. - Completed { date: time::OffsetDateTime }, + Completed { date: jiff::Zoned }, } #[derive(Debug, Clone)] diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..a80429d --- /dev/null +++ b/src/util.rs @@ -0,0 +1,10 @@ +pub fn time_to_jiff(t: time::OffsetDateTime) -> jiff::Zoned { + let tz = jiff::tz::TimeZone::fixed(jiff::tz::offset(t.offset().whole_hours())); + + jiff::Timestamp::new(t.unix_timestamp(), 0).unwrap() + .to_zoned(tz) +} + +pub fn jiff_to_time(t: &jiff::Zoned) -> time::OffsetDateTime { + time::OffsetDateTime::from_unix_timestamp(t.timestamp().as_second()).unwrap() +}