use crate::libs::config::ConfigModule;
use crate::libs::messages::Message;
use crate::{msg_error, msg_print};
use anyhow::Result;
use chrono::{Duration, Local};
use dialoguer::{theme::ColorfulTheme, Input};
use reqwest::Client;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct GitLab {
client: Client,
config: GitLabConfig,
}
#[derive(Debug, Deserialize)]
struct Event {
action_name: String,
push_data: Option<PushData>,
project_id: u32,
}
#[derive(Debug, Deserialize)]
struct PushData {
commit_to: Option<String>,
}
#[derive(Debug)]
pub struct CommitInfo {
pub sha: String,
pub message: String,
}
#[derive(Debug, Deserialize)]
struct Commit {
id: String,
message: String,
}
#[derive(Debug, Deserialize)]
struct User {
id: u32,
}
impl GitLab {
pub fn new(config: &GitLabConfig) -> Self {
Self {
client: Client::new(),
config: config.clone(),
}
}
pub async fn get_user_id(&self) -> Result<u32> {
let url = format!("{}/api/v4/user", self.config.api_url);
let response = self.client.get(&url).header("PRIVATE-TOKEN", &self.config.access_token).send().await?;
Ok(response.json::<User>().await?.id)
}
pub async fn get_today_commits(&self) -> Result<Vec<CommitInfo>> {
let today = Local::now();
let yesterday = (today - Duration::days(1)).format("%Y-%m-%d").to_string();
let tomorrow = (today + Duration::days(1)).format("%Y-%m-%d").to_string();
let user_id = match self.get_user_id().await {
Ok(id) => id,
Err(e) => {
msg_error!(Message::GitlabUserIdFailed(e.to_string()));
return Ok(Vec::new()); }
};
let url = format!(
"{}/api/v4/users/{}/events?after={}&before={}",
self.config.api_url, user_id, yesterday, tomorrow
);
let response = match self.client.get(&url).header("PRIVATE-TOKEN", &self.config.access_token).send().await {
Ok(res) => res,
Err(e) => {
msg_error!(Message::GitlabFetchFailed(e.to_string()));
return Ok(Vec::new()); }
};
let events = match response.json::<Vec<Event>>().await {
Ok(ev) => ev,
Err(e) => {
msg_error!(Message::GitlabFetchFailed(e.to_string()));
return Ok(Vec::new()); }
};
let mut commits_info = Vec::new();
for event in events {
if event.action_name == "pushed to" {
if let Some(push_data) = event.push_data {
if let Some(commit_to) = push_data.commit_to {
let commit_detail = match self.get_commit_detail(event.project_id, &commit_to).await {
Ok(detail) => detail,
Err(_) => continue, };
let clean_message = commit_detail
.message
.split_once('\n') .map(|(part, _)| part) .unwrap_or(&commit_detail.message) .to_string();
commits_info.push(CommitInfo {
sha: commit_detail.id,
message: clean_message,
});
}
}
}
}
Ok(commits_info)
}
async fn get_commit_detail(&self, project_id: u32, commit_sha: &str) -> Result<Commit> {
let url = format!("{}/api/v4/projects/{}/repository/commits/{}", self.config.api_url, project_id, commit_sha);
let response = self.client.get(&url).header("PRIVATE-TOKEN", &self.config.access_token).send().await?;
Ok(response.json::<Commit>().await?)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GitLabConfig {
pub access_token: String,
pub api_url: String,
}
impl GitLabConfig {
pub fn module() -> ConfigModule {
ConfigModule {
key: "gitlab".to_string(),
name: "GitLab".to_string(),
}
}
pub fn init(config: &Option<GitLabConfig>) -> Result<Self> {
let config = config.clone().unwrap_or(Self {
access_token: "".to_string(),
api_url: "".to_string(),
});
msg_print!(Message::ConfigModuleGitLab);
Ok(Self {
access_token: Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter your GitLab private token")
.default(config.access_token)
.interact_text()?,
api_url: Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter the GitLab API URL")
.default(config.api_url)
.interact_text()?,
})
}
}