initial commit
This commit is contained in:
commit
c127b2b907
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
target/
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
.env
|
3858
Cargo.lock
generated
Normal file
3858
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "unwritten-application"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.7.9"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
polars = { version = "0.44.2", features = ["lazy"] }
|
||||
postgres = { version = "0.19.9", features = ["with-uuid-1"] }
|
||||
tokio-postgres = "0.7.12"
|
||||
uuid = "1"
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
FROM docker.io/rust:1-slim-bullseye AS build
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
ADD Cargo.toml .
|
||||
ADD Cargo.lock .
|
||||
ADD src/ src
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
FROM docker.io/debian:bullseye-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /build/target/release/unwritten-application unwritten
|
||||
|
||||
CMD ["./unwritten"]
|
15
schema.sql
Normal file
15
schema.sql
Normal file
@ -0,0 +1,15 @@
|
||||
drop table if exists account;
|
||||
|
||||
create table if not exists accounts (
|
||||
uuid uuid primary key default gen_random_uuid (),
|
||||
name text default ''
|
||||
);
|
||||
|
||||
drop table if exists transactions;
|
||||
|
||||
create table if not exists transactions (
|
||||
uuid uuid primary key default gen_random_uuid (),
|
||||
source uuid references accounts (uuid),
|
||||
target uuid references accounts (uuid),
|
||||
amount double precision not null
|
||||
);
|
274
src/main.rs
Normal file
274
src/main.rs
Normal file
@ -0,0 +1,274 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use axum::{
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, put},
|
||||
Extension, Router,
|
||||
};
|
||||
use polars::prelude::*;
|
||||
use tokio_postgres::NoTls;
|
||||
|
||||
// Generetes a bunch of random data in the db
|
||||
async fn generate_random_data(
|
||||
Extension(client): Extension<Arc<tokio_postgres::Client>>,
|
||||
) -> impl IntoResponse {
|
||||
// For demo reason limiting to 1 set of random data
|
||||
let count = client.query("SELECT count(uuid) from accounts;", &[]).await;
|
||||
if 'cond: {
|
||||
if count.is_err() {
|
||||
break 'cond true;
|
||||
}
|
||||
let count: i64 = count.unwrap()[0].get(0);
|
||||
break 'cond count > 0;
|
||||
} {
|
||||
return "PLEASE DELETE DATA BEFORE GENERATING NEW DATA".into();
|
||||
}
|
||||
|
||||
let create_accounts = client
|
||||
.query(
|
||||
"
|
||||
INSERT INTO accounts ( name )
|
||||
SELECT ( md5(random()::text) )
|
||||
FROM generate_series(1, 10000) s(i);",
|
||||
&[],
|
||||
)
|
||||
.await;
|
||||
if create_accounts.is_err() {
|
||||
println!(
|
||||
"Failed to create accounts: {}",
|
||||
create_accounts.unwrap_err()
|
||||
);
|
||||
return "Failed to create accounts";
|
||||
}
|
||||
|
||||
// NOTE my psql version seams to limit inserts at 40000
|
||||
// so we are just going to loop a few times :)
|
||||
for _i in 0..50 {
|
||||
let create_transactions = client
|
||||
.query(
|
||||
"
|
||||
INSERT INTO transactions(source, target, amount)
|
||||
SELECT a1.uuid, a2.uuid, (random() * 100000)
|
||||
FROM accounts as a1
|
||||
CROSS JOIN LATERAL (SELECT uuid FROM accounts as a2 ORDER BY random() limit 5) as a2
|
||||
WHERE a2.uuid <> a1.uuid order by random() limit 20000;
|
||||
",
|
||||
&[],
|
||||
)
|
||||
.await;
|
||||
if create_transactions.is_err() {
|
||||
println!(
|
||||
"Failed to create transactions: {}",
|
||||
create_transactions.unwrap_err()
|
||||
);
|
||||
return "Failed to create transactions";
|
||||
}
|
||||
}
|
||||
|
||||
"DATA CREATED!"
|
||||
}
|
||||
|
||||
// Clears the data from db
|
||||
async fn clear_data(
|
||||
Extension(client): Extension<Arc<tokio_postgres::Client>>,
|
||||
) -> impl IntoResponse {
|
||||
let delete_transactions = client.execute("delete from transactions;", &[]).await;
|
||||
if delete_transactions.is_err() {
|
||||
println!(
|
||||
"Failed to delete transactions: {}",
|
||||
delete_transactions.unwrap_err()
|
||||
);
|
||||
return "Failed to delete transactions";
|
||||
}
|
||||
println!("Deleted transactions");
|
||||
|
||||
// Deletion of accounts seams to take a lot of time
|
||||
// goint to assume it's because the forign keys that the db is checking
|
||||
let delete_accounts = client.execute("delete from accounts;", &[]).await;
|
||||
if delete_accounts.is_err() {
|
||||
println!(
|
||||
"Failed to delete accounts: {}",
|
||||
delete_accounts.unwrap_err()
|
||||
);
|
||||
return "Failed to delete accounts";
|
||||
}
|
||||
println!("Deleted accounts");
|
||||
|
||||
"DATA DELETED!"
|
||||
}
|
||||
|
||||
async fn get_data(
|
||||
Extension(client): Extension<Arc<tokio_postgres::Client>>,
|
||||
Extension(state): Extension<Arc<State>>,
|
||||
) -> impl IntoResponse {
|
||||
let accounts = client.query("SELECT uuid, name from accounts;", &[]).await;
|
||||
if accounts.is_err() {
|
||||
println!(
|
||||
"Could not get accounts from database: {}",
|
||||
accounts.unwrap_err()
|
||||
);
|
||||
return "Could not get accounts from database";
|
||||
}
|
||||
let accounts = accounts.unwrap();
|
||||
|
||||
let account_df = 'acount_df: {
|
||||
let mut uuids: Vec<String> = Vec::with_capacity(accounts.len());
|
||||
let mut names: Vec<String> = Vec::with_capacity(accounts.len());
|
||||
|
||||
for account in &accounts {
|
||||
let uuid: uuid::Uuid = account.get(0);
|
||||
uuids.push(uuid.into());
|
||||
names.push(account.get(1));
|
||||
}
|
||||
|
||||
let c1 = Column::new("uuid".into(), uuids);
|
||||
let c2 = Column::new("name".into(), names);
|
||||
let df = DataFrame::new(vec![c1, c2]);
|
||||
if df.is_err() {
|
||||
println!("Failed to create account dataframe: {}", df.unwrap_err());
|
||||
return "Failed to create account dataframe".into();
|
||||
}
|
||||
break 'acount_df df.unwrap();
|
||||
};
|
||||
|
||||
println!("loaded accounts");
|
||||
|
||||
let transactions = client
|
||||
.query(
|
||||
"SELECT uuid, amount, source, target from transactions;",
|
||||
&[],
|
||||
)
|
||||
.await;
|
||||
|
||||
if transactions.is_err() {
|
||||
println!(
|
||||
"Could not get transactions from database: {}",
|
||||
transactions.unwrap_err()
|
||||
);
|
||||
return "Could not get transactions from database";
|
||||
}
|
||||
let transactions = transactions.unwrap();
|
||||
|
||||
let transactions_df = 'transctions_df: {
|
||||
let mut uuids: Vec<String> = Vec::with_capacity(accounts.len());
|
||||
let mut ammounts: Vec<f64> = Vec::with_capacity(accounts.len());
|
||||
let mut sources: Vec<String> = Vec::with_capacity(accounts.len());
|
||||
let mut targets: Vec<String> = Vec::with_capacity(accounts.len());
|
||||
|
||||
for transaction in &transactions {
|
||||
let uuid: uuid::Uuid = transaction.get(0);
|
||||
uuids.push(uuid.into());
|
||||
|
||||
ammounts.push(transaction.get(1));
|
||||
|
||||
let source: uuid::Uuid = transaction.get(2);
|
||||
sources.push(source.into());
|
||||
|
||||
let target: uuid::Uuid = transaction.get(3);
|
||||
targets.push(target.into());
|
||||
}
|
||||
|
||||
let c1 = Column::new("uuid".into(), uuids);
|
||||
let c2 = Column::new("amount".into(), ammounts);
|
||||
let c3 = Column::new("source".into(), sources);
|
||||
let c4 = Column::new("target".into(), targets);
|
||||
let df = DataFrame::new(vec![c1, c2, c3, c4]);
|
||||
if df.is_err() {
|
||||
println!(
|
||||
"Failed to create transacions dataframe: {}",
|
||||
df.unwrap_err()
|
||||
);
|
||||
return "Failed to create transactions dataframe".into();
|
||||
}
|
||||
break 'transctions_df df.unwrap();
|
||||
};
|
||||
|
||||
println!("loaded transactions");
|
||||
|
||||
let mut account_dataframe = state.accounts_dataframe.lock().unwrap();
|
||||
*account_dataframe = Some(account_df.lazy());
|
||||
|
||||
let mut transactions_dataframe = state.transactions_dataframe.lock().unwrap();
|
||||
*transactions_dataframe = Some(transactions_df.lazy());
|
||||
|
||||
println!("df generated");
|
||||
|
||||
return "DATA INJESTED!";
|
||||
}
|
||||
|
||||
async fn accounts(Extension(state): Extension<Arc<State>>) -> impl IntoResponse {
|
||||
let accounts = state.accounts_dataframe.lock().unwrap();
|
||||
if accounts.is_some() {
|
||||
let df = accounts.as_ref().unwrap().clone();
|
||||
let to_send = df.select(vec![col("uuid"), col("name")]).collect().unwrap();
|
||||
return format!("{}", to_send);
|
||||
}
|
||||
return "Injest Data First".into();
|
||||
}
|
||||
|
||||
async fn transactions(Extension(state): Extension<Arc<State>>) -> impl IntoResponse {
|
||||
let transactions = state.transactions_dataframe.lock().unwrap();
|
||||
if transactions.is_some() {
|
||||
let df = transactions.as_ref().unwrap().clone();
|
||||
let to_send = df
|
||||
.select(vec![
|
||||
col("uuid"),
|
||||
col("source"),
|
||||
col("target"),
|
||||
col("amount"),
|
||||
])
|
||||
.collect()
|
||||
.unwrap();
|
||||
return format!("{}", to_send);
|
||||
}
|
||||
return "Injest Data First".into();
|
||||
}
|
||||
|
||||
struct State {
|
||||
accounts_dataframe: Arc<Mutex<Option<LazyFrame>>>,
|
||||
transactions_dataframe: Arc<Mutex<Option<LazyFrame>>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> State {
|
||||
return State {
|
||||
accounts_dataframe: Arc::new(Mutex::new(None)),
|
||||
transactions_dataframe: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let psql_conn = std::env::var("PSQL_CONN");
|
||||
if psql_conn.is_err() {
|
||||
panic!("Please provide PSQL_CONN");
|
||||
}
|
||||
let psql_conn = psql_conn.unwrap();
|
||||
|
||||
let (client, connection) = tokio_postgres::connect(psql_conn.as_str(), NoTls)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let client = Arc::new(client);
|
||||
|
||||
let state = Arc::new(State::new());
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = connection.await {
|
||||
eprintln!("connection error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let app = Router::new()
|
||||
.route("/injest", get(get_data))
|
||||
.route("/generate_random", put(generate_random_data))
|
||||
.route("/delete_data", delete(clear_data))
|
||||
.route("/accounts", get(accounts))
|
||||
.route("/transactions", get(transactions))
|
||||
.layer(Extension(client))
|
||||
.layer(Extension(state));
|
||||
|
||||
let listner = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
axum::serve(listner, app).await.unwrap();
|
||||
}
|
Loading…
Reference in New Issue
Block a user