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 { 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, workspace_ids: Vec, project_ids: Vec, tag_ids: Vec, } use chrono::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 { Server { #[arg(long, env = "IP", default_value = "127.0.0.1")] ip: IpAddr, #[arg(long, env = "PORT", default_value = "3000")] port: u16, }, Migrate, Sync, } #[tokio::main] async fn main() { dotenvy::dotenv().expect("Failed to load .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 }; if let Commands::Server { ip, port } = cli.command { server::serve(worker, ip, port) .await .expect("Failed to start server"); } else { worker .update(TimeDelta::days(30)) .await .expect("Failed to update worker"); } }