Files
Gotcha/crates/app/src/api.rs
2026-08-03 22:06:32 +02:00

455 lines
11 KiB
Rust

use gotcha_gitea::{
Client, DEFAULT_PAGE_SIZE as PAGE_SIZE, IssueQuery, RepositoryId, api_date, diff, models,
};
use crate::{
domain::{
HistoryCommit, HomeData, IssueDetails, IssueDraft, IssueEditorData, IssueFilter,
MilestoneDetails, MilestoneDraft, Page, PullDetails, RepositoryData, Server,
},
presentation::compact_date,
};
pub async fn load_repositories(server: &Server, page: i32) -> Result<Page<RepositoryData>, String> {
let page = client(server)?
.owned_repositories_page(page, PAGE_SIZE)
.await
.map_err(message)?;
Ok(Page {
has_more: page.has_more,
items: page
.items
.into_iter()
.filter_map(|repository| {
Some(RepositoryData {
owner: repository.owner?.login?,
name: repository.name?,
description: repository
.description
.filter(|text| !text.is_empty())
.unwrap_or_else(|| "No description".into()),
language: repository
.language
.filter(|text| !text.is_empty())
.unwrap_or_else(|| "Unknown language".into()),
open_issues: repository.open_issues_count.unwrap_or_default(),
updated: compact_date(repository.updated_at.as_deref()),
default_branch: repository.default_branch.unwrap_or_else(|| "main".into()),
})
})
.collect(),
})
}
pub async fn load_issues(
server: &Server,
owner: &str,
repository: &str,
status: &str,
filter: &IssueFilter,
page: i32,
) -> Result<Page<models::Issue>, String> {
client(server)?
.issues(
&scope(owner, repository)?,
&IssueQuery {
state: status.into(),
labels: (!filter.labels.is_empty())
.then(|| filter.labels.iter().cloned().collect::<Vec<_>>().join(",")),
milestones: (!filter.milestone.is_empty()).then(|| filter.milestone.clone()),
keyword: (!filter.search_text.is_empty()).then(|| filter.search_text.clone()),
page,
limit: PAGE_SIZE,
..Default::default()
},
)
.await
.map_err(message)
}
pub async fn load_milestones_page(
server: &Server,
owner: &str,
repository: &str,
page: i32,
) -> Result<Page<models::Milestone>, String> {
client(server)?
.milestones_page(&scope(owner, repository)?, page, PAGE_SIZE)
.await
.map_err(message)
}
pub async fn load_labels(
server: &Server,
owner: &str,
repository: &str,
) -> Result<Vec<models::Label>, String> {
client(server)?
.labels(&scope(owner, repository)?)
.await
.map_err(message)
}
pub async fn load_milestones(
server: &Server,
owner: &str,
repository: &str,
) -> Result<Vec<models::Milestone>, String> {
client(server)?
.milestones(&scope(owner, repository)?)
.await
.map_err(message)
}
pub async fn load_milestone(
server: &Server,
owner: &str,
repository: &str,
id: i64,
page: i32,
) -> Result<MilestoneDetails, String> {
client(server)?
.milestone_details(&scope(owner, repository)?, id, page)
.await
.map_err(message)
}
pub async fn load_milestone_editor(
server: &Server,
owner: &str,
repository: &str,
id: i64,
) -> Result<models::Milestone, String> {
client(server)?
.milestone(&scope(owner, repository)?, id)
.await
.map_err(message)
}
pub async fn save_milestone(
server: &Server,
owner: &str,
repository: &str,
id: Option<i64>,
draft: MilestoneDraft,
) -> Result<models::Milestone, String> {
client(server)?
.save_milestone(
&scope(owner, repository)?,
id,
gotcha_gitea::MilestoneDraft {
title: draft.title,
description: draft.description,
due_on: draft.due_date.map(api_date),
state: if draft.closed { "closed" } else { "open" }.into(),
},
)
.await
.map_err(message)
}
pub async fn set_milestone_closed(
server: &Server,
owner: &str,
repository: &str,
id: i64,
closed: bool,
) -> Result<(), String> {
client(server)?
.set_milestone_closed(&scope(owner, repository)?, id, closed)
.await
.map(|_| ())
.map_err(message)
}
pub async fn delete_milestone(
server: &Server,
owner: &str,
repository: &str,
id: i64,
) -> Result<(), String> {
client(server)?
.delete_milestone(&scope(owner, repository)?, &id.to_string())
.await
.map_err(message)
}
pub async fn load_pulls(
server: &Server,
status: &str,
milestone: &str,
search_text: &str,
page: i32,
) -> Result<Page<models::Issue>, String> {
client(server)?
.search_pulls(
status,
(!milestone.is_empty()).then_some(milestone),
(!search_text.is_empty()).then_some(search_text),
page,
PAGE_SIZE,
)
.await
.map_err(message)
}
pub async fn load_pull_milestones(server: &Server) -> Result<Vec<String>, String> {
client(server)?.pull_milestones().await.map_err(message)
}
pub async fn load_branches(
server: &Server,
owner: &str,
repository: &str,
default_branch: &str,
) -> Result<Vec<String>, String> {
client(server)?
.branches(&scope(owner, repository)?, default_branch)
.await
.map_err(message)
}
pub async fn load_repository_contents(
server: &Server,
owner: &str,
repository: &str,
path: &str,
) -> Result<Vec<models::ContentsResponse>, String> {
client(server)?
.repository_contents(&scope(owner, repository)?, path)
.await
.map_err(message)
}
pub async fn load_repository_file(
server: &Server,
owner: &str,
repository: &str,
path: &str,
) -> Result<Vec<u8>, String> {
client(server)?
.repository_file(&scope(owner, repository)?, path)
.await
.map_err(message)
}
pub async fn load_branch_commits(
server: &Server,
owner: &str,
repository: &str,
branch: &str,
path: Option<&str>,
pages: u32,
) -> Result<Page<HistoryCommit>, String> {
client(server)?
.branch_history(&scope(owner, repository)?, branch, path, pages)
.await
.map_err(message)
}
pub async fn load_all_commits(
server: &Server,
owner: &str,
repository: &str,
branches: &[String],
path: Option<&str>,
pages: u32,
) -> Result<Page<HistoryCommit>, String> {
client(server)?
.all_history(&scope(owner, repository)?, branches, path, pages)
.await
.map_err(message)
}
pub async fn load_issue(
server: &Server,
owner: &str,
repository: &str,
number: i64,
_page: i32,
) -> Result<IssueDetails, String> {
client(server)?
.issue_details(&scope(owner, repository)?, number)
.await
.map_err(message)
}
pub async fn save_issue_comment(
server: &Server,
owner: &str,
repository: &str,
number: i64,
comment_id: Option<i64>,
body: String,
) -> Result<(), String> {
client(server)?
.save_issue_comment(&scope(owner, repository)?, number, comment_id, body)
.await
.map(|_| ())
.map_err(message)
}
pub async fn load_issue_editor(
server: &Server,
owner: &str,
repository: &str,
number: Option<i64>,
) -> Result<IssueEditorData, String> {
client(server)?
.issue_editor(&scope(owner, repository)?, number)
.await
.map_err(message)
}
pub async fn create_issue(
server: &Server,
owner: &str,
repository: &str,
draft: IssueDraft,
) -> Result<models::Issue, String> {
client(server)?
.create_issue_draft(&scope(owner, repository)?, shared_issue_draft(draft))
.await
.map_err(message)
}
pub async fn edit_issue(
server: &Server,
owner: &str,
repository: &str,
number: i64,
draft: IssueDraft,
) -> Result<models::Issue, String> {
client(server)?
.edit_issue_draft(
&scope(owner, repository)?,
number,
shared_issue_draft(draft),
)
.await
.map_err(message)
}
pub async fn set_issue_closed(
server: &Server,
owner: &str,
repository: &str,
number: i64,
closed: bool,
) -> Result<(), String> {
client(server)?
.set_issue_state(
&scope(owner, repository)?,
&[number],
if closed { "closed" } else { "open" },
)
.await
.map(|_| ())
.map_err(message)
}
pub async fn delete_issue(
server: &Server,
owner: &str,
repository: &str,
number: i64,
) -> Result<(), String> {
client(server)?
.delete_issue(&scope(owner, repository)?, number)
.await
.map_err(message)
}
pub async fn load_pull(
server: &Server,
owner: &str,
repository: &str,
number: i64,
page: i32,
) -> Result<PullDetails, String> {
client(server)?
.pull_details(&scope(owner, repository)?, number, page)
.await
.map_err(message)
}
pub async fn load_commit(
server: &Server,
owner: &str,
repository: &str,
sha: &str,
) -> Result<models::Commit, String> {
client(server)?
.commit(&scope(owner, repository)?, sha)
.await
.map_err(message)
}
pub async fn load_commit_diff(
server: &Server,
owner: &str,
repository: &str,
sha: &str,
path: &str,
) -> Result<diff::Parsed, String> {
let client = client(server)?;
let diff = client
.commit_diff(&scope(owner, repository)?, sha)
.await
.map_err(message)?;
Ok(diff::parse_file(&diff, path))
}
pub async fn load_pull_diff(
server: &Server,
owner: &str,
repository: &str,
number: i64,
path: &str,
) -> Result<diff::Parsed, String> {
let client = client(server)?;
let diff = client
.pull_diff(&scope(owner, repository)?, number)
.await
.map_err(message)?;
Ok(diff::parse_file(&diff, path))
}
pub async fn load_home(
server: &Server,
page: i32,
filter: gotcha_gitea::ActivityFilter,
) -> Result<HomeData, String> {
client(server)?.home(page, filter).await.map_err(message)
}
pub async fn load_activities(server: &Server, page: i32) -> Result<Page<models::Activity>, String> {
client(server)?
.activities(page, gotcha_gitea::ActivityFilter::All)
.await
.map_err(message)
}
fn client(server: &Server) -> Result<Client, String> {
Client::with_provider(&server.url, Some(&server.token), server.provider).map_err(message)
}
fn scope(owner: &str, repository: &str) -> Result<RepositoryId, String> {
RepositoryId::new(owner, repository).map_err(message)
}
fn shared_issue_draft(draft: IssueDraft) -> gotcha_gitea::IssueDraft {
gotcha_gitea::IssueDraft {
title: draft.title,
body: draft.body,
label_ids: draft.label_ids,
milestone_id: draft.milestone_id,
due_date: draft.due_date.map(api_date),
closed: draft.closed,
}
}
fn message(error: impl std::fmt::Display) -> String {
error.to_string()
}