diff --git a/client/src/api/routes/transaction.rs b/client/src/api/routes/transaction.rs index da3c34c..0eda605 100644 --- a/client/src/api/routes/transaction.rs +++ b/client/src/api/routes/transaction.rs @@ -1,6 +1,6 @@ use crate::api::types::{ paginated_response::PaginatedResponse, - transaction::{TransactionCompany, TransactionsAggregated}, + transaction::{LatestTransaction, TransactionCompany, TransactionsAggregated}, }; pub async fn get_transactions( @@ -76,3 +76,34 @@ pub async fn get_aggregated_transactions( return Ok(res); } + +pub async fn get_latest_transactions( + _: Option, + page: i64, + size: i64, +) -> Result, ()> { + use crate::env::Config; + + // TODO: Remove build-time environment variable + let api_url = Config::new().api_url; + let route = &format!("{}transaction/latest?page={}&size={}", api_url, page, size,); + + #[cfg(client)] + let res = reqwasm::http::Request::get(route) + .send() + .await + .map_err(|_| ())? + .json::>() + .await + .map_err(|_| ())?; + + #[cfg(engine)] + let res = reqwest::get(route) + .await + .map_err(|_| ())? + .json::>() + .await + .map_err(|_| ())?; + + return Ok(res); +} diff --git a/client/src/api/types/paginated_response.rs b/client/src/api/types/paginated_response.rs index c241934..88a1cda 100644 --- a/client/src/api/types/paginated_response.rs +++ b/client/src/api/types/paginated_response.rs @@ -3,7 +3,7 @@ use sycamore::prelude::*; use crate::components::base_table::TableContent; -use super::transaction::{TransactionCompany, TransactionsAggregated}; +use super::transaction::{TransactionCompany, TransactionsAggregated, LatestTransaction}; pub trait IntoTableData where @@ -106,3 +106,40 @@ where } } } + +impl IntoTableData for PaginatedResponse +where + G: GenericNode, +{ + fn into_table_data<'a>(self, cx: Scope<'a>) -> TableContent { + let headers_view = vec![ + view! {cx, "Company" }, + view! {cx, "nature" }, + view! {cx, "Total" }, + ]; + + let data_view: Vec>> = self + .list + .into_iter() + .map(|t| { + let mut res = vec![]; + res.push(view! {cx, + a (href=format!("transactions/{}", t.slug), + class="text-indigo-800 dark:text-indigo-300 hover:text-indigo-500 dark:hover:text-indigo-600 hover:underline", + ) { + (t.company_name.to_owned()) + } + }); + res.push(view! {cx, (t.nature) }); + res.push(view! {cx, (t.total.to_string()) }); + + res + }) + .collect(); + + TableContent { + headers_view, + data_view, + } + } +} diff --git a/client/src/api/types/transaction.rs b/client/src/api/types/transaction.rs index ccb1124..cce799b 100644 --- a/client/src/api/types/transaction.rs +++ b/client/src/api/types/transaction.rs @@ -44,3 +44,11 @@ pub struct TransactionsAggregated { pub slug: String, pub transaction_count: i32, } + +#[derive(Deserialize, Clone)] +pub struct LatestTransaction { + pub company_name: String, + pub slug: String, + pub nature: String, + pub total: f32, +} diff --git a/client/src/components/paginated_data_table.rs b/client/src/components/paginated_data_table.rs index 21252a1..0f68df9 100644 --- a/client/src/components/paginated_data_table.rs +++ b/client/src/components/paginated_data_table.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use perseus::prelude::*; use serde::Deserialize; use sycamore::prelude::*; diff --git a/client/src/templates/index.rs b/client/src/templates/index.rs index e88d11e..87d7239 100644 --- a/client/src/templates/index.rs +++ b/client/src/templates/index.rs @@ -3,8 +3,8 @@ use sycamore::prelude::*; use crate::{ api::{ - routes::transaction::get_aggregated_transactions, - types::transaction::TransactionsAggregated, + routes::transaction::{get_aggregated_transactions, get_latest_transactions}, + types::transaction::{LatestTransaction, TransactionsAggregated}, }, components::{ main_content_container::MainContentContainer, @@ -19,10 +19,10 @@ fn index_page(cx: Scope) -> View { let table_classes = create_ref(cx, "w-full".to_string()); - let table_transactions_24hours: PaginatedTableStateRx = + let latest_transactions: PaginatedTableStateRx = PaginatedTableStateRx { - record_label: "companies".to_owned(), - route: get_aggregated_transactions, + record_label: "transactions".to_owned(), + route: get_latest_transactions, filter: Some("72".to_string()), table_class: table_classes, }; @@ -43,9 +43,9 @@ fn index_page(cx: Scope) -> View { div(class="flex flex-wrap gap-4 justify-around") { div (class="flex-grow") { h1 (class="mb-1 text-center") { - "Latest insider activity (72h)" + "Latest transactions aggregated by nature" } - PaginatedTable(table_transactions_24hours) + PaginatedTable(latest_transactions) } div (class="flex-grow") { h1 (class="mb-1 text-center") { diff --git a/server/src/lib.rs b/server/src/lib.rs index efba629..f674194 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -55,6 +55,7 @@ async fn start_rocket() -> Result<(), sea_orm_rocket::rocket::Error> { 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 diff --git a/server/src/model/company.rs b/server/src/model/company.rs index 9076c18..c8bfc72 100644 --- a/server/src/model/company.rs +++ b/server/src/model/company.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/server/src/model/in_process_transaction.rs b/server/src/model/in_process_transaction.rs index 3e2d951..9c6bfbe 100644 --- a/server/src/model/in_process_transaction.rs +++ b/server/src/model/in_process_transaction.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/server/src/model/mod.rs b/server/src/model/mod.rs index caf606f..a3e9cf7 100644 --- a/server/src/model/mod.rs +++ b/server/src/model/mod.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 pub mod prelude; diff --git a/server/src/model/prelude.rs b/server/src/model/prelude.rs index 559ac4b..4221474 100644 --- a/server/src/model/prelude.rs +++ b/server/src/model/prelude.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 pub use super::company::Entity as Company; pub use super::in_process_transaction::Entity as InProcessTransaction; diff --git a/server/src/model/transaction.rs b/server/src/model/transaction.rs index 87d060d..3c0f738 100644 --- a/server/src/model/transaction.rs +++ b/server/src/model/transaction.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -20,6 +20,7 @@ pub struct Model { pub isin: Option, pub instrument: String, pub volume: i32, + #[sea_orm(column_type = "Float")] pub unit_price: f32, pub created_at_utc: DateTime, } diff --git a/server/src/route/transaction.rs b/server/src/route/transaction.rs index 9f5dbb8..2e1c1f5 100644 --- a/server/src/route/transaction.rs +++ b/server/src/route/transaction.rs @@ -1,15 +1,10 @@ use chrono::{Duration, NaiveDate, NaiveDateTime, Utc}; -use rocket::http::Status; -use rocket::response::status::Custom; -use sea_orm::prelude::*; -use sea_orm::FromQueryResult; -use sea_orm::ItemsAndPagesNumber; -use sea_orm::JoinType; -use sea_orm::Order; -use sea_orm::QueryOrder; -use sea_orm::QuerySelect; -use sea_orm_rocket::rocket::serde::json::Json; -use sea_orm_rocket::Connection; +use rocket::{http::Status, response::status::Custom}; +use sea_orm::{ + prelude::*, DbBackend, FromQueryResult, ItemsAndPagesNumber, JoinType, Order, QueryOrder, + QuerySelect, Statement, +}; +use sea_orm_rocket::{rocket::serde::json::Json, Connection}; use serde::{Deserialize, Serialize}; use crate::db::paginate::{paginate_also_related, PaginatedResponse}; @@ -105,6 +100,72 @@ pub async fn get_transactions( Ok(Json(res)) } +#[derive(FromQueryResult, Serialize)] +pub struct LatestTransaction { + company_name: String, + slug: String, + nature: String, + total: f32, +} + +#[get("/transaction/latest?&")] +pub async fn get_latest_transactions( + conn: Connection<'_, Db>, + page: Option, + size: Option, +) -> Result>, Custom> { + let db = conn.into_inner(); + let s = size.unwrap_or(20).min(50); + + let query_raw = "SELECT + company.name as company_name, + company.slug, + transaction.nature, + SUM(transaction.volume * transaction.unit_price) as total + FROM transaction + JOIN company ON transaction.company_id = company.id + WHERE DATE(created_at_utc) IN (SELECT DATE(MAX(created_at_utc)) FROM transaction) + GROUP BY company.name, transaction.nature + ORDER BY transaction.nature, total DESC" + .to_string(); + + let query = model::transaction::Entity::find() + .from_raw_sql(Statement::from_string( + DbBackend::MySql, + query_raw.to_string(), + )) + .into_model::(); + + let pages = query.paginate(db, s); + + let ItemsAndPagesNumber { + number_of_items: count, + number_of_pages: num_pages, + } = pages.num_items_and_pages().await.map_err(|e| { + Custom( + Status::InternalServerError, + format!("Database error: {}", e), + ) + })?; + + let p = page.unwrap_or(0).min(num_pages); + + let list = pages.fetch_page(p).await.map_err(|e| { + Custom( + Status::InternalServerError, + format!("Database error: {}", e), + ) + })?; + + let res = PaginatedResponse { + count, + num_pages, + list, + }; + + Ok(Json(res)) +} + #[get("/transaction/aggregated?&&")] pub async fn get_aggregated_transactions( conn: Connection<'_, Db>,