Add pull request and repository browsing

This commit is contained in:
Georg Bauer
2026-07-30 16:12:22 +02:00
parent 73b6882b47
commit c7de716b8e
12 changed files with 2793 additions and 975 deletions

100
crates/app/src/storage.rs Normal file
View File

@@ -0,0 +1,100 @@
use std::{env, fs, path::PathBuf};
use gotcha_gitea::Client;
use security_framework::passwords::{get_generic_password, set_generic_password};
use crate::domain::{Preferences, Server, open_status};
pub fn favorite_key(server: &str, owner: &str, repository: &str) -> String {
format!("{server}|{owner}/{repository}")
}
pub fn validate_server(name: &str, url: &str, token: &str) -> Result<Server, String> {
let name = name.trim();
let url = url.trim().trim_end_matches('/');
let token = token.trim();
if name.is_empty() {
return Err("Give this server a name.".into());
}
if token.is_empty() {
return Err("Enter an access token.".into());
}
Client::new(url, Some(token)).map_err(|error| error.to_string())?;
Ok(Server {
name: name.into(),
url: url.into(),
token: token.into(),
})
}
pub fn load_preferences() -> Result<Preferences, String> {
let path = preferences_path()?;
if !path.exists() {
return Ok(Preferences::default());
}
let mut preferences: Preferences =
serde_json::from_slice(&fs::read(&path).map_err(|error| error.to_string())?)
.map_err(|error| format!("Cannot read {}: {error}", path.display()))?;
if !matches!(preferences.issue_status.as_str(), "open" | "closed") {
preferences.issue_status = open_status();
}
if !matches!(preferences.pull_status.as_str(), "open" | "closed") {
preferences.pull_status = open_status();
}
for server in &mut preferences.servers {
server.token = String::from_utf8(
get_generic_password("de.rfc1437.gotcha", &keychain_account(server))
.map_err(|error| format!("Cannot read the token for {}: {error}", server.name))?,
)
.map_err(|_| format!("The token for {} is not valid text.", server.name))?;
}
Ok(preferences)
}
pub fn save_preferences(preferences: &Preferences) -> Result<(), String> {
let path = preferences_path()?;
let parent = path.parent().ok_or("Invalid app data directory.")?;
fs::create_dir_all(parent).map_err(|error| error.to_string())?;
let temporary = path.with_extension("tmp");
fs::write(
&temporary,
serde_json::to_vec(preferences).map_err(|error| error.to_string())?,
)
.map_err(|error| error.to_string())?;
fs::rename(&temporary, &path).map_err(|error| error.to_string())
}
pub fn save_server_token(server: &Server) -> Result<(), String> {
set_generic_password(
"de.rfc1437.gotcha",
&keychain_account(server),
server.token.as_bytes(),
)
.map_err(|error| format!("Cannot save the token for {}: {error}", server.name))
}
fn preferences_path() -> Result<PathBuf, String> {
let home = env::var_os("HOME").ok_or("Cannot find the app data directory.")?;
Ok(PathBuf::from(home).join("Library/Application Support/Gotcha/preferences.json"))
}
fn keychain_account(server: &Server) -> String {
format!("{}|{}", server.name, server.url)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_servers_and_builds_stable_favorite_keys() {
assert!(validate_server("Work", "https://gitea.example.com/", "secret").is_ok());
assert!(validate_server("", "https://gitea.example.com", "secret").is_err());
assert!(validate_server("Work", "file:///tmp/gitea", "secret").is_err());
assert_eq!(
favorite_key("https://gitea.example.com", "octo", "demo"),
"https://gitea.example.com|octo/demo"
);
assert_eq!(Preferences::default().pull_status, "open");
}
}