Add assets microservice base

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-08-28 09:17:26 +03:00
parent 068f2d8601
commit 8544fc79f5
22 changed files with 3010 additions and 1 deletions

26
backend/assets/Cargo.toml Normal file
View File

@@ -0,0 +1,26 @@
[package]
name = "assets"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
common = { path = "../common" }
apca = "0.27.2"
axum = "0.6.20"
dotenv = "0.15.0"
sqlx = { version = "0.7.1", features = [
"uuid",
"time",
"postgres",
"runtime-tokio",
] }
tokio = { version = "1.32.0", features = [
"macros",
"rt-multi-thread",
] }
deadpool = { version = "0.9.5", features = [
"rt_tokio_1",
] }
serde = "1.0.188"

View File

@@ -0,0 +1,8 @@
services:
assets:
build:
context: ..
dockerfile: Dockerfile
target: assets
hostname: assets
restart: unless-stopped

View File

@@ -0,0 +1,39 @@
use axum::{
routing::{delete, get, post},
Extension, Router, Server,
};
use common::alpaca::create_alpaca_pool;
use dotenv::dotenv;
use sqlx::PgPool;
use std::{env, error::Error, net::SocketAddr};
mod routes;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
dotenv().ok();
let database_pool = PgPool::connect(&env::var("DATABASE_URL").unwrap()).await?;
let alpaca_pool = create_alpaca_pool(
&env::var("APCA_API_BASE_URL").unwrap(),
&env::var("APCA_API_KEY_ID").unwrap(),
&env::var("APCA_API_SECRET_KEY").unwrap(),
10,
)?;
let app = Router::new()
.route("/assets", get(routes::get_assets))
.route("/assets/:symbol", get(routes::get_asset))
.route("/assets", post(routes::add_asset))
.route("/assets/:symbol", post(routes::update_asset))
.route("/assets/:symbol", delete(routes::delete_asset))
.layer(Extension(database_pool))
.layer(Extension(alpaca_pool));
let addr = SocketAddr::from(([0, 0, 0, 0], 7878));
println!("Listening on {}...", addr);
Server::bind(&addr).serve(app.into_make_service()).await?;
Ok(())
}

View File

@@ -0,0 +1,110 @@
use apca::api::v2::asset::{self, Symbol};
use axum::{extract::Path, http::StatusCode, Extension, Json};
use common::{
alpaca::AlpacaPool,
database::{Asset, Class, Exchange},
};
use serde::Deserialize;
use sqlx::{query, query_as, types::Uuid, PgPool};
pub async fn get_assets(
Extension(database_pool): Extension<PgPool>,
) -> Result<(StatusCode, Json<Vec<Asset>>), StatusCode> {
let assets = query_as!(Asset, r#"SELECT id, symbol, class as "class: Class", exchange as "exchange: Exchange", trading, date_added FROM assets"#)
.fetch_all(&database_pool)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok((StatusCode::OK, Json(assets)))
}
pub async fn get_asset(
Extension(database_pool): Extension<PgPool>,
Path(symbol): Path<String>,
) -> Result<(StatusCode, Json<Asset>), StatusCode> {
let asset = query_as!(Asset, r#"SELECT id, symbol, class as "class: Class", exchange as "exchange: Exchange", trading, date_added FROM assets WHERE symbol = $1"#, symbol)
.fetch_one(&database_pool)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok((StatusCode::OK, Json(asset)))
}
#[allow(dead_code)]
#[derive(Deserialize)]
pub struct AddAssetRequest {
symbol: String,
trading: Option<bool>,
}
pub async fn add_asset(
Extension(database_pool): Extension<PgPool>,
Extension(alpaca_pool): Extension<AlpacaPool>,
Json(request): Json<AddAssetRequest>,
) -> Result<StatusCode, StatusCode> {
if query_as!(Asset, r#"SELECT id, symbol, class as "class: Class", exchange as "exchange: Exchange", trading, date_added FROM assets WHERE symbol = $1"#, request.symbol)
.fetch_optional(&database_pool)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.is_some() {
return Err(StatusCode::CONFLICT);
}
let asset = alpaca_pool
.get()
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.issue::<asset::Get>(&Symbol::Sym(request.symbol))
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
query!(
r#"INSERT INTO assets (id, symbol, class, exchange, trading) VALUES ($1, $2, $3::CLASS, $4::EXCHANGE, $5)"#,
Uuid::parse_str(&asset.id.to_string()).unwrap(),
asset.symbol,
Class::from(asset.class) as Class,
Exchange::from(asset.exchange) as Exchange,
request.trading
)
.execute(&database_pool)
.await
.unwrap();
Ok(StatusCode::CREATED)
}
#[allow(dead_code)]
#[derive(Deserialize)]
pub struct UpdateAssetRequest {
trading: bool,
}
pub async fn update_asset(
Extension(database_pool): Extension<PgPool>,
Path(symbol): Path<String>,
Json(request): Json<UpdateAssetRequest>,
) -> Result<StatusCode, StatusCode> {
query_as!(
Asset,
r#"UPDATE assets SET trading = $1 WHERE symbol = $2"#,
request.trading,
symbol
)
.execute(&database_pool)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(StatusCode::OK)
}
pub async fn delete_asset(
Extension(database_pool): Extension<PgPool>,
Path(symbol): Path<String>,
) -> Result<StatusCode, StatusCode> {
query!(r#"DELETE FROM assets WHERE symbol = $1"#, symbol)
.execute(&database_pool)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(StatusCode::NO_CONTENT)
}