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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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 company;
|
||||||
pub mod in_process_transaction;
|
pub mod in_process_transaction;
|
||||||
pub mod 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