toggl-bridge/src/main.rs
2025-02-01 19:19:36 +00:00

138 lines
3.4 KiB
Rust

use axum::response::IntoResponse;
use sqlx::{Connection, PgPool};
use toggl::TogglApi;
use worker::Worker;
mod server;
mod toggl;
mod worker;
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error("Database error: {0}")]
SqlxError(#[from] sqlx::Error),
#[error("Toggl error: {0}")]
TogglError(#[from] toggl::TogglError),
#[error("User modified since time delta is too large. Max allowed is 90 days.")]
LookBackTooLarge,
#[error("IO error: {0}")]
IO(#[from] std::io::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
let status = match &self {
AppError::SqlxError(_) => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
AppError::TogglError(_) => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
AppError::LookBackTooLarge => axum::http::StatusCode::BAD_REQUEST,
AppError::IO(_) => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
};
(status, self.to_string()).into_response()
}
}
struct TableSummary {
client_ids: Vec<u64>,
workspace_ids: Vec<u64>,
project_ids: Vec<u64>,
tag_ids: Vec<u64>,
}
use chrono::{NaiveDate, TimeDelta};
use clap::{Parser, Subcommand};
use std::net::IpAddr;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(long, env = "DATABASE_URL")]
database_url: String,
#[arg(long, env = "API_TOKEN")]
api_token: String,
#[arg(long, env = "DEFAULT_WORKSPACE_ID")]
default_workspace_id: u64,
}
#[derive(Subcommand)]
enum Commands {
/// Start the built-in HTTP server
Server {
#[arg(long, env = "IP", default_value = "127.0.0.1")]
ip: IpAddr,
#[arg(long, env = "PORT", default_value = "3000")]
port: u16,
},
/// Migrate the database without performing any other actions
Migrate,
/// Sync changes since the database was last updated, with a default look-back of 30 days
Sync,
/// Fetch time entries within a date range
Fetch {
since: NaiveDate,
until: NaiveDate,
},
}
#[tokio::main]
async fn main() {
if dotenvy::dotenv().is_ok() {
println!("Loaded .env file");
}
// Init tracing
tracing_subscriber::fmt::init();
let cli = Cli::parse();
let toggl_api = TogglApi::new(&cli.api_token, cli.default_workspace_id);
let mut db = PgPool::connect(&cli.database_url).await.unwrap();
sqlx::migrate!("./migrations")
.run(&db)
.await
.expect("Failed to run migrations");
// Return early if we are just migrating
if let Commands::Migrate = cli.command {
return;
}
let worker = Worker { db, toggl_api };
match cli.command {
Commands::Server { ip, port } => {
server::serve(worker, ip, port)
.await
.expect("Failed to start server");
}
Commands::Sync => {
worker
.update(TimeDelta::days(30))
.await
.expect("Failed to update worker");
}
Commands::Fetch { since, until } => {
worker
.fetch_within(since, until)
.await
.expect("Failed to fetch worker");
}
Commands::Migrate => unreachable!("Migrate should have returned early"),
}
}