pub mod gitea;
pub mod github;
use crate::db::Db;
use crate::db::git_providers::{self, StoredProvider};
use crate::secrets::SecretsManager;
use anyhow::{Context, Result};
use shared::{GitAccount, GitBranch, GitProviderKind, GitRepo};
fn kind_of(p: &StoredProvider) -> GitProviderKind {
GitProviderKind::from_str(&p.kind).unwrap_or(GitProviderKind::Gitea)
}
pub struct OAuthTokens {
pub access_token: String,
pub refresh_token: Option<String>,
}
pub async fn current_user(
kind: GitProviderKind,
base_url: &str,
token: &str,
) -> Result<GitAccount> {
match kind {
GitProviderKind::Gitea => gitea::current_user(base_url, token).await,
GitProviderKind::Github => github::current_user(base_url, token).await,
}
}
pub async fn list_repos(
kind: GitProviderKind,
base_url: &str,
token: &str,
) -> Result<Vec<GitRepo>> {
match kind {
GitProviderKind::Gitea => gitea::list_repos(base_url, token).await,
GitProviderKind::Github => github::list_repos(base_url, token).await,
}
}
pub async fn list_branches(
kind: GitProviderKind,
base_url: &str,
token: &str,
repo_full_name: &str,
) -> Result<Vec<GitBranch>> {
match kind {
GitProviderKind::Gitea => gitea::list_branches(base_url, token, repo_full_name).await,
GitProviderKind::Github => github::list_branches(base_url, token, repo_full_name).await,
}
}
pub async fn exchange_code(
kind: GitProviderKind,
base_url: &str,
client_id: &str,
client_secret: &str,
code: &str,
redirect_uri: &str,
) -> Result<OAuthTokens> {
match kind {
GitProviderKind::Gitea => {
let t = gitea::exchange_code(base_url, client_id, client_secret, code, redirect_uri)
.await?;
Ok(OAuthTokens {
access_token: t.access_token,
refresh_token: t.refresh_token,
})
}
GitProviderKind::Github => {
let t = github::exchange_code(base_url, client_id, client_secret, code, redirect_uri)
.await?;
Ok(OAuthTokens {
access_token: t.access_token,
refresh_token: t.refresh_token,
})
}
}
}
pub async fn ensure_redirect_uri(
kind: GitProviderKind,
base_url: &str,
token: &str,
client_id: &str,
redirect_uri: &str,
) -> Result<()> {
match kind {
GitProviderKind::Gitea => {
gitea::ensure_redirect_uri(base_url, token, client_id, redirect_uri).await
}
GitProviderKind::Github => {
github::ensure_redirect_uri(base_url, token, client_id, redirect_uri).await
}
}
}
pub fn authorize_url(
kind: GitProviderKind,
base_url: &str,
client_id_enc: &str,
redirect_uri_enc: &str,
state_enc: &str,
) -> String {
match kind {
GitProviderKind::Gitea => format!(
"{}/login/oauth/authorize?client_id={}&redirect_uri={}&response_type=code&state={}",
base_url.trim_end_matches('/'),
client_id_enc,
redirect_uri_enc,
state_enc,
),
GitProviderKind::Github => {
github::authorize_url(base_url, client_id_enc, redirect_uri_enc, state_enc)
}
}
}
pub fn callback_path_segment(kind: GitProviderKind) -> &'static str {
kind.as_str()
}
pub fn usable_token(secrets: &SecretsManager, p: &StoredProvider) -> Result<String> {
let enc = p
.access_token_enc
.as_deref()
.context("provider Git não conectado (sem token)")?;
secrets.decrypt(enc)
}
pub async fn refresh_access_token(
db: &Db,
secrets: &SecretsManager,
p: &StoredProvider,
) -> Option<String> {
if p.auth_mode != shared::GitAuthMode::OAuth.as_str() {
return None;
}
let client_id = p.oauth_client_id.as_deref()?;
let client_secret = secrets
.decrypt(p.oauth_client_secret_enc.as_deref()?)
.ok()?;
let refresh = secrets.decrypt(p.refresh_token_enc.as_deref()?).ok()?;
let tokens = match kind_of(p) {
GitProviderKind::Gitea => {
let t = gitea::refresh(&p.base_url, client_id, &client_secret, &refresh)
.await
.ok()?;
OAuthTokens {
access_token: t.access_token,
refresh_token: t.refresh_token,
}
}
GitProviderKind::Github => {
let t = github::refresh(&p.base_url, client_id, &client_secret, &refresh)
.await
.ok()?;
OAuthTokens {
access_token: t.access_token,
refresh_token: t.refresh_token,
}
}
};
let access_enc = secrets.encrypt(&tokens.access_token).ok()?;
let refresh_enc = tokens
.refresh_token
.as_deref()
.and_then(|r| secrets.encrypt(r).ok());
git_providers::set_tokens(
db,
&p.id,
Some(&access_enc),
refresh_enc.as_deref(),
p.account_login.as_deref().unwrap_or_default(),
p.account_avatar.as_deref(),
)
.await
.ok()?;
Some(tokens.access_token)
}