132 lines
3.8 KiB
Rust
132 lines
3.8 KiB
Rust
mod error;
|
|
mod ingestion;
|
|
|
|
use crate::error::AppError;
|
|
use crate::ingestion::db;
|
|
use crate::ingestion::ingestion_logic::from_csv_row;
|
|
use crate::ingestion::routes::{monzo_batched_csv, monzo_batched_json, shortcuts_csv};
|
|
use axum::routing::{get, post};
|
|
use axum::{Extension, Router};
|
|
use clap::{Parser, Subcommand};
|
|
use migration::{Migrator, MigratorTrait};
|
|
use sea_orm::{ConnectionTrait, DatabaseConnection};
|
|
use std::fs::File;
|
|
use std::net::SocketAddr;
|
|
use std::path::PathBuf;
|
|
use tower_http::trace::TraceLayer;
|
|
use tracing::log::LevelFilter;
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
enum Commands {
|
|
/// Manually run database migrations.
|
|
Migrate {
|
|
/// Number of migration steps to perform. If not provided, all migrations will be run.
|
|
#[arg(long)]
|
|
steps: Option<u32>,
|
|
|
|
/// If we should perform migration down.
|
|
#[arg(long)]
|
|
down: bool,
|
|
},
|
|
|
|
/// Start web app for to the google-sheets app script
|
|
Serve {
|
|
/// If we should perform migration at startup.
|
|
#[clap(short, long, env, default_value_t = true)]
|
|
migrate: bool,
|
|
|
|
/// The server address to bind to.
|
|
#[clap(short, long, env, default_value = "0.0.0.0:3000")]
|
|
addr: SocketAddr,
|
|
},
|
|
|
|
/// Ingest a google-sheets CSV export into the database.
|
|
Csv {
|
|
/// The path of the CSV file to ingest.
|
|
csv_file: PathBuf,
|
|
|
|
/// The name of the account to ingest the CSV for.
|
|
#[clap(long, short)]
|
|
account: String,
|
|
},
|
|
}
|
|
|
|
/// Ingest and manage monzo transactions from the Monzo Plus auto-export feature
|
|
#[derive(Debug, clap::Parser)]
|
|
struct Cli {
|
|
/// URL to PostgreSQL database.
|
|
#[clap(short, long = "db", env)]
|
|
database_url: String,
|
|
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
async fn health_check(
|
|
Extension(db): Extension<DatabaseConnection>,
|
|
) -> Result<&'static str, AppError> {
|
|
db.execute_unprepared("SELECT 1").await?;
|
|
|
|
Ok("Ok")
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
tracing_subscriber::fmt::init();
|
|
|
|
let cli: Cli = Cli::parse();
|
|
let connection = sea_orm::ConnectOptions::new(&cli.database_url)
|
|
.sqlx_logging_level(LevelFilter::Debug)
|
|
.to_owned();
|
|
|
|
let connection = sea_orm::Database::connect(connection).await?;
|
|
|
|
match cli.command {
|
|
Commands::Migrate { steps, down } => {
|
|
if down {
|
|
Migrator::down(&connection, steps).await?;
|
|
} else {
|
|
Migrator::up(&connection, steps).await?
|
|
}
|
|
}
|
|
|
|
Commands::Serve { migrate, addr } => {
|
|
if migrate {
|
|
Migrator::up(&connection, None).await?;
|
|
}
|
|
|
|
serve_web(addr, connection).await?;
|
|
}
|
|
|
|
Commands::Csv { csv_file, account: account_name } => {
|
|
let mut csv = csv::Reader::from_reader(File::open(csv_file)?);
|
|
let data = csv.records();
|
|
let data = data
|
|
.filter_map(|f| f.ok())
|
|
.map(from_csv_row)
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
let account_id = db::get_account_id(&connection, Some(account_name)).await?;
|
|
db::insert(&connection, data, account_id).await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn serve_web(address: SocketAddr, connection: DatabaseConnection) -> anyhow::Result<()> {
|
|
let app = Router::new()
|
|
.route("/health", get(health_check))
|
|
.route("/monzo-batch-export", post(monzo_batched_json))
|
|
.route("/monzo-csv-ingestion", post(monzo_batched_csv))
|
|
.route("/shortcuts-csv-import", post(shortcuts_csv))
|
|
.layer(Extension(connection.clone()))
|
|
.layer(TraceLayer::new_for_http());
|
|
|
|
tracing::info!("listening on {}", &address);
|
|
let listener = tokio::net::TcpListener::bind(&address).await?;
|
|
axum::serve(listener, app).await?;
|
|
|
|
Ok(())
|
|
}
|