a73x

src/server/http/repo/issues.rs

Ref:   Size: 3.7 KiB

use std::sync::Arc;

use axum::extract::{Path, State};
use axum::response::{IntoResponse, Response};

use super::{AppState, CommentView, collab_counts, internal_error, not_found, open_repo};

#[derive(Debug)]
pub struct IssueListItem {
    pub id: String,
    pub short_id: String,
    pub status: String,
    pub title: String,
    pub author: String,
    pub labels: String,
    pub updated: String,
}

#[derive(Debug)]
pub struct IssueDetailView {
    pub title: String,
    pub body: String,
    pub status: String,
    pub author: String,
    pub labels: String,
    pub assignees: String,
    pub close_reason: Option<String>,
    pub comments: Vec<CommentView>,
}

#[derive(askama::Template, askama_web::WebTemplate)]
#[template(path = "issues.html")]
pub struct IssuesTemplate {
    pub site_title: String,
    pub repo_name: String,
    pub active_section: String,
    pub open_patches: usize,
    pub open_issues: usize,
    pub issues: Vec<IssueListItem>,
}

#[derive(askama::Template, askama_web::WebTemplate)]
#[template(path = "issue_detail.html")]
pub struct IssueDetailTemplate {
    pub site_title: String,
    pub repo_name: String,
    pub active_section: String,
    pub open_patches: usize,
    pub open_issues: usize,
    pub issue: IssueDetailView,
}

pub async fn issues(
    Path(repo_name): Path<String>,
    State(state): State<Arc<AppState>>,
) -> Response {
    let (_entry, repo) = match open_repo(&state, &repo_name) {
        Ok(r) => r,
        Err(resp) => return resp,
    };

    let (open_patches, open_issues) = collab_counts(&repo);

    let all_issues = git_collab::state::list_issues_with_archived(&repo).unwrap_or_default();

    let issues = all_issues
        .into_iter()
        .map(|i| {
            let id = i.id.clone();
            let short_id = id[..8.min(id.len())].to_string();
            IssueListItem {
                short_id,
                id,
                status: i.status.as_str().to_string(),
                title: i.title,
                author: i.author.name,
                labels: i.labels.join(", "),
                updated: i.last_updated,
            }
        })
        .collect();

    IssuesTemplate {
        site_title: state.site_title.clone(),
        repo_name,
        active_section: "issues".to_string(),
        open_patches,
        open_issues,
        issues,
    }
    .into_response()
}

pub async fn issue_detail(
    Path((repo_name, issue_id)): Path<(String, String)>,
    State(state): State<Arc<AppState>>,
) -> Response {
    let (_entry, repo) = match open_repo(&state, &repo_name) {
        Ok(r) => r,
        Err(resp) => return resp,
    };

    let (open_patches, open_issues) = collab_counts(&repo);

    let (ref_name, full_id) = match git_collab::state::resolve_issue_ref(&repo, &issue_id) {
        Ok(r) => r,
        Err(_) => return not_found(&state, format!("Issue '{}' not found.", issue_id)),
    };

    let is = match git_collab::state::IssueState::from_ref(&repo, &ref_name, &full_id) {
        Ok(s) => s,
        Err(_) => return internal_error(&state, "Failed to load issue state."),
    };

    let issue = IssueDetailView {
        title: is.title,
        body: is.body,
        status: is.status.as_str().to_string(),
        author: is.author.name,
        labels: is.labels.join(", "),
        assignees: is.assignees.join(", "),
        close_reason: is.close_reason,
        comments: is.comments.into_iter().map(|c| CommentView {
            author: c.author.name,
            body: c.body,
            timestamp: c.timestamp,
        }).collect(),
    };

    IssueDetailTemplate {
        site_title: state.site_title.clone(),
        repo_name,
        active_section: "issues".to_string(),
        open_patches,
        open_issues,
        issue,
    }
    .into_response()
}