138 lines
3.4 KiB
Rust
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"),
|
|
}
|
|
}
|