manager: Factor out a common context object

Clean up the global data a bit by bundling it in a context object.
This commit is contained in:
Rahix 2025-05-22 18:47:31 +02:00
parent eebbd9c453
commit bc54cd91fd
5 changed files with 84 additions and 77 deletions

View file

@ -1,14 +1,12 @@
use anyhow::Context as _;
/// Read all issues to generate the full techtree
pub async fn collect_tree(
forgejo: &forgejo_api::Forgejo,
meta: &crate::event_meta::IssueEventMeta,
) -> anyhow::Result<crate::tree::Tree> {
let issues = forgejo
pub async fn collect_tree(ctx: &crate::Context) -> anyhow::Result<crate::tree::Tree> {
let issues = ctx
.forgejo
.issue_list_issues(
&meta.issue.repository.owner,
&meta.issue.repository.name,
&ctx.owner,
&ctx.repo,
forgejo_api::structs::IssueListIssuesQuery {
// We also want the closed issues
state: Some(forgejo_api::structs::IssueListIssuesQueryState::All),
@ -43,10 +41,12 @@ pub async fn collect_tree(
}
for issue in issue_numbers.into_iter() {
let dependencies = forgejo
let dependencies = ctx
.forgejo
.issue_list_issue_dependencies(
&meta.issue.repository.owner,
&meta.issue.repository.name,
&ctx.owner,
&ctx.repo,
// Why the hell is the issue number a string here?
&issue.to_string(),
forgejo_api::structs::IssueListIssueDependenciesQuery {
limit: Some(10000),
@ -54,12 +54,7 @@ pub async fn collect_tree(
},
)
.await
.with_context(|| {
format!(
"Failed to fetch issue dependencies for #{}",
meta.issue.number
)
})?;
.with_context(|| format!("Failed to fetch issue dependencies for #{issue}",))?;
for dep in dependencies {
let dep_number = dep.number.context("Missing issue number in dependency")?;

View file

@ -8,17 +8,16 @@ pub struct BotCommentInfo {
}
pub async fn make_bot_comment(
forgejo: &forgejo_api::Forgejo,
meta: &crate::event_meta::IssueEventMeta,
ctx: &crate::Context,
issue_number: u64,
) -> anyhow::Result<CommentId> {
let initial_message =
"_Please be patient, this issue is currently being integrated into the techtree..._";
let res = forgejo
let res = ctx.forgejo
.issue_create_comment(
&meta.issue.repository.owner,
&meta.issue.repository.name,
&ctx.owner,
&ctx.repo,
issue_number,
forgejo_api::structs::CreateIssueCommentOption {
body: initial_message.to_owned(),
@ -31,14 +30,13 @@ pub async fn make_bot_comment(
}
pub async fn find_bot_comment(
forgejo: &forgejo_api::Forgejo,
meta: &crate::event_meta::RepoMeta,
ctx: &crate::Context,
issue_number: u64,
) -> anyhow::Result<Option<BotCommentInfo>> {
let mut comments = forgejo
let mut comments = ctx.forgejo
.issue_get_comments(
&meta.owner,
&meta.name,
&ctx.owner,
&ctx.repo,
issue_number,
forgejo_api::structs::IssueGetCommentsQuery {
..Default::default()
@ -61,15 +59,14 @@ pub async fn find_bot_comment(
}
pub async fn update_bot_comment(
forgejo: &forgejo_api::Forgejo,
meta: &crate::event_meta::RepoMeta,
ctx: &crate::Context,
id: CommentId,
new_body: String,
) -> anyhow::Result<()> {
forgejo
ctx.forgejo
.issue_edit_comment(
&meta.owner,
&meta.name,
&ctx.owner,
&ctx.repo,
id,
forgejo_api::structs::EditIssueCommentOption {
body: new_body,
@ -83,12 +80,11 @@ pub async fn update_bot_comment(
}
pub async fn remove_stale_label(
forgejo: &forgejo_api::Forgejo,
meta: &crate::event_meta::RepoMeta,
ctx: &crate::Context,
issue_number: u64,
) -> anyhow::Result<()> {
let labels = forgejo
.issue_get_labels(&meta.owner, &meta.name, issue_number)
let labels = ctx.forgejo
.issue_get_labels(&ctx.owner, &ctx.repo, issue_number)
.await
.context("Failed fetching issue labels")?;
@ -101,10 +97,10 @@ pub async fn remove_stale_label(
if let Some(stale_label_id) = stale_label_id {
log::info!("Removing `Stale` label from issue #{issue_number}...");
let res = forgejo
let res = ctx.forgejo
.issue_remove_label(
&meta.owner,
&meta.name,
&ctx.owner,
&ctx.repo,
issue_number,
stale_label_id,
forgejo_api::structs::DeleteLabelsOption {

View file

@ -10,6 +10,21 @@ mod render;
mod tree;
mod wiki;
pub struct Context {
/// API Accessor object
pub forgejo: forgejo_api::Forgejo,
/// Repository Owner
pub owner: String,
/// Repository Name
pub repo: String,
/// URL of the repository page
pub repo_url: url::Url,
/// URL of the repository with authentication information attached
pub repo_auth_url: url::Url,
/// Human readable timestamp of this manager run
pub timestamp: String,
}
async fn run() -> anyhow::Result<()> {
let meta = if std::env::var("TECHTREE_FAKE").ok().is_some() {
log::warn!("Fake tree!");
@ -48,21 +63,29 @@ async fn run() -> anyhow::Result<()> {
let forgejo = Forgejo::new(auth, server_url).context("Could not create API access object")?;
let ctx = Context {
forgejo,
owner: meta.issue.repository.owner.clone(),
repo: meta.issue.repository.name.clone(),
repo_url,
repo_auth_url,
timestamp,
};
let new_comment_id = if meta.action == event_meta::IssueAction::Opened {
let bot_comment =
issue::find_bot_comment(&forgejo, &meta.issue.repository, meta.issue.number)
.await
.with_context(|| {
format!(
"Failed searching for bot comment for issue #{}",
meta.issue.number
)
})?;
let bot_comment = issue::find_bot_comment(&ctx, meta.issue.number)
.await
.with_context(|| {
format!(
"Failed searching for bot comment for issue #{}",
meta.issue.number
)
})?;
let id = match bot_comment {
Some(comment) => Some(comment.id),
None => {
let res = issue::make_bot_comment(&forgejo, &meta, meta.issue.number).await;
let res = issue::make_bot_comment(&ctx, meta.issue.number).await;
match res {
Ok(id) => Some(id),
Err(e) => {
@ -86,17 +109,17 @@ async fn run() -> anyhow::Result<()> {
None
};
let tree = collect::collect_tree(&forgejo, &meta)
let tree = collect::collect_tree(&ctx)
.await
.context("Failed to collect the techtree from issue metadata")?;
log::info!("Rendering and publishing techtree to git repository...");
render::render_and_publish(&tree, &timestamp, &repo_auth_url)
render::render_and_publish(&ctx, &tree)
.await
.context("Failed to render and publish the techtree to git")?;
if false {
let mermaid = tree.to_mermaid(&repo_url.to_string());
let mermaid = tree.to_mermaid(&ctx.repo_url.to_string());
let wiki_text = format!(
r##"This page is automatically updated to show the latest and greatest FAFO techtree:
@ -106,14 +129,9 @@ async fn run() -> anyhow::Result<()> {
"##
);
log::info!("Updating the wiki overview...");
wiki::update_wiki_overview(
&forgejo,
&meta.issue.repository,
timestamp.to_string(),
wiki_text,
)
.await
.context("Failed to update the techtree wiki page")?;
wiki::update_wiki_overview(&ctx, wiki_text)
.await
.context("Failed to update the techtree wiki page")?;
}
'issues: for issue in tree.iter_issues() {
@ -123,14 +141,14 @@ async fn run() -> anyhow::Result<()> {
let comment_id = if new_comment_id.is_some() && issue == meta.issue.number {
new_comment_id.unwrap()
} else {
let bot_comment = issue::find_bot_comment(&forgejo, &meta.issue.repository, issue)
let bot_comment = issue::find_bot_comment(&ctx, issue)
.await
.with_context(|| format!("Failed searching for bot comment for issue #{issue}"))?;
if let Some(bot_comment) = bot_comment {
if bot_comment.body.contains(&hash) {
log::info!("Issue #{issue} is up-to-date, not editing comment.");
issue::remove_stale_label(&forgejo, &meta.issue.repository, issue)
issue::remove_stale_label(&ctx, issue)
.await
.with_context(|| {
format!("Failed to remove `Stale` label from issue #{issue}")
@ -142,7 +160,7 @@ async fn run() -> anyhow::Result<()> {
} else {
log::warn!("Missing bot comment in issue #{issue}");
issue::make_bot_comment(&forgejo, &meta, issue)
issue::make_bot_comment(&ctx, issue)
.await
.with_context(|| {
format!("Failed to create a retrospective bot comment on issue #{issue}")
@ -150,7 +168,7 @@ async fn run() -> anyhow::Result<()> {
}
};
let mermaid = subtree.to_mermaid(&repo_url.to_string());
let mermaid = subtree.to_mermaid(&ctx.repo_url.to_string());
let full_text = format!(
r##"## Partial Techtree
@ -158,15 +176,16 @@ async fn run() -> anyhow::Result<()> {
{mermaid}
```
<small>Digest: {hash}; Last Updated: {timestamp}</small>"##
<small>Digest: {hash}; Last Updated: {timestamp}</small>"##,
timestamp = ctx.timestamp,
);
log::info!("Updating bot comment in issue #{issue} ...");
issue::update_bot_comment(&forgejo, &meta.issue.repository, comment_id, full_text)
issue::update_bot_comment(&ctx, comment_id, full_text)
.await
.with_context(|| format!("Failed to update the bot comment in issue #{issue}"))?;
issue::remove_stale_label(&forgejo, &meta.issue.repository, issue)
issue::remove_stale_label(&ctx, issue)
.await
.with_context(|| format!("Failed to remove `Stale` label from issue #{issue}"))?;
}

View file

@ -16,9 +16,8 @@ impl SuccessExt for Command {
}
pub async fn render_and_publish(
ctx: &crate::Context,
tree: &crate::tree::Tree,
timestamp: &str,
repo_auth_url: &url::Url,
) -> anyhow::Result<()> {
let render_repo = std::path::PathBuf::from("render-git");
@ -81,7 +80,7 @@ pub async fn render_and_publish(
.arg("-C")
.arg(&render_repo)
.arg("commit")
.args(["-m", &format!("Updated techtree at {timestamp}")])
.args(["-m", &format!("Updated techtree at {}", ctx.timestamp)])
.success()
.context("Failed to add generated graph files to git index")?;
@ -91,7 +90,7 @@ pub async fn render_and_publish(
.arg("push")
.arg("--force")
.arg("--quiet")
.arg(repo_auth_url.to_string())
.arg(ctx.repo_auth_url.to_string())
.arg("HEAD:refs/heads/render")
.status()
.context("Failed to push rendered graph to forgejo repository")?;

View file

@ -2,20 +2,18 @@ use anyhow::Context as _;
use base64::prelude::*;
pub async fn update_wiki_overview(
forgejo: &forgejo_api::Forgejo,
meta: &crate::event_meta::RepoMeta,
timestamp: String,
ctx: &crate::Context,
new_body: String,
) -> anyhow::Result<()> {
// TODO: Figure out why we get a 404 when the edit was successfull...
let _ = forgejo
let _ = ctx.forgejo
.repo_edit_wiki_page(
&meta.owner,
&meta.name,
&ctx.owner,
&ctx.repo,
"Home",
forgejo_api::structs::CreateWikiPageOptions {
content_base64: Some(BASE64_STANDARD.encode(new_body.as_bytes())),
message: Some(format!("Updated to latest model at {timestamp}")),
message: Some(format!("Updated to latest model at {}", ctx.timestamp)),
title: Some("Home".to_owned()),
},
)