commit
b65cde32e4
File diff suppressed because it is too large
Load Diff
@ -1,25 +0,0 @@
|
||||
use rocket::fairing::{Fairing, Info, Kind};
|
||||
use rocket::http::Header;
|
||||
use rocket::{Request, Response};
|
||||
|
||||
pub struct Cors;
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl Fairing for Cors {
|
||||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "Add CORS headers to responses",
|
||||
kind: Kind::Response,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
|
||||
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
|
||||
response.set_header(Header::new(
|
||||
"Access-Control-Allow-Methods",
|
||||
"POST, GET, PATCH, OPTIONS",
|
||||
));
|
||||
response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
|
||||
}
|
||||
}
|
||||
@ -1,47 +1,24 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::CONFIG;
|
||||
use async_trait::async_trait;
|
||||
use sea_orm::{ConnectOptions, DatabaseConnection};
|
||||
use sea_orm_rocket::rocket;
|
||||
use sea_orm_rocket::{rocket::figment::Figment, Database};
|
||||
|
||||
use crate::CONFIG;
|
||||
pub mod paginate;
|
||||
pub mod slug;
|
||||
|
||||
#[derive(Database, Debug)]
|
||||
#[database("fast_insiders")]
|
||||
pub struct Db(SeaOrmPool);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SeaOrmPool {
|
||||
pub conn: DatabaseConnection,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl sea_orm_rocket::Pool for SeaOrmPool {
|
||||
type Error = sea_orm::DbErr;
|
||||
|
||||
type Connection = DatabaseConnection;
|
||||
|
||||
async fn init(_figment: &Figment) -> Result<Self, Self::Error> {
|
||||
let config = &CONFIG;
|
||||
let mut options: ConnectOptions = ConnectOptions::new(config.database_url.to_owned());
|
||||
options
|
||||
.max_connections(config.max_connections)
|
||||
.min_connections(config.min_connections)
|
||||
.connect_timeout(Duration::from_secs(config.connect_timeout))
|
||||
.acquire_timeout(Duration::from_secs(config.acquire_timeout))
|
||||
.idle_timeout(Duration::from_secs(config.idle_timeout))
|
||||
.max_lifetime(Duration::from_secs(config.max_lifetime))
|
||||
.sqlx_logging(config.sqlx_logging);
|
||||
|
||||
let conn = sea_orm::Database::connect(options).await?;
|
||||
|
||||
Ok(SeaOrmPool { conn })
|
||||
}
|
||||
|
||||
fn borrow(&self) -> &Self::Connection {
|
||||
&self.conn
|
||||
}
|
||||
pub async fn init() -> Result<DatabaseConnection, sea_orm::DbErr> {
|
||||
let config = &CONFIG;
|
||||
let mut options: ConnectOptions = ConnectOptions::new(config.database_url.to_owned());
|
||||
options
|
||||
.max_connections(config.max_connections)
|
||||
.min_connections(config.min_connections)
|
||||
.connect_timeout(Duration::from_secs(config.connect_timeout))
|
||||
.acquire_timeout(Duration::from_secs(config.acquire_timeout))
|
||||
.idle_timeout(Duration::from_secs(config.idle_timeout))
|
||||
.max_lifetime(Duration::from_secs(config.max_lifetime))
|
||||
.sqlx_logging(config.sqlx_logging);
|
||||
|
||||
let conn = sea_orm::Database::connect(options).await?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use sea_orm::DbErr;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::repo::in_process_transaction::InProcessTransactionError;
|
||||
|
||||
pub enum AppError {
|
||||
DbErr(DbErr),
|
||||
InProcessTransaction(InProcessTransactionError),
|
||||
NotFound(String),
|
||||
}
|
||||
|
||||
impl From<DbErr> for AppError {
|
||||
fn from(inner: DbErr) -> Self {
|
||||
AppError::DbErr(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InProcessTransactionError> for AppError {
|
||||
fn from(inner: InProcessTransactionError) -> Self {
|
||||
AppError::InProcessTransaction(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, error_message) = match self {
|
||||
AppError::DbErr(e) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("The database retruned an error: {}", e),
|
||||
),
|
||||
AppError::InProcessTransaction(e) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Error in the in process transaction repo: {}", e),
|
||||
),
|
||||
AppError::NotFound(e) => (StatusCode::NOT_FOUND, format!("Not found error: {}", e)),
|
||||
};
|
||||
|
||||
let body = Json(json!({
|
||||
"error": error_message,
|
||||
}));
|
||||
|
||||
(status, body).into_response()
|
||||
}
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
// Macros
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate pretty_env_logger;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
// External crates
|
||||
use rocket::{Build, Rocket};
|
||||
use sea_orm_rocket::rocket::fairing::{self, AdHoc};
|
||||
use sea_orm_rocket::Database;
|
||||
|
||||
// Local crates
|
||||
use migration::MigratorTrait;
|
||||
|
||||
mod amf;
|
||||
mod cors;
|
||||
mod db;
|
||||
mod env;
|
||||
mod logger;
|
||||
mod model;
|
||||
mod repo;
|
||||
mod route;
|
||||
mod task;
|
||||
use crate::task::run_tasks;
|
||||
|
||||
// Module imports
|
||||
use crate::db::Db;
|
||||
use env::Config;
|
||||
|
||||
lazy_static! {
|
||||
/// Contains variables defind in .env file
|
||||
static ref CONFIG: Config = Config::new();
|
||||
}
|
||||
|
||||
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
|
||||
let conn = &Db::fetch(&rocket).unwrap().conn;
|
||||
let _ = migration::Migrator::up(conn, None).await;
|
||||
Ok(rocket)
|
||||
}
|
||||
|
||||
async fn start_rocket() -> Result<(), sea_orm_rocket::rocket::Error> {
|
||||
rocket::build()
|
||||
.attach(Db::init())
|
||||
.attach(AdHoc::try_on_ignite("Migrations", run_migrations))
|
||||
.attach(crate::cors::Cors)
|
||||
.mount(
|
||||
"/v1",
|
||||
routes![
|
||||
route::company::get_all,
|
||||
route::company::get_by_isin,
|
||||
route::transaction::get_transactions,
|
||||
route::transaction::get_aggregated_transactions,
|
||||
route::transaction::get_latest_transactions,
|
||||
route::in_process_transaction::get_all,
|
||||
route::in_process_transaction::retry_failed_transaction,
|
||||
route::in_process_transaction::retry_all
|
||||
],
|
||||
)
|
||||
.launch()
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
logger::init_log()?;
|
||||
|
||||
// Run tasks
|
||||
tokio::task::spawn(async { run_tasks().await });
|
||||
|
||||
let result = start_rocket().await;
|
||||
|
||||
info!("Rocket: deorbit.");
|
||||
|
||||
if let Some(err) = result.err() {
|
||||
println!("Error: {}", err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -0,0 +1,169 @@
|
||||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
// Macros
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate pretty_env_logger;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
// External crates
|
||||
use axum::{
|
||||
extract::MatchedPath,
|
||||
http::{HeaderValue, Method, Request, StatusCode},
|
||||
response::Response,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use std::{net::SocketAddr, time::Duration};
|
||||
use tokio::signal;
|
||||
use tower_http::{classify::ServerErrorsFailureClass, cors::CorsLayer, trace::TraceLayer};
|
||||
use tracing::{info, info_span, Span};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
// Local crates
|
||||
use migration::MigratorTrait;
|
||||
use route::{company, in_process_transaction, transaction};
|
||||
|
||||
mod amf;
|
||||
mod db;
|
||||
mod env;
|
||||
mod error;
|
||||
mod logger;
|
||||
mod model;
|
||||
mod repo;
|
||||
mod route;
|
||||
mod task;
|
||||
use crate::task::run_tasks;
|
||||
|
||||
// Module imports
|
||||
use env::Config;
|
||||
|
||||
lazy_static! {
|
||||
/// Contains variables defined in .env file
|
||||
static ref CONFIG: Config = Config::new();
|
||||
}
|
||||
|
||||
async fn fallback() -> (StatusCode, &'static str) {
|
||||
(StatusCode::NOT_FOUND, "Not Found")
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db: DatabaseConnection,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "info,tower_http=info".into()),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let shared_state = AppState {
|
||||
db: db::init().await?,
|
||||
};
|
||||
|
||||
let _ = migration::Migrator::up(&shared_state.db, None).await;
|
||||
|
||||
let app = Router::new()
|
||||
.route("/company", get(company::get_all))
|
||||
.route("/company/:name", get(company::get_by_name))
|
||||
.route("/transaction", get(transaction::get_all))
|
||||
.route(
|
||||
"/transaction/latest",
|
||||
get(transaction::get_latest_transactions),
|
||||
)
|
||||
.route(
|
||||
"/transaction/aggregated",
|
||||
get(transaction::get_aggregated_transactions),
|
||||
)
|
||||
.route(
|
||||
"/in_process_transaction",
|
||||
get(in_process_transaction::get_all),
|
||||
)
|
||||
.route(
|
||||
"/in_process_transaction/:foreign_id/retry",
|
||||
get(in_process_transaction::retry_failed_transaction),
|
||||
)
|
||||
.route(
|
||||
"/in_process_transaction/retry_all",
|
||||
get(in_process_transaction::retry_all),
|
||||
)
|
||||
.with_state(shared_state.clone())
|
||||
.fallback(fallback)
|
||||
.layer(
|
||||
TraceLayer::new_for_http()
|
||||
.make_span_with(|request: &Request<_>| {
|
||||
let matched_path = request
|
||||
.extensions()
|
||||
.get::<MatchedPath>()
|
||||
.map(MatchedPath::as_str);
|
||||
|
||||
info_span!(
|
||||
"http_request",
|
||||
method = ?request.method(),
|
||||
full_path = ?request.uri(),
|
||||
matched_path,
|
||||
)
|
||||
})
|
||||
.on_request(|_request: &Request<_>, _span: &Span| {
|
||||
info!("New request");
|
||||
})
|
||||
.on_response(|response: &Response, latency: Duration, _span: &Span| {
|
||||
info!(
|
||||
"Response, status {}, time {}ms",
|
||||
response.status(),
|
||||
latency.as_millis()
|
||||
);
|
||||
})
|
||||
.on_failure(
|
||||
|error: ServerErrorsFailureClass, latency: Duration, _span: &Span| {
|
||||
error!("There was an error answering this request, the server nonetheless answered in {}ms, error: {}", latency.as_millis(), error);
|
||||
},
|
||||
),
|
||||
)
|
||||
.layer(
|
||||
CorsLayer::new()
|
||||
.allow_origin("*".parse::<HeaderValue>().unwrap())
|
||||
.allow_methods([Method::GET])
|
||||
);
|
||||
|
||||
// Run tasks
|
||||
tokio::task::spawn(async move { run_tasks(&shared_state.db).await });
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
let ctrl_c = async {
|
||||
signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install Ctrl+C handler");
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let terminate = async {
|
||||
signal::unix::signal(signal::unix::SignalKind::terminate())
|
||||
.expect("failed to install signal handler")
|
||||
.recv()
|
||||
.await;
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
_ = ctrl_c => {},
|
||||
_ = terminate => {},
|
||||
}
|
||||
|
||||
info!("Starting graceful shutdown");
|
||||
}
|
||||
@ -1,3 +1,43 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
|
||||
pub mod company;
|
||||
pub mod in_process_transaction;
|
||||
pub mod transaction;
|
||||
|
||||
/// Struct to deserialize paginated routes query parameters
|
||||
#[derive(Deserialize)]
|
||||
pub struct Pagination {
|
||||
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||
pub page: Option<u64>,
|
||||
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||
pub size: Option<u64>,
|
||||
}
|
||||
|
||||
/// Struct to deserialize a company slug as a query parameters
|
||||
#[derive(Deserialize)]
|
||||
pub struct CompanySlug {
|
||||
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||
pub company_slug: Option<String>,
|
||||
}
|
||||
|
||||
/// Struct to deserialize a limit as a query parameters
|
||||
#[derive(Deserialize)]
|
||||
pub struct Limit {
|
||||
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||
pub limit: Option<u64>,
|
||||
}
|
||||
|
||||
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: FromStr,
|
||||
T::Err: fmt::Display,
|
||||
{
|
||||
let opt = Option::<String>::deserialize(de)?;
|
||||
match opt.as_deref() {
|
||||
None | Some("") => Ok(None),
|
||||
Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
server::main()?;
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Reference in new issue