Skip to main content

max / makenotwork

3.8 KB · 124 lines History Blame Raw
1 //! Issue read-only views: list and detail.
2
3 use axum::{
4 extract::{Path, Query, State},
5 response::IntoResponse,
6 };
7 use serde::Deserialize;
8 use tower_sessions::Session;
9
10 use crate::{
11 auth::MaybeUserVerified,
12 db::{self, IssueStatus},
13 error::{AppError, Result},
14 helpers::get_csrf_token,
15 templates::*,
16 AppState,
17 };
18
19 use super::default_ref;
20
21 // ── Issue List ──
22
23 #[derive(Deserialize)]
24 pub(super) struct IssueListQuery {
25 status: Option<String>,
26 search: Option<String>,
27 page: Option<i64>,
28 }
29
30 /// `GET /git/{owner}/{repo}/issues`
31 #[tracing::instrument(skip_all, name = "git_issues::list")]
32 pub(super) async fn issue_list(
33 State(state): State<AppState>,
34 session: Session,
35 MaybeUserVerified(maybe_user): MaybeUserVerified,
36 Path((owner, repo_name)): Path<(String, String)>,
37 Query(query): Query<IssueListQuery>,
38 ) -> Result<impl IntoResponse> {
39 let resolved = super::resolve_repo(&state, &owner, &repo_name, maybe_user.as_ref().map(|u| u.id)).await?;
40
41 let status_filter = match query.status.as_deref() {
42 Some("closed") => Some(IssueStatus::Closed),
43 _ => Some(IssueStatus::Open), // default to open
44 };
45 let current_status = query.status.as_deref().unwrap_or("open").to_string();
46 let search = query.search.as_deref().filter(|s| !s.is_empty());
47 let page = query.page.unwrap_or(1).max(1);
48 let per_page = 25;
49
50 let (issues, total) = db::issues::list_issues(
51 &state.db, resolved.db_repo.id, status_filter, search, page, per_page,
52 ).await?;
53
54 let total_pages = (total + per_page - 1) / per_page;
55 let (open_count, closed_count) = db::issues::get_issue_counts(&state.db, resolved.db_repo.id).await?;
56 let current_ref = default_ref(&state, &owner, &repo_name);
57 let csrf_token = get_csrf_token(&session).await;
58
59 let is_owner = maybe_user.as_ref().map(|u| u.id) == Some(resolved.db_user.id);
60 let email_address = format!("{}+{}@issues.makenot.work", owner, repo_name);
61
62 Ok(GitIssueListTemplate {
63 csrf_token,
64 session_user: maybe_user,
65 owner,
66 repo_name,
67 current_ref,
68 issues,
69 open_count,
70 closed_count,
71 current_status,
72 search_query: query.search.unwrap_or_default(),
73 current_page: page,
74 total_pages,
75 is_owner,
76 email_address,
77 })
78 }
79
80 // ── Issue Detail ──
81
82 /// `GET /git/{owner}/{repo}/issues/{number}`
83 #[tracing::instrument(skip_all, name = "git_issues::detail")]
84 pub(super) async fn issue_detail(
85 State(state): State<AppState>,
86 session: Session,
87 MaybeUserVerified(maybe_user): MaybeUserVerified,
88 Path((owner, repo_name, number)): Path<(String, String, i32)>,
89 ) -> Result<impl IntoResponse> {
90 let resolved = super::resolve_repo(&state, &owner, &repo_name, maybe_user.as_ref().map(|u| u.id)).await?;
91
92 let issue = db::issues::get_issue_by_number(&state.db, resolved.db_repo.id, number)
93 .await?
94 .ok_or(AppError::NotFound)?;
95
96 let author = db::users::get_user_by_id(&state.db, issue.author_user_id)
97 .await?
98 .ok_or(AppError::NotFound)?;
99
100 let comments = db::issues::list_comments(&state.db, issue.id).await?;
101
102 let is_owner = maybe_user.as_ref().map(|u| u.id) == Some(resolved.db_user.id);
103
104 let (open_issue_count, _) = db::issues::get_issue_counts(&state.db, resolved.db_repo.id).await?;
105 let current_ref = default_ref(&state, &owner, &repo_name);
106 let csrf_token = get_csrf_token(&session).await;
107
108 let email_address = format!("{}+{}@issues.makenot.work", owner, repo_name);
109
110 Ok(GitIssueDetailTemplate {
111 csrf_token,
112 session_user: maybe_user,
113 owner,
114 repo_name,
115 current_ref,
116 issue,
117 author_username: author.username.to_string(),
118 comments,
119 is_owner,
120 open_issue_count,
121 email_address,
122 })
123 }
124