use crate::{
error::TempomatError,
jira::{self, AtlassianTokens},
tempo::oauth::{actions as tempo_actions, TempoAccessTokens},
};
use chrono::{Duration, NaiveDateTime, Utc};
use colored::Colorize;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
fs,
path::{Path, PathBuf},
};
use tracing::debug;
const AUTH_FILENAME: &str = "auth.ron";
const CONFIG_FILENAME: &str = "config.ron";
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
pub atlassian_instance: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TempoAccessMetadata {
pub last_refresh: NaiveDateTime,
pub tokens: TempoAccessTokens,
}
#[derive(Deserialize, Serialize)]
pub struct APITokens {
pub jira: AtlassianTokens,
pub tempo: TempoAccessMetadata,
}
impl APITokens {
pub async fn initialize(config: &Config) -> Result<Self, TempomatError> {
println!("Getting Tempo tokens...");
let tempo = tempo_actions::login(config).await?.into();
println!("Getting Jira tokens...");
let jira = jira::get_token()?;
println!("{}", "Successfully got access tokens!".green());
Ok(Self { tempo, jira })
}
pub async fn refresh_tokens(&mut self) -> Result<bool, TempomatError> {
if (Utc::now().naive_utc() - self.tempo.last_refresh)
> Duration::seconds(self.tempo.tokens.expires_in as i64)
{
debug!("Token expired, getting new tokens...");
let tokens = tempo_actions::refresh_token(&self.tempo.tokens).await?;
self.tempo = tokens.into();
Ok(true)
} else {
debug!("Tokens not expired, not doing anything");
Ok(false)
}
}
}
impl Saveable for APITokens {
fn path(root: &Path) -> PathBuf {
root.join(AUTH_FILENAME)
}
}
impl Saveable for Config {
fn path(root: &Path) -> PathBuf {
root.join(CONFIG_FILENAME)
}
}
pub trait Saveable: Serialize + DeserializeOwned {
fn path(root: &Path) -> PathBuf;
fn save(&self, root: &Path) -> Result<(), TempomatError> {
let path = Self::path(root);
fs::write(path, ron::to_string(self)?)?;
Ok(())
}
fn try_read(root: &Path) -> Result<Option<Self>, TempomatError> {
let path = Self::path(root);
let Ok(config) = fs::read_to_string(path) else { return Ok(None) };
let config = ron::from_str(&config)?;
Ok(Some(config))
}
}
impl From<TempoAccessTokens> for TempoAccessMetadata {
fn from(tokens: TempoAccessTokens) -> Self {
Self {
tokens,
last_refresh: Utc::now().naive_utc(),
}
}
}