Compare commits
No commits in common. "d7d7fa97185c8b49639b25b663ef6adf8ef60582" and "7fd85550ea985a7a0948fb63461f68ccbbe2a02f" have entirely different histories.
d7d7fa9718
...
7fd85550ea
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -10,11 +10,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
env:
|
||||||
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Note to self: maybe look at this if we want to build outside docker?
|
||||||
|
# - name: Build with cache
|
||||||
|
# uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
@ -30,7 +36,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
file: ./Dockerfile.cache
|
|
||||||
tags: git.joshuacoles.me/personal/monzo-ingestion:latest
|
tags: git.joshuacoles.me/personal/monzo-ingestion:latest
|
||||||
cache-from: type=registry,ref=user/app:latest
|
cache-from: type=registry,ref=user/app:latest
|
||||||
cache-to: type=inline
|
cache-to: type=inline
|
||||||
|
|||||||
@ -75,4 +75,4 @@ HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
|
|||||||
CMD curl -f http://localhost:3000/health || exit 1
|
CMD curl -f http://localhost:3000/health || exit 1
|
||||||
|
|
||||||
# What the container should run when it is started.
|
# What the container should run when it is started.
|
||||||
CMD ["/bin/server", "web", "--addr", "0.0.0.0:3000"]
|
CMD ["/bin/server"]
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
# Stage 1: Build
|
|
||||||
ARG RUST_VERSION=1.76.0
|
|
||||||
FROM lukemathwalker/cargo-chef:latest-rust-${RUST_VERSION} as chef
|
|
||||||
WORKDIR /build/
|
|
||||||
# hadolint ignore=DL3008
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
lld \
|
|
||||||
clang \
|
|
||||||
libclang-dev \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
FROM chef as planner
|
|
||||||
COPY . .
|
|
||||||
RUN cargo chef prepare --recipe-path recipe.json
|
|
||||||
|
|
||||||
FROM chef as builder
|
|
||||||
COPY --from=planner /build/recipe.json recipe.json
|
|
||||||
# Build dependencies - this is the caching Docker layer!
|
|
||||||
RUN cargo chef cook --release -p monzo-ingestion --recipe-path recipe.json
|
|
||||||
# Build application
|
|
||||||
COPY . .
|
|
||||||
RUN cargo build --release -p monzo-ingestion
|
|
||||||
|
|
||||||
# Stage 2: Run
|
|
||||||
FROM debian:bullseye-slim AS final
|
|
||||||
|
|
||||||
RUN set -ex; \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get -y install --no-install-recommends \
|
|
||||||
ca-certificates curl && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Create a non-privileged user that the app will run under.
|
|
||||||
# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user
|
|
||||||
ARG UID=10001
|
|
||||||
RUN adduser \
|
|
||||||
--disabled-password \
|
|
||||||
--gecos "" \
|
|
||||||
--home "/nonexistent" \
|
|
||||||
--shell "/sbin/nologin" \
|
|
||||||
--no-create-home \
|
|
||||||
--uid "${UID}" \
|
|
||||||
appuser
|
|
||||||
USER appuser
|
|
||||||
|
|
||||||
# Copy the executable from the "build" stage.
|
|
||||||
COPY --from=builder /build/target/release/monzo-ingestion /bin/
|
|
||||||
|
|
||||||
# Expose the port that the application listens on.
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
|
|
||||||
CMD curl -f http://localhost:3000/health || exit 1
|
|
||||||
|
|
||||||
# What the container should run when it is started.
|
|
||||||
CMD ["/bin/server"]
|
|
||||||
93
src/main.rs
93
src/main.rs
@ -2,56 +2,29 @@ mod error;
|
|||||||
mod ingestion;
|
mod ingestion;
|
||||||
|
|
||||||
use crate::error::AppError;
|
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};
|
use crate::ingestion::routes::{monzo_batched_csv, monzo_batched_json};
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
use axum::{Extension, Router};
|
use axum::{Extension, Router};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::Parser;
|
||||||
use migration::{Migrator, MigratorTrait};
|
use migration::{Migrator, MigratorTrait};
|
||||||
use sea_orm::{ConnectionTrait, DatabaseConnection};
|
use sea_orm::{ConnectionTrait, DatabaseConnection};
|
||||||
use std::fs::File;
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::PathBuf;
|
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::log::LevelFilter;
|
use tracing::log::LevelFilter;
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
|
||||||
enum Commands {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
|
|
||||||
Run {
|
|
||||||
/// If we should perform migration on 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,
|
|
||||||
},
|
|
||||||
|
|
||||||
Csv {
|
|
||||||
/// The path to the CSV file to ingest.
|
|
||||||
csv_file: PathBuf,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, clap::Parser)]
|
#[derive(Debug, clap::Parser)]
|
||||||
struct Cli {
|
struct Config {
|
||||||
|
/// If we should perform migration on 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,
|
||||||
|
|
||||||
/// URL to PostgreSQL database.
|
/// URL to PostgreSQL database.
|
||||||
#[clap(short, long = "db", env)]
|
#[clap(short, long = "db", env)]
|
||||||
database_url: String,
|
database_url: String,
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn health_check(
|
async fn health_check(
|
||||||
@ -64,48 +37,18 @@ async fn health_check(
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
tracing_subscriber::fmt::init();
|
let config: Config = Config::parse();
|
||||||
|
let connection = sea_orm::ConnectOptions::new(&config.database_url)
|
||||||
let cli: Cli = Cli::parse();
|
|
||||||
let connection = sea_orm::ConnectOptions::new(&cli.database_url)
|
|
||||||
.sqlx_logging_level(LevelFilter::Debug)
|
.sqlx_logging_level(LevelFilter::Debug)
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let connection = sea_orm::Database::connect(connection).await?;
|
let connection = sea_orm::Database::connect(connection).await?;
|
||||||
|
|
||||||
match cli.command {
|
if config.migrate {
|
||||||
Commands::Migrate { steps, down } => {
|
Migrator::up(&connection, None).await?;
|
||||||
if down {
|
|
||||||
Migrator::down(&connection, steps).await?;
|
|
||||||
} else {
|
|
||||||
Migrator::up(&connection, steps).await?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Commands::Run { migrate, addr } => {
|
|
||||||
if migrate {
|
|
||||||
Migrator::up(&connection, None).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
serve_web(addr, connection).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Commands::Csv { csv_file } => {
|
|
||||||
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<_, _>>()?;
|
|
||||||
|
|
||||||
db::insert(&connection, data).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
tracing_subscriber::fmt::init();
|
||||||
}
|
|
||||||
|
|
||||||
async fn serve_web(address: SocketAddr, connection: DatabaseConnection) -> anyhow::Result<()> {
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/health", get(health_check))
|
.route("/health", get(health_check))
|
||||||
.route("/monzo-batch-export", post(monzo_batched_json))
|
.route("/monzo-batch-export", post(monzo_batched_json))
|
||||||
@ -113,9 +56,9 @@ async fn serve_web(address: SocketAddr, connection: DatabaseConnection) -> anyho
|
|||||||
.layer(Extension(connection.clone()))
|
.layer(Extension(connection.clone()))
|
||||||
.layer(TraceLayer::new_for_http());
|
.layer(TraceLayer::new_for_http());
|
||||||
|
|
||||||
tracing::info!("listening on {}", &address);
|
tracing::debug!("listening on {}", &config.addr);
|
||||||
let listener = tokio::net::TcpListener::bind(&address).await?;
|
let listener = tokio::net::TcpListener::bind(&config.addr).await.unwrap();
|
||||||
axum::serve(listener, app).await?;
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user