From 3976e8ff9b0d3039b9105bdcb715908968fad488 Mon Sep 17 00:00:00 2001 From: Miroito Date: Fri, 3 Mar 2023 16:09:58 +0100 Subject: [PATCH] Clean up transaction routes --- client/src/api/routes/transaction.rs | 36 ++---- client/src/api/types/transaction.rs | 4 +- client/src/templates/index.rs | 76 ++++++------ ...20230303_132528_transactions_created_at.rs | 8 +- server/src/db/paginate.rs | 8 +- server/src/lib.rs | 3 +- server/src/model/transaction.rs | 2 +- server/src/route/transaction.rs | 115 ++++++------------ 8 files changed, 96 insertions(+), 156 deletions(-) diff --git a/client/src/api/routes/transaction.rs b/client/src/api/routes/transaction.rs index c3fadb8..60ec76e 100644 --- a/client/src/api/routes/transaction.rs +++ b/client/src/api/routes/transaction.rs @@ -7,17 +7,18 @@ pub async fn get_transactions( ) -> Result, ()> { use crate::env::Config; + let api_url = Config::new().api_url; + let route = &format!( + "{}transaction?{}&page={}&size={}", + api_url, + company_slug.map_or("".to_string(), |c| format!("company_slug={}", c)), + page, + size, + ); + + // TODO: Remove build-time environment variable #[cfg(client)] - { - // TODO: Remove build-time environment variable - let api_url = Config::new().api_url; - let res = reqwasm::http::Request::get(&format!( - "{}transaction{}?page={}&size={}", - api_url, - company_slug.unwrap_or_default(), - page, - size, - )) + let res = reqwasm::http::Request::get(route) .send() .await .map_err(|_| ())? @@ -25,24 +26,13 @@ pub async fn get_transactions( .await .map_err(|_| ())?; - return Ok(res); - } - #[cfg(engine)] - { - let res = reqwest::get(&format!( - "{}transaction{}?page={}&size={}", - Config::new().api_url, - company_slug.unwrap_or_default(), - page, - size, - )) + let res = reqwest::get(route) .await .map_err(|_| ())? .json::>() .await .map_err(|_| ())?; - return Ok(res); - } + return Ok(res); } diff --git a/client/src/api/types/transaction.rs b/client/src/api/types/transaction.rs index 9a65061..266f46f 100644 --- a/client/src/api/types/transaction.rs +++ b/client/src/api/types/transaction.rs @@ -17,7 +17,7 @@ pub struct Transaction { pub instrument: String, pub volume: i32, pub unit_price: f32, - pub created_at: NaiveDateTime, + pub created_at_utc: NaiveDateTime, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -33,6 +33,6 @@ pub struct TransactionCompany { pub instrument: String, pub volume: i32, pub unit_price: f32, - pub created_at: NaiveDateTime, + pub created_at_utc: NaiveDateTime, pub company: Company, } diff --git a/client/src/templates/index.rs b/client/src/templates/index.rs index 7885bbb..5568c58 100644 --- a/client/src/templates/index.rs +++ b/client/src/templates/index.rs @@ -16,7 +16,7 @@ use crate::{ #[derive(Serialize, Deserialize, Clone, ReactiveState)] #[rx(alias = "IndexPageStateRx")] pub struct IndexPageState { - pub company_slug: String, + pub company_slug: Option, } #[auto_scope] @@ -45,11 +45,7 @@ fn index_page(cx: Scope, state: &IndexPageStateRx) -> View { let paginated_table_state: PaginatedTableStateRx = PaginatedTableStateRx { route: get_transactions, - filter: if (*state.company_slug.get()).is_empty() { - None - } else { - Some((*state.company_slug.get()).clone()) - }, + filter: (*state.company_slug.get()).clone(), }; let async_select_prop: AsyncSelectRx = AsyncSelectRx { @@ -76,49 +72,49 @@ fn index_page(cx: Scope, state: &IndexPageStateRx) -> View { }); view! {cx, - main (class=if *dark_mode_3.get() { "dark" } else { "" }) { - div (class="bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-100 font-sans") { - header (class="shadow-md h-12 p-2 align-middle w-full bg-gray-100 dark:bg-slate-500/30 backdrop-blur-lg") { - div (class="flex flex-row justify-between") { - div (class="mr-10 align-middle") { - a (href="/", class="hover:underline") { - "Fast Insiders" - } - } - div (class="align-middle") { - button (on:click=toggle_dark_mode, class="mx-1 py-1 px-2 bg-slate-200 dark:bg-slate-800 rounded-full") - { "Toggle dark mode" } + main (class=if *dark_mode_3.get() { "dark" } else { "" }) { + div (class="bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-100 font-sans") { + header (class="shadow-md h-12 p-2 align-middle w-full bg-gray-100 dark:bg-slate-500/30 backdrop-blur-lg") { + div (class="flex flex-row justify-between") { + div (class="mr-10 align-middle") { + a (href="/", class="hover:underline") { + "Fast Insiders" } } + div (class="align-middle") { + button (on:click=toggle_dark_mode, class="mx-1 py-1 px-2 bg-slate-200 dark:bg-slate-800 rounded-full") + { "Toggle dark mode" } + } } - div (id="main", class="flex flex-col items-center justify-center ") { - div (class="w-4/5 m-10 p-3 bg-slate-100 dark:bg-slate-600 rounded-lg items-center justify-center") { - a (class="hover:underline", href="/") { - h1 ( - class="text-center text-lg" - ) { - "Insider Transactions published by the AMF" - } + } + div (id="main", class="flex flex-col items-center justify-center ") { + div (class="w-4/5 m-10 p-3 bg-slate-100 dark:bg-slate-600 rounded-lg items-center justify-center") { + a (class="hover:underline", href="/") { + h1 ( + class="text-center text-lg" + ) { + "Insider Transactions published by the AMF" } - BaseButton(filter_expand) + } + BaseButton(filter_expand) div {} // Without this useless div, the code doesn't run in the browser - div (id="filters", class=format!("p-2 border rounded-lg border-slate-200 dark:border-slate-800 bg-slate-200 dark:bg-slate-700 transition-all ease-in {}", - if *expand.get() { "h-40 visible" } else { "h-0 collapse" }, - ) - ) - { - div (class="w-80") { - p () {"Search for a company:"} - BaseAsyncSelect(async_select_prop) + div (id="filters", class=format!("p-2 border rounded-lg border-slate-200 dark:border-slate-800 bg-slate-200 dark:bg-slate-700 transition-all ease-in {}", + if *expand.get() { "h-40 visible" } else { "h-0 collapse" }, + ) + ) + { + div (class="w-80") { + p () {"Search for a company:"} + BaseAsyncSelect(async_select_prop) BaseButton(search_button) - } } - PaginatedTable(paginated_table_state) } + PaginatedTable(paginated_table_state) } } } } + } } pub fn get_template() -> Template { @@ -142,11 +138,7 @@ fn head(cx: Scope) -> View { async fn get_build_state( StateGeneratorInfo { path, .. }: StateGeneratorInfo<()>, ) -> Result> { - let company_slug: String = path - .split("transactions") - .nth(1) - .unwrap_or(&("/".to_owned() + &path)) - .to_string(); + let company_slug = if path.is_empty() { None } else { Some(path) }; Ok(IndexPageState { company_slug }) } diff --git a/server/migration/src/m20230303_132528_transactions_created_at.rs b/server/migration/src/m20230303_132528_transactions_created_at.rs index ec3c20c..b346b3c 100644 --- a/server/migration/src/m20230303_132528_transactions_created_at.rs +++ b/server/migration/src/m20230303_132528_transactions_created_at.rs @@ -11,7 +11,7 @@ impl MigrationTrait for Migration { Table::alter() .table(Transaction::Table) .add_column( - ColumnDef::new(Transaction::CreatedAt) + ColumnDef::new(Transaction::CreatedAtUtc) .date_time() .not_null() .default(Expr::current_timestamp()), @@ -23,7 +23,7 @@ impl MigrationTrait for Migration { let query = Query::update() .table(Transaction::Table) .value( - Transaction::CreatedAt, + Transaction::CreatedAtUtc, Expr::col(Transaction::DatePublished), ) .to_owned(); @@ -36,7 +36,7 @@ impl MigrationTrait for Migration { .alter_table( Table::alter() .table(Transaction::Table) - .drop_column(Transaction::CreatedAt) + .drop_column(Transaction::CreatedAtUtc) .to_owned(), ) .await @@ -48,5 +48,5 @@ impl MigrationTrait for Migration { enum Transaction { Table, DatePublished, - CreatedAt, + CreatedAtUtc, } diff --git a/server/src/db/paginate.rs b/server/src/db/paginate.rs index a056650..3943347 100644 --- a/server/src/db/paginate.rs +++ b/server/src/db/paginate.rs @@ -58,7 +58,7 @@ pub async fn paginate_also_related( size: Option, column: Option, order: Option, - filter: Option, + filters: Option>, ) -> Result)>, DbErr> where E: EntityTrait + Related, @@ -79,8 +79,10 @@ where selector = E::find().find_also_related::(R::default()); } - if let Some(fil) = filter { - selector = selector.filter(fil); + if let Some(fils) = filters { + for fil in fils { + selector = selector.filter(fil); + } } let pages = selector.into_model().paginate(db, s); diff --git a/server/src/lib.rs b/server/src/lib.rs index 085ae5b..72f6b52 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -53,8 +53,7 @@ async fn start_rocket() -> Result<(), sea_orm_rocket::rocket::Error> { routes![ route::company::get_all, route::company::get_by_isin, - route::transaction::get_by_company_id, - route::transaction::get_all, + route::transaction::get_transactions, route::in_process_transaction::get_all, route::in_process_transaction::retry_failed_transaction, route::in_process_transaction::retry_all diff --git a/server/src/model/transaction.rs b/server/src/model/transaction.rs index 1759ab8..87d060d 100644 --- a/server/src/model/transaction.rs +++ b/server/src/model/transaction.rs @@ -21,7 +21,7 @@ pub struct Model { pub instrument: String, pub volume: i32, pub unit_price: f32, - pub created_at: DateTime, + pub created_at_utc: DateTime, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/server/src/route/transaction.rs b/server/src/route/transaction.rs index 67206fe..8bb86a9 100644 --- a/server/src/route/transaction.rs +++ b/server/src/route/transaction.rs @@ -1,4 +1,4 @@ -use chrono::{NaiveDate, NaiveDateTime}; +use chrono::{Duration, NaiveDate, NaiveDateTime, Utc}; use rocket::http::Status; use rocket::response::status::Custom; use sea_orm::{ColumnTrait, Order}; @@ -9,71 +9,45 @@ use serde::{Deserialize, Serialize}; use crate::db::paginate::{paginate_also_related, PaginatedResponse}; use crate::{db::Db, model}; -#[get("/transaction?&")] -pub async fn get_all( - conn: Connection<'_, Db>, - page: Option, - size: Option, -) -> Result>, Custom> { - let res = paginate_also_related::< - model::transaction::Entity, - model::company::Entity, - model::transaction::Model, - model::company::Model, - model::transaction::Column, - >( - conn, - page, - size, - Some(model::transaction::Column::DatePublished), - Some(Order::Desc), - None, - ) - .await - .map_err(|e| { - Custom( - Status::InternalServerError, - format!("Database error: {}", e), - ) - })?; - - let list = res - .list - .iter() - .map(|t| TransactionCompany { - id: t.0.id, - foreign_id: t.0.foreign_id.to_owned(), - date_published: t.0.date_published, - date_executed: t.0.date_executed, - person: t.0.person.to_owned(), - exchange: t.0.exchange.to_owned(), - nature: t.0.nature.to_owned(), - isin: t.0.isin.clone(), - instrument: t.0.instrument.to_owned(), - volume: t.0.volume, - unit_price: t.0.unit_price, - created_at: t.0.created_at, - company: t.1.to_owned(), - }) - .collect(); - - let res = PaginatedResponse { - count: res.count, - num_pages: res.num_pages, - list, - }; - - Ok(Json(res)) +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct TransactionCompany { + pub id: i32, + pub foreign_id: String, + pub date_published: NaiveDate, + pub date_executed: NaiveDate, + pub person: String, + pub exchange: String, + pub nature: String, + pub isin: Option, + pub instrument: String, + pub volume: i32, + pub unit_price: f32, + pub created_at_utc: NaiveDateTime, + pub company: Option, } -#[get("/transaction/?&")] -pub async fn get_by_company_id( +#[get("/transaction?&&&")] +pub async fn get_transactions( conn: Connection<'_, Db>, - company_slug: String, + company_slug: Option, + hours: Option, page: Option, size: Option, ) -> Result>, Custom> { - let filter = model::company::Column::Slug.eq(company_slug); + let mut filters = vec![]; + if let Some(c) = company_slug { + filters.push(model::company::Column::Slug.eq(c)) + } + if let Some(h) = hours { + filters.push( + model::transaction::Column::CreatedAtUtc.gte( + Utc::now() + .naive_utc() + .checked_sub_signed(Duration::hours(h)), + ), + ) + } + let res = paginate_also_related::< model::transaction::Entity, model::company::Entity, @@ -86,7 +60,7 @@ pub async fn get_by_company_id( size, Some(model::transaction::Column::DatePublished), Some(Order::Desc), - Some(filter), + Some(filters), ) .await .map_err(|e| { @@ -111,7 +85,7 @@ pub async fn get_by_company_id( instrument: t.0.instrument.to_owned(), volume: t.0.volume, unit_price: t.0.unit_price, - created_at: t.0.created_at, + created_at_utc: t.0.created_at_utc, company: t.1.to_owned(), }) .collect(); @@ -124,20 +98,3 @@ pub async fn get_by_company_id( Ok(Json(res)) } - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct TransactionCompany { - pub id: i32, - pub foreign_id: String, - pub date_published: NaiveDate, - pub date_executed: NaiveDate, - pub person: String, - pub exchange: String, - pub nature: String, - pub isin: Option, - pub instrument: String, - pub volume: i32, - pub unit_price: f32, - pub created_at: NaiveDateTime, - pub company: Option, -}