use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
use std::pin::Pin;
use urlencoding::encode;
use super::{repo::CodebergRepo, CODEBERG_URL};
use crate::{
errors::GitMoverError,
platform::{Platform, PlatformType},
utils::Repo,
};
#[derive(Default, Debug, Clone)]
pub struct CodebergPlatform {
username: String,
token: String,
client: reqwest::Client,
}
impl CodebergPlatform {
pub fn new(username: String, token: String) -> Self {
Self {
username,
token,
client: reqwest::Client::new(),
}
}
}
impl Platform for CodebergPlatform {
fn get_remote_url(&self) -> String {
CODEBERG_URL.to_string()
}
fn get_username(&self) -> &str {
&self.username
}
fn get_type(&self) -> PlatformType {
PlatformType::Codeberg
}
fn create_repo(
&self,
repo: Repo,
) -> Pin<Box<dyn std::future::Future<Output = Result<(), GitMoverError>> + Send + '_>> {
let token = self.token.clone();
let repo_name = repo.name.clone();
let description = repo.description.clone();
let private = repo.private;
let client = self.client.clone();
Box::pin(async move {
let url = format!("https://{CODEBERG_URL}/api/v1/user/repos");
let json_body = CodebergRepo {
name: repo_name.clone(),
description: description.clone(),
private,
fork: false, };
let request = client
.post(url)
.header(AUTHORIZATION, format!("token {token}"))
.header(ACCEPT, "application/json")
.header(CONTENT_TYPE, "application/json")
.json(&json_body)
.send();
let response = request.await?;
if !response.status().is_success() {
let text = response.text().await?;
let get_repo = match self.get_repo(repo_name.as_str()).await {
Ok(repo) => repo,
Err(e) => {
return Err(GitMoverError::new(format!(
"{text} for {}: {e}",
PlatformType::Codeberg
)));
}
};
let json_body_as_repo = json_body.clone().into();
if get_repo == json_body_as_repo {
return Ok(());
}
eprintln!(
"Repository already exists with different configuration\n{get_repo:?}\n{json_body_as_repo:?}"
);
return self.edit_repo(json_body_as_repo).await;
}
Ok(())
})
}
fn get_repo(
&self,
repo_name: &str,
) -> Pin<Box<dyn std::future::Future<Output = Result<Repo, GitMoverError>> + Send + '_>> {
let token = self.token.clone();
let repo_name = repo_name.to_string();
let client = self.client.clone();
Box::pin(async move {
let url = format!(
"https://{}/api/v1/repos/{}/{}",
CODEBERG_URL,
self.get_username(),
encode(&repo_name)
);
let request = client
.get(&url)
.header(AUTHORIZATION, format!("token {token}"))
.header(ACCEPT, "application/json")
.header(CONTENT_TYPE, "application/json")
.send();
let response = request.await?;
if !response.status().is_success() {
let text = response.text().await?;
return Err(GitMoverError::new(format!(
"{text} for {}",
PlatformType::Codeberg
)));
}
let repo: CodebergRepo = response.json().await?;
Ok(repo.into())
})
}
fn edit_repo(
&self,
repo: Repo,
) -> Pin<Box<dyn std::future::Future<Output = Result<(), GitMoverError>> + Send + '_>> {
let repo = repo.clone();
let token = self.token.clone();
let client = self.client.clone();
Box::pin(async move {
let url = format!(
"https://{}/api/v1/repos/{}/{}",
CODEBERG_URL,
self.get_username(),
encode(&repo.name)
);
let json_body = CodebergRepo {
name: repo.name.clone(),
description: repo.description.clone(),
private: repo.private,
fork: repo.fork, };
let request = client
.patch(url)
.header(AUTHORIZATION, format!("token {token}"))
.header(ACCEPT, "application/json")
.header(CONTENT_TYPE, "application/json")
.json(&json_body)
.send();
let response = request.await?;
if !response.status().is_success() {
let text = response.text().await?;
return Err(GitMoverError::new(format!(
"{text} for {}",
PlatformType::Codeberg
)));
}
Ok(())
})
}
fn get_all_repos(
&self,
) -> Pin<Box<dyn std::future::Future<Output = Result<Vec<Repo>, GitMoverError>> + Send>> {
let token = self.token.clone();
let client = self.client.clone();
Box::pin(async move {
let url = format!("https://{CODEBERG_URL}/api/v1/user/repos");
let mut page: usize = 1;
let limit = 100;
let mut all_repos = Vec::new();
loop {
let request = client
.get(&url)
.header(AUTHORIZATION, format!("token {token}"))
.header(ACCEPT, "application/json")
.query(&[("page", &page.to_string()), ("limit", &limit.to_string())])
.send();
let response = request.await?;
if !response.status().is_success() {
let text = response.text().await?;
return Err(GitMoverError::new(format!(
"{text} for {}",
PlatformType::Codeberg
)));
}
let text = response.text().await?;
let repos: Vec<CodebergRepo> = serde_json::from_str(&text)?;
let mut page_repos: Vec<Repo> =
repos.into_iter().map(std::convert::Into::into).collect();
if page_repos.is_empty() {
break;
}
println!("Requested codeberg (page {}): {}", page, page_repos.len());
all_repos.append(&mut page_repos);
page += 1;
}
Ok(all_repos)
})
}
fn delete_repo(
&self,
name: &str,
) -> Pin<Box<dyn std::future::Future<Output = Result<(), GitMoverError>> + Send + '_>> {
let token = self.token.clone();
let name = name.to_string();
let client = self.client.clone();
Box::pin(async move {
let url = format!(
"https://{}/api/v1/repos/{}/{}",
CODEBERG_URL,
self.get_username(),
encode(&name)
);
let request = client
.delete(&url)
.header(AUTHORIZATION, format!("token {token}"))
.header(ACCEPT, "application/json")
.send();
let response = request.await?;
if !response.status().is_success() {
let text = response.text().await?;
return Err(GitMoverError::new(format!(
"{text} for {}",
PlatformType::Codeberg
)));
}
Ok(())
})
}
}