techtree/techtree-manager/src/main.rs
2025-05-25 15:33:46 +02:00

153 lines
5 KiB
Rust

#![allow(dead_code)]
use anyhow::Context as _;
use forgejo_api::Forgejo;
mod collect;
mod event_meta;
mod issue;
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!");
event_meta::fake()
} else {
event_meta::get_issue_event_meta_from_env().context("Failed reading issue event data")?
};
log::info!(
"Running due to event \"{:?}\" on issue #{} ...",
meta.action,
meta.issue.number
);
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
log::info!("Timestamp of this run is {timestamp}");
let token =
std::env::var("GITHUB_TOKEN").context("Failed accessing GITHUB_TOKEN auth token")?;
let server_url = url::Url::parse(
&std::env::var("GITHUB_SERVER_URL")
.context("Failed reading GITHUB_SERVER_URL server url")?,
)
.context("Failed parsing GITHUB_SERVER_URL as a url")?;
let repo_url = server_url
.join(&format!(
"{}/{}",
meta.issue.repository.owner, meta.issue.repository.name
))
.unwrap();
let mut repo_auth_url = repo_url.clone();
repo_auth_url.set_username("forgejo-actions").unwrap();
repo_auth_url.set_password(Some(&token)).unwrap();
let auth = forgejo_api::Auth::Token(&token);
let forgejo =
Forgejo::new(auth, server_url).context("Could not create Forgejo 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,
};
if meta.action == event_meta::IssueAction::Opened {
log::debug!("Running for a newly opened issue. Taking care of the comment first...");
match issue::find_or_make_bot_comment(&ctx, meta.issue.number).await {
Ok((_comment, is_new)) => {
if is_new {
log::info!(
"Waiting for a minute, as the issue is brand new. We will likely catch some more updates this way!"
);
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
}
}
Err(err) => {
log::warn!(
"Error while commenting on new issue #{}, continuing anyway... Error: {err:?}",
meta.issue.number
);
}
}
}
log::info!("Collecting tree from issue metadata...");
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...");
let rendered_tree = render::render(&ctx, &tree)
.await
.context("Failed to render the techtree")?;
render::publish(&ctx, &rendered_tree)
.await
.context("Failed to publish rendered tree to git")?;
// Wiki is disabled because the tree is too big for mermaid to handle
if false {
log::info!("Updating the wiki overview...");
wiki::update_wiki_from_tree(&ctx, &tree)
.await
.context("Failed to update the techtree wiki page")?;
}
'issues: for issue in tree.iter_issues() {
let subtree = tree.subtree_for_issue(issue).unwrap();
let hash = subtree.stable_hash();
let (bot_comment, _is_new) = issue::find_or_make_bot_comment(&ctx, issue)
.await
.with_context(|| format!("Failed to find or make bot comment in issue #{issue}"))?;
if bot_comment.body.contains(&hash) {
log::info!("Issue #{issue} is up-to-date, not editing comment.");
issue::remove_stale_label(&ctx, issue)
.await
.with_context(|| format!("Failed to remove `Stale` label from issue #{issue}"))?;
continue 'issues;
}
log::info!("Updating bot comment in issue #{issue} ...");
issue::update_bot_comment_from_subtree(&ctx, bot_comment.id, &subtree, &hash)
.await
.with_context(|| format!("Failed to update bot comment in issue #{issue}"))?;
issue::remove_stale_label(&ctx, issue)
.await
.with_context(|| format!("Failed to remove `Stale` label from issue #{issue}"))?;
}
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
run().await
}